mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-04 01:28:45 -04:00
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.
246 lines
6.7 KiB
Go
246 lines
6.7 KiB
Go
package gioui
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"strings"
|
|
|
|
"gioui.org/io/key"
|
|
"gioui.org/io/pointer"
|
|
"gioui.org/layout"
|
|
"gioui.org/op"
|
|
"gioui.org/op/clip"
|
|
"gioui.org/op/paint"
|
|
"gioui.org/unit"
|
|
"gioui.org/widget"
|
|
"github.com/vsariola/sointu/tracker"
|
|
"golang.org/x/exp/shiny/materialdesign/icons"
|
|
)
|
|
|
|
type ParamEditor struct {
|
|
list *layout.List
|
|
scrollBar *ScrollBar
|
|
Parameters []*ParameterWidget
|
|
DeleteUnitBtn *widget.Clickable
|
|
ClearUnitBtn *widget.Clickable
|
|
ChooseUnitTypeBtns []*widget.Clickable
|
|
tag bool
|
|
focused bool
|
|
requestFocus bool
|
|
}
|
|
|
|
func (pe *ParamEditor) Focus() {
|
|
pe.requestFocus = true
|
|
}
|
|
|
|
func (pe *ParamEditor) Focused() bool {
|
|
return pe.focused
|
|
}
|
|
|
|
func NewParamEditor() *ParamEditor {
|
|
ret := &ParamEditor{
|
|
DeleteUnitBtn: new(widget.Clickable),
|
|
ClearUnitBtn: new(widget.Clickable),
|
|
list: &layout.List{Axis: layout.Vertical},
|
|
scrollBar: &ScrollBar{Axis: layout.Vertical},
|
|
}
|
|
for range tracker.UnitTypeNames {
|
|
ret.ChooseUnitTypeBtns = append(ret.ChooseUnitTypeBtns, new(widget.Clickable))
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (pe *ParamEditor) Bind(t *Tracker) layout.Widget {
|
|
return func(gtx C) D {
|
|
for _, e := range gtx.Events(&pe.tag) {
|
|
switch e := e.(type) {
|
|
case key.FocusEvent:
|
|
pe.focused = e.Focus
|
|
case pointer.Event:
|
|
if e.Type == pointer.Press {
|
|
key.FocusOp{Tag: &pe.tag}.Add(gtx.Ops)
|
|
}
|
|
case key.Event:
|
|
if e.Modifiers.Contain(key.ModShortcut) {
|
|
continue
|
|
}
|
|
switch e.State {
|
|
case key.Press:
|
|
switch e.Name {
|
|
case key.NameUpArrow:
|
|
t.SetParamIndex(t.ParamIndex() - 1)
|
|
case key.NameDownArrow:
|
|
t.SetParamIndex(t.ParamIndex() + 1)
|
|
case key.NameLeftArrow:
|
|
p, err := t.Param(t.ParamIndex())
|
|
if err != nil {
|
|
break
|
|
}
|
|
if e.Modifiers.Contain(key.ModShift) {
|
|
t.SetParam(p.Value - p.LargeStep)
|
|
} else {
|
|
t.SetParam(p.Value - 1)
|
|
}
|
|
case key.NameRightArrow:
|
|
p, err := t.Param(t.ParamIndex())
|
|
if err != nil {
|
|
break
|
|
}
|
|
if e.Modifiers.Contain(key.ModShift) {
|
|
t.SetParam(p.Value + p.LargeStep)
|
|
} else {
|
|
t.SetParam(p.Value + 1)
|
|
}
|
|
case key.NameEscape:
|
|
t.InstrumentEditor.unitDragList.Focus()
|
|
}
|
|
if e.Modifiers.Contain(key.ModShortcut) {
|
|
continue
|
|
}
|
|
t.JammingPressed(e)
|
|
case key.Release:
|
|
t.JammingReleased(e)
|
|
}
|
|
}
|
|
}
|
|
if pe.requestFocus {
|
|
pe.requestFocus = false
|
|
key.FocusOp{Tag: &pe.tag}.Add(gtx.Ops)
|
|
}
|
|
editorFunc := pe.layoutUnitSliders
|
|
if y := t.Unit().Type; y == "" || y != t.InstrumentEditor.unitTypeEditor.Text() {
|
|
editorFunc = pe.layoutUnitTypeChooser
|
|
}
|
|
return Surface{Gray: 24, Focus: t.InstrumentEditor.wasFocused}.Layout(gtx, func(gtx C) D {
|
|
ret := layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
|
layout.Flexed(1, func(gtx C) D {
|
|
return editorFunc(gtx, t)
|
|
}),
|
|
layout.Rigid(pe.layoutUnitFooter(t)))
|
|
rect := image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)
|
|
pointer.PassOp{Pass: true}.Add(gtx.Ops)
|
|
pointer.Rect(rect).Add(gtx.Ops)
|
|
pointer.InputOp{Tag: &pe.tag,
|
|
Types: pointer.Press,
|
|
}.Add(gtx.Ops)
|
|
key.InputOp{Tag: &pe.tag}.Add(gtx.Ops)
|
|
return ret
|
|
})
|
|
}
|
|
}
|
|
|
|
func (pe *ParamEditor) layoutUnitSliders(gtx C, t *Tracker) D {
|
|
numItems := t.NumParams()
|
|
|
|
for len(pe.Parameters) <= numItems {
|
|
pe.Parameters = append(pe.Parameters, new(ParameterWidget))
|
|
}
|
|
|
|
listItem := func(gtx C, index int) D {
|
|
for pe.Parameters[index].Clicked() {
|
|
if !pe.focused || t.ParamIndex() != index {
|
|
pe.Focus()
|
|
t.SetParamIndex(index)
|
|
} else {
|
|
t.ResetParam()
|
|
}
|
|
}
|
|
param, err := t.Param(index)
|
|
if err != nil {
|
|
return D{}
|
|
}
|
|
oldVal := param.Value
|
|
paramStyle := t.ParamStyle(t.Theme, ¶m, pe.Parameters[index])
|
|
paramStyle.Focus = pe.focused && t.ParamIndex() == index
|
|
dims := paramStyle.Layout(gtx)
|
|
if oldVal != param.Value {
|
|
pe.Focus()
|
|
t.SetParamIndex(index)
|
|
t.SetParam(param.Value)
|
|
}
|
|
return dims
|
|
}
|
|
|
|
return layout.Stack{}.Layout(gtx,
|
|
layout.Stacked(func(gtx C) D {
|
|
return pe.list.Layout(gtx, numItems, listItem)
|
|
}),
|
|
layout.Stacked(func(gtx C) D {
|
|
gtx.Constraints.Min = gtx.Constraints.Max
|
|
return pe.scrollBar.Layout(gtx, unit.Dp(10), numItems, &pe.list.Position)
|
|
}))
|
|
}
|
|
|
|
func (pe *ParamEditor) layoutUnitFooter(t *Tracker) layout.Widget {
|
|
return func(gtx C) D {
|
|
for pe.ClearUnitBtn.Clicked() {
|
|
t.SetUnitType("")
|
|
op.InvalidateOp{}.Add(gtx.Ops)
|
|
t.InstrumentEditor.unitDragList.Focus()
|
|
}
|
|
for pe.DeleteUnitBtn.Clicked() {
|
|
t.DeleteUnit(false)
|
|
op.InvalidateOp{}.Add(gtx.Ops)
|
|
t.InstrumentEditor.unitDragList.Focus()
|
|
}
|
|
deleteUnitBtnStyle := IconButton(t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, t.CanDeleteUnit())
|
|
text := t.Unit().Type
|
|
if text == "" {
|
|
text = "Choose unit type"
|
|
} else {
|
|
text = strings.Title(text)
|
|
}
|
|
hintText := Label(text, white)
|
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
|
layout.Rigid(deleteUnitBtnStyle.Layout),
|
|
layout.Rigid(func(gtx C) D {
|
|
var dims D
|
|
if t.Unit().Type != "" {
|
|
clearUnitBtnStyle := IconButton(t.Theme, pe.ClearUnitBtn, icons.ContentClear, true)
|
|
dims = clearUnitBtnStyle.Layout(gtx)
|
|
}
|
|
return D{Size: image.Pt(gtx.Px(unit.Dp(48)), dims.Size.Y)}
|
|
}),
|
|
layout.Flexed(1, hintText),
|
|
)
|
|
}
|
|
}
|
|
|
|
func (pe *ParamEditor) layoutUnitTypeChooser(gtx C, t *Tracker) D {
|
|
listElem := func(gtx C, i int) D {
|
|
for pe.ChooseUnitTypeBtns[i].Clicked() {
|
|
t.SetUnitType(tracker.UnitTypeNames[i])
|
|
t.InstrumentEditor.unitTypeEditor.SetText(tracker.UnitTypeNames[i])
|
|
}
|
|
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 {
|
|
gtx.Constraints = layout.Exact(image.Pt(gtx.Constraints.Max.X, 20))
|
|
var color color.NRGBA
|
|
if pe.ChooseUnitTypeBtns[i].Hovered() {
|
|
color = unitTypeListHighlightColor
|
|
}
|
|
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op())
|
|
return D{Size: gtx.Constraints.Min}
|
|
}
|
|
leftMargin := layout.Inset{Left: unit.Dp(10)}
|
|
return layout.Stack{Alignment: layout.W}.Layout(gtx,
|
|
layout.Stacked(bg),
|
|
layout.Expanded(func(gtx C) D {
|
|
return leftMargin.Layout(gtx, labelStyle.Layout)
|
|
}),
|
|
layout.Expanded(pe.ChooseUnitTypeBtns[i].Layout))
|
|
}
|
|
return layout.Stack{}.Layout(gtx,
|
|
layout.Stacked(func(gtx C) D {
|
|
return pe.list.Layout(gtx, len(tracker.UnitTypeNames), listElem)
|
|
}),
|
|
layout.Expanded(func(gtx C) D {
|
|
return pe.scrollBar.Layout(gtx, unit.Dp(10), len(tracker.UnitTypeNames), &pe.list.Position)
|
|
}),
|
|
)
|
|
}
|