package gioui import ( "fmt" "image" "math" "gioui.org/io/pointer" "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 p.ParameterWidget.labelBtn.Layout(gtx, func(gtx C) D { gtx.Constraints.Min.X = gtx.Dp(unit.Dp(110)) return layout.E.Layout(gtx, Label(p.Parameter.Name, white)) }) }), layout.Rigid(func(gtx C) D { switch p.Parameter.Type { case tracker.IntegerParameter: for _, e := range gtx.Events(&p.ParameterWidget.floatWidget) { switch ev := e.(type) { case pointer.Event: if ev.Type == pointer.Scroll { delta := math.Min(math.Max(float64(ev.Scroll.Y), -1), 1) p.Parameter.Value += int(math.Round(delta)) } } } gtx.Constraints.Min.X = gtx.Dp(unit.Dp(200)) gtx.Constraints.Min.Y = gtx.Dp(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 r := image.Rectangle{Max: gtx.Constraints.Min} area := clip.Rect(r).Push(gtx.Ops) pointer.InputOp{Tag: &p.ParameterWidget.floatWidget, Types: pointer.Scroll, ScrollBounds: image.Rectangle{Min: image.Pt(0, -1e6), Max: image.Pt(0, 1e6)}}.Add(gtx.Ops) dims := sliderStyle.Layout(gtx) area.Pop() p.Parameter.Value = int(p.ParameterWidget.floatWidget.Value + 0.5) return dims case tracker.BoolParameter: gtx.Constraints.Min.X = gtx.Dp(unit.Dp(60)) gtx.Constraints.Min.Y = gtx.Dp(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, "Toggle boolean parameter") 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.Dp(unit.Dp(200)) gtx.Constraints.Min.Y = gtx.Dp(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 := "" unitName := "" 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) } } }*/