This commit is contained in:
5684185+vsariola@users.noreply.github.com
2025-06-26 19:26:52 +03:00
parent 083ccc58b5
commit 0182d004b1
4 changed files with 298 additions and 165 deletions

View File

@ -5,6 +5,7 @@ import (
"image/color" "image/color"
"gioui.org/text" "gioui.org/text"
"gioui.org/unit"
"gioui.org/widget" "gioui.org/widget"
"gioui.org/widget/material" "gioui.org/widget/material"
"golang.org/x/exp/shiny/materialdesign/icons" "golang.org/x/exp/shiny/materialdesign/icons"
@ -84,11 +85,16 @@ type Theme struct {
} }
} }
UnitEditor struct { UnitEditor struct {
Hint LabelStyle Name LabelStyle
Chooser LabelStyle Chooser LabelStyle
ParameterName LabelStyle Hint LabelStyle
InvalidParam color.NRGBA InvalidParam color.NRGBA
SendTarget color.NRGBA SendTarget color.NRGBA
Width unit.Dp
Height unit.Dp
RowTitleWidth unit.Dp
ColumnTitleHeight unit.Dp
RowTitle LabelStyle
} }
Cursor CursorStyle Cursor CursorStyle
Selection CursorStyle Selection CursorStyle
@ -102,6 +108,7 @@ type Theme struct {
} }
Split SplitStyle Split SplitStyle
ScrollBar ScrollBarStyle ScrollBar ScrollBarStyle
Knob KnobStyle
// iconCache is used to cache the icons created from iconvg data // iconCache is used to cache the icons created from iconvg data
iconCache map[*byte]*widget.Icon iconCache map[*byte]*widget.Icon

View File

@ -178,12 +178,6 @@ instrumenteditor:
disabled: { textsize: 12, color: *disabled } disabled: { textsize: 12, color: *disabled }
warning: *warningcolor warning: *warningcolor
error: *errorcolor error: *errorcolor
uniteditor:
hint: { textsize: 16, color: *highemphasis, shadowcolor: *black }
chooser: { textsize: 12, color: *white, shadowcolor: *black }
parametername: { textsize: 16, color: *white, shadowcolor: *black }
invalidparam: { r: 120, g: 120, b: 120, a: 190 }
sendtarget: { r: 120, g: 120, b: 210, a: 255 }
cursor: cursor:
active: { r: 100, g: 140, b: 255, a: 48 } active: { r: 100, g: 140, b: 255, a: 48 }
activealt: { r: 255, g: 100, b: 140, a: 48 } activealt: { r: 255, g: 100, b: 140, a: 48 }
@ -211,3 +205,21 @@ dialog:
textinset: { top: 12, bottom: 12, left: 20, right: 20 } textinset: { top: 12, bottom: 12, left: 20, right: 20 }
buttons: *textbutton buttons: *textbutton
split: { bar: 10, minsize1: 180, minsize2: 180 } split: { bar: 10, minsize1: 180, minsize2: 180 }
uniteditor:
hint: { textsize: 16, color: *highemphasis, shadowcolor: *black }
chooser: { textsize: 12, color: *white, shadowcolor: *black }
name:
{ textsize: 12, alignment: 2, color: *highemphasis, shadowcolor: *black }
invalidparam: { r: 120, g: 120, b: 120, a: 190 }
sendtarget: { r: 120, g: 120, b: 210, a: 255 }
width: 60
height: 60
rowtitlewidth: 16
columntitleheight: 16
rowtitle: { textsize: 12, color: *white, alignment: 2 }
knob:
diameter: 36
strokewidth: 4
color: *primarycolor
trackcolor: { r: 0, g: 0, b: 0, a: 255 }
value: { textsize: 12, color: *highemphasis }

View File

