sointu/tracker/gioui/parameter.go
vsariola adcf3ebce8 feat(sointu, tracker,...): restructure domain & tracker models
send targets are now by ID and Song has "Score" part, which is the notes for it. also, moved the model part separate of the actual gioui dependend stuff.

sorry to my future self about the code bomb; ended up too far and did not find an easy way to rewrite the history to make the steps smaller, so in the end, just squashed everything.
2021-02-28 14:24:54 +02:00

206 lines
6.7 KiB
Go

package gioui
import (
"fmt"
"gioui.org/layout"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"github.com/vsariola/sointu/tracker"
"golang.org/x/exp/shiny/materialdesign/icons"
)
type ParameterWidget struct {
floatWidget widget.Float
boolWidget widget.Bool
labelBtn widget.Clickable
instrBtn widget.Clickable
instrMenu Menu
unitBtn widget.Clickable
unitMenu Menu
}
type ParameterStyle struct {
tracker *Tracker
Parameter *tracker.Parameter
ParameterWidget *ParameterWidget
Theme *material.Theme
Focus bool
}
func (t *Tracker) ParamStyle(th *material.Theme, param *tracker.Parameter, paramWidget *ParameterWidget) ParameterStyle {
return ParameterStyle{
tracker: t, // TODO: we need this to pull the instrument names for ID style parameters, find out another way
Parameter: param,
Theme: th,
ParameterWidget: paramWidget,
}
}
func (p *ParameterWidget) Clicked() bool {
return p.labelBtn.Clicked()
}
func (p ParameterStyle) Layout(gtx C) D {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return layout.Stack{}.Layout(gtx,
layout.Stacked(func(gtx C) D {
gtx.Constraints.Min.X = gtx.Px(unit.Dp(110))
return layout.E.Layout(gtx, Label(p.Parameter.Name, white))
}),
layout.Expanded(p.ParameterWidget.labelBtn.Layout),
)
}),
layout.Rigid(func(gtx C) D {
switch p.Parameter.Type {
case tracker.IntegerParameter:
gtx.Constraints.Min.X = gtx.Px(unit.Dp(200))
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(40))
if p.Focus {
paint.FillShape(gtx.Ops, cursorColor, clip.Rect{
Max: gtx.Constraints.Min,
}.Op())
}
if !p.ParameterWidget.floatWidget.Dragging() {
p.ParameterWidget.floatWidget.Value = float32(p.Parameter.Value)
}
sliderStyle := material.Slider(p.Theme, &p.ParameterWidget.floatWidget, float32(p.Parameter.Min), float32(p.Parameter.Max))
sliderStyle.Color = p.Theme.Fg
dims := sliderStyle.Layout(gtx)
p.Parameter.Value = int(p.ParameterWidget.floatWidget.Value + 0.5)
return dims
case tracker.BoolParameter:
gtx.Constraints.Min.X = gtx.Px(unit.Dp(60))
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(40))
if p.Focus {
paint.FillShape(gtx.Ops, cursorColor, clip.Rect{
Max: gtx.Constraints.Min,
}.Op())
}
p.ParameterWidget.boolWidget.Value = p.Parameter.Value > p.Parameter.Min
boolStyle := material.Switch(p.Theme, &p.ParameterWidget.boolWidget)
boolStyle.Color.Disabled = p.Theme.Fg
boolStyle.Color.Enabled = white
dims := layout.Center.Layout(gtx, boolStyle.Layout)
if p.ParameterWidget.boolWidget.Value {
p.Parameter.Value = p.Parameter.Max
} else {
p.Parameter.Value = p.Parameter.Min
}
return dims
case tracker.IDParameter:
gtx.Constraints.Min.X = gtx.Px(unit.Dp(200))
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(40))
if p.Focus {
paint.FillShape(gtx.Ops, cursorColor, clip.Rect{
Max: gtx.Constraints.Min,
}.Op())
}
for clickedItem, hasClicked := p.ParameterWidget.instrMenu.Clicked(); hasClicked; {
p.Parameter.Value = p.tracker.Song().Patch[clickedItem].Units[0].ID
clickedItem, hasClicked = p.ParameterWidget.instrMenu.Clicked()
}
instrItems := make([]MenuItem, len(p.tracker.Song().Patch))
for i, instr := range p.tracker.Song().Patch {
instrItems[i].Text = instr.Name
instrItems[i].IconBytes = icons.NavigationChevronRight
}
var unitItems []MenuItem
instrName := "<instr>"
unitName := "<unit>"
targetI, targetU, err := p.tracker.Song().Patch.FindSendTarget(p.Parameter.Value)
if err == nil {
targetInstrument := p.tracker.Song().Patch[targetI]
instrName = targetInstrument.Name
units := targetInstrument.Units
unitName = fmt.Sprintf("%v: %v", targetU, units[targetU].Type)
unitItems = make([]MenuItem, len(units))
for clickedItem, hasClicked := p.ParameterWidget.unitMenu.Clicked(); hasClicked; {
p.Parameter.Value = units[clickedItem].ID
clickedItem, hasClicked = p.ParameterWidget.unitMenu.Clicked()
}
for j, unit := range units {
unitItems[j].Text = fmt.Sprintf("%v: %v", j, unit.Type)
unitItems[j].IconBytes = icons.NavigationChevronRight
}
}
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(p.tracker.layoutMenu(instrName, &p.ParameterWidget.instrBtn, &p.ParameterWidget.instrMenu, unit.Dp(200),
instrItems...,
)),
layout.Rigid(p.tracker.layoutMenu(unitName, &p.ParameterWidget.unitBtn, &p.ParameterWidget.unitMenu, unit.Dp(200),
unitItems...,
)),
)
}
return D{}
}),
layout.Rigid(func(gtx C) D {
if p.Parameter.Type != tracker.IDParameter {
return Label(p.Parameter.Hint, white)(gtx)
}
return D{}
}),
)
}
/*
func (t *Tracker) layoutParameter(gtx C, index int) D {
u := t.Unit()
ut, _ := sointu.UnitTypes[u.Type]
params := u.Parameters
var name string
var value, min, max int
var valueText string
if u.Type == "oscillator" && index == len(ut) {
name = "sample"
key := compiler.SampleOffset{Start: uint32(params["samplestart"]), LoopStart: uint16(params["loopstart"]), LoopLength: uint16(params["looplength"])}
if v, ok := tracker.GmDlsEntryMap[key]; ok {
value = v + 1
valueText = fmt.Sprintf("%v / %v", value, tracker.GmDlsEntries[v].Name)
} else {
value = 0
valueText = "0 / custom"
}
min, max = 0, len(tracker.GmDlsEntries)
} else {
if ut[index].MaxValue < ut[index].MinValue {
return layout.Dimensions{}
}
name = ut[index].Name
if u.Type == "oscillator" && (name == "samplestart" || name == "loopstart" || name == "looplength") {
if params["type"] != sointu.Sample {
return layout.Dimensions{}
}
}
value = params[name]
min, max = ut[index].MinValue, ut[index].MaxValue
if u.Type == "send" && name == "voice" {
max = t.Song().Patch.NumVoices()
} else if u.Type == "send" && name == "unit" { // set the maximum values depending on the send target
instrIndex, _, _ := t.Song().Patch.FindSendTarget(t.Unit().Parameters["target"])
if instrIndex != -1 {
max = len(t.Song().Patch[instrIndex].Units) - 1
}
} else if u.Type == "send" && name == "port" { // set the maximum values depending on the send target
instrIndex, unitIndex, _ := t.Song().Patch.FindSendTarget(t.Unit().Parameters["target"])
if instrIndex != -1 && unitIndex != -1 {
max = len(sointu.Ports[t.Song().Patch[instrIndex].Units[unitIndex].Type]) - 1
}
}
hint := t.Song().Patch.ParamHintString(t.InstrIndex(), t.UnitIndex(), name)
if hint != "" {
valueText = fmt.Sprintf("%v / %v", value, hint)
} else {
valueText = fmt.Sprintf("%v", value)
}
}
}*/