refactor(tracker): make Model methods return List, avoiding .List()

This commit is contained in:
5684185+vsariola@users.noreply.github.com
2026-01-23 22:42:25 +02:00
parent 74beb6760c
commit 1693d7ed5e
13 changed files with 215 additions and 322 deletions

View File

@ -145,7 +145,7 @@ func (m *AddTrack) Do() {
func (m *Model) DeleteTrack() Action { return MakeAction((*DeleteTrack)(m)) } func (m *Model) DeleteTrack() Action { return MakeAction((*DeleteTrack)(m)) }
func (m *DeleteTrack) Enabled() bool { return len(m.d.Song.Score.Tracks) > 0 } func (m *DeleteTrack) Enabled() bool { return len(m.d.Song.Score.Tracks) > 0 }
func (m *DeleteTrack) Do() { (*Model)(m).Tracks().List().DeleteElements(false) } func (m *DeleteTrack) Do() { (*Model)(m).Tracks().DeleteElements(false) }
// AddInstrument // AddInstrument
@ -164,7 +164,7 @@ func (m *AddInstrument) Do() {
func (m *Model) DeleteInstrument() Action { return MakeAction((*DeleteInstrument)(m)) } func (m *Model) DeleteInstrument() Action { return MakeAction((*DeleteInstrument)(m)) }
func (m *DeleteInstrument) Enabled() bool { return len((*Model)(m).d.Song.Patch) > 0 } func (m *DeleteInstrument) Enabled() bool { return len((*Model)(m).d.Song.Patch) > 0 }
func (m *DeleteInstrument) Do() { (*Model)(m).Instruments().List().DeleteElements(false) } func (m *DeleteInstrument) Do() { (*Model)(m).Instruments().DeleteElements(false) }
// SplitTrack // SplitTrack
@ -259,7 +259,7 @@ func (m *DeleteUnit) Enabled() bool {
} }
func (m *DeleteUnit) Do() { func (m *DeleteUnit) Do() {
defer (*Model)(m).change("DeleteUnitAction", PatchChange, MajorChange)() defer (*Model)(m).change("DeleteUnitAction", PatchChange, MajorChange)()
(*Model)(m).Units().List().DeleteElements(true) (*Model)(m).Units().DeleteElements(true)
} }
// ClearUnit // ClearUnit
@ -271,7 +271,7 @@ func (m *ClearUnit) Enabled() bool {
} }
func (m *ClearUnit) Do() { func (m *ClearUnit) Do() {
defer (*Model)(m).change("DeleteUnitAction", PatchChange, MajorChange)() defer (*Model)(m).change("DeleteUnitAction", PatchChange, MajorChange)()
l := ((*Model)(m)).Units().List() l := ((*Model)(m)).Units()
r := l.listRange() r := l.listRange()
for i := r.Start; i < r.End; i++ { for i := r.Start; i < r.End; i++ {
m.d.Song.Patch[m.d.InstrIndex].Units[i] = sointu.Unit{} m.d.Song.Patch[m.d.InstrIndex].Units[i] = sointu.Unit{}
@ -426,7 +426,7 @@ func (m *PlaySelected) Enabled() bool { return !m.instrEnlarged }
func (m *PlaySelected) Do() { func (m *PlaySelected) Do() {
(*Model)(m).setPanic(false) (*Model)(m).setPanic(false)
m.playing = true m.playing = true
l := (*Model)(m).OrderRows().List() l := (*Model)(m).OrderRows()
r := l.listRange() r := l.listRange()
newLoop := Loop{r.Start, r.End - r.Start} newLoop := Loop{r.Start, r.End - r.Start}
(*Model)(m).setLoop(newLoop) (*Model)(m).setLoop(newLoop)

View File

@ -272,6 +272,7 @@ func (m *UnitSearching) SetValue(val bool) {
return return
} }
m.d.UnitSearchString = m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex].Type m.d.UnitSearchString = m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex].Type
(*Model)(m).updateDerivedUnitSearch()
} }
// UnitDisabled methods // UnitDisabled methods
@ -290,7 +291,7 @@ func (m *UnitDisabled) SetValue(val bool) {
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) { if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
return return
} }
l := ((*Model)(m)).Units().List() l := ((*Model)(m)).Units()
r := l.listRange() r := l.listRange()
defer (*Model)(m).change("UnitDisabledSet", PatchChange, MajorChange)() defer (*Model)(m).change("UnitDisabledSet", PatchChange, MajorChange)()
for i := r.Start; i < r.End; i++ { for i := r.Start; i < r.End; i++ {
@ -315,7 +316,7 @@ func (t *LoopToggle) SetValue(val bool) {
m := (*Model)(t) m := (*Model)(t)
newLoop := Loop{} newLoop := Loop{}
if val { if val {
l := m.OrderRows().List() l := m.OrderRows()
r := l.listRange() r := l.listRange()
newLoop = Loop{r.Start, r.End - r.Start} newLoop = Loop{r.Start, r.End - r.Start}
} }

View File

@ -37,6 +37,7 @@ type (
tracks []derivedTrack tracks []derivedTrack
railError RailError railError RailError
presetSearch derivedPresetSearch presetSearch derivedPresetSearch
searchResults []string
} }
derivedInstrument struct { derivedInstrument struct {

View File

@ -128,7 +128,7 @@ func (s FilledDragListStyle) Layout(gtx C, element, bg func(gtx C, i int) D) D {
gtx.Execute(op.InvalidateCmd{}) gtx.Execute(op.InvalidateCmd{})
} }
_, isMutable := s.dragList.TrackerList.ListData.(tracker.MutableListData) isMutable := s.dragList.TrackerList.Mutable()
listElem := func(gtx C, index int) D { listElem := func(gtx C, index int) D {
for len(s.dragList.tags) <= index { for len(s.dragList.tags) <= index {

View File

@ -53,7 +53,7 @@ type (
func NewInstrumentEditor(m *tracker.Model) *InstrumentEditor { func NewInstrumentEditor(m *tracker.Model) *InstrumentEditor {
ret := &InstrumentEditor{ ret := &InstrumentEditor{
dragList: NewDragList(m.Units().List(), layout.Vertical), dragList: NewDragList(m.Units(), layout.Vertical),
addUnitBtn: new(Clickable), addUnitBtn: new(Clickable),
searchEditor: NewEditor(true, true, text.Start), searchEditor: NewEditor(true, true, text.Start),
DeleteUnitBtn: new(Clickable), DeleteUnitBtn: new(Clickable),
@ -62,8 +62,8 @@ func NewInstrumentEditor(m *tracker.Model) *InstrumentEditor {
CopyUnitBtn: new(Clickable), CopyUnitBtn: new(Clickable),
SelectTypeBtn: new(Clickable), SelectTypeBtn: new(Clickable),
commentEditor: NewEditor(true, true, text.Start), commentEditor: NewEditor(true, true, text.Start),
paramTable: NewScrollTable(m.Params().Table(), m.ParamVertList().List(), m.Units().List()), paramTable: NewScrollTable(m.Params().Table(), m.ParamVertList().List(), m.Units()),
searchList: NewDragList(m.SearchResults().List(), layout.Vertical), searchList: NewDragList(m.SearchResults(), layout.Vertical),
searching: m.UnitSearching(), searching: m.UnitSearching(),
} }
ret.caser = cases.Title(language.English) ret.caser = cases.Title(language.English)
@ -95,7 +95,7 @@ func (ul *InstrumentEditor) layoutList(gtx C) D {
element := func(gtx C, i int) D { element := func(gtx C, i int) D {
gtx.Constraints.Max.Y = gtx.Dp(20) gtx.Constraints.Max.Y = gtx.Dp(20)
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
u := t.Units().Item(i) u := t.Unit(i)
editorStyle := t.Theme.InstrumentEditor.UnitList.Name editorStyle := t.Theme.InstrumentEditor.UnitList.Name
signalError := t.RailError() signalError := t.RailError()
switch { switch {
@ -169,7 +169,7 @@ func (ul *InstrumentEditor) update(gtx C) {
case key.NameRightArrow: case key.NameRightArrow:
t.PatchPanel.instrEditor.paramTable.RowTitleList.Focus() t.PatchPanel.instrEditor.paramTable.RowTitleList.Focus()
case key.NameDeleteBackward: case key.NameDeleteBackward:
t.Units().SetSelectedType("") t.SetSelectedUnitType("")
t.UnitSearching().SetValue(true) t.UnitSearching().SetValue(true)
ul.searchEditor.Focus() ul.searchEditor.Focus()
case key.NameEnter, key.NameReturn: case key.NameEnter, key.NameReturn:
@ -185,12 +185,12 @@ func (ul *InstrumentEditor) update(gtx C) {
if str.Value() != "" { if str.Value() != "" {
for _, n := range sointu.UnitNames { for _, n := range sointu.UnitNames {
if strings.HasPrefix(n, str.Value()) { if strings.HasPrefix(n, str.Value()) {
t.Units().SetSelectedType(n) t.SetSelectedUnitType(n)
break break
} }
} }
} else { } else {
t.Units().SetSelectedType("") t.SetSelectedUnitType("")
} }
} }
ul.dragList.Focus() ul.dragList.Focus()
@ -202,7 +202,7 @@ func (ul *InstrumentEditor) update(gtx C) {
ul.searchEditor.Focus() ul.searchEditor.Focus()
} }
for ul.CopyUnitBtn.Clicked(gtx) { for ul.CopyUnitBtn.Clicked(gtx) {
if contents, ok := t.Units().List().CopyElements(); ok { if contents, ok := t.Units().CopyElements(); ok {
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))}) gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))})
t.Alerts().Add("Unit(s) copied to clipboard", tracker.Info) t.Alerts().Add("Unit(s) copied to clipboard", tracker.Info)
} }
@ -288,8 +288,8 @@ func (pe *InstrumentEditor) layoutTable(gtx C) D {
} }
func (pe *InstrumentEditor) ChooseUnitType(t *Tracker) { func (pe *InstrumentEditor) ChooseUnitType(t *Tracker) {
if ut, ok := t.SearchResults().Item(pe.searchList.TrackerList.Selected()); ok { if ut, ok := t.SearchResult(pe.searchList.TrackerList.Selected()); ok {
t.Units().SetSelectedType(ut) t.SetSelectedUnitType(ut)
pe.paramTable.RowTitleList.Focus() pe.paramTable.RowTitleList.Focus()
} }
} }
@ -321,7 +321,7 @@ func (pe *InstrumentEditor) layoutRack(gtx C) D {
if y < 0 || y >= len(pe.Parameters) { if y < 0 || y >= len(pe.Parameters) {
return D{} return D{}
} }
item := t.Units().Item(y) item := t.Unit(y)
sr := Rail(t.Theme, item.Signals) sr := Rail(t.Theme, item.Signals)
label := Label(t.Theme, &t.Theme.UnitEditor.UnitList.Name, item.Type) label := Label(t.Theme, &t.Theme.UnitEditor.UnitList.Name, item.Type)
switch { switch {
@ -360,7 +360,7 @@ func (pe *InstrumentEditor) layoutRack(gtx C) D {
} }
param := t.Model.Params().Item(point) param := t.Model.Params().Item(point)
paramStyle := Param(param, t.Theme, pe.Parameters[y][x], pe.paramTable.Table.Cursor() == point, t.Units().Item(y).Disabled) paramStyle := Param(param, t.Theme, pe.Parameters[y][x], pe.paramTable.Table.Cursor() == point, t.Unit(y).Disabled)
paramStyle.Layout(gtx) paramStyle.Layout(gtx)
if x == t.Model.Params().RowWidth(y) { if x == t.Model.Params().RowWidth(y) {
if y == cursor.Y { if y == cursor.Y {
@ -373,7 +373,7 @@ func (pe *InstrumentEditor) layoutRack(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, "---")
}) })
} else { } else {
comment := t.Units().Item(y).Comment comment := t.Unit(y).Comment
if comment != "" { if comment != "" {
style := t.Theme.InstrumentEditor.UnitComment.AsLabelStyle() style := t.Theme.InstrumentEditor.UnitComment.AsLabelStyle()
label := Label(t.Theme, &style, comment) label := Label(t.Theme, &style, comment)
@ -530,16 +530,9 @@ func (pe *InstrumentEditor) layoutFooter(gtx C) D {
func (pe *InstrumentEditor) layoutUnitTypeChooser(gtx C) D { func (pe *InstrumentEditor) layoutUnitTypeChooser(gtx C) D {
t := TrackerFromContext(gtx) t := TrackerFromContext(gtx)
var namesArray [256]string
names := namesArray[:0]
for _, item := range t.Model.SearchResults().Iterate {
names = append(names, item)
}
element := func(gtx C, i int) D { element := func(gtx C, i int) D {
if i < 0 || i >= len(names) { name, _ := t.SearchResult(i)
return D{} w := Label(t.Theme, &t.Theme.UnitEditor.Chooser, name)
}
w := Label(t.Theme, &t.Theme.UnitEditor.Chooser, names[i])
if i == pe.searchList.TrackerList.Selected() { if i == pe.searchList.TrackerList.Selected() {
return pe.SelectTypeBtn.Layout(gtx, w.Layout) return pe.SelectTypeBtn.Layout(gtx, w.Layout)
} }

View File

@ -289,7 +289,7 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
if err != nil { if err != nil {
break break
} }
instr := t.Model.Instruments().List().Selected() instr := t.Model.Instruments().Selected()
n := noteAsValue(t.Model.Octave().Value(), val-12) n := noteAsValue(t.Model.Octave().Value(), val-12)
t.KeyNoteMap.Press(e.Name, tracker.NoteEvent{Channel: instr, Note: n}) t.KeyNoteMap.Press(e.Name, tracker.NoteEvent{Channel: instr, Note: n})
} }

View File

@ -93,8 +93,8 @@ func NewNoteEditor(model *tracker.Model) *NoteEditor {
TrackMidiInBtn: new(Clickable), TrackMidiInBtn: new(Clickable),
scrollTable: NewScrollTable( scrollTable: NewScrollTable(
model.Notes().Table(), model.Notes().Table(),
model.Tracks().List(), model.Tracks(),
model.NoteRows().List(), model.NoteRows(),
), ),
} }
for k, a := range keyBindingMap { for k, a := range keyBindingMap {

View File

@ -42,8 +42,8 @@ func NewOrderEditor(m *tracker.Model) *OrderEditor {
return &OrderEditor{ return &OrderEditor{
scrollTable: NewScrollTable( scrollTable: NewScrollTable(
m.Order().Table(), m.Order().Table(),
m.Tracks().List(), m.Tracks(),
m.OrderRows().List(), m.OrderRows(),
), ),
} }
} }

View File

@ -179,7 +179,7 @@ func (it *InstrumentTools) Layout(gtx C) D {
func (it *InstrumentTools) update(gtx C, tr *Tracker) { func (it *InstrumentTools) update(gtx C, tr *Tracker) {
for it.copyInstrumentBtn.Clicked(gtx) { for it.copyInstrumentBtn.Clicked(gtx) {
if contents, ok := tr.Instruments().List().CopyElements(); ok { if contents, ok := tr.Instruments().CopyElements(); ok {
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))}) gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))})
tr.Alerts().Add("Instrument copied to clipboard", tracker.Info) tr.Alerts().Add("Instrument copied to clipboard", tracker.Info)
} }
@ -208,7 +208,7 @@ func (it *InstrumentTools) Tags(level int, yield TagYieldFunc) bool {
func MakeInstrList(model *tracker.Model) InstrumentList { func MakeInstrList(model *tracker.Model) InstrumentList {
return InstrumentList{ return InstrumentList{
instrumentDragList: NewDragList(model.Instruments().List(), layout.Horizontal), instrumentDragList: NewDragList(model.Instruments(), layout.Horizontal),
nameEditor: NewEditor(true, true, text.Middle), nameEditor: NewEditor(true, true, text.Middle),
} }
} }
@ -221,7 +221,7 @@ func (il *InstrumentList) Layout(gtx C) D {
element := func(gtx C, i int) D { element := func(gtx C, i int) D {
grabhandle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, strconv.Itoa(i+1)) grabhandle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, strconv.Itoa(i+1))
label := func(gtx C) D { label := func(gtx C) D {
name, level, mute, ok := (*tracker.Instruments)(t.Model).Item(i) name, level, mute, ok := t.Instrument(i)
if !ok { if !ok {
labelStyle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, "") labelStyle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, "")
return layout.Center.Layout(gtx, labelStyle.Layout) return layout.Center.Layout(gtx, labelStyle.Layout)

View File

@ -6,7 +6,6 @@ import (
"iter" "iter"
"math" "math"
"math/bits" "math/bits"
"strings"
"github.com/vsariola/sointu" "github.com/vsariola/sointu"
"github.com/vsariola/sointu/vm" "github.com/vsariola/sointu/vm"
@ -15,7 +14,7 @@ import (
type ( type (
List struct { List struct {
ListData data ListData
} }
ListData interface { ListData interface {
@ -27,49 +26,32 @@ type (
} }
MutableListData interface { MutableListData interface {
change(kind string, severity ChangeSeverity) func() Change(kind string, severity ChangeSeverity) func()
cancel() Cancel()
move(r Range, delta int) (ok bool) Move(r Range, delta int) (ok bool)
delete(r Range) (ok bool) Delete(r Range) (ok bool)
marshal(r Range) ([]byte, error) Marshal(r Range) ([]byte, error)
unmarshal([]byte) (r Range, err error) Unmarshal([]byte) (r Range, err error)
}
UnitListItem struct {
Type, Comment string
Disabled bool
Signals Rail
} }
// Range is used to represent a range [Start,End) of integers // Range is used to represent a range [Start,End) of integers
Range struct { Range struct {
Start, End int Start, End int
} }
UnitYieldFunc func(index int, item UnitListItem) (ok bool)
UnitSearchYieldFunc func(index int, item string) (ok bool)
Instruments Model // Instruments is a list of instruments, implementing ListData & MutableListData interfaces
Units Model // Units is a list of all the units in the selected instrument, implementing ListData & MutableListData interfaces
Tracks Model // Tracks is a list of all the tracks, implementing ListData & MutableListData interfaces
OrderRows Model // OrderRows is a list of all the order rows, implementing ListData & MutableListData interfaces
NoteRows Model // NoteRows is a list of all the note rows, implementing ListData & MutableListData interfaces
SearchResults Model // SearchResults is a unmutable list of all the search results, implementing ListData interface
) )
// Model methods func MakeList(data ListData) List { return List{data} }
func (m *Model) Instruments() *Instruments { return (*Instruments)(m) } func (l List) Selected() int { return max(min(l.data.Selected(), l.data.Count()-1), 0) }
func (m *Model) Units() *Units { return (*Units)(m) } func (l List) Selected2() int { return max(min(l.data.Selected2(), l.data.Count()-1), 0) }
func (m *Model) Tracks() *Tracks { return (*Tracks)(m) } func (l List) SetSelected(value int) { l.data.SetSelected(max(min(value, l.data.Count()-1), 0)) }
func (m *Model) OrderRows() *OrderRows { return (*OrderRows)(m) } func (l List) SetSelected2(value int) { l.data.SetSelected2(max(min(value, l.data.Count()-1), 0)) }
func (m *Model) NoteRows() *NoteRows { return (*NoteRows)(m) } func (l List) Count() int { return l.data.Count() }
func (m *Model) SearchResults() *SearchResults { return (*SearchResults)(m) }
// MoveElements moves the selected elements in a list by delta. The list must // MoveElements moves the selected elements in a list by delta. The list must
// implement the MutableListData interface. // implement the MutableListData interface.
func (v List) MoveElements(delta int) bool { func (v List) MoveElements(delta int) bool {
s, ok := v.ListData.(MutableListData) s, ok := v.data.(MutableListData)
if !ok { if !ok {
return false return false
} }
@ -77,9 +59,9 @@ func (v List) MoveElements(delta int) bool {
if delta == 0 || r.Start+delta < 0 || r.End+delta > v.Count() { if delta == 0 || r.Start+delta < 0 || r.End+delta > v.Count() {
return false return false
} }
defer s.change("MoveElements", MajorChange)() defer s.Change("MoveElements", MajorChange)()
if !s.move(r, delta) { if !s.Move(r, delta) {
s.cancel() s.Cancel()
return false return false
} }
v.SetSelected(v.Selected() + delta) v.SetSelected(v.Selected() + delta)
@ -90,7 +72,7 @@ func (v List) MoveElements(delta int) bool {
// DeleteElements deletes the selected elements in a list. The list must // DeleteElements deletes the selected elements in a list. The list must
// implement the MutableListData interface. // implement the MutableListData interface.
func (v List) DeleteElements(backwards bool) bool { func (v List) DeleteElements(backwards bool) bool {
d, ok := v.ListData.(MutableListData) d, ok := v.data.(MutableListData)
if !ok { if !ok {
return false return false
} }
@ -98,9 +80,9 @@ func (v List) DeleteElements(backwards bool) bool {
if r.Len() == 0 { if r.Len() == 0 {
return false return false
} }
defer d.change("DeleteElements", MajorChange)() defer d.Change("DeleteElements", MajorChange)()
if !d.delete(r) { if !d.Delete(r) {
d.cancel() d.Cancel()
return false return false
} }
if backwards && r.Start > 0 { if backwards && r.Start > 0 {
@ -115,7 +97,7 @@ func (v List) DeleteElements(backwards bool) bool {
// the MutableListData interface. Returns the copied data, marshaled into byte // the MutableListData interface. Returns the copied data, marshaled into byte
// slice, and true if successful. // slice, and true if successful.
func (v List) CopyElements() ([]byte, bool) { func (v List) CopyElements() ([]byte, bool) {
m, ok := v.ListData.(MutableListData) m, ok := v.data.(MutableListData)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -123,7 +105,7 @@ func (v List) CopyElements() ([]byte, bool) {
if r.Len() == 0 { if r.Len() == 0 {
return nil, false return nil, false
} }
ret, err := m.marshal(r) ret, err := m.Marshal(r)
if err != nil { if err != nil {
return nil, false return nil, false
} }
@ -134,14 +116,14 @@ func (v List) CopyElements() ([]byte, bool) {
// byte slice. The list must implement the MutableListData interface. Returns // byte slice. The list must implement the MutableListData interface. Returns
// true if successful. // true if successful.
func (v List) PasteElements(data []byte) (ok bool) { func (v List) PasteElements(data []byte) (ok bool) {
m, ok := v.ListData.(MutableListData) m, ok := v.data.(MutableListData)
if !ok { if !ok {
return false return false
} }
defer m.change("PasteElements", MajorChange)() defer m.Change("PasteElements", MajorChange)()
r, err := m.unmarshal(data) r, err := m.Unmarshal(data)
if err != nil { if err != nil {
m.cancel() m.Cancel()
return false return false
} }
v.SetSelected(r.Start) v.SetSelected(r.Start)
@ -149,19 +131,23 @@ func (v List) PasteElements(data []byte) (ok bool) {
return true return true
} }
func (v List) Mutable() bool {
_, ok := v.data.(MutableListData)
return ok
}
func (v *List) listRange() (r Range) { func (v *List) listRange() (r Range) {
r.Start = max(min(v.Selected(), v.Selected2()), 0) r.Start = max(min(v.Selected(), v.Selected2()), 0)
r.End = min(max(v.Selected(), v.Selected2())+1, v.Count()) r.End = min(max(v.Selected(), v.Selected2())+1, v.Count())
return return
} }
// Instruments methods // instruments is a list of instruments, implementing ListData & MutableListData interfaces
type instruments Model
func (v *Instruments) List() List { func (m *Model) Instruments() List { return List{(*instruments)(m)} }
return List{v}
}
func (v *Instruments) Item(i int) (name string, maxLevel float32, mute bool, ok bool) { func (v *Model) Instrument(i int) (name string, maxLevel float32, mute bool, ok bool) {
if i < 0 || i >= len(v.d.Song.Patch) { if i < 0 || i >= len(v.d.Song.Patch) {
return "", 0, false, false return "", 0, false, false
} }
@ -182,37 +168,20 @@ func (v *Instruments) Item(i int) (name string, maxLevel float32, mute bool, ok
ok = true ok = true
return return
} }
func (v *Instruments) FirstID(i int) (id int, ok bool) {
if i < 0 || i >= len(v.d.Song.Patch) {
return 0, false
}
if len(v.d.Song.Patch[i].Units) == 0 {
return 0, false
}
return v.d.Song.Patch[i].Units[0].ID, true
}
func (v *Instruments) Selected() int { func (v *instruments) Count() int { return len(v.d.Song.Patch) }
return max(min(v.d.InstrIndex, v.Count()-1), 0) func (v *instruments) Selected() int { return v.d.InstrIndex }
} func (v *instruments) Selected2() int { return v.d.InstrIndex2 }
func (v *instruments) SetSelected2(value int) { v.d.InstrIndex2 = value }
func (v *Instruments) Selected2() int { func (v *instruments) SetSelected(value int) {
return max(min(v.d.InstrIndex2, v.Count()-1), 0) v.d.InstrIndex = value
}
func (v *Instruments) SetSelected(value int) {
v.d.InstrIndex = max(min(value, v.Count()-1), 0)
v.d.UnitIndex = 0 v.d.UnitIndex = 0
v.d.UnitIndex2 = 0 v.d.UnitIndex2 = 0
v.d.UnitSearching = false v.d.UnitSearching = false
v.d.UnitSearchString = "" v.d.UnitSearchString = ""
} }
func (v *Instruments) SetSelected2(value int) { func (v *instruments) Move(r Range, delta int) (ok bool) {
v.d.InstrIndex2 = max(min(value, v.Count()-1), 0)
}
func (v *Instruments) move(r Range, delta int) (ok bool) {
voiceDelta := 0 voiceDelta := 0
if delta < 0 { if delta < 0 {
voiceDelta = -VoiceRange(v.d.Song.Patch, Range{r.Start + delta, r.Start}).Len() voiceDelta = -VoiceRange(v.d.Song.Patch, Range{r.Start + delta, r.Start}).Len()
@ -226,28 +195,24 @@ func (v *Instruments) move(r Range, delta int) (ok bool) {
return (*Model)(v).sliceInstrumentsTracks(true, v.linkInstrTrack, ranges[:]...) return (*Model)(v).sliceInstrumentsTracks(true, v.linkInstrTrack, ranges[:]...)
} }
func (v *Instruments) delete(r Range) (ok bool) { func (v *instruments) Delete(r Range) (ok bool) {
ranges := Complement(VoiceRange(v.d.Song.Patch, r)) ranges := Complement(VoiceRange(v.d.Song.Patch, r))
return (*Model)(v).sliceInstrumentsTracks(true, v.linkInstrTrack, ranges[:]...) return (*Model)(v).sliceInstrumentsTracks(true, v.linkInstrTrack, ranges[:]...)
} }
func (v *Instruments) change(n string, severity ChangeSeverity) func() { func (v *instruments) Change(n string, severity ChangeSeverity) func() {
return (*Model)(v).change("Instruments."+n, SongChange, severity) return (*Model)(v).change("Instruments."+n, SongChange, severity)
} }
func (v *Instruments) cancel() { func (v *instruments) Cancel() {
v.changeCancel = true v.changeCancel = true
} }
func (v *Instruments) Count() int { func (v *instruments) Marshal(r Range) ([]byte, error) {
return len(v.d.Song.Patch)
}
func (v *Instruments) marshal(r Range) ([]byte, error) {
return (*Model)(v).marshalVoices(VoiceRange(v.d.Song.Patch, r)) return (*Model)(v).marshalVoices(VoiceRange(v.d.Song.Patch, r))
} }
func (m *Instruments) unmarshal(data []byte) (r Range, err error) { func (m *instruments) Unmarshal(data []byte) (r Range, err error) {
voiceIndex := m.d.Song.Patch.FirstVoiceForInstrument(m.d.InstrIndex) voiceIndex := m.d.Song.Patch.FirstVoiceForInstrument(m.d.InstrIndex)
r, _, ok := (*Model)(m).unmarshalVoices(voiceIndex, data, true, m.linkInstrTrack) r, _, ok := (*Model)(m).unmarshalVoices(voiceIndex, data, true, m.linkInstrTrack)
if !ok { if !ok {
@ -256,13 +221,37 @@ func (m *Instruments) unmarshal(data []byte) (r Range, err error) {
return r, nil return r, nil
} }
// Units methods // units is a list of all the units in the selected instrument, implementing ListData & MutableListData interfaces
type (
units Model
UnitListItem struct {
Type, Comment string
Disabled bool
Signals Rail
}
)
func (v *Units) List() List { func (m *Model) Units() List { return List{(*units)(m)} }
return List{v}
func (v *Model) Unit(index int) UnitListItem {
i := v.d.InstrIndex
if i < 0 || i >= len(v.d.Song.Patch) || index < 0 || index >= (*units)(v).Count() {
return UnitListItem{}
}
unit := v.d.Song.Patch[v.d.InstrIndex].Units[index]
signals := Rail{}
if i >= 0 && i < len(v.derived.patch) && index >= 0 && index < len(v.derived.patch[i].rails) {
signals = v.derived.patch[i].rails[index]
}
return UnitListItem{
Type: unit.Type,
Comment: unit.Comment,
Disabled: unit.Disabled,
Signals: signals,
}
} }
func (m *Units) SelectedType() string { func (m *Model) SelectedUnitType() string {
if m.d.InstrIndex < 0 || if m.d.InstrIndex < 0 ||
m.d.InstrIndex >= len(m.d.Song.Patch) || m.d.InstrIndex >= len(m.d.Song.Patch) ||
m.d.UnitIndex < 0 || m.d.UnitIndex < 0 ||
@ -272,7 +261,7 @@ func (m *Units) SelectedType() string {
return m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex].Type return m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex].Type
} }
func (m *Units) SetSelectedType(t string) { func (m *Model) SetSelectedUnitType(t string) {
if m.d.InstrIndex < 0 || if m.d.InstrIndex < 0 ||
m.d.InstrIndex >= len(m.d.Song.Patch) { m.d.InstrIndex >= len(m.d.Song.Patch) {
return return
@ -293,58 +282,28 @@ func (m *Units) SetSelectedType(t string) {
if oldUnit.Type == unit.Type { if oldUnit.Type == unit.Type {
return return
} }
defer m.change("SetSelectedType", MajorChange)() defer (*units)(m).Change("SetSelectedType", MajorChange)()
m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex] = unit m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex] = unit
m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex].ID = oldUnit.ID // keep the ID of the replaced unit m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex].ID = oldUnit.ID // keep the ID of the replaced unit
} }
func (v *Units) Item(index int) UnitListItem { func (v *units) Selected() int { return v.d.UnitIndex }
i := v.d.InstrIndex func (v *units) Selected2() int { return v.d.UnitIndex2 }
if i < 0 || i >= len(v.d.Song.Patch) || index < 0 || index >= v.Count() { func (v *units) SetSelected2(value int) { v.d.UnitIndex2 = value }
return UnitListItem{} func (m *units) SetSelected(value int) {
} m.d.UnitIndex = value
unit := v.d.Song.Patch[v.d.InstrIndex].Units[index]
signals := Rail{}
if i >= 0 && i < len(v.derived.patch) && index >= 0 && index < len(v.derived.patch[i].rails) {
signals = v.derived.patch[i].rails[index]
}
return UnitListItem{
Type: unit.Type,
Comment: unit.Comment,
Disabled: unit.Disabled,
Signals: signals,
}
}
func (v *Units) Selected() int {
return max(min(v.d.UnitIndex, v.Count()-1), 0)
}
func (v *Units) Selected2() int {
return max(min(v.d.UnitIndex2, v.Count()-1), 0)
}
func (v *Units) SetSelected(value int) {
m := (*Model)(v)
m.d.UnitIndex = max(min(value, v.Count()-1), 0)
m.d.ParamIndex = 0 m.d.ParamIndex = 0
m.d.UnitSearching = false m.d.UnitSearching = false
m.d.UnitSearchString = "" m.d.UnitSearchString = ""
} }
func (v *units) Count() int {
func (v *Units) SetSelected2(value int) { if v.d.InstrIndex < 0 || v.d.InstrIndex >= len(v.d.Song.Patch) {
(*Model)(v).d.UnitIndex2 = max(min(value, v.Count()-1), 0)
}
func (v *Units) Count() int {
m := (*Model)(v)
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
return 0 return 0
} }
return len(m.d.Song.Patch[(*Model)(v).d.InstrIndex].Units) return len(v.d.Song.Patch[v.d.InstrIndex].Units)
} }
func (v *Units) move(r Range, delta int) (ok bool) { func (v *units) Move(r Range, delta int) (ok bool) {
m := (*Model)(v) m := (*Model)(v)
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) { if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
return false return false
@ -356,7 +315,7 @@ func (v *Units) move(r Range, delta int) (ok bool) {
return true return true
} }
func (v *Units) delete(r Range) (ok bool) { func (v *units) Delete(r Range) (ok bool) {
m := (*Model)(v) m := (*Model)(v)
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) { if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
return false return false
@ -366,15 +325,15 @@ func (v *Units) delete(r Range) (ok bool) {
return true return true
} }
func (v *Units) change(n string, severity ChangeSeverity) func() { func (v *units) Change(n string, severity ChangeSeverity) func() {
return (*Model)(v).change("UnitListView."+n, PatchChange, severity) return (*Model)(v).change("UnitListView."+n, PatchChange, severity)
} }
func (v *Units) cancel() { func (v *units) Cancel() {
(*Model)(v).changeCancel = true (*Model)(v).changeCancel = true
} }
func (v *Units) marshal(r Range) ([]byte, error) { func (v *units) Marshal(r Range) ([]byte, error) {
m := (*Model)(v) m := (*Model)(v)
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) { if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
return nil, errors.New("UnitListView.marshal: no instruments") return nil, errors.New("UnitListView.marshal: no instruments")
@ -387,7 +346,7 @@ func (v *Units) marshal(r Range) ([]byte, error) {
return ret, nil return ret, nil
} }
func (v *Units) unmarshal(data []byte) (r Range, err error) { func (v *units) Unmarshal(data []byte) (r Range, err error) {
m := (*Model)(v) m := (*Model)(v)
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) { if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
return Range{}, errors.New("UnitListView.unmarshal: no instruments") return Range{}, errors.New("UnitListView.unmarshal: no instruments")
@ -409,29 +368,18 @@ func (v *Units) unmarshal(data []byte) (r Range, err error) {
return Range{sel, sel + len(pastedUnits.Units)}, nil return Range{sel, sel + len(pastedUnits.Units)}, nil
} }
// Tracks methods // tracks is a list of all the tracks, implementing ListData & MutableListData interfaces
type tracks Model
func (v *Tracks) List() List { func (m *Model) Tracks() List { return List{(*tracks)(m)} }
return List{v}
}
func (v *Tracks) Selected() int { func (v *tracks) Selected() int { return v.d.Cursor.Track }
return max(min(v.d.Cursor.Track, v.Count()-1), 0) func (v *tracks) Selected2() int { return v.d.Cursor2.Track }
} func (v *tracks) SetSelected(value int) { v.d.Cursor.Track = value }
func (v *tracks) SetSelected2(value int) { v.d.Cursor2.Track = value }
func (v *tracks) Count() int { return len((*Model)(v).d.Song.Score.Tracks) }
func (v *Tracks) Selected2() int { func (v *tracks) Move(r Range, delta int) (ok bool) {
return max(min(v.d.Cursor2.Track, v.Count()-1), 0)
}
func (v *Tracks) SetSelected(value int) {
v.d.Cursor.Track = max(min(value, v.Count()-1), 0)
}
func (v *Tracks) SetSelected2(value int) {
v.d.Cursor2.Track = max(min(value, v.Count()-1), 0)
}
func (v *Tracks) move(r Range, delta int) (ok bool) {
voiceDelta := 0 voiceDelta := 0
if delta < 0 { if delta < 0 {
voiceDelta = -VoiceRange(v.d.Song.Score.Tracks, Range{r.Start + delta, r.Start}).Len() voiceDelta = -VoiceRange(v.d.Song.Score.Tracks, Range{r.Start + delta, r.Start}).Len()
@ -445,28 +393,24 @@ func (v *Tracks) move(r Range, delta int) (ok bool) {
return (*Model)(v).sliceInstrumentsTracks(v.linkInstrTrack, true, ranges[:]...) return (*Model)(v).sliceInstrumentsTracks(v.linkInstrTrack, true, ranges[:]...)
} }
func (v *Tracks) delete(r Range) (ok bool) { func (v *tracks) Delete(r Range) (ok bool) {
ranges := Complement(VoiceRange(v.d.Song.Score.Tracks, r)) ranges := Complement(VoiceRange(v.d.Song.Score.Tracks, r))
return (*Model)(v).sliceInstrumentsTracks(v.linkInstrTrack, true, ranges[:]...) return (*Model)(v).sliceInstrumentsTracks(v.linkInstrTrack, true, ranges[:]...)
} }
func (v *Tracks) change(n string, severity ChangeSeverity) func() { func (v *tracks) Change(n string, severity ChangeSeverity) func() {
return (*Model)(v).change("TrackList."+n, SongChange, severity) return (*Model)(v).change("TrackList."+n, SongChange, severity)
} }
func (v *Tracks) cancel() { func (v *tracks) Cancel() {
v.changeCancel = true v.changeCancel = true
} }
func (v *Tracks) Count() int { func (v *tracks) Marshal(r Range) ([]byte, error) {
return len((*Model)(v).d.Song.Score.Tracks)
}
func (v *Tracks) marshal(r Range) ([]byte, error) {
return (*Model)(v).marshalVoices(VoiceRange(v.d.Song.Score.Tracks, r)) return (*Model)(v).marshalVoices(VoiceRange(v.d.Song.Score.Tracks, r))
} }
func (m *Tracks) unmarshal(data []byte) (r Range, err error) { func (m *tracks) Unmarshal(data []byte) (r Range, err error) {
voiceIndex := m.d.Song.Score.FirstVoiceForTrack(m.d.Cursor.Track) voiceIndex := m.d.Song.Score.FirstVoiceForTrack(m.d.Cursor.Track)
_, r, ok := (*Model)(m).unmarshalVoices(voiceIndex, data, m.linkInstrTrack, true) _, r, ok := (*Model)(m).unmarshalVoices(voiceIndex, data, m.linkInstrTrack, true)
if !ok { if !ok {
@ -475,37 +419,23 @@ func (m *Tracks) unmarshal(data []byte) (r Range, err error) {
return r, nil return r, nil
} }
// OrderRows methods // orderRows is a list of all the order rows, implementing ListData & MutableListData interfaces
type orderRows Model
func (v *OrderRows) List() List { func (m *Model) OrderRows() List { return List{(*orderRows)(m)} }
return List{v}
}
func (v *OrderRows) Selected() int { func (v *orderRows) Count() int { return v.d.Song.Score.Length }
p := v.d.Cursor.OrderRow func (v *orderRows) Selected() int { return v.d.Cursor.OrderRow }
p = max(min(p, v.Count()-1), 0) func (v *orderRows) Selected2() int { return v.d.Cursor2.OrderRow }
return p func (v *orderRows) SetSelected2(value int) { v.d.Cursor2.OrderRow = value }
} func (v *orderRows) SetSelected(value int) {
if value != v.d.Cursor.OrderRow {
func (v *OrderRows) Selected2() int {
p := v.d.Cursor2.OrderRow
p = max(min(p, v.Count()-1), 0)
return p
}
func (v *OrderRows) SetSelected(value int) {
y := max(min(value, v.Count()-1), 0)
if y != v.d.Cursor.OrderRow {
v.follow = false v.follow = false
} }
v.d.Cursor.OrderRow = y v.d.Cursor.OrderRow = value
} }
func (v *OrderRows) SetSelected2(value int) { func (v *orderRows) Move(r Range, delta int) (ok bool) {
v.d.Cursor2.OrderRow = max(min(value, v.Count()-1), 0)
}
func (v *OrderRows) move(r Range, delta int) (ok bool) {
swaps := r.Swaps(delta) swaps := r.Swaps(delta)
for i, t := range v.d.Song.Score.Tracks { for i, t := range v.d.Song.Score.Tracks {
for a, b := range swaps { for a, b := range swaps {
@ -517,7 +447,7 @@ func (v *OrderRows) move(r Range, delta int) (ok bool) {
return true return true
} }
func (v *OrderRows) delete(r Range) (ok bool) { func (v *orderRows) Delete(r Range) (ok bool) {
for i, t := range v.d.Song.Score.Tracks { for i, t := range v.d.Song.Score.Tracks {
r2 := r.Intersect(Range{0, len(t.Order)}) r2 := r.Intersect(Range{0, len(t.Order)})
v.d.Song.Score.Tracks[i].Order = append(t.Order[:r2.Start], t.Order[r2.End:]...) v.d.Song.Score.Tracks[i].Order = append(t.Order[:r2.Start], t.Order[r2.End:]...)
@ -525,23 +455,19 @@ func (v *OrderRows) delete(r Range) (ok bool) {
return true return true
} }
func (v *OrderRows) change(n string, severity ChangeSeverity) func() { func (v *orderRows) Change(n string, severity ChangeSeverity) func() {
return (*Model)(v).change("OrderRowList."+n, ScoreChange, severity) return (*Model)(v).change("OrderRowList."+n, ScoreChange, severity)
} }
func (v *OrderRows) cancel() { func (v *orderRows) Cancel() {
v.changeCancel = true v.changeCancel = true
} }
func (v *OrderRows) Count() int {
return v.d.Song.Score.Length
}
type marshalOrderRows struct { type marshalOrderRows struct {
Columns [][]int `yaml:",flow"` Columns [][]int `yaml:",flow"`
} }
func (v *OrderRows) marshal(r Range) ([]byte, error) { func (v *orderRows) Marshal(r Range) ([]byte, error) {
var table marshalOrderRows var table marshalOrderRows
for i := range v.d.Song.Score.Tracks { for i := range v.d.Song.Score.Tracks {
table.Columns = append(table.Columns, make([]int, r.Len())) table.Columns = append(table.Columns, make([]int, r.Len()))
@ -552,7 +478,7 @@ func (v *OrderRows) marshal(r Range) ([]byte, error) {
return yaml.Marshal(table) return yaml.Marshal(table)
} }
func (v *OrderRows) unmarshal(data []byte) (r Range, err error) { func (v *orderRows) Unmarshal(data []byte) (r Range, err error) {
var table marshalOrderRows var table marshalOrderRows
err = yaml.Unmarshal(data, &table) err = yaml.Unmarshal(data, &table)
if err != nil { if err != nil {
@ -581,33 +507,23 @@ func (v *OrderRows) unmarshal(data []byte) (r Range, err error) {
return return
} }
// NoteRows methods // noteRows is a list of all the note rows, implementing ListData & MutableListData interfaces
type noteRows Model
func (v *NoteRows) List() List { func (m *Model) NoteRows() List { return List{(*noteRows)(m)} }
return List{v}
}
func (v *NoteRows) Selected() int { func (n *noteRows) Count() int { return n.d.Song.Score.Length * n.d.Song.Score.RowsPerPattern }
return v.d.Song.Score.SongRow(v.d.Song.Score.Clamp(v.d.Cursor.SongPos)) func (n *noteRows) Selected() int { return n.d.Song.Score.SongRow(n.d.Cursor.SongPos) }
} func (n *noteRows) Selected2() int { return n.d.Song.Score.SongRow(n.d.Cursor2.SongPos) }
func (n *noteRows) SetSelected2(v int) { n.d.Cursor2.SongPos = n.d.Song.Score.SongPos(v) }
func (v *NoteRows) Selected2() int { func (n *noteRows) SetSelected(value int) {
return v.d.Song.Score.SongRow(v.d.Song.Score.Clamp(v.d.Cursor2.SongPos)) if value != n.d.Song.Score.SongRow(n.d.Cursor.SongPos) {
} n.follow = false
func (v *NoteRows) SetSelected(value int) {
if value != v.d.Song.Score.SongRow(v.d.Cursor.SongPos) {
v.follow = false
} }
v.d.Cursor.SongPos = v.d.Song.Score.Clamp(v.d.Song.Score.SongPos(value)) n.d.Cursor.SongPos = n.d.Song.Score.Clamp(n.d.Song.Score.SongPos(value))
} }
func (v *NoteRows) SetSelected2(value int) { func (v *noteRows) Move(r Range, delta int) (ok bool) {
v.d.Cursor2.SongPos = v.d.Song.Score.Clamp(v.d.Song.Score.SongPos(value))
}
func (v *NoteRows) move(r Range, delta int) (ok bool) {
for a, b := range r.Swaps(delta) { for a, b := range r.Swaps(delta) {
apos := v.d.Song.Score.SongPos(a) apos := v.d.Song.Score.SongPos(a)
bpos := v.d.Song.Score.SongPos(b) bpos := v.d.Song.Score.SongPos(b)
@ -621,7 +537,7 @@ func (v *NoteRows) move(r Range, delta int) (ok bool) {
return true return true
} }
func (v *NoteRows) delete(r Range) (ok bool) { func (v *noteRows) Delete(r Range) (ok bool) {
for _, track := range v.d.Song.Score.Tracks { for _, track := range v.d.Song.Score.Tracks {
for i := r.Start; i < r.End; i++ { for i := r.Start; i < r.End; i++ {
pos := v.d.Song.Score.SongPos(i) pos := v.d.Song.Score.SongPos(i)
@ -631,23 +547,19 @@ func (v *NoteRows) delete(r Range) (ok bool) {
return true return true
} }
func (v *NoteRows) change(n string, severity ChangeSeverity) func() { func (v *noteRows) Change(n string, severity ChangeSeverity) func() {
return (*Model)(v).change("NoteRowList."+n, ScoreChange, severity) return (*Model)(v).change("NoteRowList."+n, ScoreChange, severity)
} }
func (v *NoteRows) cancel() { func (v *noteRows) Cancel() {
(*Model)(v).changeCancel = true (*Model)(v).changeCancel = true
} }
func (v *NoteRows) Count() int {
return (*Model)(v).d.Song.Score.Length * v.d.Song.Score.RowsPerPattern
}
type marshalNoteRows struct { type marshalNoteRows struct {
NoteRows [][]byte `yaml:",flow"` NoteRows [][]byte `yaml:",flow"`
} }
func (v *NoteRows) marshal(r Range) ([]byte, error) { func (v *noteRows) Marshal(r Range) ([]byte, error) {
var table marshalNoteRows var table marshalNoteRows
for i, track := range v.d.Song.Score.Tracks { for i, track := range v.d.Song.Score.Tracks {
table.NoteRows = append(table.NoteRows, make([]byte, r.Len())) table.NoteRows = append(table.NoteRows, make([]byte, r.Len()))
@ -660,7 +572,7 @@ func (v *NoteRows) marshal(r Range) ([]byte, error) {
return yaml.Marshal(table) return yaml.Marshal(table)
} }
func (v *NoteRows) unmarshal(data []byte) (r Range, err error) { func (v *noteRows) Unmarshal(data []byte) (r Range, err error) {
var table marshalNoteRows var table marshalNoteRows
if err := yaml.Unmarshal(data, &table); err != nil { if err := yaml.Unmarshal(data, &table); err != nil {
return Range{}, fmt.Errorf("NoteRowList.unmarshal: %v", err) return Range{}, fmt.Errorf("NoteRowList.unmarshal: %v", err)
@ -683,58 +595,26 @@ func (v *NoteRows) unmarshal(data []byte) (r Range, err error) {
return return
} }
// SearchResults // searchResults is a unmutable list of all the search results, implementing ListData interface
type (
searchResults Model
UnitSearchYieldFunc func(index int, item string) (ok bool)
)
func (v *SearchResults) List() List { func (m *Model) SearchResults() List { return List{(*searchResults)(m)} }
return List{v} func (l *Model) SearchResult(i int) (name string, ok bool) {
} if i < 0 || i >= len(l.derived.searchResults) {
func (l *SearchResults) Iterate(yield UnitSearchYieldFunc) {
index := 0
for _, name := range sointu.UnitNames {
if !strings.HasPrefix(name, l.d.UnitSearchString) {
continue
}
if !yield(index, name) {
break
}
index++
}
}
func (l *SearchResults) Item(index int) (name string, ok bool) {
for i, n := range l.Iterate {
if i == index {
return n, true
}
}
return "", false return "", false
}
func (l *SearchResults) Selected() int {
return max(min(l.d.UnitSearchIndex, l.Count()-1), 0)
}
func (l *SearchResults) Selected2() int {
return max(min(l.d.UnitSearchIndex, l.Count()-1), 0)
}
func (l *SearchResults) SetSelected(value int) {
l.d.UnitSearchIndex = max(min(value, l.Count()-1), 0)
}
func (l *SearchResults) SetSelected2(value int) {
}
func (l *SearchResults) Count() (count int) {
for _, n := range sointu.UnitNames {
if strings.HasPrefix(n, l.d.UnitSearchString) {
count++
} }
} return l.derived.searchResults[i], true
return
} }
func (l *searchResults) Selected() int { return l.d.UnitSearchIndex }
func (l *searchResults) Selected2() int { return l.d.UnitSearchIndex }
func (l *searchResults) SetSelected(value int) { l.d.UnitSearchIndex = value }
func (l *searchResults) SetSelected2(value int) {}
func (l *searchResults) Count() (count int) { return len(l.derived.searchResults) }
func (r Range) Len() int { return r.End - r.Start } func (r Range) Len() int { return r.End - r.Start }
func (r Range) Swaps(delta int) iter.Seq2[int, int] { func (r Range) Swaps(delta int) iter.Seq2[int, int] {

View File

@ -216,6 +216,8 @@ func NewModel(broker *Broker, synthers []sointu.Synther, midiContext MIDIContext
m.updateDeriveData(SongChange) m.updateDeriveData(SongChange)
m.presets.load() m.presets.load()
m.updateDerivedPresetSearch() m.updateDerivedPresetSearch()
m.derived.searchResults = make([]string, 0, len(sointu.UnitNames))
m.updateDerivedUnitSearch()
return m return m
} }

View File

@ -44,12 +44,12 @@ func (s *modelFuzzState) Iterate(yield func(string, func(p string, t *testing.T)
s.IterateInt("Step", s.model.Step(), yield, seed) s.IterateInt("Step", s.model.Step(), yield, seed)
s.IterateInt("Octave", s.model.Octave(), yield, seed) s.IterateInt("Octave", s.model.Octave(), yield, seed)
// Lists // Lists
s.IterateList("Instruments", s.model.Instruments().List(), yield, seed) s.IterateList("Instruments", s.model.Instruments(), yield, seed)
s.IterateList("Units", s.model.Units().List(), yield, seed) s.IterateList("Units", s.model.Units(), yield, seed)
s.IterateList("Tracks", s.model.Tracks().List(), yield, seed) s.IterateList("Tracks", s.model.Tracks(), yield, seed)
s.IterateList("OrderRows", s.model.OrderRows().List(), yield, seed) s.IterateList("OrderRows", s.model.OrderRows(), yield, seed)
s.IterateList("NoteRows", s.model.NoteRows().List(), yield, seed) s.IterateList("NoteRows", s.model.NoteRows(), yield, seed)
s.IterateList("UnitSearchResults", s.model.SearchResults().List(), yield, seed) s.IterateList("UnitSearchResults", s.model.SearchResults(), yield, seed)
s.IterateList("PresetDirs", s.model.PresetDirList().List(), yield, seed) s.IterateList("PresetDirs", s.model.PresetDirList().List(), yield, seed)
s.IterateList("PresetResults", s.model.PresetResultList().List(), yield, seed) s.IterateList("PresetResults", s.model.PresetResultList().List(), yield, seed)
// Bools // Bools

View File

@ -1,5 +1,11 @@
package tracker package tracker
import (
"strings"
"github.com/vsariola/sointu"
)
type ( type (
String struct { String struct {
value StringValue value StringValue
@ -67,8 +73,18 @@ func (v *UnitSearch) Value() string {
func (v *UnitSearch) SetValue(value string) bool { func (v *UnitSearch) SetValue(value string) bool {
v.d.UnitSearchString = value v.d.UnitSearchString = value
v.d.UnitSearching = true v.d.UnitSearching = true
(*Model)(v).updateDerivedUnitSearch()
return true return true
} }
func (v *Model) updateDerivedUnitSearch() {
// update search results based on current search string
v.derived.searchResults = v.derived.searchResults[:0]
for _, name := range sointu.UnitNames {
if strings.HasPrefix(name, v.UnitSearch().Value()) {
v.derived.searchResults = append(v.derived.searchResults, name)
}
}
}
// InstrumentNameString // InstrumentNameString