feat(tracker, gioui): make a Editor for inputting the unit type manually

The keyboard shortcuts were too wonky, so removed them altogether. Had to remove also unit wrapping from model (now it just clamps the parameter to the current units) as it did not play nice with the new editor.

Closes #70.
This commit is contained in:
vsariola 2021-05-13 19:49:44 +03:00
parent ede70380f2
commit 7885c306ee
4 changed files with 102 additions and 88 deletions

View File

@ -19,6 +19,7 @@ import (
"gioui.org/widget" "gioui.org/widget"
"gioui.org/widget/material" "gioui.org/widget/material"
"gioui.org/x/eventx" "gioui.org/x/eventx"
"github.com/vsariola/sointu/tracker"
"golang.org/x/exp/shiny/materialdesign/icons" "golang.org/x/exp/shiny/materialdesign/icons"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -33,6 +34,7 @@ type InstrumentEditor struct {
commentExpandBtn *widget.Clickable commentExpandBtn *widget.Clickable
commentEditor *widget.Editor commentEditor *widget.Editor
nameEditor *widget.Editor nameEditor *widget.Editor
unitTypeEditor *widget.Editor
instrumentDragList *DragList instrumentDragList *DragList
instrumentScrollBar *ScrollBar instrumentScrollBar *ScrollBar
unitDragList *DragList unitDragList *DragList
@ -56,6 +58,7 @@ func NewInstrumentEditor() *InstrumentEditor {
commentExpandBtn: new(widget.Clickable), commentExpandBtn: new(widget.Clickable),
commentEditor: new(widget.Editor), commentEditor: new(widget.Editor),
nameEditor: &widget.Editor{SingleLine: true, Submit: true, Alignment: text.Middle}, nameEditor: &widget.Editor{SingleLine: true, Submit: true, Alignment: text.Middle},
unitTypeEditor: &widget.Editor{SingleLine: true, Submit: true, Alignment: text.Start},
instrumentDragList: &DragList{List: &layout.List{Axis: layout.Horizontal}, HoverItem: -1}, instrumentDragList: &DragList{List: &layout.List{Axis: layout.Horizontal}, HoverItem: -1},
instrumentScrollBar: &ScrollBar{Axis: layout.Horizontal}, instrumentScrollBar: &ScrollBar{Axis: layout.Horizontal},
unitDragList: &DragList{List: &layout.List{Axis: layout.Vertical}, HoverItem: -1}, unitDragList: &DragList{List: &layout.List{Axis: layout.Vertical}, HoverItem: -1},
@ -78,7 +81,7 @@ func (ie *InstrumentEditor) Focused() bool {
} }
func (ie *InstrumentEditor) ChildFocused() bool { func (ie *InstrumentEditor) ChildFocused() bool {
return ie.paramEditor.Focused() || ie.instrumentDragList.Focused() || ie.commentEditor.Focused() || ie.nameEditor.Focused() return ie.paramEditor.Focused() || ie.instrumentDragList.Focused() || ie.commentEditor.Focused() || ie.nameEditor.Focused() || ie.unitTypeEditor.Focused()
} }
func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D { func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
@ -349,11 +352,8 @@ func (ie *InstrumentEditor) layoutInstrumentEditor(gtx C, t *Tracker) D {
element := func(gtx C, i int) D { element := func(gtx C, i int) D {
gtx.Constraints = layout.Exact(image.Pt(gtx.Px(unit.Dp(120)), gtx.Px(unit.Dp(20)))) gtx.Constraints = layout.Exact(image.Pt(gtx.Px(unit.Dp(120)), gtx.Px(unit.Dp(20))))
u := units[i] u := units[i]
unitNameLabel := LabelStyle{Text: u.Type, ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)} var color color.NRGBA = white
if unitNameLabel.Text == "" {
unitNameLabel.Text = "---"
unitNameLabel.Alignment = layout.Center
}
var stackText string var stackText string
if i < len(ie.stackUse) { if i < len(ie.stackUse) {
stackText = strconv.FormatInt(int64(ie.stackUse[i]), 10) stackText = strconv.FormatInt(int64(ie.stackUse[i]), 10)
@ -362,27 +362,80 @@ func (ie *InstrumentEditor) layoutInstrumentEditor(gtx C, t *Tracker) D {
prevStackUse = ie.stackUse[i-1] prevStackUse = ie.stackUse[i-1]
} }
if stackNeed := u.StackNeed(); stackNeed > prevStackUse { if stackNeed := u.StackNeed(); stackNeed > prevStackUse {
unitNameLabel.Color = errorColor color = errorColor
typeString := u.Type typeString := u.Type
if u.Parameters["stereo"] == 1 { if u.Parameters["stereo"] == 1 {
typeString += " (stereo)" typeString += " (stereo)"
} }
t.Alert.Update(fmt.Sprintf("%v needs at least %v input signals, got %v", typeString, stackNeed, prevStackUse), Error, 0) t.Alert.Update(fmt.Sprintf("%v needs at least %v input signals, got %v", typeString, stackNeed, prevStackUse), Error, 0)
} else if i == len(units)-1 && ie.stackUse[i] != 0 { } else if i == len(units)-1 && ie.stackUse[i] != 0 {
unitNameLabel.Color = warningColor color = warningColor
t.Alert.Update(fmt.Sprintf("Instrument leaves %v signal(s) on the stack", ie.stackUse[i]), Warning, 0) t.Alert.Update(fmt.Sprintf("Instrument leaves %v signal(s) on the stack", ie.stackUse[i]), Warning, 0)
} }
} }
var unitName layout.Widget
if i == t.UnitIndex() {
for _, ev := range ie.unitTypeEditor.Events() {
_, ok := ev.(widget.SubmitEvent)
if ok {
ie.unitDragList.Focus()
if text := ie.unitTypeEditor.Text(); text != "" {
for _, n := range tracker.UnitTypeNames {
if strings.HasPrefix(n, ie.unitTypeEditor.Text()) {
t.SetUnitType(n)
break
}
}
} else {
t.SetUnitType("")
}
continue
}
}
if !ie.unitTypeEditor.Focused() && !ie.paramEditor.Focused() && ie.unitTypeEditor.Text() != t.Unit().Type {
ie.unitTypeEditor.SetText(t.Unit().Type)
}
editor := material.Editor(t.Theme, ie.unitTypeEditor, "---")
editor.Color = color
editor.HintColor = instrumentNameHintColor
editor.TextSize = unit.Sp(12)
editor.Font = labelDefaultFont
unitName = func(gtx C) D {
spy, spiedGtx := eventx.Enspy(gtx)
ret := editor.Layout(spiedGtx)
for _, group := range spy.AllEvents() {
for _, event := range group.Items {
switch e := event.(type) {
case key.Event:
if e.Name == key.NameEscape {
ie.unitDragList.Focus()
}
}
}
}
return ret
}
} else {
unitNameLabel := LabelStyle{Text: u.Type, ShadeColor: black, Color: color, Font: labelDefaultFont, FontSize: unit.Sp(12)}
if unitNameLabel.Text == "" {
unitNameLabel.Text = "---"
}
unitName = unitNameLabel.Layout
}
stackLabel := LabelStyle{Text: stackText, ShadeColor: black, Color: mediumEmphasisTextColor, Font: labelDefaultFont, FontSize: unit.Sp(12)} stackLabel := LabelStyle{Text: stackText, ShadeColor: black, Color: mediumEmphasisTextColor, Font: labelDefaultFont, FontSize: unit.Sp(12)}
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, unitNameLabel.Layout), layout.Flexed(1, unitName),
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return rightMargin.Layout(gtx, stackLabel.Layout) return rightMargin.Layout(gtx, stackLabel.Layout)
}), }),
) )
} }
defer op.Save(gtx.Ops).Load()
pointer.PassOp{Pass: true}.Add(gtx.Ops)
unitList := FilledDragList(t.Theme, ie.unitDragList, len(units), element, t.SwapUnits) unitList := FilledDragList(t.Theme, ie.unitDragList, len(units), element, t.SwapUnits)
ie.unitDragList.SelectedItem = t.UnitIndex() ie.unitDragList.SelectedItem = t.UnitIndex()
return Surface{Gray: 30, Focus: ie.wasFocused}.Layout(gtx, func(gtx C) D { return Surface{Gray: 30, Focus: ie.wasFocused}.Layout(gtx, func(gtx C) D {
@ -395,41 +448,44 @@ func (ie *InstrumentEditor) layoutInstrumentEditor(gtx C, t *Tracker) D {
prevUnitIndex := t.UnitIndex() prevUnitIndex := t.UnitIndex()
if t.UnitIndex() != ie.unitDragList.SelectedItem { if t.UnitIndex() != ie.unitDragList.SelectedItem {
t.SetUnitIndex(ie.unitDragList.SelectedItem) t.SetUnitIndex(ie.unitDragList.SelectedItem)
ie.unitTypeEditor.SetText(t.Unit().Type)
} }
for _, group := range spy.AllEvents() { if ie.unitDragList.Focused() {
for _, event := range group.Items { for _, group := range spy.AllEvents() {
switch e := event.(type) { for _, event := range group.Items {
case key.Event: switch e := event.(type) {
switch e.State { case key.Event:
case key.Press: switch e.State {
switch e.Name { case key.Press:
case key.NameUpArrow: switch e.Name {
if prevUnitIndex == 0 { case key.NameUpArrow:
ie.instrumentDragList.Focus() if prevUnitIndex == 0 {
ie.instrumentDragList.Focus()
}
case key.NameRightArrow:
ie.paramEditor.Focus()
case key.NameDeleteBackward:
t.SetUnitType("")
ie.unitTypeEditor.Focus()
l := len(ie.unitTypeEditor.Text())
ie.unitTypeEditor.SetCaret(l, l)
case key.NameDeleteForward:
t.DeleteUnit(true)
case key.NameReturn:
if e.Modifiers.Contain(key.ModShortcut) {
t.AddUnit(true)
}
ie.unitTypeEditor.Focus()
l := len(ie.unitTypeEditor.Text())
ie.unitTypeEditor.SetCaret(l, l)
} }
case key.NameRightArrow:
ie.paramEditor.Focus()
case key.NameDeleteForward, key.NameDeleteBackward:
t.DeleteUnit(e.Name == key.NameDeleteForward)
case key.NameReturn:
t.AddUnit(!e.Modifiers.Contain(key.ModShortcut))
}
name := e.Name
if !e.Modifiers.Contain(key.ModShift) {
name = strings.ToLower(name)
}
if val, ok := unitKeyMap[name]; ok {
if e.Modifiers.Contain(key.ModShortcut) { if e.Modifiers.Contain(key.ModShortcut) {
t.SetUnitType(val)
continue continue
} }
t.JammingPressed(e)
case key.Release:
t.JammingReleased(e)
} }
if e.Modifiers.Contain(key.ModShortcut) {
continue
}
t.JammingPressed(e)
case key.Release:
t.JammingReleased(e)
} }
} }
} }

View File

@ -43,38 +43,6 @@ var noteMap = map[string]int{
"P": 16, "P": 16,
} }
var unitKeyMap = map[string]string{
"e": "envelope",
"o": "oscillator",
"m": "mulp",
"M": "mul",
"a": "addp",
"A": "add",
"p": "pan",
"S": "push",
"P": "pop",
"O": "out",
"l": "loadnote",
"L": "loadval",
"h": "xch",
"d": "delay",
"D": "distort",
"H": "hold",
"b": "crush",
"g": "gain",
"i": "invgain",
"f": "filter",
"I": "clip",
"E": "speed",
"r": "compressor",
"u": "outaux",
"U": "aux",
"s": "send",
"n": "noise",
"N": "in",
"R": "receive",
}
// KeyEvent handles incoming key events and returns true if repaint is needed. // KeyEvent handles incoming key events and returns true if repaint is needed.
func (t *Tracker) KeyEvent(e key.Event) bool { func (t *Tracker) KeyEvent(e key.Event) bool {
if e.State == key.Press { if e.State == key.Press {

View File

@ -108,7 +108,7 @@ func (pe *ParamEditor) Bind(t *Tracker) layout.Widget {
key.FocusOp{Tag: &pe.tag}.Add(gtx.Ops) key.FocusOp{Tag: &pe.tag}.Add(gtx.Ops)
} }
editorFunc := pe.layoutUnitSliders editorFunc := pe.layoutUnitSliders
if t.Unit().Type == "" { if y := t.Unit().Type; y == "" || y != t.InstrumentEditor.unitTypeEditor.Text() {
editorFunc = pe.layoutUnitTypeChooser editorFunc = pe.layoutUnitTypeChooser
} }
return Surface{Gray: 24, Focus: t.InstrumentEditor.wasFocused}.Layout(gtx, func(gtx C) D { return Surface{Gray: 24, Focus: t.InstrumentEditor.wasFocused}.Layout(gtx, func(gtx C) D {
@ -210,8 +210,13 @@ func (pe *ParamEditor) layoutUnitTypeChooser(gtx C, t *Tracker) D {
listElem := func(gtx C, i int) D { listElem := func(gtx C, i int) D {
for pe.ChooseUnitTypeBtns[i].Clicked() { for pe.ChooseUnitTypeBtns[i].Clicked() {
t.SetUnitType(tracker.UnitTypeNames[i]) t.SetUnitType(tracker.UnitTypeNames[i])
t.InstrumentEditor.unitTypeEditor.SetText(tracker.UnitTypeNames[i])
} }
labelStyle := LabelStyle{Text: tracker.UnitTypeNames[i], ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)} text := tracker.UnitTypeNames[i]
if t.InstrumentEditor.unitTypeEditor.Focused() && !strings.HasPrefix(text, t.InstrumentEditor.unitTypeEditor.Text()) {
return D{}
}
labelStyle := LabelStyle{Text: text, ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)}
bg := func(gtx C) D { bg := func(gtx C) D {
gtx.Constraints = layout.Exact(image.Pt(gtx.Constraints.Max.X, 20)) gtx.Constraints = layout.Exact(image.Pt(gtx.Constraints.Max.X, 20))
var color color.NRGBA var color color.NRGBA

View File

@ -794,22 +794,7 @@ func (m *Model) clampPositions() {
} }
m.instrIndex = clamp(m.instrIndex, 0, len(m.song.Patch)-1) m.instrIndex = clamp(m.instrIndex, 0, len(m.song.Patch)-1)
m.unitIndex = clamp(m.unitIndex, 0, len(m.Instrument().Units)-1) m.unitIndex = clamp(m.unitIndex, 0, len(m.Instrument().Units)-1)
for m.paramIndex < 0 { m.paramIndex = clamp(m.paramIndex, 0, m.NumParams()-1)
if m.unitIndex == 0 {
m.paramIndex = 0
break
}
m.unitIndex--
m.paramIndex += m.NumParams()
}
for n := m.NumParams(); m.paramIndex >= n; n = m.NumParams() {
if m.unitIndex == len(m.Instrument().Units)-1 {
m.paramIndex = n - 1
break
}
m.paramIndex -= n
m.unitIndex++
}
} }
func (m *Model) NumParams() int { func (m *Model) NumParams() int {