feat: units can have comments

Closes #114
This commit is contained in:
5684185+vsariola@users.noreply.github.com 2024-10-13 23:02:13 +03:00
parent 160eb8eea9
commit 9779beee99
6 changed files with 98 additions and 9 deletions

View File

@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Added ### Added
- Units can have comments, to make it easier to distinguish between units of
same type within an instrument. These comments are also shown when choosing
the send target. ([#114][i114])
- A toggle button for copying non-unique patterns before editing. When enabled - A toggle button for copying non-unique patterns before editing. When enabled
and if the pattern is used in multiple places, the pattern is copied first. and if the pattern is used in multiple places, the pattern is copied first.
([#77][i77]) ([#77][i77])
@ -232,6 +235,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
[i77]: https://github.com/vsariola/sointu/issues/77 [i77]: https://github.com/vsariola/sointu/issues/77
[i94]: https://github.com/vsariola/sointu/issues/94 [i94]: https://github.com/vsariola/sointu/issues/94
[i112]: https://github.com/vsariola/sointu/issues/112 [i112]: https://github.com/vsariola/sointu/issues/112
[i114]: https://github.com/vsariola/sointu/issues/114
[i116]: https://github.com/vsariola/sointu/issues/116 [i116]: https://github.com/vsariola/sointu/issues/116
[i120]: https://github.com/vsariola/sointu/issues/120 [i120]: https://github.com/vsariola/sointu/issues/120
[i121]: https://github.com/vsariola/sointu/issues/121 [i121]: https://github.com/vsariola/sointu/issues/121

View File

@ -49,6 +49,11 @@ type (
// Disabled is a flag that can be set to true to disable the unit. // Disabled is a flag that can be set to true to disable the unit.
// Disabled units are considered to be not present in the patch. // Disabled units are considered to be not present in the patch.
Disabled bool `yaml:",omitempty"` Disabled bool `yaml:",omitempty"`
// Comment is a free-form comment about the unit that can be displayed
// instead of/besides the type of the unit in the GUI, to make it easier
// to track what the unit is doing & to make it easier to target sends.
Comment string `yaml:",omitempty"`
} }
// UnitParameter documents one parameter that an unit takes // UnitParameter documents one parameter that an unit takes
@ -291,7 +296,7 @@ func (u *Unit) Copy() Unit {
} }
varArgs := make([]int, len(u.VarArgs)) varArgs := make([]int, len(u.VarArgs))
copy(varArgs, u.VarArgs) copy(varArgs, u.VarArgs)
return Unit{Type: u.Type, Parameters: parameters, VarArgs: varArgs, ID: u.ID, Disabled: u.Disabled} return Unit{Type: u.Type, Parameters: parameters, VarArgs: varArgs, ID: u.ID, Disabled: u.Disabled, Comment: u.Comment}
} }
// StackChange returns how this unit will affect the signal stack. "pop" and // StackChange returns how this unit will affect the signal stack. "pop" and

View File

