From 09c93420e4d3cd4895ad2f5210bb4739997bd4be Mon Sep 17 00:00:00 2001 From: "5684185+vsariola@users.noreply.github.com" <5684185+vsariola@users.noreply.github.com> Date: Sat, 18 Oct 2025 00:09:12 +0300 Subject: [PATCH] drafting --- tracker/gioui/focus.go | 12 +++++ tracker/gioui/instrument_editor.go | 28 +++++++++- tracker/gioui/instrument_presets.go | 28 +++++++--- tracker/gioui/instrument_properties.go | 32 ++++++----- tracker/gioui/keybindings.go | 10 +++- tracker/gioui/patch_panel.go | 75 ++++++++++++-------------- tracker/gioui/theme.go | 4 ++ tracker/gioui/theme.yml | 3 ++ 8 files changed, 125 insertions(+), 67 deletions(-) diff --git a/tracker/gioui/focus.go b/tracker/gioui/focus.go index c722baa..b90b67c 100644 --- a/tracker/gioui/focus.go +++ b/tracker/gioui/focus.go @@ -8,6 +8,9 @@ import ( ) type TagYieldFunc func(level int, tag event.Tag) bool +type Tagged interface { + Tags(level int, yield TagYieldFunc) bool +} // FocusNext navigates to the next focusable tag in the tracker. If stepInto is // true, it will focus the next tag regardless of its depth; otherwise it will @@ -77,3 +80,12 @@ func (t *Tracker) findFocusedLevel(gtx C) (level int, ok bool) { }) return level, ok } + +func firstTag(t Tagged) (tag event.Tag, ok bool) { + t.Tags(0, func(level int, t event.Tag) bool { + tag = t + ok = true + return false + }) + return tag, ok +} diff --git a/tracker/gioui/instrument_editor.go b/tracker/gioui/instrument_editor.go index 588667b..58ade67 100644 --- a/tracker/gioui/instrument_editor.go +++ b/tracker/gioui/instrument_editor.go @@ -27,6 +27,11 @@ import ( ) type ( + InstrumentEditor struct { + unitList UnitList + unitEditor UnitEditor + } + UnitList struct { dragList *DragList searchEditor *Editor @@ -54,6 +59,25 @@ type ( } ) +func MakeInstrumentEditor(model *tracker.Model) InstrumentEditor { + return InstrumentEditor{ + unitList: MakeUnitList(model), + unitEditor: *NewUnitEditor(model), + } +} + +func (ie *InstrumentEditor) layout(gtx C) D { + return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, + layout.Rigid(ie.unitList.Layout), + layout.Flexed(1, ie.unitEditor.Layout), + ) +} + +func (ie *InstrumentEditor) Tags(level int, yield TagYieldFunc) bool { + return ie.unitList.Tags(level, yield) && + ie.unitEditor.Tags(level, yield) +} + // UnitList methods func MakeUnitList(m *tracker.Model) UnitList { @@ -148,7 +172,7 @@ func (ul *UnitList) update(gtx C, t *Tracker) { if e, ok := event.(key.Event); ok && e.State == key.Press { switch e.Name { case key.NameRightArrow: - t.PatchPanel.unitEditor.paramTable.RowTitleList.Focus() + t.PatchPanel.instrEditor.unitEditor.paramTable.RowTitleList.Focus() case key.NameDeleteBackward: t.Units().SetSelectedType("") t.UnitSearching().SetValue(true) @@ -299,7 +323,7 @@ func (pe *UnitEditor) update(gtx C, t *Tracker) { if e, ok := e.(key.Event); ok && e.State == key.Press { switch e.Name { case key.NameLeftArrow: - t.PatchPanel.unitList.dragList.Focus() + t.PatchPanel.instrEditor.unitList.dragList.Focus() case key.NameDeleteBackward: t.ClearUnit().Do() t.UnitSearch().SetValue("") diff --git a/tracker/gioui/instrument_presets.go b/tracker/gioui/instrument_presets.go index 47dae0f..e92d74a 100644 --- a/tracker/gioui/instrument_presets.go +++ b/tracker/gioui/instrument_presets.go @@ -20,7 +20,6 @@ type ( userPresetsBtn *Clickable builtinPresetsBtn *Clickable clearSearchBtn *Clickable - dirBtn *Clickable dirList *DragList resultList *DragList } @@ -33,12 +32,21 @@ func NewInstrumentPresets(m *tracker.Model) *InstrumentPresets { clearSearchBtn: new(Clickable), userPresetsBtn: new(Clickable), builtinPresetsBtn: new(Clickable), - dirBtn: new(Clickable), dirList: NewDragList(m.PresetDirList().List(), layout.Vertical), resultList: NewDragList(m.PresetResultList().List(), layout.Vertical), } } +func (ip *InstrumentPresets) Tags(level int, yield TagYieldFunc) bool { + return yield(level, &ip.searchEditor.widgetEditor) && + yield(level+1, ip.clearSearchBtn) && + yield(level+1, ip.builtinPresetsBtn) && + yield(level+1, ip.userPresetsBtn) && + yield(level+1, ip.gmDlsBtn) && + yield(level, ip.dirList) && + yield(level, ip.resultList) +} + func (ip *InstrumentPresets) update(gtx C) { for { event, ok := gtx.Event( @@ -78,23 +86,27 @@ func (ip *InstrumentPresets) layout(gtx C) D { userPresetsFilterBtn := ToggleBtn(tr.UserPresetFilter(), tr.Theme, ip.userPresetsBtn, "User", "Show only user presets") builtinPresetsFilterBtn := ToggleBtn(tr.BuiltinPresetsFilter(), tr.Theme, ip.builtinPresetsBtn, "Builtin", "Show only builtin presets") dirElem := func(gtx C, i int) D { - return Label(tr.Theme, &tr.Theme.Dialog.Text, tr.Model.PresetDirList().Value(i)).Layout(gtx) + return Label(tr.Theme, &tr.Theme.InstrumentEditor.Presets.Directory, tr.Model.PresetDirList().Value(i)).Layout(gtx) } dirs := func(gtx C) D { gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(140), gtx.Constraints.Max.Y)) - style := FilledDragList(tr.Theme, ip.dirList) - dims := style.Layout(gtx, dirElem, nil) - style.LayoutScrollBar(gtx) + fdl := FilledDragList(tr.Theme, ip.dirList) + dims := fdl.Layout(gtx, dirElem, nil) + fdl.LayoutScrollBar(gtx) return dims } dirSurface := func(gtx C) D { return Surface{Gray: 30, Focus: tr.PatchPanel.TreeFocused(gtx)}.Layout(gtx, dirs) } resultElem := func(gtx C, i int) D { - return Label(tr.Theme, &tr.Theme.Dialog.Text, tr.Model.PresetResultList().Value(i)).Layout(gtx) + return Label(tr.Theme, &tr.Theme.InstrumentEditor.Presets.Result, tr.Model.PresetResultList().Value(i)).Layout(gtx) } results := func(gtx C) D { - return FilledDragList(tr.Theme, ip.resultList).Layout(gtx, resultElem, nil) + gtx.Constraints.Min.Y = gtx.Constraints.Max.Y + fdl := FilledDragList(tr.Theme, ip.resultList) + dims := fdl.Layout(gtx, resultElem, nil) + fdl.LayoutScrollBar(gtx) + return dims } bottom := func(gtx C) D { return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, diff --git a/tracker/gioui/instrument_properties.go b/tracker/gioui/instrument_properties.go index 5d6b7b1..d8d964c 100644 --- a/tracker/gioui/instrument_properties.go +++ b/tracker/gioui/instrument_properties.go @@ -14,6 +14,7 @@ import ( type ( InstrumentProperties struct { + nameEditor *Editor commentEditor *Editor list *layout.List soloBtn *Clickable @@ -31,25 +32,23 @@ type ( func NewInstrumentProperties() *InstrumentProperties { ret := &InstrumentProperties{ list: &layout.List{Axis: layout.Vertical}, + nameEditor: NewEditor(true, true, text.Start), commentEditor: NewEditor(false, false, text.Start), soloBtn: new(Clickable), muteBtn: new(Clickable), voices: NewNumericUpDownState(), splitInstrumentBtn: new(Clickable), } - ret.soloHint = makeHint("Solo", " (%s)", "SoloInstrument") - ret.unsoloHint = makeHint("Unsolo", " (%s)", "SoloInstrument") - ret.muteHint = makeHint("Mute", " (%s)", "MuteInstrument") - ret.unmuteHint = makeHint("Unmute", " (%s)", "MuteInstrument") + ret.soloHint = makeHint("Solo", " (%s)", "SoloToggle") + ret.unsoloHint = makeHint("Unsolo", " (%s)", "SoloToggle") + ret.muteHint = makeHint("Mute", " (%s)", "MuteToggle") + ret.unmuteHint = makeHint("Unmute", " (%s)", "MuteToggle") ret.splitInstrumentHint = makeHint("Split instrument", " (%s)", "SplitInstrument") return ret } -// update -func (ip *InstrumentProperties) update(gtx C, tr *Tracker) { - /*for ip.commentEditor.Update(gtx, tr.InstrumentComment()) != EditorEventNone { - tr.PatchPanel.instrList.instrumentDragList.Focus() - }*/ +func (ip *InstrumentProperties) Tags(level int, yield TagYieldFunc) bool { + return yield(level, &ip.commentEditor.widgetEditor) } // layout @@ -66,17 +65,22 @@ func (ip *InstrumentProperties) layout(gtx C) D { layout.Rigid(splitInstrumentBtn.Layout), ) } - return ip.list.Layout(gtx, 7, func(gtx C, index int) D { + + return ip.list.Layout(gtx, 9, func(gtx C, index int) D { switch index { case 0: - return layoutInstrumentPropertyLine(gtx, "Voices", voiceLine) + return layoutInstrumentPropertyLine(gtx, "Name", func(gtx C) D { + return ip.nameEditor.Layout(gtx, tr.InstrumentName(), tr.Theme, &tr.Theme.InstrumentEditor.InstrumentComment, "Instr") + }) case 2: + return layoutInstrumentPropertyLine(gtx, "Voices", voiceLine) + case 4: muteBtn := ToggleIconBtn(tr.Mute(), tr.Theme, ip.muteBtn, icons.ToggleCheckBoxOutlineBlank, icons.ToggleCheckBox, ip.muteHint, ip.unmuteHint) return layoutInstrumentPropertyLine(gtx, "Mute", muteBtn.Layout) - case 4: + case 6: soloBtn := ToggleIconBtn(tr.Solo(), tr.Theme, ip.soloBtn, icons.ToggleCheckBoxOutlineBlank, icons.ToggleCheckBox, ip.soloHint, ip.unsoloHint) return layoutInstrumentPropertyLine(gtx, "Solo", soloBtn.Layout) - case 6: + case 8: return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx C) D { return ip.commentEditor.Layout(gtx, tr.InstrumentComment(), tr.Theme, &tr.Theme.InstrumentEditor.InstrumentComment, "Comment") }) @@ -93,7 +97,7 @@ func layoutInstrumentPropertyLine(gtx C, text string, content layout.Widget) D { gtx.Constraints.Max.X = min(gtx.Dp(unit.Dp(200)), gtx.Constraints.Max.X) label := Label(tr.Theme, &tr.Theme.InstrumentEditor.Properties.Label, text) return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(layout.Spacer{Width: 6}.Layout), + layout.Rigid(layout.Spacer{Width: 6, Height: 36}.Layout), layout.Rigid(label.Layout), layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }), layout.Rigid(content), diff --git a/tracker/gioui/keybindings.go b/tracker/gioui/keybindings.go index a2115dc..9d645b2 100644 --- a/tracker/gioui/keybindings.go +++ b/tracker/gioui/keybindings.go @@ -7,6 +7,7 @@ import ( "strings" "gioui.org/io/clipboard" + "gioui.org/io/event" "gioui.org/io/key" "github.com/vsariola/sointu/tracker" "gopkg.in/yaml.v2" @@ -257,13 +258,20 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) { case "Paste": gtx.Execute(clipboard.ReadCmd{Tag: t}) case "OrderEditorFocus": + t.InstrEnlarged().SetValue(false) gtx.Execute(key.FocusCmd{Tag: t.OrderEditor.scrollTable}) case "TrackEditorFocus": + t.InstrEnlarged().SetValue(false) gtx.Execute(key.FocusCmd{Tag: t.TrackEditor.scrollTable}) case "InstrumentListFocus": gtx.Execute(key.FocusCmd{Tag: t.PatchPanel.instrList.instrumentDragList}) case "UnitListFocus": - gtx.Execute(key.FocusCmd{Tag: t.PatchPanel.unitList.dragList}) + var tag event.Tag + t.PatchPanel.BottomTags(0, func(level int, t event.Tag) bool { + tag = t + return false + }) + gtx.Execute(key.FocusCmd{Tag: tag}) case "FocusPrev": t.FocusPrev(gtx, false) case "FocusPrevInto": diff --git a/tracker/gioui/patch_panel.go b/tracker/gioui/patch_panel.go index 3a60de5..4eeec62 100644 --- a/tracker/gioui/patch_panel.go +++ b/tracker/gioui/patch_panel.go @@ -22,10 +22,10 @@ type ( PatchPanel struct { instrList InstrumentList tools InstrumentTools - unitList UnitList - unitEditor UnitEditor instrProps InstrumentProperties instrPresets InstrumentPresets + instrEditor InstrumentEditor + *tracker.Model } InstrumentList struct { @@ -38,9 +38,6 @@ type ( PresetsTab *Clickable CommentTab *Clickable - presetMenuBtn *Clickable - presetMenu MenuState - presetMenuItems []ActionMenuItem saveInstrumentBtn *Clickable loadInstrumentBtn *Clickable copyInstrumentBtn *Clickable @@ -57,8 +54,6 @@ type ( enlargeHint, shrinkHint string addInstrumentHint string - expandCommentHint string - collapseCommentHint string deleteInstrumentHint string } ) @@ -67,12 +62,12 @@ type ( func NewPatchPanel(model *tracker.Model) *PatchPanel { return &PatchPanel{ + instrEditor: MakeInstrumentEditor(model), instrList: MakeInstrList(model), tools: MakeInstrumentTools(model), - unitList: MakeUnitList(model), - unitEditor: *NewUnitEditor(model), instrProps: *NewInstrumentProperties(), instrPresets: *NewInstrumentPresets(model), + Model: model, } } @@ -85,10 +80,7 @@ func (pp *PatchPanel) Layout(gtx C) D { case tr.InstrPresets().Value(): return pp.instrPresets.layout(gtx) default: // editor - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Rigid(pp.unitList.Layout), - layout.Flexed(1, pp.unitEditor.Layout), - ) + return pp.instrEditor.layout(gtx) } } return layout.Flex{Axis: layout.Vertical}.Layout(gtx, @@ -98,11 +90,21 @@ func (pp *PatchPanel) Layout(gtx C) D { ) } +func (pp *PatchPanel) BottomTags(level int, yield TagYieldFunc) bool { + switch { + case pp.InstrComment().Value(): + return pp.instrProps.Tags(level, yield) + case pp.InstrPresets().Value(): + return pp.instrPresets.Tags(level, yield) + default: // editor + return pp.instrEditor.Tags(level, yield) + } +} + func (pp *PatchPanel) Tags(level int, yield TagYieldFunc) bool { return pp.instrList.Tags(level, yield) && pp.tools.Tags(level, yield) && - pp.unitList.Tags(level, yield) && - pp.unitEditor.Tags(level, yield) + pp.BottomTags(level, yield) } // TreeFocused returns true if any of the tags in the patch panel is focused @@ -123,9 +125,6 @@ func MakeInstrumentTools(m *tracker.Model) InstrumentTools { copyInstrumentBtn: new(Clickable), saveInstrumentBtn: new(Clickable), loadInstrumentBtn: new(Clickable), - presetMenuBtn: new(Clickable), - presetMenuItems: []ActionMenuItem{}, - expandCommentHint: makeHint("Expand comment", " (%s)", "CommentExpandedToggle"), deleteInstrumentHint: makeHint("Delete\ninstrument", "\n(%s)", "DeleteInstrument"), octave: NewNumericUpDownState(), enlargeBtn: new(Clickable), @@ -152,8 +151,8 @@ func (it *InstrumentTools) Layout(gtx C) D { instrEnlargedBtn := ToggleIconBtn(t.Model.InstrEnlarged(), t.Theme, it.enlargeBtn, icons.NavigationFullscreen, icons.NavigationFullscreenExit, it.enlargeHint, it.shrinkHint) addInstrumentBtn := ActionIconBtn(t.Model.AddInstrument(), t.Theme, it.newInstrumentBtn, icons.ContentAdd, it.addInstrumentHint) - // saveInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, it.saveInstrumentBtn, icons.ContentSave, "Save instrument") - // loadInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, it.loadInstrumentBtn, icons.FileFolderOpen, "Load instrument") + saveInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, it.saveInstrumentBtn, icons.ContentSave, "Save instrument") + loadInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, it.loadInstrumentBtn, icons.FileFolderOpen, "Load instrument") copyInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, it.copyInstrumentBtn, icons.ContentContentCopy, "Copy instrument") deleteInstrumentBtn := ActionIconBtn(t.DeleteInstrument(), t.Theme, it.deleteInstrumentBtn, icons.ActionDelete, it.deleteInstrumentHint) btns := func(gtx C) D { @@ -164,33 +163,17 @@ func (it *InstrumentTools) Layout(gtx C) D { layout.Rigid(commentBtn.Layout), layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }), layout.Rigid(layout.Spacer{Width: 4}.Layout), - /*layout.Rigid(func(gtx C) D { - presetBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, it.presetMenuBtn, icons.NavigationMenu, "Load preset") - dims := presetBtn.Layout(gtx) - op.Offset(image.Pt(0, dims.Size.Y)).Add(gtx.Ops) - m := Menu(t.Theme, &it.presetMenu) - m.Style = &t.Theme.Menu.Preset - m.Layout(gtx, it.presetMenuItems...) - return dims - }),*/ - // layout.Rigid(saveInstrumentBtn.Layout), - // layout.Rigid(loadInstrumentBtn.Layout), layout.Rigid(Label(t.Theme, &t.Theme.InstrumentEditor.Octave, "Octave").Layout), layout.Rigid(octave.Layout), layout.Rigid(linkInstrTrackBtn.Layout), layout.Rigid(instrEnlargedBtn.Layout), layout.Rigid(copyInstrumentBtn.Layout), + layout.Rigid(saveInstrumentBtn.Layout), + layout.Rigid(loadInstrumentBtn.Layout), layout.Rigid(deleteInstrumentBtn.Layout), layout.Rigid(addInstrumentBtn.Layout), ) } - /*comment := func(gtx C) D { - defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop() - ret := layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx C) D { - return it.commentEditor.Layout(gtx, t.InstrumentComment(), t.Theme, &t.Theme.InstrumentEditor.InstrumentComment, "Comment") - }) - return ret - }*/ return Surface{Gray: 37, Focus: t.PatchPanel.TreeFocused(gtx)}.Layout(gtx, btns) } @@ -215,9 +198,6 @@ func (it *InstrumentTools) update(gtx C, tr *Tracker) { } tr.LoadInstrument(reader) } - for it.presetMenuBtn.Clicked(gtx) { - it.presetMenu.visible = true - } } func (it *InstrumentTools) Tags(level int, yield TagYieldFunc) bool { @@ -298,7 +278,18 @@ func (il *InstrumentList) update(gtx C, t *Tracker) { if e, ok := event.(key.Event); ok && e.State == key.Press { switch e.Name { case key.NameDownArrow: - t.PatchPanel.unitList.dragList.Focus() + var tagged Tagged + switch { + case t.InstrComment().Value(): + tagged = &t.PatchPanel.instrProps + case t.InstrPresets().Value(): + tagged = &t.PatchPanel.instrPresets + default: // editor + tagged = &t.PatchPanel.instrEditor + } + if tag, ok := firstTag(tagged); ok { + gtx.Execute(key.FocusCmd{Tag: tag}) + } case key.NameReturn, key.NameEnter: il.nameEditor.Focus() } diff --git a/tracker/gioui/theme.go b/tracker/gioui/theme.go index 7437d08..836d531 100644 --- a/tracker/gioui/theme.go +++ b/tracker/gioui/theme.go @@ -91,6 +91,10 @@ type Theme struct { Warning color.NRGBA Error color.NRGBA } + Presets struct { + Directory LabelStyle + Result LabelStyle + } } UnitEditor struct { Name LabelStyle diff --git a/tracker/gioui/theme.yml b/tracker/gioui/theme.yml index 5c74e7e..f5f4ef5 100644 --- a/tracker/gioui/theme.yml +++ b/tracker/gioui/theme.yml @@ -190,6 +190,9 @@ instrumenteditor: disabled: { textsize: 12, color: *disabled } warning: *warningcolor error: *errorcolor + presets: + directory: { textsize: 12, color: *white, maxlines: 1 } + result: { textsize: 12, color: *white, maxlines: 1 } cursor: active: { r: 100, g: 140, b: 255, a: 48 } activealt: { r: 255, g: 100, b: 140, a: 48 }