diff --git a/tracker/action.go b/tracker/action.go index a4144cf..18c2939 100644 --- a/tracker/action.go +++ b/tracker/action.go @@ -332,22 +332,22 @@ func (m *Redo) Do() { // AddSemiTone func (m *Model) AddSemitone() Action { return MakeEnabledAction((*AddSemitone)(m)) } -func (m *AddSemitone) Do() { Table{(*Notes)(m)}.Add(1) } +func (m *AddSemitone) Do() { Table{(*Notes)(m)}.Add(1, false) } // SubtractSemitone func (m *Model) SubtractSemitone() Action { return MakeEnabledAction((*SubtractSemitone)(m)) } -func (m *SubtractSemitone) Do() { Table{(*Notes)(m)}.Add(-1) } +func (m *SubtractSemitone) Do() { Table{(*Notes)(m)}.Add(-1, false) } // AddOctave func (m *Model) AddOctave() Action { return MakeEnabledAction((*AddOctave)(m)) } -func (m *AddOctave) Do() { Table{(*Notes)(m)}.Add(12) } +func (m *AddOctave) Do() { Table{(*Notes)(m)}.Add(1, true) } // SubtractOctave func (m *Model) SubtractOctave() Action { return MakeEnabledAction((*SubtractOctave)(m)) } -func (m *SubtractOctave) Do() { Table{(*Notes)(m)}.Add(-12) } +func (m *SubtractOctave) Do() { Table{(*Notes)(m)}.Add(-1, true) } // EditNoteOff diff --git a/tracker/gioui/keybindings.yml b/tracker/gioui/keybindings.yml index 20e257c..4b0281d 100644 --- a/tracker/gioui/keybindings.yml +++ b/tracker/gioui/keybindings.yml @@ -93,3 +93,6 @@ - { key: "P", action: "Note28" } - { key: "+", action: "Increase" } - { key: "-", action: "Decrease" } +- { key: "+", shortcut: true, action: "IncreaseMore" } # increase a large step +- { key: "-", shortcut: true, action: "DecreaseMore" } # decrease a large step + diff --git a/tracker/gioui/scroll_table.go b/tracker/gioui/scroll_table.go index 2cb1105..1f37640 100644 --- a/tracker/gioui/scroll_table.go +++ b/tracker/gioui/scroll_table.go @@ -62,7 +62,7 @@ func NewScrollTable(table tracker.Table, vertList, horizList tracker.List) *Scro } for k, a := range keyBindingMap { switch a { - case "Copy", "Paste", "Cut", "Increase", "Decrease": + case "Copy", "Paste", "Cut", "Increase", "Decrease", "IncreaseMore", "DecreaseMore": ret.eventFilters = append(ret.eventFilters, key.Filter{Focus: ret, Name: k.Name, Required: k.Modifiers}) } } @@ -305,11 +305,11 @@ func (s *ScrollTable) command(gtx C, e key.Event, p image.Point) { case "Paste": gtx.Execute(clipboard.ReadCmd{Tag: s}) return - case "Increase": - s.Table.Add(1) + case "Increase", "IncreaseMore": + s.Table.Add(1, a == "IncreaseMore") return - case "Decrease": - s.Table.Add(-1) + case "Decrease", "DecreaseMore": + s.Table.Add(-1, a == "DecreaseMore") return } } diff --git a/tracker/gioui/unit_editor.go b/tracker/gioui/unit_editor.go index f1e0beb..847e76e 100644 --- a/tracker/gioui/unit_editor.go +++ b/tracker/gioui/unit_editor.go @@ -93,6 +93,12 @@ func (pe *UnitEditor) update(gtx C, t *Tracker) { for pe.commentEditor.Update(gtx, t.UnitComment()) != EditorEventNone { t.FocusPrev(gtx, false) } + for pe.ClearUnitBtn.Clicked(gtx) { + t.ClearUnit().Do() + t.UnitSearch().SetValue("") + t.UnitSearching().SetValue(true) + pe.searchList.Focus() + } for { e, ok := gtx.Event( key.Filter{Focus: pe.searchList, Name: key.NameEnter}, @@ -123,20 +129,13 @@ func (pe *UnitEditor) update(gtx C, t *Tracker) { break } if e, ok := e.(key.Event); ok && e.State == key.Press { - params := t.Model.Params() - doRange := func(f func(p tracker.Parameter)) { - for i := params.Table().Range().TopLeft.Y; i <= params.Table().Range().BottomRight.Y; i++ { - item := params.Item(tracker.Point{X: params.Table().Range().TopLeft.X, Y: i}) - f(item) - } - } switch e.Name { case key.NameLeftArrow: - doRange(func(item tracker.Parameter) { item.Add(-1, e.Modifiers.Contain(key.ModShortcut)) }) + t.Model.Params().Table().Add(-1, e.Modifiers.Contain(key.ModShortcut)) case key.NameRightArrow: - doRange(func(item tracker.Parameter) { item.Add(1, e.Modifiers.Contain(key.ModShortcut)) }) + t.Model.Params().Table().Add(1, e.Modifiers.Contain(key.ModShortcut)) case key.NameDeleteBackward, key.NameDeleteForward: - doRange(func(item tracker.Parameter) { item.Reset() }) + t.Model.Params().Table().Clear() } } } @@ -145,6 +144,7 @@ func (pe *UnitEditor) update(gtx C, t *Tracker) { key.Filter{Focus: pe.paramTable.RowTitleList, Name: key.NameEnter}, key.Filter{Focus: pe.paramTable.RowTitleList, Name: key.NameReturn}, key.Filter{Focus: pe.paramTable.RowTitleList, Name: key.NameLeftArrow}, + key.Filter{Focus: pe.paramTable.RowTitleList, Name: key.NameDeleteBackward}, ) if !ok { break @@ -154,6 +154,7 @@ func (pe *UnitEditor) update(gtx C, t *Tracker) { case key.NameLeftArrow: t.PatchPanel.unitList.dragList.Focus() case key.NameDeleteBackward: + t.ClearUnit().Do() t.UnitSearch().SetValue("") t.UnitSearching().SetValue(true) pe.searchList.Focus() @@ -369,29 +370,25 @@ func (pe *UnitEditor) layoutFooter(gtx C) D { t := TrackerFromContext(gtx) st := t.Units().SelectedType() text := "Choose unit type" - if st != "" { + if !t.UnitSearching().Value() { text = pe.caser.String(st) } hintText := Label(t.Theme, &t.Theme.UnitEditor.Hint, text) deleteUnitBtn := ActionIconBtn(t.DeleteUnit(), t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, "Delete unit (Ctrl+Backspace)") copyUnitBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, pe.CopyUnitBtn, icons.ContentContentCopy, pe.copyHint) disableUnitBtn := ToggleIconBtn(t.UnitDisabled(), t.Theme, pe.DisableUnitBtn, icons.AVVolumeUp, icons.AVVolumeOff, pe.disableUnitHint, pe.enableUnitHint) - w := layout.Spacer{Width: t.Theme.IconButton.Enabled.Size}.Layout - if st != "" { - clearUnitBtn := ActionIconBtn(t.ClearUnit(), t.Theme, pe.ClearUnitBtn, icons.ContentClear, "Clear unit") - w = clearUnitBtn.Layout - } + clearUnitBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, pe.ClearUnitBtn, icons.ContentClear, "Clear unit") return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(deleteUnitBtn.Layout), layout.Rigid(copyUnitBtn.Layout), layout.Rigid(disableUnitBtn.Layout), - layout.Rigid(w), + layout.Rigid(clearUnitBtn.Layout), layout.Rigid(func(gtx C) D { - gtx.Constraints.Min.X = gtx.Dp(120) + gtx.Constraints.Min.X = gtx.Dp(130) return hintText.Layout(gtx) }), layout.Flexed(1, func(gtx C) D { - return pe.commentEditor.Layout(gtx, t.UnitComment(), t.Theme, &t.Theme.InstrumentEditor.UnitComment, "---") + return pe.commentEditor.Layout(gtx, t.UnitComment(), t.Theme, &t.Theme.InstrumentEditor.UnitComment, "Comment") }), ) } diff --git a/tracker/model_test.go b/tracker/model_test.go index dccc2f0..e639814 100644 --- a/tracker/model_test.go +++ b/tracker/model_test.go @@ -239,7 +239,7 @@ func (s *modelFuzzState) IterateTable(name string, table tracker.Table, yield fu table.Fill(seed % 16) }) yield(name+".Add", func(p string, t *testing.T) { - table.Add(seed % 16) + table.Add((seed>>1)%16, seed%2 == 0) }) } diff --git a/tracker/params.go b/tracker/params.go index fbe2763..e473085 100644 --- a/tracker/params.go +++ b/tracker/params.go @@ -8,6 +8,7 @@ import ( "github.com/vsariola/sointu" "github.com/vsariola/sointu/vm" + "gopkg.in/yaml.v3" ) type ( @@ -196,23 +197,68 @@ func (pt *Params) set(p Point, value int) { q := pt.Item(p) q.SetValue(value) } -func (pt *Params) add(rect Rect, delta int) (ok bool) { +func (pt *Params) add(rect Rect, delta int, largeStep bool) (ok bool) { for y := rect.TopLeft.Y; y <= rect.BottomRight.Y; y++ { for x := rect.TopLeft.X; x <= rect.BottomRight.X; x++ { p := Point{x, y} q := pt.Item(p) - if !q.SetValue(q.Value() + delta) { + if !q.Add(delta, largeStep) { return false } } } return true } -func (pt *Params) marshal(rect Rect) (data []byte, ok bool) { - panic("NOT IMPLEMENTED") + +type paramsTable struct { + Params [][]int `yaml:",flow"` } -func (pt *Params) unmarshalAtCursor(data []byte) (ok bool) { - panic("NOT IMPLEMENTED") + +func (pt *Params) marshal(rect Rect) (data []byte, ok bool) { + width := rect.BottomRight.X - rect.TopLeft.X + 1 + height := rect.BottomRight.Y - rect.TopLeft.Y + 1 + var table = paramsTable{Params: make([][]int, 0, width)} + for x := 0; x < width; x++ { + table.Params = append(table.Params, make([]int, 0, rect.BottomRight.Y-rect.TopLeft.Y+1)) + for y := 0; y < height; y++ { + p := pt.Item(Point{x + rect.TopLeft.X, y + rect.TopLeft.Y}) + table.Params[x] = append(table.Params[x], p.Value()) + } + } + ret, err := yaml.Marshal(table) + if err != nil { + return nil, false + } + return ret, true +} +func (pt *Params) unmarshal(data []byte) (paramsTable, bool) { + var table paramsTable + yaml.Unmarshal(data, &table) + if len(table.Params) == 0 { + return paramsTable{}, false + } + for i := 0; i < len(table.Params); i++ { + if len(table.Params[i]) > 0 { + return table, true + } + } + return paramsTable{}, false +} + +func (pt *Params) unmarshalAtCursor(data []byte) (ret bool) { + table, ok := pt.unmarshal(data) + if !ok { + return false + } + for i := 0; i < len(table.Params); i++ { + for j, q := range table.Params[i] { + x := i + pt.Cursor().X + y := j + pt.Cursor().Y + p := pt.Item(Point{x, y}) + ret = p.SetValue(q) || ret + } + } + return ret } func (pt *Params) unmarshalRange(rect Rect, data []byte) (ok bool) { panic("NOT IMPLEMENTED") diff --git a/tracker/table.go b/tracker/table.go index 9f78456..28e6335 100644 --- a/tracker/table.go +++ b/tracker/table.go @@ -24,7 +24,7 @@ type ( clear(p Point) set(p Point, value int) - add(rect Rect, delta int) (ok bool) + add(rect Rect, delta int, largestep bool) (ok bool) marshal(rect Rect) (data []byte, ok bool) unmarshalAtCursor(data []byte) (ok bool) unmarshalRange(rect Rect, data []byte) (ok bool) @@ -135,9 +135,9 @@ func (v Table) Fill(value int) { } } -func (v Table) Add(delta int) { +func (v Table) Add(delta int, largeStep bool) { defer v.change("Add", MinorChange)() - if !v.add(v.Range(), delta) { + if !v.add(v.Range(), delta, largeStep) { v.cancel() } } @@ -227,7 +227,10 @@ func (m *Order) set(p Point, value int) { m.d.Song.Score.Tracks[p.X].Order.Set(p.Y, value) } -func (v *Order) add(rect Rect, delta int) (ok bool) { +func (v *Order) add(rect Rect, delta int, largeStep bool) (ok bool) { + if largeStep { + delta *= 8 + } for x := rect.TopLeft.X; x <= rect.BottomRight.X; x++ { for y := rect.TopLeft.Y; y <= rect.BottomRight.Y; y++ { if !v.add1(Point{x, y}, delta) { @@ -440,7 +443,10 @@ func (v *Notes) set(p Point, value int) { v.SetValue(p, byte(value)) } -func (v *Notes) add(rect Rect, delta int) (ok bool) { +func (v *Notes) add(rect Rect, delta int, largeStep bool) (ok bool) { + if largeStep { + delta *= 12 + } for x := rect.BottomRight.X; x >= rect.TopLeft.X; x-- { for y := rect.BottomRight.Y; y >= rect.TopLeft.Y; y-- { if x < 0 || x >= len(v.d.Song.Score.Tracks) || y < 0 || y >= v.d.Song.Score.LengthInRows() {