@ -4,10 +4,13 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"image" "image"
"image/color"
"io" "io"
"math" "math"
"strconv"
"gioui.org/f32" "gioui.org/f32"
"gioui.org/gesture"
"gioui.org/io/clipboard" "gioui.org/io/clipboard"
"gioui.org/io/event" "gioui.org/io/event"
"gioui.org/io/key" "gioui.org/io/key"
@ -17,9 +20,9 @@ import (
"gioui.org/op/clip" "gioui.org/op/clip"
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/text" "gioui.org/text"
"gioui.org/unit"
"gioui.org/widget" "gioui.org/widget"
"gioui.org/widget/material" "gioui.org/widget/material"
"gioui.org/x/component"
"github.com/vsariola/sointu" "github.com/vsariola/sointu"
"github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/tracker"
"golang.org/x/exp/shiny/materialdesign/icons" "golang.org/x/exp/shiny/materialdesign/icons"
@ -27,24 +30,52 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
) )
type UnitEditor struct { type (
paramTable *ScrollTable UnitEditor struct {
searchList *DragList paramTable *ScrollTable
Parameters [][]*ParameterWidget searchList *DragList
DeleteUnitBtn *Clickable Parameters [][]*ParameterWidget
CopyUnitBtn *Clickable DeleteUnitBtn *Clickable
ClearUnitBtn *Clickable CopyUnitBtn *Clickable
DisableUnitBtn *Clickable ClearUnitBtn *Clickable
SelectTypeBtn *Clickable DisableUnitBtn *Clickable
commentEditor *Editor SelectTypeBtn *Clickable
caser cases.Caser commentEditor *Editor
caser cases.Caser
copyHint string copyHint string
disableUnitHint string disableUnitHint string
enableUnitHint string enableUnitHint string
searching tracker.Bool searching tracker.Bool
} }
KnobState struct {
click gesture.Click
drag gesture.Drag
dragStartPt f32.Point // used to calculate the drag amount
dragStartVal int
tipArea TipArea
}
KnobStyle struct {
Diameter unit.Dp
StrokeWidth unit.Dp
Color color.NRGBA
TrackColor color.NRGBA
Value LabelStyle
Title LabelStyle
}
KnobWidget struct {
Theme *Theme
Value tracker.Parameter
State *KnobState
Style *KnobStyle
Hint string
Scroll bool
}
)
func NewUnitEditor(m *tracker.Model) *UnitEditor { func NewUnitEditor(m *tracker.Model) *UnitEditor {
ret := &UnitEditor{ ret := &UnitEditor{
@ -157,25 +188,28 @@ func (pe *UnitEditor) layoutSliders(gtx C) D {
for len(pe.Parameters) < pe.paramTable.Table.Height() { for len(pe.Parameters) < pe.paramTable.Table.Height() {
pe.Parameters = append(pe.Parameters, make([]*ParameterWidget, 0)) pe.Parameters = append(pe.Parameters, make([]*ParameterWidget, 0))
} }
cellWidth := gtx.Dp(t.Theme.UnitEditor.Width)
cellHeight := gtx.Dp(t.Theme.UnitEditor.Height)
rowTitleWidth := gtx.Dp(t.Theme.UnitEditor.RowTitleWidth)
columnTitleHeight := gtx.Dp(t.Theme.UnitEditor.ColumnTitleHeight)
for i := range pe.Parameters { for i := range pe.Parameters {
for len(pe.Parameters[i]) < width { for len(pe.Parameters[i]) < width {
pe.Parameters[i] = append(pe.Parameters[i], &ParameterWidget{}) pe.Parameters[i] = append(pe.Parameters[i], &ParameterWidget{})
} }
} }
coltitle := func(gtx C, x int) D { coltitle := func(gtx C, x int) D {
return D{Size: image.Pt(gtx.Dp(100), gtx.Dp(10))} return D{Size: image.Pt(cellWidth, columnTitleHeight)}
} }
rowtitle := func(gtx C, y int) D { rowtitle := func(gtx C, y int) D {
h := gtx.Dp(100)
//defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop() //defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop()
defer op.Affine(f32.Affine2D{}.Rotate(f32.Pt(0, 0), -90*math.Pi/180).Offset(f32.Point{X: 0, Y: float32(h)})).Push(gtx.Ops).Pop() defer op.Affine(f32.Affine2D{}.Rotate(f32.Pt(0, 0), -90*math.Pi/180).Offset(f32.Point{X: 0, Y: float32(cellHeight)})).Push(gtx.Ops).Pop()
gtx.Constraints = layout.Exact(image.Pt(1e6, 1e6)) gtx.Constraints = layout.Exact(image.Pt(cellHeight, rowTitleWidth))
Label(t.Theme, &t.Theme.OrderEditor.TrackTitle, t.Units().Item(y).Type).Layout(gtx) Label(t.Theme, &t.Theme.UnitEditor.RowTitle, t.Units().Item(y).Type).Layout(gtx)
return D{Size: image.Pt(gtx.Dp(10), h)} return D{Size: image.Pt(rowTitleWidth, cellHeight)}
} }
cursor := t.Model.Params().Cursor() cursor := t.Model.Params().Cursor()
cell := func(gtx C, x, y int) D { cell := func(gtx C, x, y int) D {
gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(100), gtx.Dp(100))) gtx.Constraints = layout.Exact(image.Pt(cellWidth, cellHeight))
point := tracker.Point{X: x, Y: y} point := tracker.Point{X: x, Y: y}
if y < 0 || y >= len(pe.Parameters) || x < 0 || x >= len(pe.Parameters[y]) { if y < 0 || y >= len(pe.Parameters) || x < 0 || x >= len(pe.Parameters[y]) {
return D{} return D{}
@ -192,14 +226,14 @@ func (pe *UnitEditor) layoutSliders(gtx C) D {
pe.Parameters[y][x].Parameter = param pe.Parameters[y][x].Parameter = param
paramStyle := t.ParamStyle(t.Theme, pe.Parameters[y][x]) paramStyle := t.ParamStyle(t.Theme, pe.Parameters[y][x])
paramStyle.Focus = pe.paramTable.Table.Cursor() == tracker.Point{X: x, Y: y} paramStyle.Focus = pe.paramTable.Table.Cursor() == tracker.Point{X: x, Y: y}
dims := paramStyle.Layout(gtx) paramStyle.Layout(gtx)
return D{Size: image.Pt(gtx.Constraints.Max.X, dims.Size.Y)} return D{Size: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}
} }
table := FilledScrollTable(t.Theme, pe.paramTable) table := FilledScrollTable(t.Theme, pe.paramTable)
table.RowTitleWidth = 10 table.RowTitleWidth = t.Theme.UnitEditor.RowTitleWidth
table.ColumnTitleHeight = 10 table.ColumnTitleHeight = t.Theme.UnitEditor.ColumnTitleHeight
table.CellWidth = 100 table.CellWidth = t.Theme.UnitEditor.Width
table.CellHeight = 100 table.CellHeight = t.Theme.UnitEditor.Height
return table.Layout(gtx, cell, coltitle, rowtitle, nil, nil) return table.Layout(gtx, cell, coltitle, rowtitle, nil, nil)
} }
@ -268,14 +302,14 @@ func (t *UnitEditor) Tags(level int, yield TagYieldFunc) bool {
} }
type ParameterWidget struct { type ParameterWidget struct {
floatWidget widget.Float knobState KnobState
boolWidget widget.Bool boolWidget widget.Bool
instrBtn Clickable instrBtn Clickable
instrMenu MenuState instrMenu MenuState
unitBtn Clickable unitBtn Clickable
unitMenu MenuState unitMenu MenuState
Parameter tracker.Parameter Parameter tracker.Parameter
tipArea TipArea tipArea TipArea
} }
type ParameterStyle struct { type ParameterStyle struct {
@ -302,120 +336,89 @@ func (t *Tracker) ParamStyle(th *Theme, paramWidget *ParameterWidget) ParameterS
} }
func (p ParameterStyle) Layout(gtx C) D { func (p ParameterStyle) Layout(gtx C) D {
info, infoOk := p.w.Parameter.Info() //_, _ := p.w.Parameter.Info()
title := Label(p.Theme, &p.Theme.UnitEditor.ParameterName, p.w.Parameter.Name()) title := Label(p.Theme, &p.Theme.UnitEditor.Name, p.w.Parameter.Name())
title.Alignment = text.Middle widget := func(gtx C) D {
return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, switch p.w.Parameter.Type() {
layout.Rigid(title.Layout), case tracker.IntegerParameter:
layout.Rigid(func(gtx C) D { k := Knob(p.w.Parameter, p.Theme, &p.w.knobState, p.w.Parameter.Hint().Label, p.Focus)
gtx.Constraints.Min.X = gtx.Dp(100) return k.Layout(gtx)
gtx.Constraints.Min.Y = gtx.Dp(50) case tracker.BoolParameter:
switch p.w.Parameter.Type() { ra := p.w.Parameter.Range()
case tracker.IntegerParameter: p.w.boolWidget.Value = p.w.Parameter.Value() > ra.Min
for p.Focus { boolStyle := material.Switch(&p.Theme.Material, &p.w.boolWidget, "Toggle boolean parameter")
e, ok := gtx.Event(pointer.Filter{ boolStyle.Color.Disabled = p.Theme.Material.Fg
Target: &p.w.floatWidget, defer pointer.PassOp{}.Push(gtx.Ops).Pop()
Kinds: pointer.Scroll, dims := layout.Center.Layout(gtx, boolStyle.Layout)
ScrollY: pointer.ScrollRange{Min: -1e6, Max: 1e6}, if p.w.boolWidget.Value {
}) p.w.Parameter.SetValue(ra.Max)
if !ok { } else {
break p.w.Parameter.SetValue(ra.Min)
}
return dims
case tracker.IDParameter:
instrItems := make([]ActionMenuItem, p.tracker.Instruments().Count())
for i := range instrItems {
i := i
name, _, _, _ := p.tracker.Instruments().Item(i)
instrItems[i].Text = name
instrItems[i].Icon = icons.NavigationChevronRight
instrItems[i].Action = tracker.MakeEnabledAction((tracker.DoFunc)(func() {
if id, ok := p.tracker.Instruments().FirstID(i); ok {
p.w.Parameter.SetValue(id)
} }
if ev, ok := e.(pointer.Event); ok && ev.Kind == pointer.Scroll { }))
delta := math.Min(math.Max(float64(ev.Scroll.Y), -1), 1) }
p.w.Parameter.SetValue(p.w.Parameter.Value() - int(delta)) var unitItems []ActionMenuItem
} instrName := "<instr>"
} unitName := "<unit>"
ra := p.w.Parameter.Range() targetInstrName, units, targetUnitIndex, ok := p.tracker.UnitInfo(p.w.Parameter.Value())
if !p.w.floatWidget.Dragging() { if ok {
p.w.floatWidget.Value = (float32(p.w.Parameter.Value()) - float32(ra.Min)) / float32(ra.Max-ra.Min) instrName = targetInstrName
} unitName = buildUnitLabel(targetUnitIndex, units[targetUnitIndex])
sliderStyle := material.Slider(&p.Theme.Material, &p.w.floatWidget) unitItems = make([]ActionMenuItem, len(units))
if infoOk { for j, unit := range units {
sliderStyle.Color = p.Theme.UnitEditor.SendTarget id := unit.ID
} unitItems[j].Text = buildUnitLabel(j, unit)
r := image.Rectangle{Max: gtx.Constraints.Min} unitItems[j].Icon = icons.NavigationChevronRight
defer clip.Rect(r).Push(gtx.Ops).Pop() unitItems[j].Action = tracker.MakeEnabledAction((tracker.DoFunc)(func() {
defer pointer.PassOp{}.Push(gtx.Ops).Pop() p.w.Parameter.SetValue(id)
if p.Focus {
event.Op(gtx.Ops, &p.w.floatWidget)
}
dims := sliderStyle.Layout(gtx)
p.w.Parameter.SetValue(int(p.w.floatWidget.Value*float32(ra.Max-ra.Min) + float32(ra.Min) + 0.5))
return dims
case tracker.BoolParameter:
ra := p.w.Parameter.Range()
p.w.boolWidget.Value = p.w.Parameter.Value() > ra.Min
boolStyle := material.Switch(&p.Theme.Material, &p.w.boolWidget, "Toggle boolean parameter")
boolStyle.Color.Disabled = p.Theme.Material.Fg
defer pointer.PassOp{}.Push(gtx.Ops).Pop()
dims := layout.Center.Layout(gtx, boolStyle.Layout)
if p.w.boolWidget.Value {
p.w.Parameter.SetValue(ra.Max)
} else {
p.w.Parameter.SetValue(ra.Min)
}
return dims
case tracker.IDParameter:
instrItems := make([]ActionMenuItem, p.tracker.Instruments().Count())
for i := range instrItems {
i := i
name, _, _, _ := p.tracker.Instruments().Item(i)
instrItems[i].Text = name
instrItems[i].Icon = icons.NavigationChevronRight
instrItems[i].Action = tracker.MakeEnabledAction((tracker.DoFunc)(func() {
if id, ok := p.tracker.Instruments().FirstID(i); ok {
p.w.Parameter.SetValue(id)
}
})) }))
} }
var unitItems []ActionMenuItem
instrName := "<instr>"
unitName := "<unit>"
targetInstrName, units, targetUnitIndex, ok := p.tracker.UnitInfo(p.w.Parameter.Value())
if ok {
instrName = targetInstrName
unitName = buildUnitLabel(targetUnitIndex, units[targetUnitIndex])
unitItems = make([]ActionMenuItem, len(units))
for j, unit := range units {
id := unit.ID
unitItems[j].Text = buildUnitLabel(j, unit)
unitItems[j].Icon = icons.NavigationChevronRight
unitItems[j].Action = tracker.MakeEnabledAction((tracker.DoFunc)(func() {
p.w.Parameter.SetValue(id)
}))
}
}
defer pointer.PassOp{}.Push(gtx.Ops).Pop()
instrBtn := MenuBtn(p.tracker.Theme, &p.w.instrMenu, &p.w.instrBtn, instrName)
unitBtn := MenuBtn(p.tracker.Theme, &p.w.unitMenu, &p.w.unitBtn, unitName)
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return instrBtn.Layout(gtx, instrItems...)
}),
layout.Rigid(func(gtx C) D {
return unitBtn.Layout(gtx, unitItems...)
}),
)
} }
return D{} defer pointer.PassOp{}.Push(gtx.Ops).Pop()
}), instrBtn := MenuBtn(p.tracker.Theme, &p.w.instrMenu, &p.w.instrBtn, instrName)
layout.Rigid(func(gtx C) D { unitBtn := MenuBtn(p.tracker.Theme, &p.w.unitMenu, &p.w.unitBtn, unitName)
if p.w.Parameter.Type() != tracker.IDParameter { return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
hint := p.w.Parameter.Hint() layout.Rigid(func(gtx C) D {
label := Label(p.tracker.Theme, &p.tracker.Theme.UnitEditor.Hint, hint.Label) return instrBtn.Layout(gtx, instrItems...)
label.Alignment = text.Middle }),
if !hint.Valid { layout.Rigid(func(gtx C) D {
label.Color = p.tracker.Theme.UnitEditor.InvalidParam return unitBtn.Layout(gtx, unitItems...)
} }),
if info == "" { )
return label.Layout(gtx) }
} return D{}
tooltip := component.PlatformTooltip(p.SendTargetTheme, info) }
return p.w.tipArea.Layout(gtx, tooltip, label.Layout) return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(title.Layout),
layout.Flexed(1, func(gtx C) D { return layout.Center.Layout(gtx, widget) }),
/* layout.Rigid(func(gtx C) D {
if p.w.Parameter.Type() != tracker.IDParameter {
hint := p.w.Parameter.Hint()
label := Label(p.tracker.Theme, &p.tracker.Theme.UnitEditor.Hint, hint.Label)
label.Alignment = text.Middle
if !hint.Valid {
label.Color = p.tracker.Theme.UnitEditor.InvalidParam
} }
return D{} if info == "" {
}), return label.Layout(gtx)
}
tooltip := component.PlatformTooltip(p.SendTargetTheme, info)
return p.w.tipArea.Layout(gtx, tooltip, label.Layout)
}
return D{}
}),*/
) )
} }
@ -426,3 +429,113 @@ func buildUnitLabel(index int, u sointu.Unit) string {
} }
return fmt.Sprintf("%d: %s", index, text) return fmt.Sprintf("%d: %s", index, text)
} }
func Knob(v tracker.Parameter, th *Theme, state *KnobState, hint string, scroll bool) KnobWidget {
return KnobWidget{
Theme: th,
Value: v,
State: state,
Style: &th.Knob,
Hint: hint,
Scroll: scroll,
}
}
func (k *KnobWidget) Layout(gtx C) D {
k.update(gtx)
knob := func(gtx C) D {
m := k.Value.Range()
amount := float32(k.Value.Value()-m.Min) / float32(m.Max-m.Min)
sw := gtx.Dp(k.Style.StrokeWidth)
d := gtx.Dp(k.Style.Diameter)
defer clip.Rect(image.Rectangle{Max: image.Pt(d, d)}).Push(gtx.Ops).Pop()
event.Op(gtx.Ops, k.State)
k.State.drag.Add(gtx.Ops)
k.State.click.Add(gtx.Ops)
strokeKnobArc(gtx, k.Style.TrackColor, sw, d, 1)
strokeKnobArc(gtx, k.Style.Color, sw, d, amount)
return D{Size: image.Pt(d, d)}
}
label := Label(k.Theme, &k.Style.Value, strconv.Itoa(k.Value.Value()))
w := func(gtx C) D {
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
layout.Stacked(knob),
layout.Stacked(label.Layout))
}
if k.Hint != "" {
c := gtx.Constraints
gtx.Constraints.Max = image.Pt(1e6, 1e6)
return k.State.tipArea.Layout(gtx, Tooltip(k.Theme, k.Hint), func(gtx C) D {
gtx.Constraints = c
return w(gtx)
})
}
return w(gtx)
}
func (k *KnobWidget) update(gtx C) {
for {
p, ok := k.State.drag.Update(gtx.Metric, gtx.Source, gesture.Both)
if !ok {
break
}
switch p.Kind {
case pointer.Press:
k.State.dragStartPt = p.Position
k.State.dragStartVal = k.Value.Value()
case pointer.Drag:
// update the value based on the drag amount
m := k.Value.Range()
d := p.Position.Sub(k.State.dragStartPt)
amount := float32(d.X-d.Y) / float32(gtx.Dp(k.Style.Diameter)) / 4
newValue := int(float32(k.State.dragStartVal) + amount*float32(m.Max-m.Min))
k.Value.SetValue(newValue)
k.State.tipArea.Appear(gtx.Now)
}
}
for {
g, ok := k.State.click.Update(gtx.Source)
if !ok {
break
}
if g.Kind == gesture.KindClick && g.NumClicks > 1 {
k.Value.Reset()
}
}
for k.Scroll {
e, ok := gtx.Event(pointer.Filter{
Target: k.State,
Kinds: pointer.Scroll,
ScrollY: pointer.ScrollRange{Min: -1e6, Max: 1e6},
})
if !ok {
break
}
if ev, ok := e.(pointer.Event); ok && ev.Kind == pointer.Scroll {
delta := math.Min(math.Max(float64(ev.Scroll.Y), -1), 1)
k.Value.SetValue(k.Value.Value() - int(delta))
k.State.tipArea.Appear(gtx.Now)
}
}
}
func strokeKnobArc(gtx C, color color.NRGBA, strokeWidth, diameter int, amount float32) {
var path clip.Path
rad := float32(diameter) / 2
amount = min(max(amount, 0), 1)
if amount <= 0 {
return
}
angle := amount * 8 * math.Pi / 5
center := f32.Point{X: rad, Y: rad}
r2 := rad - float32(strokeWidth)/2
start := f32.Point{X: rad - r2*float32(math.Sin(math.Pi/5)), Y: rad + r2*float32(math.Cos(math.Pi/5))}
path.Begin(gtx.Ops)
path.MoveTo(start)
path.ArcTo(center, center, angle)
paint.FillShape(gtx.Ops, color,
clip.Stroke{
Path: path.End(),
Width: float32(strokeWidth),
}.Op())
}

