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
@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
type DragList struct {
|
type DragList struct {
|
||||||
SelectedItem int
|
SelectedItem int
|
||||||
|
SelectedItem2 int
|
||||||
HoverItem int
|
HoverItem int
|
||||||
List *layout.List
|
List *layout.List
|
||||||
drag bool
|
drag bool
|
||||||
@ -61,9 +62,9 @@ func (s *FilledDragListStyle) Layout(gtx C) D {
|
|||||||
|
|
||||||
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
|
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()
|
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 {
|
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)
|
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)
|
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) {
|
for _, ke := range gtx.Events(&s.dragList.mainTag) {
|
||||||
switch ke := ke.(type) {
|
switch ke := ke.(type) {
|
||||||
case key.FocusEvent:
|
case key.FocusEvent:
|
||||||
@ -102,6 +107,9 @@ func (s *FilledDragListStyle) Layout(gtx C) D {
|
|||||||
swap = delta
|
swap = delta
|
||||||
} else {
|
} else {
|
||||||
s.dragList.SelectedItem += delta
|
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 {
|
} else {
|
||||||
color = s.SelectedColor
|
color = s.SelectedColor
|
||||||
}
|
}
|
||||||
|
} else if between(s.dragList.SelectedItem, index, s.dragList.SelectedItem2) {
|
||||||
|
color = s.SelectedColor
|
||||||
} else if s.dragList.HoverItem == index {
|
} else if s.dragList.HoverItem == index {
|
||||||
color = s.HoverColor
|
color = s.HoverColor
|
||||||
}
|
}
|
||||||
@ -145,6 +155,9 @@ func (s *FilledDragListStyle) Layout(gtx C) D {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
s.dragList.SelectedItem = index
|
s.dragList.SelectedItem = index
|
||||||
|
if !e.Modifiers.Contain(key.ModShift) {
|
||||||
|
s.dragList.SelectedItem2 = index
|
||||||
|
}
|
||||||
key.FocusOp{Tag: &s.dragList.mainTag}.Add(gtx.Ops)
|
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)
|
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 {
|
a := intMin(s.dragList.SelectedItem, s.dragList.SelectedItem2)
|
||||||
s.swap(s.dragList.SelectedItem, s.dragList.SelectedItem+swap)
|
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.SelectedItem += swap
|
||||||
|
s.dragList.SelectedItem2 += swap
|
||||||
s.dragList.swapped = true
|
s.dragList.swapped = true
|
||||||
} else {
|
} else {
|
||||||
s.dragList.swapped = false
|
s.dragList.swapped = false
|
||||||
}
|
}
|
||||||
return dims
|
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()
|
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
|
||||||
unitList := FilledDragList(t.Theme, ie.unitDragList, len(units), element, t.SwapUnits)
|
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 Surface{Gray: 30, Focus: ie.wasFocused}.Layout(gtx, func(gtx C) D {
|
||||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
@ -477,24 +476,23 @@ func (ie *InstrumentEditor) layoutInstrumentEditor(gtx C, t *Tracker) D {
|
|||||||
l := len(ie.unitTypeEditor.Text())
|
l := len(ie.unitTypeEditor.Text())
|
||||||
ie.unitTypeEditor.SetCaret(l, l)
|
ie.unitTypeEditor.SetCaret(l, l)
|
||||||
case key.NameDeleteForward:
|
case key.NameDeleteForward:
|
||||||
t.DeleteUnit(true)
|
t.DeleteUnits(true, ie.unitDragList.SelectedItem, ie.unitDragList.SelectedItem2)
|
||||||
case "C":
|
case "C", "X":
|
||||||
contents, err := yaml.Marshal(t.Unit())
|
units := t.DeleteUnits(true, ie.unitDragList.SelectedItem, ie.unitDragList.SelectedItem2)
|
||||||
|
contents, err := yaml.Marshal(units)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
clipboard.WriteOp{Text: string(contents)}.Add(gtx.Ops)
|
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"
|
||||||
}
|
}
|
||||||
case "X":
|
t.Alert.Update(alertText, Notify, time.Second*3)
|
||||||
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:
|
case key.NameReturn:
|
||||||
if e.Modifiers.Contain(key.ModShortcut) {
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
t.AddUnit(true)
|
t.AddUnit(true)
|
||||||
ie.unitDragList.SelectedItem = t.UnitIndex()
|
ie.unitDragList.SelectedItem2 = ie.unitDragList.SelectedItem
|
||||||
ie.unitTypeEditor.SetText("")
|
ie.unitTypeEditor.SetText("")
|
||||||
}
|
}
|
||||||
ie.unitTypeEditor.Focus()
|
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)
|
dims := unitList.Layout(gtx)
|
||||||
if t.UnitIndex() != ie.unitDragList.SelectedItem {
|
if t.UnitIndex() != ie.unitDragList.SelectedItem {
|
||||||
t.SetUnitIndex(ie.unitDragList.SelectedItem)
|
t.SetUnitIndex(ie.unitDragList.SelectedItem)
|
||||||
|
@ -179,7 +179,7 @@ func (pe *ParamEditor) layoutUnitFooter(t *Tracker) layout.Widget {
|
|||||||
t.InstrumentEditor.unitDragList.Focus()
|
t.InstrumentEditor.unitDragList.Focus()
|
||||||
}
|
}
|
||||||
for pe.DeleteUnitBtn.Clickable.Clicked() {
|
for pe.DeleteUnitBtn.Clickable.Clicked() {
|
||||||
t.DeleteUnit(false)
|
t.DeleteUnits(false, t.UnitIndex(), t.UnitIndex())
|
||||||
op.InvalidateOp{}.Add(gtx.Ops)
|
op.InvalidateOp{}.Add(gtx.Ops)
|
||||||
t.InstrumentEditor.unitDragList.Focus()
|
t.InstrumentEditor.unitDragList.Focus()
|
||||||
}
|
}
|
||||||
|
@ -67,13 +67,13 @@ type Tracker struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) UnmarshalContent(bytes []byte) error {
|
func (t *Tracker) UnmarshalContent(bytes []byte) error {
|
||||||
var unit sointu.Unit
|
var units []sointu.Unit
|
||||||
if errJSON := json.Unmarshal(bytes, &unit); errJSON == nil {
|
if errJSON := json.Unmarshal(bytes, &units); errJSON == nil {
|
||||||
t.PasteUnit(unit)
|
t.PasteUnits(units)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if errYaml := yaml.Unmarshal(bytes, &unit); errYaml == nil {
|
if errYaml := yaml.Unmarshal(bytes, &units); errYaml == nil {
|
||||||
t.PasteUnit(unit)
|
t.PasteUnits(units)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var instr sointu.Instrument
|
var instr sointu.Instrument
|
||||||
|
@ -641,18 +641,20 @@ func (m *Model) SetUnitType(t string) {
|
|||||||
m.notifyPatchChange()
|
m.notifyPatchChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) PasteUnit(unit sointu.Unit) {
|
func (m *Model) PasteUnits(units []sointu.Unit) {
|
||||||
m.saveUndo("PasteUnit", 0)
|
m.saveUndo("PasteUnits", 0)
|
||||||
newUnits := make([]sointu.Unit, len(m.Instrument().Units)+1)
|
newUnits := make([]sointu.Unit, len(m.Instrument().Units)+len(units))
|
||||||
m.unitIndex++
|
m.unitIndex++
|
||||||
copy(newUnits, m.Instrument().Units[:m.unitIndex])
|
copy(newUnits, m.Instrument().Units[:m.unitIndex])
|
||||||
copy(newUnits[m.unitIndex+1:], m.Instrument().Units[m.unitIndex:])
|
copy(newUnits[m.unitIndex+len(units):], m.Instrument().Units[m.unitIndex:])
|
||||||
|
for _, unit := range units {
|
||||||
if _, ok := m.usedIDs[unit.ID]; ok {
|
if _, ok := m.usedIDs[unit.ID]; ok {
|
||||||
m.maxID++
|
m.maxID++
|
||||||
unit.ID = 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.song.Patch[m.instrIndex].Units = newUnits
|
||||||
m.paramIndex = 0
|
m.paramIndex = 0
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
@ -724,23 +726,38 @@ func (m *Model) DeleteOrderRow(forward bool) {
|
|||||||
m.notifyScoreChange()
|
m.notifyScoreChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) DeleteUnit(forward bool) {
|
func (m *Model) DeleteUnits(forward bool, a, b int) []sointu.Unit {
|
||||||
if !m.CanDeleteUnit() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
instr := m.Instrument()
|
instr := m.Instrument()
|
||||||
m.saveUndo("DeleteUnit", 0)
|
m.saveUndo("DeleteUnits", 0)
|
||||||
delete(m.usedIDs, instr.Units[m.unitIndex].ID)
|
a, b = intMin(a, b), intMax(a, b)
|
||||||
newUnits := make([]sointu.Unit, len(instr.Units)-1)
|
if a < 0 {
|
||||||
copy(newUnits, instr.Units[:m.unitIndex])
|
a = 0
|
||||||
copy(newUnits[m.unitIndex:], instr.Units[m.unitIndex+1:])
|
}
|
||||||
m.song.Patch[m.instrIndex].Units = newUnits
|
if b > len(instr.Units)-1 {
|
||||||
if !forward && m.unitIndex > 0 {
|
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--
|
m.unitIndex--
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
deletedUnits := instr.Units[a : b+1]
|
||||||
|
m.song.Patch[m.instrIndex].Units = newUnits
|
||||||
m.paramIndex = 0
|
m.paramIndex = 0
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.notifyPatchChange()
|
m.notifyPatchChange()
|
||||||
|
return deletedUnits
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) CanDeleteUnit() bool {
|
func (m *Model) CanDeleteUnit() bool {
|
||||||
@ -1377,3 +1394,17 @@ func clamp(a, min, max int) int {
|
|||||||
}
|
}
|
||||||
return a
|
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