feat(tracker): adding, setting, unsetting and deleting units

This commit is contained in:
vsariola 2021-02-04 21:50:41 +02:00
parent 29b289d2fb
commit 6307dd51de
8 changed files with 316 additions and 86 deletions

View File

@ -33,6 +33,9 @@ func Encode(patch *sointu.Patch, featureSet FeatureSet) (*EncodedPatch, error) {
return nil, errors.New("Each instrument must have at least 1 voice")
}
for _, unit := range instr.Units {
if unit.Type == "" { // empty units are just ignored & skipped
continue
}
if unit.Type == "oscillator" && unit.Parameters["type"] == 4 {
s := SampleOffset{Start: uint32(unit.Parameters["start"]), LoopStart: uint16(unit.Parameters["loopstart"]), LoopLength: uint16(unit.Parameters["looplength"])}
index, ok := sampleOffsetMap[s]

View File

@ -1,14 +1,65 @@
package tracker
import "github.com/vsariola/sointu"
import (
"sort"
"github.com/vsariola/sointu"
)
var defaultUnits = map[string]sointu.Unit{
"envelope": {Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 64, "decay": 64, "sustain": 64, "release": 64, "gain": 64}},
"oscillator": {Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 64, "shape": 64, "gain": 64, "type": sointu.Sine}},
"noise": {Type: "noise", Parameters: map[string]int{"stereo": 0, "shape": 64, "gain": 64}},
"mulp": {Type: "mulp", Parameters: map[string]int{"stereo": 0}},
"mul": {Type: "mul", Parameters: map[string]int{"stereo": 0}},
"add": {Type: "add", Parameters: map[string]int{"stereo": 0}},
"addp": {Type: "addp", Parameters: map[string]int{"stereo": 0}},
"push": {Type: "push", Parameters: map[string]int{"stereo": 0}},
"pop": {Type: "pop", Parameters: map[string]int{"stereo": 0}},
"xch": {Type: "xch", Parameters: map[string]int{"stereo": 0}},
"receive": {Type: "receive", Parameters: map[string]int{"stereo": 0}},
"loadnote": {Type: "loadnote", Parameters: map[string]int{"stereo": 0}},
"loadval": {Type: "loadval", Parameters: map[string]int{"stereo": 0, "value": 64}},
"pan": {Type: "pan", Parameters: map[string]int{"stereo": 0, "panning": 64}},
"gain": {Type: "gain", Parameters: map[string]int{"stereo": 0, "gain": 64}},
"invgain": {Type: "invgain", Parameters: map[string]int{"stereo": 0, "invgain": 64}},
"crush": {Type: "crush", Parameters: map[string]int{"stereo": 0, "resolution": 64}},
"clip": {Type: "clip", Parameters: map[string]int{"stereo": 0}},
"hold": {Type: "hold", Parameters: map[string]int{"stereo": 0, "holdfreq": 64}},
"distort": {Type: "distort", Parameters: map[string]int{"stereo": 0, "drive": 64}},
"filter": {Type: "filter", Parameters: map[string]int{"stereo": 0, "frequency": 64, "resonance": 64, "lowpass": 1, "bandpass": 0, "highpass": 0, "negbandpass": 0, "neghighpass": 0}},
"out": {Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 64}},
"outaux": {Type: "outaux", Parameters: map[string]int{"stereo": 1, "outgain": 64, "auxgain": 64}},
"aux": {Type: "aux", Parameters: map[string]int{"stereo": 1, "gain": 64, "channel": 2}},
"delay": {Type: "delay",
Parameters: map[string]int{"damp": 0, "dry": 128, "feedback": 96, "notetracking": 0, "pregain": 40, "stereo": 0},
VarArgs: []int{1116, 1188, 1276, 1356, 1422, 1492, 1556, 1618,
1140, 1212, 1300, 1380, 1446, 1516, 1580, 1642,
}},
"in": {Type: "in", Parameters: map[string]int{"stereo": 1, "channel": 2}},
"speed": {Type: "speed", Parameters: map[string]int{}},
"compressor": {Type: "compressor", Parameters: map[string]int{"stereo": 0, "attack": 64, "release": 64, "invgain": 64, "threshold": 64, "ratio": 64}},
}
var allUnits []string
func init() {
allUnits = make([]string, 0, len(sointu.UnitTypes))
for k := range sointu.UnitTypes {
allUnits = append(allUnits, k)
}
sort.Strings(allUnits)
}
var defaultInstrument = sointu.Instrument{
NumVoices: 1,
Units: []sointu.Unit{
{Type: "envelope", Parameters: map[string]int{"stereo": 1, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 64}},
{Type: "oscillator", Parameters: map[string]int{"stereo": 1, "transpose": 64, "detune": 64, "phase": 0, "color": 128, "shape": 64, "gain": 64, "type": sointu.Sine}},
{Type: "mulp", Parameters: map[string]int{"stereo": 1}},
{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 64}},
defaultUnits["envelope"],
defaultUnits["oscillator"],
defaultUnits["mulp"],
defaultUnits["delay"],
defaultUnits["pan"],
defaultUnits["out"],
},
}
@ -20,16 +71,5 @@ var defaultSong = sointu.Song{
{NumVoices: 2, Sequence: []byte{0, 0, 0, 1}, Patterns: [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}, {64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 75, 0, 75, 0, 80, 0}}},
{NumVoices: 2, Sequence: []byte{0, 0, 0, 1}, Patterns: [][]byte{{0, 0, 64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0}, {32, 0, 64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 68, 0, 68, 0}}},
},
Patch: sointu.Patch{
Instruments: []sointu.Instrument{{NumVoices: 4, Units: []sointu.Unit{
{Type: "envelope", Parameters: map[string]int{"stereo": 1, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 64}},
{Type: "oscillator", Parameters: map[string]int{"stereo": 1, "transpose": 64, "detune": 64, "phase": 0, "color": 128, "shape": 64, "gain": 64, "type": sointu.Sine}},
{Type: "mulp", Parameters: map[string]int{"stereo": 1}},
{Type: "delay",
Parameters: map[string]int{"damp": 0, "dry": 128, "feedback": 96, "notetracking": 0, "pregain": 40, "stereo": 1},
VarArgs: []int{1116, 1188, 1276, 1356, 1422, 1492, 1556, 1618,
1140, 1212, 1300, 1380, 1446, 1516, 1580, 1642,
}},
{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 64}},
}}}},
Patch: sointu.Patch{Instruments: []sointu.Instrument{{NumVoices: 4, Units: defaultInstrument.Units}}},
}

