mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
feat(tracker): allow copying and pasting songs to/from the window
This commit is contained in:
parent
11b5b5b322
commit
4da225ec33
@ -4,7 +4,9 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gioui.org/app"
|
||||
"gioui.org/io/key"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var noteMap = map[string]int{
|
||||
@ -75,19 +77,32 @@ var unitKeyMap = map[string]string{
|
||||
}
|
||||
|
||||
// KeyEvent handles incoming key events and returns true if repaint is needed.
|
||||
func (t *Tracker) KeyEvent(e key.Event) bool {
|
||||
func (t *Tracker) KeyEvent(w *app.Window, e key.Event) bool {
|
||||
if e.State == key.Press {
|
||||
if t.InstrumentNameEditor.Focused() {
|
||||
return false
|
||||
}
|
||||
switch e.Name {
|
||||
case "C":
|
||||
if e.Modifiers.Contain(key.ModShortcut) {
|
||||
contents, err := yaml.Marshal(t.song)
|
||||
if err == nil {
|
||||
w.WriteClipboard(string(contents))
|
||||
}
|
||||
return true
|
||||
}
|
||||
case "V":
|
||||
if e.Modifiers.Contain(key.ModShortcut) {
|
||||
w.ReadClipboard()
|
||||
return true
|
||||
}
|
||||
case "Z":
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
if e.Modifiers.Contain(key.ModShortcut) {
|
||||
t.Undo()
|
||||
return true
|
||||
}
|
||||
case "Y":
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
if e.Modifiers.Contain(key.ModShortcut) {
|
||||
t.Redo()
|
||||
return true
|
||||
}
|
||||
@ -118,14 +133,14 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
||||
case key.NameUpArrow:
|
||||
switch t.EditMode {
|
||||
case EditPatterns:
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
if e.Modifiers.Contain(key.ModShortcut) {
|
||||
t.Cursor.SongRow = SongRow{}
|
||||
} else {
|
||||
t.Cursor.Row -= t.song.RowsPerPattern
|
||||
}
|
||||
t.NoteTracking = false
|
||||
case EditTracks:
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
if e.Modifiers.Contain(key.ModShortcut) {
|
||||
t.Cursor.Row -= t.song.RowsPerPattern
|
||||
} else {
|
||||
t.Cursor.Row--
|
||||
@ -144,14 +159,14 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
||||
case key.NameDownArrow:
|
||||
switch t.EditMode {
|
||||
case EditPatterns:
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
if e.Modifiers.Contain(key.ModShortcut) {
|
||||
t.Cursor.Row = t.song.TotalRows() - 1
|
||||
} else {
|
||||
t.Cursor.Row += t.song.RowsPerPattern
|
||||
}
|
||||
t.NoteTracking = false
|
||||
case EditTracks:
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
if e.Modifiers.Contain(key.ModShortcut) {
|
||||
t.Cursor.Row += t.song.RowsPerPattern
|
||||
} else {
|
||||
t.Cursor.Row++
|
||||
@ -170,13 +185,13 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
||||
case key.NameLeftArrow:
|
||||
switch t.EditMode {
|
||||
case EditPatterns:
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
if e.Modifiers.Contain(key.ModShortcut) {
|
||||
t.Cursor.Track = 0
|
||||
} else {
|
||||
t.Cursor.Track--
|
||||
}
|
||||
case EditTracks:
|
||||
if t.CursorColumn == 0 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModCtrl) {
|
||||
if t.CursorColumn == 0 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModShortcut) {
|
||||
t.Cursor.Track--
|
||||
t.CursorColumn = 1
|
||||
} else {
|
||||
@ -199,13 +214,13 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
||||
case key.NameRightArrow:
|
||||
switch t.EditMode {
|
||||
case EditPatterns:
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
if e.Modifiers.Contain(key.ModShortcut) {
|
||||
t.Cursor.Track = len(t.song.Tracks) - 1
|
||||
} else {
|
||||
t.Cursor.Track++
|
||||
}
|
||||
case EditTracks:
|
||||
if t.CursorColumn == 0 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModCtrl) {
|
||||
if t.CursorColumn == 0 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModShortcut) {
|
||||
t.Cursor.Track++
|
||||
t.CursorColumn = 0
|
||||
} else {
|
||||
@ -228,7 +243,7 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
||||
case "+":
|
||||
switch t.EditMode {
|
||||
case EditTracks:
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
if e.Modifiers.Contain(key.ModShortcut) {
|
||||
t.AdjustSelectionPitch(12)
|
||||
} else {
|
||||
t.AdjustSelectionPitch(1)
|
||||
@ -238,7 +253,7 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
||||
case "-":
|
||||
switch t.EditMode {
|
||||
case EditTracks:
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
if e.Modifiers.Contain(key.ModShortcut) {
|
||||
t.AdjustSelectionPitch(-12)
|
||||
} else {
|
||||
t.AdjustSelectionPitch(-1)
|
||||
@ -285,7 +300,7 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
||||
name = strings.ToLower(name)
|
||||
}
|
||||
if val, ok := unitKeyMap[name]; ok {
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
if e.Modifiers.Contain(key.ModShortcut) {
|
||||
t.SetUnit(val)
|
||||
return true
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"os"
|
||||
|
||||
"gioui.org/app"
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/layout"
|
||||
@ -21,7 +22,12 @@ func (t *Tracker) Run(w *app.Window) error {
|
||||
case system.DestroyEvent:
|
||||
return e.Err
|
||||
case key.Event:
|
||||
if t.KeyEvent(e) {
|
||||
if t.KeyEvent(w, e) {
|
||||
w.Invalidate()
|
||||
}
|
||||
case clipboard.Event:
|
||||
err := t.UnmarshalSong([]byte(e.Text))
|
||||
if err == nil {
|
||||
w.Invalidate()
|
||||
}
|
||||
case system.FrameEvent:
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"math"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/clip"
|
||||
@ -12,6 +13,7 @@ import (
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget/material"
|
||||
"golang.org/x/exp/shiny/materialdesign/icons"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func (t *Tracker) layoutSongPanel(gtx C) D {
|
||||
@ -41,6 +43,12 @@ func (t *Tracker) layoutSongButtons(gtx C) D {
|
||||
t.SaveSongFile()
|
||||
}
|
||||
|
||||
for t.CopySongBtn.Clicked() {
|
||||
if contents, err := yaml.Marshal(t.song); err == nil {
|
||||
clipboard.WriteOp{Text: string(contents)}.Add(gtx.Ops)
|
||||
}
|
||||
}
|
||||
|
||||
newBtnStyle := material.IconButton(t.Theme, t.NewSongFileBtn, widgetForIcon(icons.ContentClear))
|
||||
newBtnStyle.Background = transparent
|
||||
newBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
||||
@ -51,10 +59,16 @@ func (t *Tracker) layoutSongButtons(gtx C) D {
|
||||
loadBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
||||
loadBtnStyle.Color = primaryColor
|
||||
|
||||
copySongBtnStyle := material.IconButton(t.Theme, t.CopySongBtn, widgetForIcon(icons.ContentContentCopy))
|
||||
copySongBtnStyle.Background = transparent
|
||||
copySongBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
||||
copySongBtnStyle.Color = primaryColor
|
||||
|
||||
menuContents := func(gtx C) D {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(newBtnStyle.Layout),
|
||||
layout.Rigid(loadBtnStyle.Layout),
|
||||
layout.Rigid(copySongBtnStyle.Layout),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
package tracker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -11,6 +13,7 @@ import (
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
"github.com/vsariola/sointu"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type EditMode int
|
||||
@ -50,6 +53,7 @@ type Tracker struct {
|
||||
TrackVoices *NumberInput
|
||||
InstrumentNameEditor *widget.Editor
|
||||
NewTrackBtn *widget.Clickable
|
||||
CopySongBtn *widget.Clickable
|
||||
NewInstrumentBtn *widget.Clickable
|
||||
DeleteInstrumentBtn *widget.Clickable
|
||||
LoadSongFileBtn *widget.Clickable
|
||||
@ -107,6 +111,20 @@ func (t *Tracker) LoadSong(song sointu.Song) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tracker) UnmarshalSong(bytes []byte) error {
|
||||
var song sointu.Song
|
||||
if errJSON := json.Unmarshal(bytes, &song); errJSON != nil {
|
||||
if errYaml := yaml.Unmarshal(bytes, &song); errYaml != nil {
|
||||
return fmt.Errorf("the song could not be parsed as .json (%v) or .yml (%v)", errJSON, errYaml)
|
||||
}
|
||||
}
|
||||
if song.BPM > 0 {
|
||||
t.LoadSong(song)
|
||||
return nil
|
||||
}
|
||||
return errors.New("was able to unmarshal a song, but the bpm was 0")
|
||||
}
|
||||
|
||||
func clamp(a, min, max int) int {
|
||||
if a < min {
|
||||
return min
|
||||
@ -631,6 +649,7 @@ func New(audioContext sointu.AudioContext, synthService sointu.SynthService) *Tr
|
||||
NewInstrumentBtn: new(widget.Clickable),
|
||||
DeleteInstrumentBtn: new(widget.Clickable),
|
||||
NewSongFileBtn: new(widget.Clickable),
|
||||
CopySongBtn: new(widget.Clickable),
|
||||
FileMenuBtn: new(widget.Clickable),
|
||||
LoadSongFileBtn: new(widget.Clickable),
|
||||
SaveSongFileBtn: new(widget.Clickable),
|
||||
|
Loading…
Reference in New Issue
Block a user