@ -393,7 +393,8 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
count := intMin(ie.unitDragList.TrackerList.Count(), 256) count := intMin(ie.unitDragList.TrackerList.Count(), 256)
element := func(gtx C, i int) D { element := func(gtx C, i int) D {
gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(unit.Dp(120)), gtx.Dp(unit.Dp(20)))) gtx.Constraints.Max.Y = gtx.Dp(20)
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
if i < 0 || i > 255 { if i < 0 || i > 255 {
return layout.Dimensions{Size: gtx.Constraints.Min} return layout.Dimensions{Size: gtx.Constraints.Min}
} }
@ -418,7 +419,7 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
stackLabel := LabelStyle{Text: stackText, ShadeColor: black, Color: mediumEmphasisTextColor, Font: labelDefaultFont, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper} stackLabel := LabelStyle{Text: stackText, ShadeColor: black, Color: mediumEmphasisTextColor, Font: labelDefaultFont, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper}
rightMargin := layout.Inset{Right: unit.Dp(10)} rightMargin := layout.Inset{Right: unit.Dp(10)}
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Flexed(1, func(gtx C) D { layout.Rigid(func(gtx C) D {
if i == ie.unitDragList.TrackerList.Selected() { if i == ie.unitDragList.TrackerList.Selected() {
editor := material.Editor(t.Theme, ie.searchEditor, "---") editor := material.Editor(t.Theme, ie.searchEditor, "---")
editor.Color = color editor.Color = color
@ -473,6 +474,11 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
return unitNameLabel.Layout(gtx) return unitNameLabel.Layout(gtx)
} }
}), }),
layout.Flexed(1, func(gtx C) D {
unitNameLabel := LabelStyle{Text: u.Comment, ShadeColor: black, Color: mediumEmphasisTextColor, Font: f, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper}
inset := layout.Inset{Left: unit.Dp(5)}
return inset.Layout(gtx, unitNameLabel.Layout)
}),
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return rightMargin.Layout(gtx, stackLabel.Layout) return rightMargin.Layout(gtx, stackLabel.Layout)
}), }),
@ -517,7 +523,7 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
return layout.Stack{Alignment: layout.SE}.Layout(gtx, return layout.Stack{Alignment: layout.SE}.Layout(gtx,
layout.Expanded(func(gtx C) D { 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() 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(unit.Dp(120)), gtx.Constraints.Max.Y)) gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(140), gtx.Constraints.Max.Y))
dims := unitList.Layout(gtx) dims := unitList.Layout(gtx)
unitList.LayoutScrollBar(gtx) unitList.LayoutScrollBar(gtx)
return dims return dims

View File

