mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-20 14:04:34 -04:00
fix(tracker/gioui): using keys to choose Unit Type and tab ordering
This commit is contained in:
parent
192909328c
commit
18d7848367
@ -52,9 +52,10 @@
|
|||||||
- { key: ">", action: "OctaveSubtract" }
|
- { key: ">", action: "OctaveSubtract" }
|
||||||
- { key: "<", shift: true, action: "OctaveAdd" }
|
- { key: "<", shift: true, action: "OctaveAdd" }
|
||||||
- { key: "<", action: "OctaveSubtract" }
|
- { key: "<", action: "OctaveSubtract" }
|
||||||
|
- { key: "⎋", action: "FocusPrev" } # Esc key
|
||||||
- { key: "Tab", shift: true, action: "FocusPrev" }
|
- { key: "Tab", shift: true, action: "FocusPrev" }
|
||||||
- { key: "Tab", action: "FocusNext" }
|
|
||||||
- { key: "Tab", shift: true, shortcut: true, action: "FocusPrevInto" }
|
- { key: "Tab", shift: true, shortcut: true, action: "FocusPrevInto" }
|
||||||
|
- { key: "Tab", action: "FocusNext" }
|
||||||
- { key: "Tab", shortcut: true, action: "FocusNextInto" }
|
- { key: "Tab", shortcut: true, action: "FocusNextInto" }
|
||||||
- { key: "A", action: "NoteOff" }
|
- { key: "A", action: "NoteOff" }
|
||||||
- { key: "1", action: "NoteOff" }
|
- { key: "1", action: "NoteOff" }
|
||||||
|
@ -471,15 +471,12 @@ func (ul *UnitList) update(gtx C, t *Tracker) {
|
|||||||
key.Filter{Focus: ul.dragList, Name: key.NameEnter, Optional: key.ModCtrl},
|
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.NameReturn, Optional: key.ModCtrl},
|
||||||
key.Filter{Focus: ul.dragList, Name: key.NameDeleteBackward},
|
key.Filter{Focus: ul.dragList, Name: key.NameDeleteBackward},
|
||||||
key.Filter{Focus: ul.dragList, Name: key.NameEscape},
|
|
||||||
)
|
)
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if e, ok := event.(key.Event); ok && e.State == key.Press {
|
if e, ok := event.(key.Event); ok && e.State == key.Press {
|
||||||
switch e.Name {
|
switch e.Name {
|
||||||
case key.NameEscape:
|
|
||||||
t.PatchPanel.instrList.instrumentDragList.Focus()
|
|
||||||
case key.NameRightArrow:
|
case key.NameRightArrow:
|
||||||
t.PatchPanel.unitEditor.sliderList.Focus()
|
t.PatchPanel.unitEditor.sliderList.Focus()
|
||||||
case key.NameDeleteBackward:
|
case key.NameDeleteBackward:
|
||||||
@ -496,5 +493,5 @@ func (ul *UnitList) update(gtx C, t *Tracker) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ul *UnitList) Tags(curLevel int, yield TagYieldFunc) bool {
|
func (ul *UnitList) Tags(curLevel int, yield TagYieldFunc) bool {
|
||||||
return yield(curLevel, ul.dragList)
|
return yield(curLevel, ul.dragList) && yield(curLevel+1, &ul.searchEditor.widgetEditor)
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import (
|
|||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"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"
|
||||||
@ -41,6 +40,8 @@ type UnitEditor struct {
|
|||||||
copyHint string
|
copyHint string
|
||||||
disableUnitHint string
|
disableUnitHint string
|
||||||
enableUnitHint string
|
enableUnitHint string
|
||||||
|
|
||||||
|
searching tracker.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUnitEditor(m *tracker.Model) *UnitEditor {
|
func NewUnitEditor(m *tracker.Model) *UnitEditor {
|
||||||
@ -53,6 +54,7 @@ func NewUnitEditor(m *tracker.Model) *UnitEditor {
|
|||||||
commentEditor: NewEditor(true, true, text.Start),
|
commentEditor: NewEditor(true, true, text.Start),
|
||||||
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),
|
||||||
|
searching: m.UnitSearching(),
|
||||||
}
|
}
|
||||||
ret.caser = cases.Title(language.English)
|
ret.caser = cases.Title(language.English)
|
||||||
ret.copyHint = makeHint("Copy unit", " (%s)", "Copy")
|
ret.copyHint = makeHint("Copy unit", " (%s)", "Copy")
|
||||||
@ -63,44 +65,93 @@ func NewUnitEditor(m *tracker.Model) *UnitEditor {
|
|||||||
|
|
||||||
func (pe *UnitEditor) Layout(gtx C) D {
|
func (pe *UnitEditor) Layout(gtx C) D {
|
||||||
t := TrackerFromContext(gtx)
|
t := TrackerFromContext(gtx)
|
||||||
for {
|
pe.update(gtx, t)
|
||||||
e, ok := gtx.Event(
|
|
||||||
key.Filter{Focus: pe.sliderList, Name: key.NameLeftArrow, Optional: key.ModShift},
|
|
||||||
key.Filter{Focus: pe.sliderList, Name: key.NameRightArrow, Optional: key.ModShift},
|
|
||||||
key.Filter{Focus: pe.sliderList, Name: key.NameEscape},
|
|
||||||
)
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
switch e := e.(type) {
|
|
||||||
case key.Event:
|
|
||||||
if e.State == key.Press {
|
|
||||||
pe.command(gtx, e, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
|
|
||||||
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
|
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
|
||||||
editorFunc := pe.layoutSliders
|
editorFunc := pe.layoutSliders
|
||||||
|
if pe.showingChooser() {
|
||||||
if t.UnitSearching().Value() || pe.sliderList.TrackerList.Count() == 0 {
|
|
||||||
editorFunc = pe.layoutUnitTypeChooser
|
editorFunc = pe.layoutUnitTypeChooser
|
||||||
}
|
}
|
||||||
return Surface{Gray: 24, Focus: t.PatchPanel.TreeFocused(gtx)}.Layout(gtx, func(gtx C) D {
|
return Surface{Gray: 24, Focus: t.PatchPanel.TreeFocused(gtx)}.Layout(gtx, func(gtx C) D {
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
layout.Flexed(1, func(gtx C) D {
|
layout.Flexed(1, editorFunc),
|
||||||
return editorFunc(gtx, t)
|
layout.Rigid(pe.layoutFooter),
|
||||||
}),
|
|
||||||
layout.Rigid(func(gtx C) D {
|
|
||||||
return pe.layoutFooter(gtx, t)
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pe *UnitEditor) layoutSliders(gtx C, t *Tracker) D {
|
func (pe *UnitEditor) showingChooser() bool {
|
||||||
numItems := pe.sliderList.TrackerList.Count()
|
return pe.searching.Value() || pe.sliderList.TrackerList.Count() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pe *UnitEditor) update(gtx C, t *Tracker) {
|
||||||
|
for pe.CopyUnitBtn.Clicked(gtx) {
|
||||||
|
if contents, ok := t.Units().List().CopyElements(); ok {
|
||||||
|
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))})
|
||||||
|
t.Alerts().Add("Unit(s) copied to clipboard", tracker.Info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for pe.SelectTypeBtn.Clicked(gtx) {
|
||||||
|
pe.ChooseUnitType(t)
|
||||||
|
}
|
||||||
|
for pe.commentEditor.Update(gtx, t.UnitComment()) != EditorEventNone {
|
||||||
|
t.FocusPrev(gtx, false)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
e, ok := gtx.Event(
|
||||||
|
key.Filter{Focus: pe.searchList, Name: key.NameEnter},
|
||||||
|
key.Filter{Focus: pe.searchList, Name: key.NameReturn},
|
||||||
|
)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if e, ok := e.(key.Event); ok && e.State == key.Press {
|
||||||
|
pe.ChooseUnitType(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
e, ok := gtx.Event(
|
||||||
|
key.Filter{Focus: pe.sliderList, Name: key.NameLeftArrow, Optional: key.ModShift},
|
||||||
|
key.Filter{Focus: pe.sliderList, Name: key.NameRightArrow, Optional: key.ModShift},
|
||||||
|
key.Filter{Focus: pe.searchList, Name: key.NameEnter},
|
||||||
|
key.Filter{Focus: pe.searchList, Name: key.NameReturn},
|
||||||
|
)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if e, ok := e.(key.Event); ok && e.State == key.Press {
|
||||||
|
params := t.Model.Params()
|
||||||
|
item := params.SelectedItem()
|
||||||
|
switch e.Name {
|
||||||
|
case key.NameLeftArrow:
|
||||||
|
if e.Modifiers.Contain(key.ModShift) {
|
||||||
|
item.SetValue(item.Value() - item.LargeStep())
|
||||||
|
} else {
|
||||||
|
item.SetValue(item.Value() - 1)
|
||||||
|
}
|
||||||
|
case key.NameRightArrow:
|
||||||
|
if e.Modifiers.Contain(key.ModShift) {
|
||||||
|
item.SetValue(item.Value() + item.LargeStep())
|
||||||
|
} else {
|
||||||
|
item.SetValue(item.Value() + 1)
|
||||||
|
}
|
||||||
|
case key.NameEnter, key.NameReturn:
|
||||||
|
item.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pe *UnitEditor) ChooseUnitType(t *Tracker) {
|
||||||
|
if ut, ok := t.SearchResults().Item(pe.searchList.TrackerList.Selected()); ok {
|
||||||
|
t.Units().SetSelectedType(ut)
|
||||||
|
t.PatchPanel.unitList.dragList.Focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pe *UnitEditor) layoutSliders(gtx C) D {
|
||||||
|
t := TrackerFromContext(gtx)
|
||||||
|
numItems := pe.sliderList.TrackerList.Count()
|
||||||
|
// create enough parameter widget to match the number of parameters
|
||||||
for len(pe.Parameters) < numItems {
|
for len(pe.Parameters) < numItems {
|
||||||
pe.Parameters = append(pe.Parameters, new(ParameterWidget))
|
pe.Parameters = append(pe.Parameters, new(ParameterWidget))
|
||||||
}
|
}
|
||||||
@ -127,64 +178,50 @@ func (pe *UnitEditor) layoutSliders(gtx C, t *Tracker) D {
|
|||||||
return dims
|
return dims
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D {
|
func (pe *UnitEditor) layoutFooter(gtx C) D {
|
||||||
for pe.CopyUnitBtn.Clicked(gtx) {
|
t := TrackerFromContext(gtx)
|
||||||
if contents, ok := t.Units().List().CopyElements(); ok {
|
st := t.Units().SelectedType()
|
||||||
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))})
|
text := "Choose unit type"
|
||||||
t.Alerts().Add("Unit copied to clipboard", tracker.Info)
|
if st != "" {
|
||||||
}
|
text = pe.caser.String(st)
|
||||||
}
|
|
||||||
text := t.Units().SelectedType()
|
|
||||||
if text == "" {
|
|
||||||
text = "Choose unit type"
|
|
||||||
} else {
|
|
||||||
text = pe.caser.String(text)
|
|
||||||
}
|
}
|
||||||
hintText := Label(t.Theme, &t.Theme.UnitEditor.Hint, text)
|
hintText := Label(t.Theme, &t.Theme.UnitEditor.Hint, text)
|
||||||
deleteUnitBtn := ActionIconBtn(t.DeleteUnit(), t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, "Delete unit (Ctrl+Backspace)")
|
deleteUnitBtn := ActionIconBtn(t.DeleteUnit(), t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, "Delete unit (Ctrl+Backspace)")
|
||||||
copyUnitBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, pe.CopyUnitBtn, icons.ContentContentCopy, pe.copyHint)
|
copyUnitBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, pe.CopyUnitBtn, icons.ContentContentCopy, pe.copyHint)
|
||||||
disableUnitBtn := ToggleIconBtn(t.UnitDisabled(), t.Theme, pe.DisableUnitBtn, icons.AVVolumeUp, icons.AVVolumeOff, pe.disableUnitHint, pe.enableUnitHint)
|
disableUnitBtn := ToggleIconBtn(t.UnitDisabled(), t.Theme, pe.DisableUnitBtn, icons.AVVolumeUp, icons.AVVolumeOff, pe.disableUnitHint, pe.enableUnitHint)
|
||||||
|
w := layout.Spacer{Width: t.Theme.IconButton.Enabled.Size}.Layout
|
||||||
|
if st != "" {
|
||||||
|
clearUnitBtn := ActionIconBtn(t.ClearUnit(), t.Theme, pe.ClearUnitBtn, icons.ContentClear, "Clear unit")
|
||||||
|
w = clearUnitBtn.Layout
|
||||||
|
}
|
||||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||||
layout.Rigid(deleteUnitBtn.Layout),
|
layout.Rigid(deleteUnitBtn.Layout),
|
||||||
layout.Rigid(copyUnitBtn.Layout),
|
layout.Rigid(copyUnitBtn.Layout),
|
||||||
layout.Rigid(disableUnitBtn.Layout),
|
layout.Rigid(disableUnitBtn.Layout),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(w),
|
||||||
var dims D
|
|
||||||
if t.Units().SelectedType() != "" {
|
|
||||||
clearUnitBtn := ActionIconBtn(t.ClearUnit(), t.Theme, pe.ClearUnitBtn, icons.ContentClear, "Clear unit")
|
|
||||||
dims = clearUnitBtn.Layout(gtx)
|
|
||||||
}
|
|
||||||
return D{Size: image.Pt(gtx.Dp(unit.Dp(48)), dims.Size.Y)}
|
|
||||||
}),
|
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
gtx.Constraints.Min.X = gtx.Dp(120)
|
gtx.Constraints.Min.X = gtx.Dp(120)
|
||||||
return hintText.Layout(gtx)
|
return hintText.Layout(gtx)
|
||||||
}),
|
}),
|
||||||
layout.Flexed(1, func(gtx C) D {
|
layout.Flexed(1, func(gtx C) D {
|
||||||
for pe.commentEditor.Update(gtx, t.UnitComment()) != EditorEventNone {
|
|
||||||
t.FocusPrev(gtx, false)
|
|
||||||
}
|
|
||||||
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, "---")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pe *UnitEditor) layoutUnitTypeChooser(gtx C, t *Tracker) D {
|
func (pe *UnitEditor) layoutUnitTypeChooser(gtx C) D {
|
||||||
var names [256]string
|
t := TrackerFromContext(gtx)
|
||||||
for i, item := range t.Model.SearchResults().Iterate {
|
var namesArray [256]string
|
||||||
if i >= 256 {
|
names := namesArray[:0]
|
||||||
break
|
for _, item := range t.Model.SearchResults().Iterate {
|
||||||
}
|
names = append(names, item)
|
||||||
names[i] = item
|
|
||||||
}
|
}
|
||||||
element := func(gtx C, i int) D {
|
element := func(gtx C, i int) D {
|
||||||
|
if i < 0 || i >= len(names) {
|
||||||
|
return D{}
|
||||||
|
}
|
||||||
w := Label(t.Theme, &t.Theme.UnitEditor.Chooser, names[i])
|
w := Label(t.Theme, &t.Theme.UnitEditor.Chooser, names[i])
|
||||||
|
|
||||||
if i == pe.searchList.TrackerList.Selected() {
|
if i == pe.searchList.TrackerList.Selected() {
|
||||||
for pe.SelectTypeBtn.Clicked(gtx) {
|
|
||||||
t.Units().SetSelectedType(names[i])
|
|
||||||
}
|
|
||||||
return pe.SelectTypeBtn.Layout(gtx, w.Layout)
|
return pe.SelectTypeBtn.Layout(gtx, w.Layout)
|
||||||
}
|
}
|
||||||
return w.Layout(gtx)
|
return w.Layout(gtx)
|
||||||
@ -196,33 +233,12 @@ func (pe *UnitEditor) layoutUnitTypeChooser(gtx C, t *Tracker) D {
|
|||||||
return dims
|
return dims
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pe *UnitEditor) command(gtx C, e key.Event, t *Tracker) {
|
|
||||||
params := t.Model.Params()
|
|
||||||
switch e.State {
|
|
||||||
case key.Press:
|
|
||||||
switch e.Name {
|
|
||||||
case key.NameLeftArrow:
|
|
||||||
i := params.SelectedItem()
|
|
||||||
if e.Modifiers.Contain(key.ModShift) {
|
|
||||||
i.SetValue(i.Value() - i.LargeStep())
|
|
||||||
} else {
|
|
||||||
i.SetValue(i.Value() - 1)
|
|
||||||
}
|
|
||||||
case key.NameRightArrow:
|
|
||||||
i := params.SelectedItem()
|
|
||||||
if e.Modifiers.Contain(key.ModShift) {
|
|
||||||
i.SetValue(i.Value() + i.LargeStep())
|
|
||||||
} else {
|
|
||||||
i.SetValue(i.Value() + 1)
|
|
||||||
}
|
|
||||||
case key.NameEscape:
|
|
||||||
t.FocusPrev(gtx, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *UnitEditor) Tags(level int, yield TagYieldFunc) bool {
|
func (t *UnitEditor) Tags(level int, yield TagYieldFunc) bool {
|
||||||
return yield(level, t.sliderList) && yield(level+1, &t.commentEditor.widgetEditor)
|
widget := t.sliderList
|
||||||
|
if t.showingChooser() {
|
||||||
|
widget = t.searchList
|
||||||
|
}
|
||||||
|
return yield(level, widget) && yield(level+1, &t.commentEditor.widgetEditor)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ParameterWidget struct {
|
type ParameterWidget struct {
|
||||||
|
@ -706,6 +706,15 @@ func (l *SearchResults) Iterate(yield UnitSearchYieldFunc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *SearchResults) Item(index int) (name string, ok bool) {
|
||||||
|
for i, n := range l.Iterate {
|
||||||
|
if i == index {
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
func (l *SearchResults) Selected() int {
|
func (l *SearchResults) Selected() int {
|
||||||
return max(min(l.d.UnitSearchIndex, l.Count()-1), 0)
|
return max(min(l.d.UnitSearchIndex, l.Count()-1), 0)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user