This commit is contained in:
5684185+vsariola@users.noreply.github.com
2025-07-04 15:05:49 +03:00
parent 4e295a3a2f
commit 3c6c24c6af
9 changed files with 206 additions and 67 deletions

View File

@ -93,6 +93,16 @@ type (
*Model
}
ShowLicense Model
ChooseSendSource struct {
ID int
*Model
}
ChooseSendTarget struct {
ID int
Port int
*Model
}
)
// Action methods
@ -517,6 +527,40 @@ func (d DeleteOrderRow) Do() {
m.d.Cursor2.OrderRow = m.d.Cursor.OrderRow
}
// ChooseSendSource
func (m *Model) IsChoosingSendTarget() bool {
return m.d.SendSource > 0
}
func (m *Model) ChooseSendSource(id int) Action {
return MakeEnabledAction(ChooseSendSource{ID: id, Model: m})
}
func (s ChooseSendSource) Do() {
defer (*Model)(s.Model).change("ChooseSendSource", NoChange, MinorChange)()
s.Model.d.SendSource = s.ID
}
// ChooseSendTarget
func (m *Model) ChooseSendTarget(id int, port int) Action {
return MakeEnabledAction(ChooseSendTarget{ID: id, Port: port, Model: m})
}
func (s ChooseSendTarget) Do() {
defer (*Model)(s.Model).change("ChooseSendTarget", SongChange, MinorChange)()
sourceID := (*Model)(s.Model).d.SendSource
s.d.SendSource = 0
if sourceID <= 0 || s.ID <= 0 || s.Port < 0 || s.Port > 7 {
return
}
si, su, err := s.d.Song.Patch.FindUnit(sourceID)
if err != nil {
return
}
s.d.Song.Patch[si].Units[su].Parameters["target"] = s.ID
s.d.Song.Patch[si].Units[su].Parameters["port"] = s.Port
}
// NewSong
func (m *Model) NewSong() Action { return MakeEnabledAction((*NewSong)(m)) }

View File