@ -32,7 +32,9 @@ type UnitEditor struct {
ClearUnitBtn *ActionClickable ClearUnitBtn *ActionClickable
DisableUnitBtn *BoolClickable DisableUnitBtn *BoolClickable
SelectTypeBtn *widget.Clickable SelectTypeBtn *widget.Clickable
commentEditor *widget.Editor
caser cases.Caser caser cases.Caser
commentFilters []event.Filter
copyHint string copyHint string
disableUnitHint string disableUnitHint string
@ -46,10 +48,19 @@ func NewUnitEditor(m *tracker.Model) *UnitEditor {
DisableUnitBtn: NewBoolClickable(m.UnitDisabled().Bool()), DisableUnitBtn: NewBoolClickable(m.UnitDisabled().Bool()),
CopyUnitBtn: new(TipClickable), CopyUnitBtn: new(TipClickable),
SelectTypeBtn: new(widget.Clickable), SelectTypeBtn: new(widget.Clickable),
commentEditor: &widget.Editor{SingleLine: true, Submit: true, MaxLen: 16},
sliderList: NewDragList(m.Params().List(), layout.Vertical), sliderList: NewDragList(m.Params().List(), layout.Vertical),
searchList: NewDragList(m.SearchResults().List(), layout.Vertical), searchList: NewDragList(m.SearchResults().List(), layout.Vertical),
} }
ret.caser = cases.Title(language.English) ret.caser = cases.Title(language.English)
for k, a := range keyBindingMap {
if len(a) < 4 || a[:4] != "Note" {
continue
}
ret.commentFilters = append(ret.commentFilters, key.Filter{Name: k.Name, Required: k.Modifiers, Focus: ret.commentEditor})
}
ret.commentFilters = append(ret.commentFilters, key.Filter{Name: key.NameSpace, Focus: ret.commentEditor})
ret.commentFilters = append(ret.commentFilters, key.Filter{Name: key.NameEscape, Focus: ret.commentEditor})
ret.copyHint = makeHint("Copy unit", " (%s)", "Copy") ret.copyHint = makeHint("Copy unit", " (%s)", "Copy")
ret.disableUnitHint = makeHint("Disable unit", " (%s)", "UnitDisabledToggle") ret.disableUnitHint = makeHint("Disable unit", " (%s)", "UnitDisabledToggle")
ret.enableUnitHint = makeHint("Enable unit", " (%s)", "UnitDisabledToggle") ret.enableUnitHint = makeHint("Enable unit", " (%s)", "UnitDisabledToggle")
@ -139,6 +150,11 @@ func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D {
text = pe.caser.String(text) text = pe.caser.String(text)
} }
hintText := Label(text, white, t.Theme.Shaper) hintText := Label(text, white, t.Theme.Shaper)
commentStyle := material.Editor(t.Theme, pe.commentEditor, "---")
commentStyle.Font = labelDefaultFont
commentStyle.TextSize = labelDefaultFontSize
commentStyle.Color = mediumEmphasisTextColor
commentStyle.HintColor = mediumEmphasisTextColor
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(deleteUnitBtnStyle.Layout), layout.Rigid(deleteUnitBtnStyle.Layout),
layout.Rigid(copyUnitBtnStyle.Layout), layout.Rigid(copyUnitBtnStyle.Layout),
@ -151,7 +167,39 @@ func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D {
} }
return D{Size: image.Pt(gtx.Dp(unit.Dp(48)), dims.Size.Y)} return D{Size: image.Pt(gtx.Dp(unit.Dp(48)), dims.Size.Y)}
}), }),
layout.Flexed(1, hintText), layout.Rigid(func(gtx C) D {
gtx.Constraints.Min.X = gtx.Dp(120)
return hintText(gtx)
}),
layout.Flexed(1, func(gtx C) D {
s := t.UnitComment().String()
if pe.commentEditor.Text() != s.Value() {
pe.commentEditor.SetText(s.Value())
}
for {
ev, ok := pe.commentEditor.Update(gtx)
if !ok {
break
}
_, ok = ev.(widget.SubmitEvent)
if ok {
t.InstrumentEditor.Focus()
continue
}
}
for {
event, ok := gtx.Event(pe.commentFilters...)
if !ok {
break
}
if e, ok := event.(key.Event); ok && e.State == key.Press && e.Name == key.NameEscape {
t.InstrumentEditor.Focus()
}
}
ret := commentStyle.Layout(gtx)
s.Set(commentStyle.Editor.Text())
return ret
}),
) )
} }
@ -317,11 +365,11 @@ func (p ParameterStyle) Layout(gtx C) D {
targetInstrument := p.tracker.Instrument(targetI) targetInstrument := p.tracker.Instrument(targetI)
instrName = targetInstrument.Name instrName = targetInstrument.Name
units := targetInstrument.Units units := targetInstrument.Units
unitName = fmt.Sprintf("%v: %v", targetU, units[targetU].Type) unitName = fmt.Sprintf("%d: %s %s", targetU, units[targetU].Type, units[targetU].Comment)
unitItems = make([]MenuItem, len(units)) unitItems = make([]MenuItem, len(units))
for j, unit := range units { for j, unit := range units {
id := unit.ID id := unit.ID
unitItems[j].Text = fmt.Sprintf("%v: %v", j, unit.Type) unitItems[j].Text = fmt.Sprintf("%d: %s %s", j, unit.Type, unit.Comment)
unitItems[j].IconBytes = icons.NavigationChevronRight unitItems[j].IconBytes = icons.NavigationChevronRight
unitItems[j].Doer = tracker.Allow(func() { unitItems[j].Doer = tracker.Allow(func() {
tracker.Int{IntData: p.w.Parameter}.Set(id) tracker.Int{IntData: p.w.Parameter}.Set(id)
@ -333,7 +381,7 @@ func (p ParameterStyle) Layout(gtx C) D {
layout.Rigid(p.tracker.layoutMenu(gtx, instrName, &p.w.instrBtn, &p.w.instrMenu, unit.Dp(200), layout.Rigid(p.tracker.layoutMenu(gtx, instrName, &p.w.instrBtn, &p.w.instrMenu, unit.Dp(200),
instrItems..., instrItems...,
)), )),
layout.Rigid(p.tracker.layoutMenu(gtx, unitName, &p.w.unitBtn, &p.w.unitMenu, unit.Dp(200), layout.Rigid(p.tracker.layoutMenu(gtx, unitName, &p.w.unitBtn, &p.w.unitMenu, unit.Dp(240),
unitItems..., unitItems...,
)), )),
) )

View File

@ -33,7 +33,7 @@ type (
} }
UnitListItem struct { UnitListItem struct {
Type string Type, Comment string
Disabled bool Disabled bool
StackNeed, StackBefore, StackAfter int StackNeed, StackBefore, StackAfter int
} }
@ -332,6 +332,7 @@ func (v *Units) Iterate(yield UnitYieldFunc) {
stackAfter := stackBefore + unit.StackChange() stackAfter := stackBefore + unit.StackChange()
if !yield(UnitListItem{ if !yield(UnitListItem{
Type: unit.Type, Type: unit.Type,
Comment: unit.Comment,
Disabled: unit.Disabled, Disabled: unit.Disabled,
StackNeed: unit.StackNeed(), StackNeed: unit.StackNeed(),
StackBefore: stackBefore, StackBefore: stackBefore,

View File

@ -15,6 +15,7 @@ type (
InstrumentName Model InstrumentName Model
InstrumentComment Model InstrumentComment Model
UnitSearch Model UnitSearch Model
UnitComment Model
) )
func (v String) Set(value string) { func (v String) Set(value string) {
@ -30,6 +31,7 @@ func (m *Model) FilePath() *FilePath { return (*FilePath)(m) }
func (m *Model) InstrumentName() *InstrumentName { return (*InstrumentName)(m) } func (m *Model) InstrumentName() *InstrumentName { return (*InstrumentName)(m) }
func (m *Model) InstrumentComment() *InstrumentComment { return (*InstrumentComment)(m) } func (m *Model) InstrumentComment() *InstrumentComment { return (*InstrumentComment)(m) }
func (m *Model) UnitSearch() *UnitSearch { return (*UnitSearch)(m) } func (m *Model) UnitSearch() *UnitSearch { return (*UnitSearch)(m) }
func (m *Model) UnitComment() *UnitComment { return (*UnitComment)(m) }
// FilePathString // FilePathString
@ -108,3 +110,26 @@ func (v *InstrumentComment) setValue(value string) {
func (v *InstrumentComment) change(kind string) func() { func (v *InstrumentComment) change(kind string) func() {
return (*Model)(v).change("InstrumentComment."+kind, PatchChange, MinorChange) return (*Model)(v).change("InstrumentComment."+kind, PatchChange, MinorChange)
} }
// UnitComment
func (v *UnitComment) String() String { return String{v} }
func (v *UnitComment) Value() string {
if v.d.InstrIndex < 0 || v.d.InstrIndex >= len(v.d.Song.Patch) ||
v.d.UnitIndex < 0 || v.d.UnitIndex >= len(v.d.Song.Patch[v.d.InstrIndex].Units) {
return ""
}
return v.d.Song.Patch[v.d.InstrIndex].Units[v.d.UnitIndex].Comment
}
func (v *UnitComment) setValue(value string) {
if v.d.InstrIndex < 0 || v.d.InstrIndex >= len(v.d.Song.Patch) ||
v.d.UnitIndex < 0 || v.d.UnitIndex >= len(v.d.Song.Patch[v.d.InstrIndex].Units) {
return
}
v.d.Song.Patch[v.d.InstrIndex].Units[v.d.UnitIndex].Comment = value
}
func (v *UnitComment) change(kind string) func() {
return (*Model)(v).change("UnitComment."+kind, PatchChange, MinorChange)
}