This commit is contained in:
5684185+vsariola@users.noreply.github.com
2025-07-08 18:56:21 +03:00
parent 3c66237fc7
commit 9abb34e575
7 changed files with 137 additions and 161 deletions

View File

@ -536,6 +536,10 @@ func (m *Model) ChooseSendSource(id int) Action {
}
func (s ChooseSendSource) Do() {
defer (*Model)(s.Model).change("ChooseSendSource", NoChange, MinorChange)()
if s.Model.d.SendSource == s.ID {
s.Model.d.SendSource = 0 // unselect
return
}
s.Model.d.SendSource = s.ID
}

View File

@ -22,12 +22,33 @@ import (
)
type (
KnobState struct {
click gesture.Click
ParamState struct {
drag gesture.Drag
dragStartPt f32.Point // used to calculate the drag amount
dragStartVal int
tipArea TipArea
click gesture.Click
clickable Clickable
}
ParamWidget struct {
Parameter tracker.Parameter
State *ParamState
Theme *Theme
Focus bool
Disabled bool
}
PortStyle struct {
Diameter unit.Dp
StrokeWidth unit.Dp
Color color.NRGBA
}
PortWidget struct {
Theme *Theme
Style *PortStyle
State *ParamState
}
KnobStyle struct {
@ -55,7 +76,7 @@ type (
KnobWidget struct {
Theme *Theme
Value tracker.Parameter
State *KnobState
State *ParamState
Style *KnobStyle
Hint string
Scroll bool
@ -84,7 +105,7 @@ type (
SwitchWidget struct {
Theme *Theme
Value tracker.Parameter
State *KnobState
State *ParamState
Style *SwitchStyle
Hint string
Scroll bool
@ -92,9 +113,58 @@ type (
}
)
// KnobState
// ParamState
func (s *KnobState) update(gtx C, param tracker.Parameter, scroll bool) {
func Param(Parameter tracker.Parameter, th *Theme, paramWidget *ParamState, focus, disabled bool) ParamWidget {
return ParamWidget{
Theme: th,
State: paramWidget,
Parameter: Parameter,
Focus: focus,
Disabled: disabled,
}
}
func (p ParamWidget) Layout(gtx C) D {
title := Label(p.Theme, &p.Theme.UnitEditor.Name, p.Parameter.Name())
t := TrackerFromContext(gtx)
widget := func(gtx C) D {
if port, ok := p.Parameter.Port(); t.IsChoosingSendTarget() && ok {
for p.State.clickable.Clicked(gtx) {
t.ChooseSendTarget(p.Parameter.UnitID(), port).Do()
}
k := Port(p.Theme, p.State)
return k.Layout(gtx)
}
switch p.Parameter.Type() {
case tracker.IntegerParameter:
k := Knob(p.Parameter, p.Theme, p.State, p.Parameter.Hint().Label, p.Focus, p.Disabled)
return k.Layout(gtx)
case tracker.BoolParameter:
s := Switch(p.Parameter, p.Theme, p.State, p.Parameter.Hint().Label, p.Focus, p.Disabled)
return s.Layout(gtx)
case tracker.IDParameter:
for p.State.clickable.Clicked(gtx) {
t.ChooseSendSource(p.Parameter.UnitID()).Do()
}
btn := Btn(t.Theme, &t.Theme.Button.Text, &p.State.clickable, "Set", p.Parameter.Hint().Label)
if p.Disabled {
btn.Style = &t.Theme.Button.Disabled
}
return btn.Layout(gtx)
}
if _, ok := p.Parameter.Port(); ok {
k := Port(p.Theme, p.State)
return k.Layout(gtx)
}
return D{}
}
title.Layout(gtx)
layout.Center.Layout(gtx, widget)
return D{Size: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}
}
func (s *ParamState) update(gtx C, param tracker.Parameter, scroll bool) {
for {
p, ok := s.drag.Update(gtx.Metric, gtx.Source, gesture.Both)
if !ok {
@ -140,9 +210,9 @@ func (s *KnobState) update(gtx C, param tracker.Parameter, scroll bool) {
}
}
// Knob
// KnobWidget
func Knob(v tracker.Parameter, th *Theme, state *KnobState, hint string, scroll, disabled bool) KnobWidget {
func Knob(v tracker.Parameter, th *Theme, state *ParamState, hint string, scroll, disabled bool) KnobWidget {
ret := KnobWidget{
Theme: th,
Value: v,
@ -251,9 +321,9 @@ func (k *KnobWidget) strokeIndicator(gtx C, amount float32) {
paint.FillShape(gtx.Ops, k.Style.Indicator.Color, s.Op(gtx.Ops))
}
// Switch
// SwitchWidget
func Switch(v tracker.Parameter, th *Theme, state *KnobState, hint string, scroll, disabled bool) SwitchWidget {
func Switch(v tracker.Parameter, th *Theme, state *ParamState, hint string, scroll, disabled bool) SwitchWidget {
return SwitchWidget{
Theme: th,
Value: v,
@ -325,3 +395,34 @@ func (s *SwitchWidget) Layout(gtx C) D {
w.Layout(gtx, bg)
return D{Size: image.Pt(width, height)}
}
//
func Port(t *Theme, p *ParamState) PortWidget {
return PortWidget{Theme: t, Style: &t.Port, State: p}
}
func (p *PortWidget) Layout(gtx C) D {
return p.State.clickable.layout(p.State, gtx, func(gtx C) D {
d := gtx.Dp(p.Style.Diameter)
defer clip.Rect(image.Rectangle{Max: image.Pt(d, d)}).Push(gtx.Ops).Pop()
p.strokeCircle(gtx)
return D{Size: image.Pt(d, d)}
})
}
func (p *PortWidget) strokeCircle(gtx C) {
sw := float32(gtx.Dp(p.Style.StrokeWidth))
d := float32(gtx.Dp(p.Style.Diameter))
rad := d / 2
center := f32.Point{X: rad, Y: rad}
var path clip.Path
path.Begin(gtx.Ops)
path.MoveTo(f32.Pt(sw/2, rad))
path.ArcTo(center, center, float32(math.Pi*2))
paint.FillShape(gtx.Ops, p.Style.Color,
clip.Stroke{
Path: path.End(),
Width: sw,
}.Op())
}

View File

@ -1,66 +0,0 @@
package gioui
import (
"image"
"image/color"
"math"
"gioui.org/f32"
"gioui.org/gesture"
"gioui.org/io/event"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
)
type (
PortState struct {
click gesture.Click
}
PortStyle struct {
Diameter unit.Dp
StrokeWidth unit.Dp
Color color.NRGBA
}
PortWidget struct {
Theme *Theme
Style *PortStyle
State *PortState
}
)
func Port(t *Theme, p *PortState) PortWidget {
return PortWidget{Theme: t, Style: &t.Port, State: p}
}
func (p *PortWidget) Layout(gtx C) D {
d := gtx.Dp(p.Style.Diameter)
defer clip.Rect(image.Rectangle{Max: image.Pt(d, d)}).Push(gtx.Ops).Pop()
event.Op(gtx.Ops, p.State)
p.State.click.Add(gtx.Ops)
p.strokeCircle(gtx)
return D{Size: image.Pt(d, d)}
}
func (p *PortState) Clicked(gtx C) bool {
ev, ok := p.click.Update(gtx.Source)
return ok && ev.Kind == gesture.KindClick
}
func (p *PortWidget) strokeCircle(gtx C) {
sw := float32(gtx.Dp(p.Style.StrokeWidth))
d := float32(gtx.Dp(p.Style.Diameter))
rad := d / 2
center := f32.Point{X: rad, Y: rad}
var path clip.Path
path.Begin(gtx.Ops)
path.MoveTo(f32.Pt(sw/2, rad))
path.ArcTo(center, center, float32(math.Pi*2))
paint.FillShape(gtx.Ops, p.Style.Color,
clip.Stroke{
Path: path.End(),
Width: sw,
}.Op())
}

View File

@ -15,7 +15,7 @@ import (
const maxSignalsDrawn = 16
type (
SignalRailStyle struct {
RailStyle struct {
Color color.NRGBA
LineWidth unit.Dp
SignalWidth unit.Dp
@ -23,22 +23,22 @@ type (
PortColor color.NRGBA
}
SignalRailWidget struct {
Style *SignalRailStyle
RailWidget struct {
Style *RailStyle
Signal tracker.Rail
Height unit.Dp
}
)
func SignalRail(th *Theme, signal tracker.Rail) SignalRailWidget {
return SignalRailWidget{
func Rail(th *Theme, signal tracker.Rail) RailWidget {
return RailWidget{
Style: &th.SignalRail,
Signal: signal,
Height: th.UnitEditor.Height,
}
}
func (s SignalRailWidget) Layout(gtx C) D {
func (s RailWidget) Layout(gtx C) D {
sw := gtx.Dp(s.Style.SignalWidth)
h := gtx.Dp(s.Height)
if s.Signal.PassThrough == 0 && len(s.Signal.StackUse.Inputs) == 0 && s.Signal.StackUse.NumOutputs == 0 {

View File

@ -118,7 +118,7 @@ type Theme struct {
Knob KnobStyle
DisabledKnob KnobStyle
Switch SwitchStyle
SignalRail SignalRailStyle
SignalRail RailStyle
Port PortStyle
// iconCache is used to cache the icons created from iconvg data

View File

@ -26,7 +26,7 @@ type (
UnitEditor struct {
paramTable *ScrollTable
searchList *DragList
Parameters [][]*ParameterState
Parameters [][]*ParamState
DeleteUnitBtn *Clickable
CopyUnitBtn *Clickable
ClearUnitBtn *Clickable
@ -140,7 +140,7 @@ func (pe *UnitEditor) update(gtx C, t *Tracker) {
}
c := t.Model.Params().Cursor()
if c.X >= 0 && c.Y >= 0 && c.Y < len(pe.Parameters) && c.X < len(pe.Parameters[c.Y]) {
ta := &pe.Parameters[c.Y][c.X].knobState.tipArea
ta := &pe.Parameters[c.Y][c.X].tipArea
ta.Appear(gtx.Now)
ta.Exit.SetTarget(gtx.Now.Add(ta.ExitDuration))
}
@ -188,7 +188,7 @@ func (pe *UnitEditor) layoutRack(gtx C) D {
// create enough parameter widget to match the number of parameters
width := pe.paramTable.Table.Width()
for len(pe.Parameters) < pe.paramTable.Table.Height() {
pe.Parameters = append(pe.Parameters, make([]*ParameterState, 0))
pe.Parameters = append(pe.Parameters, make([]*ParamState, 0))
}
cellWidth := gtx.Dp(t.Theme.UnitEditor.Width)
cellHeight := gtx.Dp(t.Theme.UnitEditor.Height)
@ -199,7 +199,7 @@ func (pe *UnitEditor) layoutRack(gtx C) D {
columnTitleHeight := gtx.Dp(0)
for i := range pe.Parameters {
for len(pe.Parameters[i]) < width {
pe.Parameters[i] = append(pe.Parameters[i], &ParameterState{knobState: KnobState{tipArea: TipArea{ExitDuration: time.Second * 2}}})
pe.Parameters[i] = append(pe.Parameters[i], &ParamState{tipArea: TipArea{ExitDuration: time.Second * 2}})
}
}
coltitle := func(gtx C, x int) D {
@ -210,7 +210,7 @@ func (pe *UnitEditor) layoutRack(gtx C) D {
return D{}
}
item := t.Units().Item(y)
sr := SignalRail(t.Theme, item.Signals)
sr := Rail(t.Theme, item.Signals)
label := Label(t.Theme, &t.Theme.UnitEditor.UnitList.Name, item.Type)
switch {
case item.Disabled:
@ -248,7 +248,7 @@ func (pe *UnitEditor) layoutRack(gtx C) D {
}
param := t.Model.Params().Item(point)
paramStyle := t.ParamStyle(param, t.Theme, pe.Parameters[y][x], pe.paramTable.Table.Cursor() == point, t.Units().Item(y).Disabled)
paramStyle := Param(param, t.Theme, pe.Parameters[y][x], pe.paramTable.Table.Cursor() == point, t.Units().Item(y).Disabled)
paramStyle.Layout(gtx)
comment := t.Units().Item(y).Comment
if comment != "" && x == t.Model.Params().RowWidth(y) {
@ -443,66 +443,3 @@ func (t *UnitEditor) Tags(level int, yield TagYieldFunc) bool {
}
return yield(level+1, t.paramTable.RowTitleList) && yield(level, t.paramTable) && yield(level+1, &t.commentEditor.widgetEditor)
}
type ParameterState struct {
knobState KnobState
clickable Clickable
portState PortState
}
type ParameterStyle struct {
Parameter tracker.Parameter
State *ParameterState
Theme *Theme
Focus bool
Disabled bool
}
func (t *Tracker) ParamStyle(Parameter tracker.Parameter, th *Theme, paramWidget *ParameterState, focus, disabled bool) ParameterStyle {
return ParameterStyle{
Theme: th,
State: paramWidget,
Parameter: Parameter,
Focus: focus,
Disabled: disabled,
}
}
func (p ParameterStyle) Layout(gtx C) D {
title := Label(p.Theme, &p.Theme.UnitEditor.Name, p.Parameter.Name())
t := TrackerFromContext(gtx)
widget := func(gtx C) D {
if port, ok := p.Parameter.Port(); t.IsChoosingSendTarget() && ok {
for p.State.portState.Clicked(gtx) {
t.ChooseSendTarget(p.Parameter.UnitID(), port).Do()
}
k := Port(p.Theme, &p.State.portState)
return k.Layout(gtx)
}
switch p.Parameter.Type() {
case tracker.IntegerParameter:
k := Knob(p.Parameter, p.Theme, &p.State.knobState, p.Parameter.Hint().Label, p.Focus, p.Disabled)
return k.Layout(gtx)
case tracker.BoolParameter:
s := Switch(p.Parameter, p.Theme, &p.State.knobState, p.Parameter.Hint().Label, p.Focus, p.Disabled)
return s.Layout(gtx)
case tracker.IDParameter:
for p.State.clickable.Clicked(gtx) {
t.ChooseSendSource(p.Parameter.UnitID()).Do()
}
btn := Btn(t.Theme, &t.Theme.Button.Text, &p.State.clickable, "Set", p.Parameter.Hint().Label)
if p.Disabled {
btn.Style = &t.Theme.Button.Disabled
}
return btn.Layout(gtx)
}
if _, ok := p.Parameter.Port(); ok {
k := Port(p.Theme, &p.State.portState)
return k.Layout(gtx)
}
return D{}
}
title.Layout(gtx)
layout.Center.Layout(gtx, widget)
return D{Size: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}
}

View File

@ -1,21 +1,21 @@
name: Clavi
name: clavi
numvoices: 1
units:
- type: envelope
id: 1
id: 225
parameters: {attack: 0, decay: 62, gain: 77, release: 52, stereo: 1, sustain: 66}
- type: oscillator
id: 2
id: 226
parameters: {color: 64, detune: 68, gain: 67, looplength: 84, loopstart: 290, phase: 0, samplestart: 401297, shape: 90, stereo: 1, transpose: 76, type: 4, unison: 3}
- type: mulp
id: 3
id: 227
parameters: {stereo: 1}
- type: pan
id: 5
parameters: {damp: 0, dry: 128, feedback: 96, notetracking: 2, panning: 64, pregain: 40, stereo: 1}
id: 228
parameters: {panning: 64, stereo: 1}
- type: filter
id: 1058
parameters: {bandpass: -1, frequency: 96, highpass: -1, lowpass: 1, panning: 64, resonance: 128, stereo: 1}
parameters: {bandpass: -1, frequency: 96, highpass: -1, lowpass: 1, resonance: 128, stereo: 1}
- type: outaux
id: 6
parameters: {auxgain: 15, outgain: 54, panning: 64, stereo: 1}
id: 1059
parameters: {auxgain: 15, outgain: 54, stereo: 1}