View File

@ -2,7 +2,8 @@ package tracker
import (
"fmt"
"sort"
"image"
"image/color"
"gioui.org/layout"
"gioui.org/op"
@ -52,7 +53,7 @@ func (t *Tracker) layoutInstruments() layout.Widget {
func (t *Tracker) layoutInstrumentHeader() layout.Widget {
headerBg := func(gtx C) D {
paint.FillShape(gtx.Ops, trackMenuSurfaceColor, clip.Rect{
paint.FillShape(gtx.Ops, instrumentSurfaceColor, clip.Rect{
Max: gtx.Constraints.Min,
}.Op())
return layout.Dimensions{Size: gtx.Constraints.Min}
@ -129,82 +130,65 @@ func (t *Tracker) layoutInstrumentNames() layout.Widget {
}
}
func (t *Tracker) layoutInstrumentEditor() layout.Widget {
for t.AddUnitBtn.Clicked() {
t.AddUnit()
}
addUnitBtnStyle := material.IconButton(t.Theme, t.AddUnitBtn, widgetForIcon(icons.ContentAdd))
addUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(4))
margin := layout.UniformInset(unit.Dp(2))
return func(gtx C) D {
paint.FillShape(gtx.Ops, instrumentSurfaceColor, clip.Rect{
Max: gtx.Constraints.Max,
}.Op())
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(t.layoutUnitList()),
layout.Rigid(t.layoutUnitControls()))
layout.Rigid(func(gtx C) D {
return layout.Stack{Alignment: layout.SE}.Layout(gtx,
layout.Expanded(t.layoutUnitList()),
layout.Stacked(func(gtx C) D {
return margin.Layout(gtx, addUnitBtnStyle.Layout)
}))
}),
layout.Rigid(t.layoutUnitEditor()))
}
}
func (t *Tracker) layoutUnitList() layout.Widget {
return func(gtx C) D {
paint.FillShape(gtx.Ops, unitListSurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
defer op.Save(gtx.Ops).Load()
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
units := t.song.Patch.Instruments[t.CurrentInstrument].Units
count := len(units)
if len(t.UnitBtns) < count {
tail := make([]*widget.Clickable, count-len(t.UnitBtns))
for t := range tail {
tail[t] = new(widget.Clickable)
}
t.UnitBtns = append(t.UnitBtns, tail...)
for len(t.UnitBtns) < count {
t.UnitBtns = append(t.UnitBtns, new(widget.Clickable))
}
children := make([]layout.FlexChild, len(t.song.Patch.Instruments[t.CurrentInstrument].Units))
for i, u := range t.song.Patch.Instruments[t.CurrentInstrument].Units {
listElem := func(gtx C, i int) D {
for t.UnitBtns[i].Clicked() {
t.CurrentUnit = i
op.InvalidateOp{}.Add(gtx.Ops)
}
i2 := i
u := t.song.Patch.Instruments[t.CurrentInstrument].Units[i]
labelStyle := LabelStyle{Text: u.Type, ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)}
children[i] = layout.Rigid(func(gtx C) D {
dims := labelStyle.Layout(gtx)
gtx.Constraints = layout.Exact(dims.Size)
t.UnitBtns[i2].Layout(gtx)
return dims
})
if labelStyle.Text == "" {
labelStyle.Text = "---"
labelStyle.Alignment = layout.Center
}
bg := func(gtx C) D {
gtx.Constraints = layout.Exact(image.Pt(120, 20))
var color color.NRGBA
if t.CurrentUnit == i {
color = unitListSelectedColor
} else if t.UnitBtns[i].Hovered() {
color = unitListHighlightColor
}
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op())
return D{Size: gtx.Constraints.Min}
}
return layout.Stack{Alignment: layout.W}.Layout(gtx,
layout.Stacked(bg),
layout.Expanded(labelStyle.Layout),
layout.Expanded(t.UnitBtns[i].Layout))
}
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...)
}
}
func (t *Tracker) layoutUnitControls() layout.Widget {
return func(gtx C) D {
params := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Parameters
count := len(params)
children := make([]layout.FlexChild, 0, count)
if len(t.ParameterSliders) < count {
tail := make([]*widget.Float, count-len(t.ParameterSliders))
for t := range tail {
tail[t] = new(widget.Float)
}
t.ParameterSliders = append(t.ParameterSliders, tail...)
}
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys {
for t.ParameterSliders[i].Changed() {
params[k] = int(t.ParameterSliders[i].Value)
// TODO: tracker should have functions to update parameters and
// to do this efficiently i.e. not compile the whole patch again
t.LoadSong(t.song)
}
t.ParameterSliders[i].Value = float32(params[k])
sliderStyle := material.Slider(t.Theme, t.ParameterSliders[i], 0, 128)
sliderStyle.Color = t.Theme.Fg
k2 := k // avoid k changing in the closure
children = append(children, layout.Rigid(func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(Label(k2, white)),
layout.Rigid(func(gtx C) D {
gtx.Constraints.Min.X = 200
return sliderStyle.Layout(gtx)
}))
}))
}
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...)
return t.UnitList.Layout(gtx, len(t.song.Patch.Instruments[t.CurrentInstrument].Units), listElem)
}
}

