feat: add ability to select & move multiple units (closes #71)

This commit is contained in:
5684185+vsariola@users.noreply.github.com 2023-07-21 00:39:29 +03:00
parent 5884a8d195
commit 61776f397a
5 changed files with 126 additions and 55 deletions

View File

@ -14,16 +14,17 @@ import (
)
type DragList struct {
SelectedItem int
HoverItem int
List *layout.List
drag bool
dragID pointer.ID
tags []bool
swapped bool
focused bool
requestFocus bool
mainTag bool
SelectedItem int
SelectedItem2 int
HoverItem int
List *layout.List
drag bool
dragID pointer.ID
tags []bool
swapped bool
focused bool
requestFocus bool
mainTag bool
}
type FilledDragListStyle struct {
@ -61,9 +62,9 @@ func (s *FilledDragListStyle) Layout(gtx C) D {
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
keys := key.Set("↑|↓|Ctrl-↑|Ctrl-↓")
keys := key.Set("↑|↓|Ctrl-↑|Ctrl-↓|Shift-↑|Shift-↓")
if s.dragList.List.Axis == layout.Horizontal {
keys = key.Set("←|→|Ctrl-←|Ctrl-→")
keys = key.Set("←|→|Ctrl-←|Ctrl-→|Shift-←|Shift-→")
}
key.InputOp{Tag: &s.dragList.mainTag, Keys: keys}.Add(gtx.Ops)
@ -78,6 +79,10 @@ func (s *FilledDragListStyle) Layout(gtx C) D {
key.FocusOp{Tag: &s.dragList.mainTag}.Add(gtx.Ops)
}
if !s.dragList.focused {
s.dragList.SelectedItem2 = s.dragList.SelectedItem
}
for _, ke := range gtx.Events(&s.dragList.mainTag) {
switch ke := ke.(type) {
case key.FocusEvent:
@ -102,6 +107,9 @@ func (s *FilledDragListStyle) Layout(gtx C) D {
swap = delta
} else {
s.dragList.SelectedItem += delta
if !ke.Modifiers.Contain(key.ModShift) {
s.dragList.SelectedItem2 = s.dragList.SelectedItem
}
}
}
}
@ -119,6 +127,8 @@ func (s *FilledDragListStyle) Layout(gtx C) D {
} else {
color = s.SelectedColor
}
} else if between(s.dragList.SelectedItem, index, s.dragList.SelectedItem2) {
color = s.SelectedColor
} else if s.dragList.HoverItem == index {
color = s.HoverColor
}
@ -145,6 +155,9 @@ func (s *FilledDragListStyle) Layout(gtx C) D {
break
}
s.dragList.SelectedItem = index
if !e.Modifiers.Contain(key.ModShift) {
s.dragList.SelectedItem2 = index
}
key.FocusOp{Tag: &s.dragList.mainTag}.Add(gtx.Ops)
}
}
@ -208,12 +221,41 @@ func (s *FilledDragListStyle) Layout(gtx C) D {
)
}
dims := s.dragList.List.Layout(gtx, s.Count, listElem)
if !s.dragList.swapped && swap != 0 && s.dragList.SelectedItem+swap >= 0 && s.dragList.SelectedItem+swap < s.Count {
s.swap(s.dragList.SelectedItem, s.dragList.SelectedItem+swap)
a := intMin(s.dragList.SelectedItem, s.dragList.SelectedItem2)
b := intMax(s.dragList.SelectedItem, s.dragList.SelectedItem2)
if !s.dragList.swapped && swap != 0 && a+swap >= 0 && b+swap < s.Count {
if swap < 0 {
for i := a; i <= b; i++ {
s.swap(i, i+swap)
}
} else {
for i := b; i >= a; i-- {
s.swap(i, i+swap)
}
}
s.dragList.SelectedItem += swap
s.dragList.SelectedItem2 += swap
s.dragList.swapped = true
} else {
s.dragList.swapped = false
}
return dims
}
func between(a, b, c int) bool {
return (a <= b && b <= c) || (c <= b && b <= a)
}
func intMax(a, b int) int {
if a > b {
return a
}
return b
}
func intMin(a, b int) int {
if a < b {
return a
}
return b
}

View File

