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"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gioui.org/app"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var noteMap = map[string]int{
|
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.
|
// 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 e.State == key.Press {
|
||||||
if t.InstrumentNameEditor.Focused() {
|
if t.InstrumentNameEditor.Focused() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
switch e.Name {
|
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":
|
case "Z":
|
||||||
if e.Modifiers.Contain(key.ModCtrl) {
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
t.Undo()
|
t.Undo()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case "Y":
|
case "Y":
|
||||||
if e.Modifiers.Contain(key.ModCtrl) {
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
t.Redo()
|
t.Redo()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -118,14 +133,14 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
|||||||
case key.NameUpArrow:
|
case key.NameUpArrow:
|
||||||
switch t.EditMode {
|
switch t.EditMode {
|
||||||
case EditPatterns:
|
case EditPatterns:
|
||||||
if e.Modifiers.Contain(key.ModCtrl) {
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
t.Cursor.SongRow = SongRow{}
|
t.Cursor.SongRow = SongRow{}
|
||||||
} else {
|
} else {
|
||||||
t.Cursor.Row -= t.song.RowsPerPattern
|
t.Cursor.Row -= t.song.RowsPerPattern
|
||||||
}
|
}
|
||||||
t.NoteTracking = false
|
t.NoteTracking = false
|
||||||
case EditTracks:
|
case EditTracks:
|
||||||
if e.Modifiers.Contain(key.ModCtrl) {
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
t.Cursor.Row -= t.song.RowsPerPattern
|
t.Cursor.Row -= t.song.RowsPerPattern
|
||||||
} else {
|
} else {
|
||||||
t.Cursor.Row--
|
t.Cursor.Row--
|
||||||
@ -144,14 +159,14 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
|||||||
case key.NameDownArrow:
|
case key.NameDownArrow:
|
||||||
switch t.EditMode {
|
switch t.EditMode {
|
||||||
case EditPatterns:
|
case EditPatterns:
|
||||||
if e.Modifiers.Contain(key.ModCtrl) {
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
t.Cursor.Row = t.song.TotalRows() - 1
|
t.Cursor.Row = t.song.TotalRows() - 1
|
||||||
} else {
|
} else {
|
||||||
t.Cursor.Row += t.song.RowsPerPattern
|
t.Cursor.Row += t.song.RowsPerPattern
|
||||||
}
|
}
|
||||||
t.NoteTracking = false
|
t.NoteTracking = false
|
||||||
case EditTracks:
|
case EditTracks:
|
||||||
if e.Modifiers.Contain(key.ModCtrl) {
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
t.Cursor.Row += t.song.RowsPerPattern
|
t.Cursor.Row += t.song.RowsPerPattern
|
||||||
} else {
|
} else {
|
||||||
t.Cursor.Row++
|
t.Cursor.Row++
|
||||||
@ -170,13 +185,13 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
|||||||
case key.NameLeftArrow:
|
case key.NameLeftArrow:
|
||||||
switch t.EditMode {
|
switch t.EditMode {
|
||||||
case EditPatterns:
|
case EditPatterns:
|
||||||
if e.Modifiers.Contain(key.ModCtrl) {
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
t.Cursor.Track = 0
|
t.Cursor.Track = 0
|
||||||
} else {
|
} else {
|
||||||
t.Cursor.Track--
|
t.Cursor.Track--
|
||||||
}
|
}
|
||||||
case EditTracks:
|
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.Cursor.Track--
|
||||||
t.CursorColumn = 1
|
t.CursorColumn = 1
|
||||||
} else {
|
} else {
|
||||||
@ -199,13 +214,13 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
|||||||
case key.NameRightArrow:
|
case key.NameRightArrow:
|
||||||
switch t.EditMode {
|
switch t.EditMode {
|
||||||
case EditPatterns:
|
case EditPatterns:
|
||||||
if e.Modifiers.Contain(key.ModCtrl) {
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
t.Cursor.Track = len(t.song.Tracks) - 1
|
t.Cursor.Track = len(t.song.Tracks) - 1
|
||||||
} else {
|
} else {
|
||||||
t.Cursor.Track++
|
t.Cursor.Track++
|
||||||
}
|
}
|
||||||
case EditTracks:
|
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.Cursor.Track++
|
||||||
t.CursorColumn = 0
|
t.CursorColumn = 0
|
||||||
} else {
|
} else {
|
||||||
@ -228,7 +243,7 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
|||||||
case "+":
|
case "+":
|
||||||
switch t.EditMode {
|
switch t.EditMode {
|
||||||
case EditTracks:
|
case EditTracks:
|
||||||
if e.Modifiers.Contain(key.ModCtrl) {
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
t.AdjustSelectionPitch(12)
|
t.AdjustSelectionPitch(12)
|
||||||
} else {
|
} else {
|
||||||
t.AdjustSelectionPitch(1)
|
t.AdjustSelectionPitch(1)
|
||||||
@ -238,7 +253,7 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
|||||||
case "-":
|
case "-":
|
||||||
switch t.EditMode {
|
switch t.EditMode {
|
||||||
case EditTracks:
|
case EditTracks:
|
||||||
if e.Modifiers.Contain(key.ModCtrl) {
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
t.AdjustSelectionPitch(-12)
|
t.AdjustSelectionPitch(-12)
|
||||||
} else {
|
} else {
|
||||||
t.AdjustSelectionPitch(-1)
|
t.AdjustSelectionPitch(-1)
|
||||||
@ -285,7 +300,7 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
|||||||
name = strings.ToLower(name)
|
name = strings.ToLower(name)
|
||||||
}
|
}
|
||||||
if val, ok := unitKeyMap[name]; ok {
|
if val, ok := unitKeyMap[name]; ok {
|
||||||
if e.Modifiers.Contain(key.ModCtrl) {
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
t.SetUnit(val)
|
t.SetUnit(val)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"gioui.org/app"
|
"gioui.org/app"
|
||||||
|
"gioui.org/io/clipboard"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
@ -21,7 +22,12 @@ func (t *Tracker) Run(w *app.Window) error {
|
|||||||
case system.DestroyEvent:
|
case system.DestroyEvent:
|
||||||
return e.Err
|
return e.Err
|
||||||
case key.Event:
|
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()
|
w.Invalidate()
|
||||||
}
|
}
|
||||||
case system.FrameEvent:
|
case system.FrameEvent:
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
|
"gioui.org/io/clipboard"
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
@ -12,6 +13,7 @@ import (
|
|||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
"gioui.org/widget/material"
|
"gioui.org/widget/material"
|
||||||
"golang.org/x/exp/shiny/materialdesign/icons"
|
"golang.org/x/exp/shiny/materialdesign/icons"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t *Tracker) layoutSongPanel(gtx C) D {
|
func (t *Tracker) layoutSongPanel(gtx C) D {
|
||||||
@ -41,6 +43,12 @@ func (t *Tracker) layoutSongButtons(gtx C) D {
|
|||||||
t.SaveSongFile()
|
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 := material.IconButton(t.Theme, t.NewSongFileBtn, widgetForIcon(icons.ContentClear))
|
||||||
newBtnStyle.Background = transparent
|
newBtnStyle.Background = transparent
|
||||||
newBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
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.Inset = layout.UniformInset(unit.Dp(6))
|
||||||
loadBtnStyle.Color = primaryColor
|
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 {
|
menuContents := func(gtx C) D {
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
layout.Rigid(newBtnStyle.Layout),
|
layout.Rigid(newBtnStyle.Layout),
|
||||||
layout.Rigid(loadBtnStyle.Layout),
|
layout.Rigid(loadBtnStyle.Layout),
|
||||||
|
layout.Rigid(copySongBtnStyle.Layout),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package tracker
|
package tracker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -11,6 +13,7 @@ import (
|
|||||||
"gioui.org/widget"
|
"gioui.org/widget"
|
||||||
"gioui.org/widget/material"
|
"gioui.org/widget/material"
|
||||||
"github.com/vsariola/sointu"
|
"github.com/vsariola/sointu"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EditMode int
|
type EditMode int
|
||||||
@ -50,6 +53,7 @@ type Tracker struct {
|
|||||||
TrackVoices *NumberInput
|
TrackVoices *NumberInput
|
||||||
InstrumentNameEditor *widget.Editor
|
InstrumentNameEditor *widget.Editor
|
||||||
NewTrackBtn *widget.Clickable
|
NewTrackBtn *widget.Clickable
|
||||||
|
CopySongBtn *widget.Clickable
|
||||||
NewInstrumentBtn *widget.Clickable
|
NewInstrumentBtn *widget.Clickable
|
||||||
DeleteInstrumentBtn *widget.Clickable
|
DeleteInstrumentBtn *widget.Clickable
|
||||||
LoadSongFileBtn *widget.Clickable
|
LoadSongFileBtn *widget.Clickable
|
||||||
@ -107,6 +111,20 @@ func (t *Tracker) LoadSong(song sointu.Song) error {
|
|||||||
return nil
|
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 {
|
func clamp(a, min, max int) int {
|
||||||
if a < min {
|
if a < min {
|
||||||
return min
|
return min
|
||||||
@ -631,6 +649,7 @@ func New(audioContext sointu.AudioContext, synthService sointu.SynthService) *Tr
|
|||||||
NewInstrumentBtn: new(widget.Clickable),
|
NewInstrumentBtn: new(widget.Clickable),
|
||||||
DeleteInstrumentBtn: new(widget.Clickable),
|
DeleteInstrumentBtn: new(widget.Clickable),
|
||||||
NewSongFileBtn: new(widget.Clickable),
|
NewSongFileBtn: new(widget.Clickable),
|
||||||
|
CopySongBtn: new(widget.Clickable),
|
||||||
FileMenuBtn: new(widget.Clickable),
|
FileMenuBtn: new(widget.Clickable),
|
||||||
LoadSongFileBtn: new(widget.Clickable),
|
LoadSongFileBtn: new(widget.Clickable),
|
||||||
SaveSongFileBtn: new(widget.Clickable),
|
SaveSongFileBtn: new(widget.Clickable),
|
||||||
|
Loading…
Reference in New Issue
Block a user