View File

@ -17,12 +17,13 @@ type LabelStyle struct {
Text string
Color color.NRGBA
ShadeColor color.NRGBA
Alignment layout.Direction
Font text.Font
FontSize unit.Value
}
func (l LabelStyle) Layout(gtx layout.Context) layout.Dimensions {
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
return layout.Stack{Alignment: l.Alignment}.Layout(gtx,
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
defer op.Save(gtx.Ops).Load()
paint.ColorOp{Color: l.ShadeColor}.Add(gtx.Ops)
@ -46,6 +47,6 @@ func (l LabelStyle) Layout(gtx layout.Context) layout.Dimensions {
)
}
func Label(text string, color color.NRGBA) layout.Widget {
return LabelStyle{Text: text, Color: color, ShadeColor: black, Font: labelDefaultFont, FontSize: labelDefaultFontSize}.Layout
func Label(str string, color color.NRGBA) layout.Widget {
return LabelStyle{Text: str, Color: color, ShadeColor: black, Font: labelDefaultFont, FontSize: labelDefaultFontSize, Alignment: layout.W}.Layout
}

View File

@ -56,6 +56,7 @@ func (s PopupStyle) Layout(gtx C, contents layout.Widget) D {
}
bg := func(gtx C) D {
pointer.PassOp{Pass: true}.Add(gtx.Ops)
pointer.InputOp{Tag: s.Visible,
Types: pointer.Press,
}.Add(gtx.Ops)

View File

@ -85,9 +85,16 @@ var patternSelectionColor = color.NRGBA{R: 19, G: 40, B: 60, A: 128}
var inactiveBtnColor = color.NRGBA{R: 61, G: 55, B: 55, A: 255}
var instrumentSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255}
var instrumentSurfaceColor = color.NRGBA{R: 45, G: 45, B: 45, A: 255}
var songSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255}
var popupSurfaceColor = color.NRGBA{R: 45, G: 45, B: 46, A: 255}
var popupSurfaceColor = color.NRGBA{R: 50, G: 50, B: 51, A: 255}
var popupShadowColor = color.NRGBA{R: 0, G: 0, B: 0, A: 192}
var unitListSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255}
var unitListSelectedColor = color.NRGBA{R: 55, G: 55, B: 61, A: 255}
var unitListHighlightColor = color.NRGBA{R: 42, G: 45, B: 61, A: 255}
var unitSurfaceColor = color.NRGBA{R: 30, G: 30, B: 30, A: 255}
var unitTypeListHighlightColor = color.NRGBA{R: 42, G: 45, B: 61, A: 255}