@ -453,7 +453,6 @@ func (ie *InstrumentEditor) layoutInstrumentEditor(gtx C, t *Tracker) D {
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
unitList := FilledDragList(t.Theme, ie.unitDragList, len(units), element, t.SwapUnits)
ie.unitDragList.SelectedItem = t.UnitIndex()
return Surface{Gray: 30, Focus: ie.wasFocused}.Layout(gtx, func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(func(gtx C) D {
@ -477,24 +476,23 @@ func (ie *InstrumentEditor) layoutInstrumentEditor(gtx C, t *Tracker) D {
l := len(ie.unitTypeEditor.Text())
ie.unitTypeEditor.SetCaret(l, l)
case key.NameDeleteForward:
t.DeleteUnit(true)
case "C":
contents, err := yaml.Marshal(t.Unit())
t.DeleteUnits(true, ie.unitDragList.SelectedItem, ie.unitDragList.SelectedItem2)
case "C", "X":
units := t.DeleteUnits(true, ie.unitDragList.SelectedItem, ie.unitDragList.SelectedItem2)
contents, err := yaml.Marshal(units)
if err == nil {
clipboard.WriteOp{Text: string(contents)}.Add(gtx.Ops)
t.Alert.Update("Unit copied to clipboard", Notify, time.Second*3)
alertText := "Unit(s) copied to clipboard"
if e.Name == "X" {
alertText = "Unit(s) cut to clipboard"
}
t.Alert.Update(alertText, Notify, time.Second*3)
}
case "X":
contents, err := yaml.Marshal(t.Unit())
if err == nil {
clipboard.WriteOp{Text: string(contents)}.Add(gtx.Ops)
t.Alert.Update("Unit cut to clipboard", Notify, time.Second*3)
}
t.DeleteUnit(true)
ie.unitDragList.SelectedItem2 = t.UnitIndex()
case key.NameReturn:
if e.Modifiers.Contain(key.ModShortcut) {
t.AddUnit(true)
ie.unitDragList.SelectedItem = t.UnitIndex()
ie.unitDragList.SelectedItem2 = ie.unitDragList.SelectedItem
ie.unitTypeEditor.SetText("")
}
ie.unitTypeEditor.Focus()
@ -504,7 +502,7 @@ func (ie *InstrumentEditor) layoutInstrumentEditor(gtx C, t *Tracker) D {
}
}
}
ie.unitDragList.SelectedItem = t.UnitIndex()
dims := unitList.Layout(gtx)
if t.UnitIndex() != ie.unitDragList.SelectedItem {
t.SetUnitIndex(ie.unitDragList.SelectedItem)

View File

@ -179,7 +179,7 @@ func (pe *ParamEditor) layoutUnitFooter(t *Tracker) layout.Widget {
t.InstrumentEditor.unitDragList.Focus()
}
for pe.DeleteUnitBtn.Clickable.Clicked() {
t.DeleteUnit(false)
t.DeleteUnits(false, t.UnitIndex(), t.UnitIndex())
op.InvalidateOp{}.Add(gtx.Ops)
t.InstrumentEditor.unitDragList.Focus()
}

View File

@ -67,13 +67,13 @@ type Tracker struct {
}
func (t *Tracker) UnmarshalContent(bytes []byte) error {
var unit sointu.Unit
if errJSON := json.Unmarshal(bytes, &unit); errJSON == nil {
t.PasteUnit(unit)
var units []sointu.Unit
if errJSON := json.Unmarshal(bytes, &units); errJSON == nil {
t.PasteUnits(units)
return nil
}
if errYaml := yaml.Unmarshal(bytes, &unit); errYaml == nil {
t.PasteUnit(unit)
if errYaml := yaml.Unmarshal(bytes, &units); errYaml == nil {
t.PasteUnits(units)
return nil
}
var instr sointu.Instrument

View File

@ -641,18 +641,20 @@ func (m *Model) SetUnitType(t string) {
m.notifyPatchChange()
}
func (m *Model) PasteUnit(unit sointu.Unit) {
m.saveUndo("PasteUnit", 0)
newUnits := make([]sointu.Unit, len(m.Instrument().Units)+1)
func (m *Model) PasteUnits(units []sointu.Unit) {
m.saveUndo("PasteUnits", 0)
newUnits := make([]sointu.Unit, len(m.Instrument().Units)+len(units))
m.unitIndex++
copy(newUnits, m.Instrument().Units[:m.unitIndex])
copy(newUnits[m.unitIndex+1:], m.Instrument().Units[m.unitIndex:])
if _, ok := m.usedIDs[unit.ID]; ok {
m.maxID++
unit.ID = m.maxID
copy(newUnits[m.unitIndex+len(units):], m.Instrument().Units[m.unitIndex:])
for _, unit := range units {
if _, ok := m.usedIDs[unit.ID]; ok {
m.maxID++
unit.ID = m.maxID
}
m.usedIDs[unit.ID] = true
}
m.usedIDs[unit.ID] = true
newUnits[m.unitIndex] = unit
copy(newUnits[m.unitIndex:m.unitIndex+len(units)], units)
m.song.Patch[m.instrIndex].Units = newUnits
m.paramIndex = 0
m.clampPositions()
@ -724,23 +726,38 @@ func (m *Model) DeleteOrderRow(forward bool) {
m.notifyScoreChange()
}
func (m *Model) DeleteUnit(forward bool) {
if !m.CanDeleteUnit() {
return
}
func (m *Model) DeleteUnits(forward bool, a, b int) []sointu.Unit {
instr := m.Instrument()
m.saveUndo("DeleteUnit", 0)
delete(m.usedIDs, instr.Units[m.unitIndex].ID)
newUnits := make([]sointu.Unit, len(instr.Units)-1)
copy(newUnits, instr.Units[:m.unitIndex])
copy(newUnits[m.unitIndex:], instr.Units[m.unitIndex+1:])
m.song.Patch[m.instrIndex].Units = newUnits
if !forward && m.unitIndex > 0 {
m.unitIndex--
m.saveUndo("DeleteUnits", 0)
a, b = intMin(a, b), intMax(a, b)
if a < 0 {
a = 0
}
if b > len(instr.Units)-1 {
b = len(instr.Units) - 1
}
for i := a; i <= b; i++ {
delete(m.usedIDs, instr.Units[i].ID)
}
var newUnits []sointu.Unit
if a == 0 && b == len(instr.Units)-1 {
newUnits = make([]sointu.Unit, 1)
m.unitIndex = 0
} else {
newUnits = make([]sointu.Unit, len(instr.Units)-(b-a+1))
copy(newUnits, instr.Units[:a])
copy(newUnits[a:], instr.Units[b+1:])
m.unitIndex = a
if forward {
m.unitIndex--
}
}
deletedUnits := instr.Units[a : b+1]
m.song.Patch[m.instrIndex].Units = newUnits
m.paramIndex = 0
m.clampPositions()
m.notifyPatchChange()
return deletedUnits
}
func (m *Model) CanDeleteUnit() bool {
@ -1377,3 +1394,17 @@ func clamp(a, min, max int) int {
}
return a
}
func intMax(a, b int) int {
if a > b {
return a
}
return b
}
func intMin(a, b int) int {
if a < b {
return a
}
return b
}