View File

@ -143,6 +143,7 @@ func (pt *Params) Cursor2() Point { return pt.Cursor() }
func (pt *Params) SetCursor(p Point) { func (pt *Params) SetCursor(p Point) {
pt.d.ParamIndex = max(min(p.X, pt.Width()-1), 0) pt.d.ParamIndex = max(min(p.X, pt.Width()-1), 0)
pt.d.UnitIndex = max(min(p.Y, pt.Height()-1), 0) pt.d.UnitIndex = max(min(p.Y, pt.Height()-1), 0)
pt.d.UnitIndex2 = pt.d.UnitIndex
} }
func (pt *Params) SetCursor2(p Point) {} func (pt *Params) SetCursor2(p Point) {}
func (pt *Params) Width() int { func (pt *Params) Width() int {
@ -227,7 +228,7 @@ func (n *namedParameter) Hint(p *Parameter) ParameterHint {
label := strconv.Itoa(val) label := strconv.Itoa(val)
if p.up.DisplayFunc != nil { if p.up.DisplayFunc != nil {
valueInUnits, units := p.up.DisplayFunc(val) valueInUnits, units := p.up.DisplayFunc(val)
label = fmt.Sprintf("%d / %s %s", val, valueInUnits, units) label = fmt.Sprintf("%s %s", valueInUnits, units)
} }
if p.unit.Type == "send" { if p.unit.Type == "send" {
instrIndex, targetType, ok := p.m.UnitHintInfo(p.unit.Parameters["target"]) instrIndex, targetType, ok := p.m.UnitHintInfo(p.unit.Parameters["target"])
@ -305,9 +306,9 @@ func (g *gmDlsEntryParameter) Name(p *Parameter) string {
return "sample" return "sample"
} }
func (g *gmDlsEntryParameter) Hint(p *Parameter) ParameterHint { func (g *gmDlsEntryParameter) Hint(p *Parameter) ParameterHint {
label := "0 / custom" label := "custom"
if v := g.Value(p); v > 0 { if v := g.Value(p); v > 0 {
label = fmt.Sprintf("%v / %v", v, GmDlsEntries[v-1].Name) label = GmDlsEntries[v-1].Name
} }
return ParameterHint{label, true} return ParameterHint{label, true}
} }
@ -350,11 +351,11 @@ func (d *delayTimeParameter) Hint(p *Parameter) ParameterHint {
switch p.unit.Parameters["notetracking"] { switch p.unit.Parameters["notetracking"] {
default: default:
case 0: case 0:
text = fmt.Sprintf("%v / %.3f rows", val, float32(val)/float32(p.m.d.Song.SamplesPerRow())) text = fmt.Sprintf("%.3f rows", float32(val)/float32(p.m.d.Song.SamplesPerRow()))
case 1: case 1:
relPitch := float64(val) / 10787 relPitch := float64(val) / 10787
semitones := -math.Log2(relPitch) * 12 semitones := -math.Log2(relPitch) * 12
text = fmt.Sprintf("%v / %.3f st", val, semitones) text = fmt.Sprintf("%.3f st", semitones)
case 2: case 2:
k := 0 k := 0
v := val v := val
@ -467,9 +468,9 @@ func (r *reverbParameter) Name(p *Parameter) string {
} }
func (r *reverbParameter) Hint(p *Parameter) ParameterHint { func (r *reverbParameter) Hint(p *Parameter) ParameterHint {
i := r.Value(p) i := r.Value(p)
label := "0 / custom" label := "custom"
if i > 0 { if i > 0 {
label = fmt.Sprintf("%v / %v", i, reverbs[i-1].name) label = reverbs[i-1].name
} }
return ParameterHint{label, true} return ParameterHint{label, true}
} }