View File

@ -25,6 +25,9 @@ type Tracker struct {
CursorColumn int
CurrentInstrument int
CurrentUnit int
UnitGroupMenuVisible bool
UnitGroupMenuIndex int
UnitSubMenuIndex int
NoteTracking bool
Theme *material.Theme
Octave *NumberInput
@ -47,7 +50,13 @@ type Tracker struct {
FileMenuVisible bool
ParameterSliders []*widget.Float
UnitBtns []*widget.Clickable
UnitList *layout.List
DeleteUnitBtn *widget.Clickable
ClearUnitBtn *widget.Clickable
ChooseUnitTypeList *layout.List
ChooseUnitTypeBtns []*widget.Clickable
InstrumentBtns []*widget.Clickable
AddUnitBtn *widget.Clickable
InstrumentList *layout.List
TrackHexCheckBoxes []*widget.Bool
TrackShowHex []bool
@ -329,6 +338,51 @@ func (t *Tracker) SetRowsPerPattern(value int) {
}
}
func (t *Tracker) SetUnit(typ string) {
unit, ok := defaultUnits[typ]
if !ok {
return
}
if unit.Type == t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type {
return
}
t.SaveUndo()
t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit] = unit.Copy()
t.sequencer.SetPatch(t.song.Patch)
}
func (t *Tracker) AddUnit() {
t.SaveUndo()
units := make([]sointu.Unit, len(t.song.Patch.Instruments[t.CurrentInstrument].Units)+1)
copy(units, t.song.Patch.Instruments[t.CurrentInstrument].Units[:t.CurrentUnit+1])
copy(units[t.CurrentUnit+2:], t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit+1:])
t.song.Patch.Instruments[t.CurrentInstrument].Units = units
t.CurrentUnit++
t.sequencer.SetPatch(t.song.Patch)
}
func (t *Tracker) ClearUnit() {
t.SaveUndo()
t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type = ""
t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Parameters = make(map[string]int)
t.sequencer.SetPatch(t.song.Patch)
}
func (t *Tracker) DeleteUnit() {
if len(t.song.Patch.Instruments[t.CurrentInstrument].Units) <= 1 {
return
}
t.SaveUndo()
units := make([]sointu.Unit, len(t.song.Patch.Instruments[t.CurrentInstrument].Units)-1)
copy(units, t.song.Patch.Instruments[t.CurrentInstrument].Units[:t.CurrentUnit])
copy(units[t.CurrentUnit:], t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit+1:])
t.song.Patch.Instruments[t.CurrentInstrument].Units = units
if t.CurrentUnit > 0 {
t.CurrentUnit--
}
t.sequencer.SetPatch(t.song.Patch)
}
func (t *Tracker) ClampPositions() {
t.PlayPosition.Clamp(t.song)
t.Cursor.Clamp(t.song)
@ -416,6 +470,10 @@ func New(audioContext sointu.AudioContext) *Tracker {
SubtractSemitoneBtn: new(widget.Clickable),
AddOctaveBtn: new(widget.Clickable),
SubtractOctaveBtn: new(widget.Clickable),
AddUnitBtn: new(widget.Clickable),
DeleteUnitBtn: new(widget.Clickable),
ClearUnitBtn: new(widget.Clickable),
UnitList: &layout.List{Axis: layout.Vertical},
setPlaying: make(chan bool),
rowJump: make(chan int),
patternJump: make(chan int),
@ -427,12 +485,16 @@ func New(audioContext sointu.AudioContext) *Tracker {
TopHorizontalSplit: new(Split),
BottomHorizontalSplit: new(Split),
VerticalSplit: new(Split),
ChooseUnitTypeList: &layout.List{Axis: layout.Vertical},
}
t.Octave.Value = 4
t.VerticalSplit.Axis = layout.Vertical
t.BottomHorizontalSplit.Ratio = -.5
t.Theme.Palette.Fg = primaryColor
t.Theme.Palette.ContrastFg = black
for range allUnits {
t.ChooseUnitTypeBtns = append(t.ChooseUnitTypeBtns, new(widget.Clickable))
}
go t.sequencerLoop(t.closer)
if err := t.LoadSong(defaultSong.Copy()); err != nil {
panic(fmt.Errorf("cannot load default song: %w", err))

132
tracker/uniteditor.go Normal file
View File

@ -0,0 +1,132 @@
package tracker
import (
"image"
"image/color"
"sort"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"golang.org/x/exp/shiny/materialdesign/icons"
)
func (t *Tracker) layoutUnitEditor() layout.Widget {
editorFunc := t.layoutUnitSliders
if t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type == "" {
editorFunc = t.layoutUnitTypeChooser
}
return func(gtx C) D {
paint.FillShape(gtx.Ops, unitSurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Flexed(1, editorFunc()),
layout.Rigid(t.layoutUnitFooter()),
)
}
}
func (t *Tracker) layoutUnitSliders() layout.Widget {
return func(gtx C) D {
params := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Parameters
count := len(params)
children := make([]layout.FlexChild, 0, count)
if len(t.ParameterSliders) < count {
tail := make([]*widget.Float, count-len(t.ParameterSliders))
for t := range tail {
tail[t] = new(widget.Float)
}
t.ParameterSliders = append(t.ParameterSliders, tail...)
}
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys {
for t.ParameterSliders[i].Changed() {
params[k] = int(t.ParameterSliders[i].Value)
// TODO: tracker should have functions to update parameters and
// to do this efficiently i.e. not compile the whole patch again
t.LoadSong(t.song)
}
t.ParameterSliders[i].Value = float32(params[k])
sliderStyle := material.Slider(t.Theme, t.ParameterSliders[i], 0, 128)
sliderStyle.Color = t.Theme.Fg
k2 := k // avoid k changing in the closure
children = append(children, layout.Rigid(func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(Label(k2, white)),
layout.Rigid(func(gtx C) D {
gtx.Constraints.Min.X = 200
return sliderStyle.Layout(gtx)
}))
}))
}
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...)
}
}
func (t *Tracker) layoutUnitFooter() layout.Widget {
return func(gtx C) D {
for t.ClearUnitBtn.Clicked() {
t.ClearUnit()
op.InvalidateOp{}.Add(gtx.Ops)
}
for t.DeleteUnitBtn.Clicked() {
t.DeleteUnit()
op.InvalidateOp{}.Add(gtx.Ops)
}
deleteUnitBtnStyle := material.IconButton(t.Theme, t.DeleteUnitBtn, widgetForIcon(icons.ActionDelete))
deleteUnitBtnStyle.Background = transparent
deleteUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
if len(t.song.Patch.Instruments[t.CurrentInstrument].Units) > 1 {
deleteUnitBtnStyle.Color = primaryColor
} else {
deleteUnitBtnStyle.Color = disabledTextColor
}
if t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type == "" {
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
layout.Rigid(deleteUnitBtnStyle.Layout))
}
clearUnitBtnStyle := material.IconButton(t.Theme, t.ClearUnitBtn, widgetForIcon(icons.ContentClear))
clearUnitBtnStyle.Color = primaryColor
clearUnitBtnStyle.Background = transparent
clearUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
layout.Rigid(clearUnitBtnStyle.Layout),
layout.Rigid(deleteUnitBtnStyle.Layout))
}
}
func (t *Tracker) layoutUnitTypeChooser() layout.Widget {
return func(gtx C) D {
paint.FillShape(gtx.Ops, unitSurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
listElem := func(gtx C, i int) D {
for t.ChooseUnitTypeBtns[i].Clicked() {
u := defaultUnits[allUnits[i]]
t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit] = (&u).Copy()
}
labelStyle := LabelStyle{Text: allUnits[i], ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)}
bg := func(gtx C) D {
gtx.Constraints = layout.Exact(image.Pt(120, 20))
var color color.NRGBA
if t.ChooseUnitTypeBtns[i].Hovered() {
color = unitTypeListHighlightColor
}
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op())
return D{Size: gtx.Constraints.Min}
}
return layout.Stack{Alignment: layout.W}.Layout(gtx,
layout.Stacked(bg),
layout.Expanded(labelStyle.Layout),
layout.Expanded(t.ChooseUnitTypeBtns[i].Layout))
}
return t.ChooseUnitTypeList.Layout(gtx, len(allUnits), listElem)
}
}