drafting preset explorer

This commit is contained in:
5684185+vsariola@users.noreply.github.com
2025-10-15 12:19:25 +03:00
parent 3f365707c2
commit 57aef3bed3
10 changed files with 274 additions and 190 deletions

View File

@ -11,22 +11,24 @@ type (
SetValue(bool) SetValue(bool)
} }
Panic Model Panic Model
IsRecording Model IsRecording Model
Playing Model Playing Model
InstrEnlarged Model InstrEnlarged Model
Effect Model Effect Model
TrackMidiIn Model TrackMidiIn Model
CommentExpanded Model Follow Model
Follow Model UnitSearching Model
UnitSearching Model UnitDisabled Model
UnitDisabled Model LoopToggle Model
LoopToggle Model UniquePatterns Model
UniquePatterns Model Mute Model
Mute Model Solo Model
Solo Model LinkInstrTrack Model
LinkInstrTrack Model Oversampling Model
Oversampling Model InstrEditor Model
InstrPresets Model
InstrComment Model
) )
func MakeBool(valueEnabler interface { func MakeBool(valueEnabler interface {
@ -101,11 +103,31 @@ func (m *Model) InstrEnlarged() Bool { return MakeEnabledBool((*InstrEnlar
func (m *InstrEnlarged) Value() bool { return m.instrEnlarged } func (m *InstrEnlarged) Value() bool { return m.instrEnlarged }
func (m *InstrEnlarged) SetValue(val bool) { m.instrEnlarged = val } func (m *InstrEnlarged) SetValue(val bool) { m.instrEnlarged = val }
// CommentExpanded methods // InstrEditor methods
func (m *Model) CommentExpanded() Bool { return MakeEnabledBool((*CommentExpanded)(m)) } func (m *Model) InstrEditor() Bool { return MakeEnabledBool((*InstrEditor)(m)) }
func (m *CommentExpanded) Value() bool { return m.commentExpanded } func (m *InstrEditor) Value() bool { return m.d.InstrumentTab == InstrumentEditorTab }
func (m *CommentExpanded) SetValue(val bool) { m.commentExpanded = val } func (m *InstrEditor) SetValue(val bool) {
if val {
m.d.InstrumentTab = InstrumentEditorTab
}
}
func (m *Model) InstrComment() Bool { return MakeEnabledBool((*InstrComment)(m)) }
func (m *InstrComment) Value() bool { return m.d.InstrumentTab == InstrumentCommentTab }
func (m *InstrComment) SetValue(val bool) {
if val {
m.d.InstrumentTab = InstrumentCommentTab
}
}
func (m *Model) InstrPresets() Bool { return MakeEnabledBool((*InstrPresets)(m)) }
func (m *InstrPresets) Value() bool { return m.d.InstrumentTab == InstrumentPresetsTab }
func (m *InstrPresets) SetValue(val bool) {
if val {
m.d.InstrumentTab = InstrumentPresetsTab
}
}
// Follow methods // Follow methods

View File

@ -186,8 +186,5 @@ success:
instrument.NumVoices = clamp(instrument.NumVoices, 1, 32-numVoices) instrument.NumVoices = clamp(instrument.NumVoices, 1, 32-numVoices)
m.assignUnitIDs(instrument.Units) m.assignUnitIDs(instrument.Units)
m.d.Song.Patch[m.d.InstrIndex] = instrument m.d.Song.Patch[m.d.InstrIndex] = instrument
if m.d.Song.Patch[m.d.InstrIndex].Comment != "" {
m.commentExpanded = true
}
return true return true
} }

View File

@ -73,6 +73,13 @@ type (
Button Button
} }
// TabButton is a button used in a tab bar.
TabButton struct {
IndicatorHeight unit.Dp
IndicatorColor color.NRGBA
ToggleButton
}
// IconButton is a button with an icon. // IconButton is a button with an icon.
IconButton struct { IconButton struct {
Theme *Theme Theme *Theme
@ -126,6 +133,19 @@ func ToggleBtn(b tracker.Bool, th *Theme, c *Clickable, text string, tip string)
} }
} }
func TabBtn(b tracker.Bool, th *Theme, c *Clickable, text string, tip string) TabButton {
return TabButton{
IndicatorHeight: th.Button.Tab.IndicatorHeight,
IndicatorColor: th.Button.Tab.IndicatorColor,
ToggleButton: ToggleButton{
Bool: b,
DisabledStyle: &th.Button.Disabled,
OffStyle: &th.Button.Tab.Inactive,
Button: Btn(th, &th.Button.Tab.Active, c, text, tip),
},
}
}
func IconBtn(th *Theme, st *IconButtonStyle, c *Clickable, icon []byte, tip string) IconButton { func IconBtn(th *Theme, st *IconButtonStyle, c *Clickable, icon []byte, tip string) IconButton {
return IconButton{ return IconButton{
Theme: th, Theme: th,
@ -288,6 +308,26 @@ func (b *ToggleIconButton) Layout(gtx C) D {
return b.IconButton.Layout(gtx) return b.IconButton.Layout(gtx)
} }
func (b *TabButton) Layout(gtx C) D {
return layout.Stack{Alignment: layout.S}.Layout(gtx,
layout.Stacked(b.ToggleButton.Layout),
layout.Expanded(func(gtx C) D {
if !b.ToggleButton.Bool.Value() {
return D{}
}
w := gtx.Constraints.Min.X
h := gtx.Dp(b.IndicatorHeight)
r := clip.RRect{
Rect: image.Rect(0, 0, w, h),
NE: h, NW: h, SE: 0, SW: 0,
}
defer r.Push(gtx.Ops).Pop()
paint.Fill(gtx.Ops, b.IndicatorColor)
return layout.Dimensions{Size: image.Pt(w, h)}
}),
)
}
// Click executes a simple programmatic click. // Click executes a simple programmatic click.
func (b *Clickable) Click() { func (b *Clickable) Click() {
b.requestClicks++ b.requestClicks++

View File

@ -208,8 +208,6 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
t.InstrEnlarged().Toggle() t.InstrEnlarged().Toggle()
case "LinkInstrTrackToggle": case "LinkInstrTrackToggle":
t.LinkInstrTrack().Toggle() t.LinkInstrTrack().Toggle()
case "CommentExpandedToggle":
t.CommentExpanded().Toggle()
case "FollowToggle": case "FollowToggle":
t.Follow().Toggle() t.Follow().Toggle()
case "UnitDisabledToggle": case "UnitDisabledToggle":

View File

@ -6,17 +6,14 @@ import (
"image/color" "image/color"
"io" "io"
"strconv" "strconv"
"strings"
"gioui.org/io/clipboard" "gioui.org/io/clipboard"
"gioui.org/io/event" "gioui.org/io/event"
"gioui.org/io/key" "gioui.org/io/key"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip" "gioui.org/op/clip"
"gioui.org/text" "gioui.org/text"
"gioui.org/unit" "gioui.org/unit"
"github.com/vsariola/sointu"
"github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/tracker"
"golang.org/x/exp/shiny/materialdesign/icons" "golang.org/x/exp/shiny/materialdesign/icons"
) )
@ -46,6 +43,10 @@ type (
} }
InstrumentTools struct { InstrumentTools struct {
EditorTab *Clickable
PresetsTab *Clickable
CommentTab *Clickable
Voices *NumericUpDownState Voices *NumericUpDownState
splitInstrumentBtn *Clickable splitInstrumentBtn *Clickable
commentExpandBtn *Clickable commentExpandBtn *Clickable
@ -60,8 +61,6 @@ type (
copyInstrumentBtn *Clickable copyInstrumentBtn *Clickable
deleteInstrumentBtn *Clickable deleteInstrumentBtn *Clickable
commentExpanded tracker.Bool
muteHint, unmuteHint string muteHint, unmuteHint string
soloHint, unsoloHint string soloHint, unsoloHint string
expandCommentHint string expandCommentHint string
@ -69,13 +68,6 @@ type (
splitInstrumentHint string splitInstrumentHint string
deleteInstrumentHint string deleteInstrumentHint string
} }
UnitList struct {
dragList *DragList
searchEditor *Editor
addUnitBtn *Clickable
addUnitAction tracker.Action
}
) )
// PatchPanel methods // PatchPanel methods
@ -119,6 +111,9 @@ func (pp *PatchPanel) TreeFocused(gtx C) bool {
func MakeInstrumentTools(m *tracker.Model) InstrumentTools { func MakeInstrumentTools(m *tracker.Model) InstrumentTools {
ret := InstrumentTools{ ret := InstrumentTools{
EditorTab: new(Clickable),
PresetsTab: new(Clickable),
CommentTab: new(Clickable),
Voices: NewNumericUpDownState(), Voices: NewNumericUpDownState(),
deleteInstrumentBtn: new(Clickable), deleteInstrumentBtn: new(Clickable),
splitInstrumentBtn: new(Clickable), splitInstrumentBtn: new(Clickable),
@ -131,7 +126,6 @@ func MakeInstrumentTools(m *tracker.Model) InstrumentTools {
muteBtn: new(Clickable), muteBtn: new(Clickable),
presetMenuItems: []ActionMenuItem{}, presetMenuItems: []ActionMenuItem{},
commentEditor: NewEditor(false, false, text.Start), commentEditor: NewEditor(false, false, text.Start),
commentExpanded: m.CommentExpanded(),
expandCommentHint: makeHint("Expand comment", " (%s)", "CommentExpandedToggle"), expandCommentHint: makeHint("Expand comment", " (%s)", "CommentExpandedToggle"),
collapseCommentHint: makeHint("Collapse comment", " (%s)", "CommentExpandedToggle"), collapseCommentHint: makeHint("Collapse comment", " (%s)", "CommentExpandedToggle"),
deleteInstrumentHint: makeHint("Delete\ninstrument", "\n(%s)", "DeleteInstrument"), deleteInstrumentHint: makeHint("Delete\ninstrument", "\n(%s)", "DeleteInstrument"),
@ -151,27 +145,33 @@ func (it *InstrumentTools) Layout(gtx C) D {
t := TrackerFromContext(gtx) t := TrackerFromContext(gtx)
it.update(gtx, t) it.update(gtx, t)
voicesLabel := Label(t.Theme, &t.Theme.InstrumentEditor.Voices, "Voices") voicesLabel := Label(t.Theme, &t.Theme.InstrumentEditor.Voices, "Voices")
editorBtn := TabBtn(t.Model.InstrEditor(), t.Theme, it.EditorTab, "Editor", "")
presetsBtn := TabBtn(t.Model.InstrPresets(), t.Theme, it.PresetsTab, "Presets", "")
commentBtn := TabBtn(t.Model.InstrComment(), t.Theme, it.CommentTab, "Comment", "")
splitInstrumentBtn := ActionIconBtn(t.SplitInstrument(), t.Theme, it.splitInstrumentBtn, icons.CommunicationCallSplit, it.splitInstrumentHint) splitInstrumentBtn := ActionIconBtn(t.SplitInstrument(), t.Theme, it.splitInstrumentBtn, icons.CommunicationCallSplit, it.splitInstrumentHint)
commentExpandedBtn := ToggleIconBtn(t.CommentExpanded(), t.Theme, it.commentExpandBtn, icons.NavigationExpandMore, icons.NavigationExpandLess, it.expandCommentHint, it.collapseCommentHint)
soloBtn := ToggleIconBtn(t.Solo(), t.Theme, it.soloBtn, icons.SocialGroup, icons.SocialPerson, it.soloHint, it.unsoloHint) soloBtn := ToggleIconBtn(t.Solo(), t.Theme, it.soloBtn, icons.SocialGroup, icons.SocialPerson, it.soloHint, it.unsoloHint)
muteBtn := ToggleIconBtn(t.Mute(), t.Theme, it.muteBtn, icons.AVVolumeUp, icons.AVVolumeOff, it.muteHint, it.unmuteHint) muteBtn := ToggleIconBtn(t.Mute(), t.Theme, it.muteBtn, icons.AVVolumeUp, icons.AVVolumeOff, it.muteHint, it.unmuteHint)
saveInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, it.saveInstrumentBtn, icons.ContentSave, "Save 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") // 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") 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) deleteInstrumentBtn := ActionIconBtn(t.DeleteInstrument(), t.Theme, it.deleteInstrumentBtn, icons.ActionDelete, it.deleteInstrumentHint)
instrumentVoices := NumUpDown(t.Model.InstrumentVoices(), t.Theme, it.Voices, "Number of voices for this instrument") instrumentVoices := NumUpDown(t.Model.InstrumentVoices(), t.Theme, it.Voices, "Number of voices for this instrument")
btns := func(gtx C) D { btns := func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(layout.Spacer{Width: 6}.Layout), layout.Rigid(layout.Spacer{Width: 6}.Layout),
layout.Rigid(editorBtn.Layout),
layout.Rigid(presetsBtn.Layout),
layout.Rigid(commentBtn.Layout),
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
layout.Rigid(voicesLabel.Layout), layout.Rigid(voicesLabel.Layout),
layout.Rigid(layout.Spacer{Width: 4}.Layout), layout.Rigid(layout.Spacer{Width: 4}.Layout),
layout.Rigid(instrumentVoices.Layout), layout.Rigid(instrumentVoices.Layout),
layout.Rigid(splitInstrumentBtn.Layout), layout.Rigid(splitInstrumentBtn.Layout),
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
layout.Rigid(commentExpandedBtn.Layout),
layout.Rigid(soloBtn.Layout), layout.Rigid(soloBtn.Layout),
layout.Rigid(muteBtn.Layout), layout.Rigid(muteBtn.Layout),
layout.Rigid(func(gtx C) D { /*layout.Rigid(func(gtx C) D {
presetBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, it.presetMenuBtn, icons.NavigationMenu, "Load preset") presetBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, it.presetMenuBtn, icons.NavigationMenu, "Load preset")
dims := presetBtn.Layout(gtx) dims := presetBtn.Layout(gtx)
op.Offset(image.Pt(0, dims.Size.Y)).Add(gtx.Ops) op.Offset(image.Pt(0, dims.Size.Y)).Add(gtx.Ops)
@ -179,26 +179,21 @@ func (it *InstrumentTools) Layout(gtx C) D {
m.Style = &t.Theme.Menu.Preset m.Style = &t.Theme.Menu.Preset
m.Layout(gtx, it.presetMenuItems...) m.Layout(gtx, it.presetMenuItems...)
return dims return dims
}), }),*/
layout.Rigid(saveInstrumentBtn.Layout), // layout.Rigid(saveInstrumentBtn.Layout),
layout.Rigid(loadInstrumentBtn.Layout), // layout.Rigid(loadInstrumentBtn.Layout),
layout.Rigid(copyInstrumentBtn.Layout), layout.Rigid(copyInstrumentBtn.Layout),
layout.Rigid(deleteInstrumentBtn.Layout), layout.Rigid(deleteInstrumentBtn.Layout),
) )
} }
comment := func(gtx C) D { /*comment := func(gtx C) D {
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()
ret := layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx C) D { 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 it.commentEditor.Layout(gtx, t.InstrumentComment(), t.Theme, &t.Theme.InstrumentEditor.InstrumentComment, "Comment")
}) })
return ret return ret
} }*/
return Surface{Gray: 37, Focus: t.PatchPanel.TreeFocused(gtx)}.Layout(gtx, func(gtx C) D { return Surface{Gray: 37, Focus: t.PatchPanel.TreeFocused(gtx)}.Layout(gtx, btns)
if t.CommentExpanded().Value() {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(btns), layout.Rigid(comment))
}
return btns(gtx)
})
} }
func (it *InstrumentTools) update(gtx C, tr *Tracker) { func (it *InstrumentTools) update(gtx C, tr *Tracker) {
@ -231,9 +226,6 @@ func (it *InstrumentTools) update(gtx C, tr *Tracker) {
} }
func (it *InstrumentTools) Tags(level int, yield TagYieldFunc) bool { func (it *InstrumentTools) Tags(level int, yield TagYieldFunc) bool {
if it.commentExpanded.Value() {
return yield(level+1, &it.commentEditor.widgetEditor)
}
return true return true
} }
@ -351,132 +343,3 @@ func (il *InstrumentList) update(gtx C, t *Tracker) {
func (il *InstrumentList) Tags(level int, yield TagYieldFunc) bool { func (il *InstrumentList) Tags(level int, yield TagYieldFunc) bool {
return yield(level, il.instrumentDragList) return yield(level, il.instrumentDragList)
} }
// UnitList methods
func MakeUnitList(m *tracker.Model) UnitList {
ret := UnitList{
dragList: NewDragList(m.Units().List(), layout.Vertical),
addUnitBtn: new(Clickable),
searchEditor: NewEditor(true, true, text.Start),
}
ret.addUnitAction = tracker.MakeEnabledAction(tracker.DoFunc(func() {
m.AddUnit(false).Do()
ret.searchEditor.Focus()
}))
return ret
}
func (ul *UnitList) Layout(gtx C) D {
t := TrackerFromContext(gtx)
ul.update(gtx, t)
element := func(gtx C, i int) D {
gtx.Constraints.Max.Y = gtx.Dp(20)
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
u := t.Units().Item(i)
editorStyle := t.Theme.InstrumentEditor.UnitList.Name
signalError := t.RailError()
switch {
case u.Disabled:
editorStyle = t.Theme.InstrumentEditor.UnitList.NameDisabled
case signalError.Err != nil && signalError.UnitIndex == i:
editorStyle.Color = t.Theme.InstrumentEditor.UnitList.Error
}
unitName := func(gtx C) D {
if i == ul.dragList.TrackerList.Selected() {
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
return ul.searchEditor.Layout(gtx, t.Model.UnitSearch(), t.Theme, &editorStyle, "---")
} else {
text := u.Type
if text == "" {
text = "---"
}
l := editorStyle.AsLabelStyle()
return Label(t.Theme, &l, text).Layout(gtx)
}
}
stackText := strconv.FormatInt(int64(u.Signals.StackAfter()), 10)
commentLabel := Label(t.Theme, &t.Theme.InstrumentEditor.UnitList.Comment, u.Comment)
stackLabel := Label(t.Theme, &t.Theme.InstrumentEditor.UnitList.Stack, stackText)
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(unitName),
layout.Rigid(layout.Spacer{Width: 5}.Layout),
layout.Flexed(1, commentLabel.Layout),
layout.Rigid(stackLabel.Layout),
layout.Rigid(layout.Spacer{Width: 10}.Layout),
)
}
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
unitList := FilledDragList(t.Theme, ul.dragList)
surface := func(gtx C) D {
return layout.Stack{Alignment: layout.SE}.Layout(gtx,
layout.Expanded(func(gtx C) D {
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(140), gtx.Constraints.Max.Y))
dims := unitList.Layout(gtx, element, nil)
unitList.LayoutScrollBar(gtx)
return dims
}),
layout.Stacked(func(gtx C) D {
margin := layout.Inset{Right: unit.Dp(20), Bottom: unit.Dp(1)}
addUnitBtn := IconBtn(t.Theme, &t.Theme.IconButton.Emphasis, ul.addUnitBtn, icons.ContentAdd, "Add unit (Enter)")
return margin.Layout(gtx, addUnitBtn.Layout)
}),
)
}
return Surface{Gray: 30, Focus: t.PatchPanel.TreeFocused(gtx)}.Layout(gtx, surface)
}
func (ul *UnitList) update(gtx C, t *Tracker) {
for ul.addUnitBtn.Clicked(gtx) {
ul.addUnitAction.Do()
t.UnitSearching().SetValue(true)
ul.searchEditor.Focus()
}
for {
event, ok := gtx.Event(
key.Filter{Focus: ul.dragList, Name: key.NameRightArrow},
key.Filter{Focus: ul.dragList, Name: key.NameEnter, Optional: key.ModCtrl},
key.Filter{Focus: ul.dragList, Name: key.NameReturn, Optional: key.ModCtrl},
key.Filter{Focus: ul.dragList, Name: key.NameDeleteBackward},
)
if !ok {
break
}
if e, ok := event.(key.Event); ok && e.State == key.Press {
switch e.Name {
case key.NameRightArrow:
t.PatchPanel.unitEditor.paramTable.RowTitleList.Focus()
case key.NameDeleteBackward:
t.Units().SetSelectedType("")
t.UnitSearching().SetValue(true)
ul.searchEditor.Focus()
case key.NameEnter, key.NameReturn:
t.Model.AddUnit(e.Modifiers.Contain(key.ModCtrl)).Do()
t.UnitSearching().SetValue(true)
ul.searchEditor.Focus()
}
}
}
str := t.Model.UnitSearch()
for ev := ul.searchEditor.Update(gtx, str); ev != EditorEventNone; ev = ul.searchEditor.Update(gtx, str) {
if ev == EditorEventSubmit {
if str.Value() != "" {
for _, n := range sointu.UnitNames {
if strings.HasPrefix(n, str.Value()) {
t.Units().SetSelectedType(n)
break
}
}
} else {
t.Units().SetSelectedType("")
}
}
ul.dragList.Focus()
t.UnitSearching().SetValue(false)
}
}
func (ul *UnitList) Tags(curLevel int, yield TagYieldFunc) bool {
return yield(curLevel, ul.dragList) && yield(curLevel+1, &ul.searchEditor.widgetEditor)
}

View File

@ -19,6 +19,12 @@ type Theme struct {
Text ButtonStyle Text ButtonStyle
Disabled ButtonStyle Disabled ButtonStyle
Menu ButtonStyle Menu ButtonStyle
Tab struct {
Active ButtonStyle
Inactive ButtonStyle
IndicatorHeight unit.Dp
IndicatorColor color.NRGBA
}
} }
IconButton struct { IconButton struct {
Enabled IconButtonStyle Enabled IconButtonStyle

View File

@ -54,6 +54,17 @@ button:
cornerradius: 0 cornerradius: 0
height: *buttonheight height: *buttonheight
inset: *buttoninset inset: *buttoninset
tab:
active: *textbutton
inactive:
background: *transparentcolor
color: *highemphasis
textsize: *buttontextsize
cornerradius: *buttoncornerradius
height: *buttonheight
inset: *buttoninset
indicatorheight: 2
indicatorcolor: *primarycolor
iconbutton: iconbutton:
enabled: enabled:
color: *primarycolor color: *primarycolor

View File

@ -6,6 +6,8 @@ import (
"image/color" "image/color"
"io" "io"
"math" "math"
"strconv"
"strings"
"time" "time"
"gioui.org/f32" "gioui.org/f32"
@ -16,6 +18,8 @@ import (
"gioui.org/op/clip" "gioui.org/op/clip"
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/text" "gioui.org/text"
"gioui.org/unit"
"github.com/vsariola/sointu"
"github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/tracker"
"golang.org/x/exp/shiny/materialdesign/icons" "golang.org/x/exp/shiny/materialdesign/icons"
"golang.org/x/text/cases" "golang.org/x/text/cases"
@ -23,6 +27,13 @@ import (
) )
type ( type (
UnitList struct {
dragList *DragList
searchEditor *Editor
addUnitBtn *Clickable
addUnitAction tracker.Action
}
UnitEditor struct { UnitEditor struct {
paramTable *ScrollTable paramTable *ScrollTable
searchList *DragList searchList *DragList
@ -43,6 +54,135 @@ type (
} }
) )
// UnitList methods
func MakeUnitList(m *tracker.Model) UnitList {
ret := UnitList{
dragList: NewDragList(m.Units().List(), layout.Vertical),
addUnitBtn: new(Clickable),
searchEditor: NewEditor(true, true, text.Start),
}
ret.addUnitAction = tracker.MakeEnabledAction(tracker.DoFunc(func() {
m.AddUnit(false).Do()
ret.searchEditor.Focus()
}))
return ret
}
func (ul *UnitList) Layout(gtx C) D {
t := TrackerFromContext(gtx)
ul.update(gtx, t)
element := func(gtx C, i int) D {
gtx.Constraints.Max.Y = gtx.Dp(20)
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
u := t.Units().Item(i)
editorStyle := t.Theme.InstrumentEditor.UnitList.Name
signalError := t.RailError()
switch {
case u.Disabled:
editorStyle = t.Theme.InstrumentEditor.UnitList.NameDisabled
case signalError.Err != nil && signalError.UnitIndex == i:
editorStyle.Color = t.Theme.InstrumentEditor.UnitList.Error
}
unitName := func(gtx C) D {
if i == ul.dragList.TrackerList.Selected() {
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
return ul.searchEditor.Layout(gtx, t.Model.UnitSearch(), t.Theme, &editorStyle, "---")
} else {
text := u.Type
if text == "" {
text = "---"
}
l := editorStyle.AsLabelStyle()
return Label(t.Theme, &l, text).Layout(gtx)
}
}
stackText := strconv.FormatInt(int64(u.Signals.StackAfter()), 10)
commentLabel := Label(t.Theme, &t.Theme.InstrumentEditor.UnitList.Comment, u.Comment)
stackLabel := Label(t.Theme, &t.Theme.InstrumentEditor.UnitList.Stack, stackText)
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(unitName),
layout.Rigid(layout.Spacer{Width: 5}.Layout),
layout.Flexed(1, commentLabel.Layout),
layout.Rigid(stackLabel.Layout),
layout.Rigid(layout.Spacer{Width: 10}.Layout),
)
}
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
unitList := FilledDragList(t.Theme, ul.dragList)
surface := func(gtx C) D {
return layout.Stack{Alignment: layout.SE}.Layout(gtx,
layout.Expanded(func(gtx C) D {
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(140), gtx.Constraints.Max.Y))
dims := unitList.Layout(gtx, element, nil)
unitList.LayoutScrollBar(gtx)
return dims
}),
layout.Stacked(func(gtx C) D {
margin := layout.Inset{Right: unit.Dp(20), Bottom: unit.Dp(1)}
addUnitBtn := IconBtn(t.Theme, &t.Theme.IconButton.Emphasis, ul.addUnitBtn, icons.ContentAdd, "Add unit (Enter)")
return margin.Layout(gtx, addUnitBtn.Layout)
}),
)
}
return Surface{Gray: 30, Focus: t.PatchPanel.TreeFocused(gtx)}.Layout(gtx, surface)
}
func (ul *UnitList) update(gtx C, t *Tracker) {
for ul.addUnitBtn.Clicked(gtx) {
ul.addUnitAction.Do()
t.UnitSearching().SetValue(true)
ul.searchEditor.Focus()
}
for {
event, ok := gtx.Event(
key.Filter{Focus: ul.dragList, Name: key.NameRightArrow},
key.Filter{Focus: ul.dragList, Name: key.NameEnter, Optional: key.ModCtrl},
key.Filter{Focus: ul.dragList, Name: key.NameReturn, Optional: key.ModCtrl},
key.Filter{Focus: ul.dragList, Name: key.NameDeleteBackward},
)
if !ok {
break
}
if e, ok := event.(key.Event); ok && e.State == key.Press {
switch e.Name {
case key.NameRightArrow:
t.PatchPanel.unitEditor.paramTable.RowTitleList.Focus()
case key.NameDeleteBackward:
t.Units().SetSelectedType("")
t.UnitSearching().SetValue(true)
ul.searchEditor.Focus()
case key.NameEnter, key.NameReturn:
t.Model.AddUnit(e.Modifiers.Contain(key.ModCtrl)).Do()
t.UnitSearching().SetValue(true)
ul.searchEditor.Focus()
}
}
}
str := t.Model.UnitSearch()
for ev := ul.searchEditor.Update(gtx, str); ev != EditorEventNone; ev = ul.searchEditor.Update(gtx, str) {
if ev == EditorEventSubmit {
if str.Value() != "" {
for _, n := range sointu.UnitNames {
if strings.HasPrefix(n, str.Value()) {
t.Units().SetSelectedType(n)
break
}
}
} else {
t.Units().SetSelectedType("")
}
}
ul.dragList.Focus()
t.UnitSearching().SetValue(false)
}
}
func (ul *UnitList) Tags(curLevel int, yield TagYieldFunc) bool {
return yield(curLevel, ul.dragList) && yield(curLevel+1, &ul.searchEditor.widgetEditor)
}
func NewUnitEditor(m *tracker.Model) *UnitEditor { func NewUnitEditor(m *tracker.Model) *UnitEditor {
ret := &UnitEditor{ ret := &UnitEditor{
DeleteUnitBtn: new(Clickable), DeleteUnitBtn: new(Clickable),

View File

@ -36,14 +36,14 @@ type (
RecoveryFilePath string RecoveryFilePath string
ChangedSinceRecovery bool ChangedSinceRecovery bool
SendSource int SendSource int
InstrumentTab InstrumentTab
} }
Model struct { Model struct {
d modelData d modelData
derived derivedModelData derived derivedModelData
instrEnlarged bool instrEnlarged bool
commentExpanded bool
prevUndoKind string prevUndoKind string
undoSkipCounter int undoSkipCounter int
@ -129,6 +129,8 @@ type (
String() string String() string
Open() error Open() error
} }
InstrumentTab int
) )
const ( const (
@ -161,6 +163,12 @@ const (
License License
) )
const (
InstrumentEditorTab InstrumentTab = iota
InstrumentPresetsTab
InstrumentCommentTab
)
const maxUndo = 64 const maxUndo = 64
func (m *Model) PlayPosition() sointu.SongPos { return m.playerStatus.SongPos } func (m *Model) PlayPosition() sointu.SongPos { return m.playerStatus.SongPos }

View File

@ -55,7 +55,6 @@ func (s *modelFuzzState) Iterate(yield func(string, func(p string, t *testing.T)
s.IterateBool("Playing", s.model.Playing(), yield, seed) s.IterateBool("Playing", s.model.Playing(), yield, seed)
s.IterateBool("InstrEnlarged", s.model.InstrEnlarged(), yield, seed) s.IterateBool("InstrEnlarged", s.model.InstrEnlarged(), yield, seed)
s.IterateBool("Effect", s.model.Effect(), yield, seed) s.IterateBool("Effect", s.model.Effect(), yield, seed)
s.IterateBool("CommentExpanded", s.model.CommentExpanded(), yield, seed)
s.IterateBool("Follow", s.model.Follow(), yield, seed) s.IterateBool("Follow", s.model.Follow(), yield, seed)
s.IterateBool("UniquePatterns", s.model.UniquePatterns(), yield, seed) s.IterateBool("UniquePatterns", s.model.UniquePatterns(), yield, seed)
s.IterateBool("LinkInstrTrack", s.model.LinkInstrTrack(), yield, seed) s.IterateBool("LinkInstrTrack", s.model.LinkInstrTrack(), yield, seed)