@ -176,6 +176,7 @@ func (m *Model) deriveParams(unit *sointu.Unit) []Parameter {
if !ok {
return ret
}
portIndex := 0
for i, up := range unitType {
if !up.CanSet && !up.CanModulate {
continue // skip parameters that cannot be set or modulated
@ -186,7 +187,12 @@ func (m *Model) deriveParams(unit *sointu.Unit) []Parameter {
if unit.Type == "send" && up.Name == "port" {
continue
}
ret = append(ret, Parameter{m: m, unit: unit, up: &unitType[i], vtable: &namedParameter{}})
q := 0
if up.CanModulate {
portIndex++
q = portIndex
}
ret = append(ret, Parameter{m: m, unit: unit, up: &unitType[i], vtable: &namedParameter{}, port: q})
}
if unit.Type == "oscillator" && unit.Parameters["type"] == sointu.Sample {
ret = append(ret, Parameter{m: m, unit: unit, vtable: &gmDlsEntryParameter{}})

View File

@ -149,15 +149,6 @@ func (k *KnobWidget) update(gtx C) {
}
}
func (k *KnobWidget) strokeBg(gtx C) {
diam := gtx.Dp(k.Style.Diameter)
circle := clip.Ellipse{
Min: image.Pt(0, 0),
Max: image.Pt(diam, diam),
}.Op(gtx.Ops)
paint.FillShape(gtx.Ops, k.Style.Bg, circle)
}
func (k *KnobWidget) strokeKnobArc(gtx C, color color.NRGBA, strokeWidth, diameter int, start, end float32) {
rad := float32(diameter) / 2
end = min(max(end, 0), 1)

66
tracker/gioui/port.go Normal file
View File

@ -0,0 +1,66 @@
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

@ -85,16 +85,20 @@ type Theme struct {
}
}
UnitEditor struct {
Name LabelStyle
Chooser LabelStyle
Hint LabelStyle
InvalidParam color.NRGBA
SendTarget color.NRGBA
Width unit.Dp
Height unit.Dp
RowTitle LabelStyle
RowTitleWidth unit.Dp
Error color.NRGBA
Name LabelStyle
Chooser LabelStyle
Hint LabelStyle
InvalidParam color.NRGBA
SendTarget color.NRGBA
Width unit.Dp
Height unit.Dp
UnitList struct {
LabelWidth unit.Dp
Name LabelStyle
Disabled LabelStyle
Error color.NRGBA
}
Error color.NRGBA
}
Cursor CursorStyle
Selection CursorStyle
@ -110,6 +114,7 @@ type Theme struct {
ScrollBar ScrollBarStyle
Knob KnobStyle
SignalRail SignalRailStyle
Port PortStyle
// iconCache is used to cache the icons created from iconvg data
iconCache map[*byte]*widget.Icon

View File

@ -214,9 +214,12 @@ uniteditor:
sendtarget: *secondarycolor
width: 60
height: 70
rowtitle: { textsize: 12, color: *white, alignment: 2 }
rowtitlewidth: 16
error: *errorcolor
unitlist:
labelwidth: 16
name: { textsize: 12, color: *white, alignment: 2 }
disabled:
{ textsize: 12, color: *disabled, font: { style: 1 }, alignment: 2 }
error: *errorcolor
knob:
diameter: 36
value: { textsize: 12, color: *highemphasis }
@ -231,3 +234,7 @@ signalrail:
linewidth: 2
portdiameter: 8
portcolor: *primarycolor
port:
diameter: 36
strokewidth: 4
color: *secondarycolor

View File

@ -31,7 +31,7 @@ type (
UnitEditor struct {
paramTable *ScrollTable
searchList *DragList
Parameters [][]*ParameterWidget
Parameters [][]*ParameterState
DeleteUnitBtn *Clickable
CopyUnitBtn *Clickable
ClearUnitBtn *Clickable
@ -157,18 +157,18 @@ 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([]*ParameterWidget, 0))
pe.Parameters = append(pe.Parameters, make([]*ParameterState, 0))
}
cellWidth := gtx.Dp(t.Theme.UnitEditor.Width)
cellHeight := gtx.Dp(t.Theme.UnitEditor.Height)
rowTitleLabelWidth := gtx.Dp(t.Theme.UnitEditor.RowTitleWidth)
rowTitleLabelWidth := gtx.Dp(t.Theme.UnitEditor.UnitList.LabelWidth)
rowTitleSignalWidth := gtx.Dp(t.Theme.SignalRail.SignalWidth) * t.SignalRail().MaxWidth()
rowTitleWidth := rowTitleLabelWidth + rowTitleSignalWidth
signalError := t.SignalRail().Error()
columnTitleHeight := gtx.Dp(0)
for i := range pe.Parameters {
for len(pe.Parameters[i]) < width {
pe.Parameters[i] = append(pe.Parameters[i], &ParameterWidget{})
pe.Parameters[i] = append(pe.Parameters[i], &ParameterState{})
}
}
coltitle := func(gtx C, x int) D {
@ -184,9 +184,12 @@ func (pe *UnitEditor) layoutRack(gtx C) D {
}
sr := SignalRail(t.Theme, t.SignalRail().Item(y))
label := Label(t.Theme, &t.Theme.UnitEditor.RowTitle, t.Units().Item(y).Type)
if signalError.Err != nil && signalError.UnitIndex == y {
label.Color = t.Theme.UnitEditor.Error
label := Label(t.Theme, &t.Theme.UnitEditor.UnitList.Name, t.Units().Item(y).Type)
switch {
case t.Units().Item(y).Disabled:
label.LabelStyle = t.Theme.UnitEditor.UnitList.Disabled
case signalError.Err != nil && signalError.UnitIndex == y:
label.Color = t.Theme.UnitEditor.UnitList.Error
}
gtx.Constraints = layout.Exact(image.Pt(rowTitleWidth, cellHeight))
sr.Layout(gtx)
@ -210,10 +213,8 @@ func (pe *UnitEditor) layoutRack(gtx C) D {
paint.FillShape(gtx.Ops, c, clip.Rect{Min: image.Pt(0, 0), Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op())
}
param := t.Model.Params().Item(tracker.Point{X: x, Y: y})
pe.Parameters[y][x].Parameter = param
paramStyle := t.ParamStyle(t.Theme, pe.Parameters[y][x])
paramStyle.Focus = pe.paramTable.Table.Cursor() == tracker.Point{X: x, Y: y}
param := t.Model.Params().Item(point)
paramStyle := t.ParamStyle(param, t.Theme, pe.Parameters[y][x], pe.paramTable.Table.Cursor() == point)
paramStyle.Layout(gtx)
return D{Size: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}
}
@ -351,63 +352,61 @@ func (t *UnitEditor) Tags(level int, yield TagYieldFunc) bool {
return yield(level, widget) && yield(level+1, &t.commentEditor.widgetEditor)
}
type ParameterWidget struct {
type ParameterState struct {
knobState KnobState
boolWidget widget.Bool
instrBtn Clickable
instrMenu MenuState
unitBtn Clickable
unitMenu MenuState
Parameter tracker.Parameter
tipArea TipArea
clickable Clickable
portState PortState
}
type ParameterStyle struct {
tracker *Tracker
w *ParameterWidget
Theme *Theme
SendTargetTheme *material.Theme
Focus bool
Parameter tracker.Parameter
State *ParameterState
Theme *Theme
Focus bool
}
func (t *Tracker) ParamStyle(th *Theme, paramWidget *ParameterWidget) ParameterStyle {
sendTargetTheme := th.Material.WithPalette(material.Palette{
Bg: th.Material.Bg,
Fg: th.UnitEditor.SendTarget,
ContrastBg: th.Material.ContrastBg,
ContrastFg: th.Material.ContrastFg,
})
func (t *Tracker) ParamStyle(Parameter tracker.Parameter, th *Theme, paramWidget *ParameterState, focus bool) ParameterStyle {
return ParameterStyle{
tracker: t, // TODO: we need this to pull the instrument names for ID style parameters, find out another way
Theme: th,
SendTargetTheme: &sendTargetTheme,
w: paramWidget,
Theme: th,
State: paramWidget,
Parameter: Parameter,
Focus: focus,
}
}
func (p ParameterStyle) Layout(gtx C) D {
//_, _ := p.w.Parameter.Info()
title := Label(p.Theme, &p.Theme.UnitEditor.Name, p.w.Parameter.Name())
title := Label(p.Theme, &p.Theme.UnitEditor.Name, p.Parameter.Name())
t := TrackerFromContext(gtx)
widget := func(gtx C) D {
switch p.w.Parameter.Type() {
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.w.Parameter, p.Theme, &p.w.knobState, p.w.Parameter.Hint().Label, p.Focus)
k := Knob(p.Parameter, p.Theme, &p.State.knobState, p.Parameter.Hint().Label, p.Focus)
return k.Layout(gtx)
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")
ra := p.Parameter.Range()
p.State.boolWidget.Value = p.Parameter.Value() > ra.Min
boolStyle := material.Switch(&p.Theme.Material, &p.State.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)
if p.State.boolWidget.Value {
p.Parameter.SetValue(ra.Max)
} else {
p.w.Parameter.SetValue(ra.Min)
p.Parameter.SetValue(ra.Min)
}
return dims
case tracker.IDParameter:
return drawCircle(gtx, gtx.Dp(p.Theme.Knob.Diameter), p.Theme.Knob.Pos.Bg)
btn := ActionBtn(t.ChooseSendSource(p.Parameter.UnitID()), t.Theme, &p.State.clickable, "Set", p.Parameter.Hint().Label)
return btn.Layout(gtx)
/*instrItems := make([]ActionMenuItem, p.tracker.Instruments().Count())
for i := range instrItems {
i := i
@ -449,6 +448,10 @@ func (p ParameterStyle) Layout(gtx C) D {
}),
)*/
}
if _, ok := p.Parameter.Port(); ok {
k := Port(p.Theme, &p.State.portState)
return k.Layout(gtx)
}
return D{}
}
title.Layout(gtx)

View File

@ -240,6 +240,7 @@ func (m *Model) change(kind string, t ChangeType, severity ChangeSeverity) func(
m.d.UnitIndex2 = clamp(m.d.UnitIndex2, 0, unitCount-1)
m.d.UnitSearching = false // if we change anything in the patch, reset the unit searching
m.d.UnitSearchString = ""
m.d.SendSource = 0
m.updateDerivedPatchData()
TrySend(m.broker.ToPlayer, any(m.d.Song.Patch.Copy()))
}

View File

@ -20,6 +20,7 @@ type (
up *sointu.UnitParameter
index int
vtable parameterVtable
port int
}
parameterVtable interface {
@ -70,6 +71,12 @@ func (p *Parameter) Value() int {
}
return p.vtable.Value(p)
}
func (p *Parameter) Port() (int, bool) {
if p.port <= 0 {
return 0, false
}
return p.port - 1, true
}
func (p *Parameter) SetValue(value int) bool {
if p.vtable == nil {
return false
@ -123,6 +130,12 @@ func (p *Parameter) Reset() {
}
p.vtable.Reset(p)
}
func (p *Parameter) UnitID() int {
if p.unit == nil {
return 0
}
return p.unit.ID
}
//
@ -212,6 +225,9 @@ func (n *namedParameter) Range(p *Parameter) IntRange {
return IntRange{Min: p.up.MinValue, Max: p.up.MaxValue}
}
func (n *namedParameter) Type(p *Parameter) ParameterType {
if p.up == nil || !p.up.CanSet {
return NoParameter
}
if p.unit.Type == "send" && p.up.Name == "target" {
return IDParameter
}