mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-04 01:28:45 -04:00
feat: add ability to select & move multiple units (closes #71)
This commit is contained in:
parent
5884a8d195
commit
61776f397a
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user