diff --git a/tracker/draglist.go b/tracker/draglist.go index 3a71ad2..d93f5d4 100644 --- a/tracker/draglist.go +++ b/tracker/draglist.go @@ -61,7 +61,6 @@ func (s *FilledDragListStyle) Layout(gtx C) D { s.dragList.tags = append(s.dragList.tags, false) } bg := func(gtx C) D { - gtx.Constraints = layout.Exact(image.Pt(120, 20)) var color color.NRGBA if s.dragList.SelectedItem == index { color = s.SelectedColor diff --git a/tracker/instruments.go b/tracker/instruments.go index 80bf95d..54f0eab 100644 --- a/tracker/instruments.go +++ b/tracker/instruments.go @@ -9,7 +9,6 @@ import ( "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" ) @@ -19,13 +18,13 @@ type D = layout.Dimensions func (t *Tracker) updateInstrumentScroll() { if t.CurrentInstrument > 7 { - t.InstrumentList.Position.First = t.CurrentInstrument - 7 + t.InstrumentDragList.List.Position.First = t.CurrentInstrument - 7 } else { - t.InstrumentList.Position.First = 0 + t.InstrumentDragList.List.Position.First = 0 } } -func (t *Tracker) layoutInstruments() layout.Widget { +func (t *Tracker) layoutInstruments(gtx C) D { btnStyle := material.IconButton(t.Theme, t.NewInstrumentBtn, widgetForIcon(icons.ContentAdd)) btnStyle.Background = transparent btnStyle.Inset = layout.UniformInset(unit.Dp(6)) @@ -34,23 +33,21 @@ func (t *Tracker) layoutInstruments() layout.Widget { } else { btnStyle.Color = disabledTextColor } - return func(gtx C) D { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Flex{}.Layout( - gtx, - layout.Flexed(1, t.layoutInstrumentNames()), - layout.Rigid(func(gtx C) D { - return layout.E.Layout(gtx, btnStyle.Layout) - }), - ) - }), - layout.Rigid(t.layoutInstrumentHeader()), - layout.Flexed(1, t.layoutInstrumentEditor())) - } + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Flex{}.Layout( + gtx, + layout.Flexed(1, t.layoutInstrumentNames), + layout.Rigid(func(gtx C) D { + return layout.E.Layout(gtx, btnStyle.Layout) + }), + ) + }), + layout.Rigid(t.layoutInstrumentHeader), + layout.Flexed(1, t.layoutInstrumentEditor)) } -func (t *Tracker) layoutInstrumentHeader() layout.Widget { +func (t *Tracker) layoutInstrumentHeader(gtx C) D { headerBg := func(gtx C) D { paint.FillShape(gtx.Ops, instrumentSurfaceColor, clip.Rect{ Max: gtx.Constraints.Min, @@ -87,48 +84,35 @@ func (t *Tracker) layoutInstrumentHeader() layout.Widget { for t.DeleteInstrumentBtn.Clicked() { t.DeleteInstrument() } - return func(gtx C) D { - return layout.Stack{Alignment: layout.Center}.Layout(gtx, - layout.Expanded(headerBg), - layout.Stacked(header)) - } + return layout.Stack{Alignment: layout.Center}.Layout(gtx, + layout.Expanded(headerBg), + layout.Stacked(header)) } -func (t *Tracker) layoutInstrumentNames() layout.Widget { - return func(gtx C) D { - gtx.Constraints.Max.Y = gtx.Px(unit.Dp(36)) +func (t *Tracker) layoutInstrumentNames(gtx C) D { + + element := func(gtx C, i int) D { gtx.Constraints.Min.Y = gtx.Px(unit.Dp(36)) - - count := len(t.song.Patch.Instruments) - if len(t.InstrumentBtns) < count { - tail := make([]*widget.Clickable, count-len(t.InstrumentBtns)) - for t := range tail { - tail[t] = new(widget.Clickable) - } - t.InstrumentBtns = append(t.InstrumentBtns, tail...) - } - - defer op.Save(gtx.Ops).Load() - - t.InstrumentList.Layout(gtx, count, func(gtx C, index int) D { - for t.InstrumentBtns[index].Clicked() { - t.CurrentInstrument = index - } - btnStyle := material.Button(t.Theme, t.InstrumentBtns[index], fmt.Sprintf("%v", index)) - btnStyle.CornerRadius = unit.Dp(0) - btnStyle.Color = t.Theme.Fg - if t.CurrentInstrument == index { - btnStyle.Background = instrumentSurfaceColor - } else { - btnStyle.Background = transparent - } - return btnStyle.Layout(gtx) + labelStyle := LabelStyle{Text: fmt.Sprintf("%v", i), ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)} + return layout.Inset{Left: unit.Dp(10), Right: unit.Dp(10)}.Layout(gtx, func(gtx C) D { + return layout.Center.Layout(gtx, labelStyle.Layout) }) - - return layout.Dimensions{Size: gtx.Constraints.Max} } + + instrumentList := FilledDragList(t.Theme, t.InstrumentDragList, len(t.song.Patch.Instruments), element, t.SwapInstruments) + instrumentList.SelectedColor = instrumentSurfaceColor + instrumentList.HoverColor = instrumentHoverColor + instrumentList.SurfaceColor = transparent + + t.InstrumentDragList.SelectedItem = t.CurrentInstrument + dims := instrumentList.Layout(gtx) + if t.CurrentInstrument != t.InstrumentDragList.SelectedItem { + t.CurrentInstrument = t.InstrumentDragList.SelectedItem + op.InvalidateOp{}.Add(gtx.Ops) + } + return dims } -func (t *Tracker) layoutInstrumentEditor() layout.Widget { +func (t *Tracker) layoutInstrumentEditor(gtx C) D { for t.AddUnitBtn.Clicked() { t.AddUnit() } @@ -149,23 +133,21 @@ func (t *Tracker) layoutInstrumentEditor() layout.Widget { unitList := FilledDragList(t.Theme, t.UnitDragList, len(t.song.Patch.Instruments[t.CurrentInstrument].Units), element, t.SwapUnits) - return func(gtx C) D { - t.UnitDragList.SelectedItem = t.CurrentUnit - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Stack{Alignment: layout.SE}.Layout(gtx, - layout.Expanded(func(gtx C) D { - dims := unitList.Layout(gtx) - if t.CurrentUnit != t.UnitDragList.SelectedItem { - t.CurrentUnit = t.UnitDragList.SelectedItem - op.InvalidateOp{}.Add(gtx.Ops) - } - return dims - }), - layout.Stacked(func(gtx C) D { - return margin.Layout(gtx, addUnitBtnStyle.Layout) - })) - }), - layout.Rigid(t.layoutUnitEditor())) - } + t.UnitDragList.SelectedItem = t.CurrentUnit + return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Stack{Alignment: layout.SE}.Layout(gtx, + layout.Expanded(func(gtx C) D { + dims := unitList.Layout(gtx) + if t.CurrentUnit != t.UnitDragList.SelectedItem { + t.CurrentUnit = t.UnitDragList.SelectedItem + op.InvalidateOp{}.Add(gtx.Ops) + } + return dims + }), + layout.Stacked(func(gtx C) D { + return margin.Layout(gtx, addUnitBtnStyle.Layout) + })) + }), + layout.Rigid(t.layoutUnitEditor)) } diff --git a/tracker/layout.go b/tracker/layout.go index 0053aff..619159a 100644 --- a/tracker/layout.go +++ b/tracker/layout.go @@ -196,7 +196,7 @@ func (t *Tracker) layoutControls(gtx layout.Context) layout.Dimensions { return t.TopHorizontalSplit.Layout(gtx, t.layoutSongPanel, - t.layoutInstruments(), + t.layoutInstruments, ) } diff --git a/tracker/theme.go b/tracker/theme.go index d15abb7..d35a467 100644 --- a/tracker/theme.go +++ b/tracker/theme.go @@ -86,6 +86,7 @@ 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: 45, G: 45, B: 45, A: 255} +var instrumentHoverColor = color.NRGBA{R: 30, G: 31, B: 38, A: 255} var songSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255} diff --git a/tracker/tracker.go b/tracker/tracker.go index b57e0e6..59c7c73 100644 --- a/tracker/tracker.go +++ b/tracker/tracker.go @@ -53,9 +53,8 @@ type Tracker struct { ClearUnitBtn *widget.Clickable ChooseUnitTypeList *layout.List ChooseUnitTypeBtns []*widget.Clickable - InstrumentBtns []*widget.Clickable AddUnitBtn *widget.Clickable - InstrumentList *layout.List + InstrumentDragList *DragList TrackHexCheckBoxes []*widget.Bool TrackShowHex []bool TopHorizontalSplit *Split @@ -82,7 +81,7 @@ func (t *Tracker) LoadSong(song sointu.Song) error { defer t.songPlayMutex.Unlock() t.song = song t.ClampPositions() - if l := len(t.song.Patch.Instruments); t.CurrentInstrument >= len(t.song.Patch.Instruments) { + if l := len(t.song.Patch.Instruments); t.CurrentInstrument >= l { t.CurrentInstrument = l - 1 } if l := len(t.song.Patch.Instruments[t.CurrentInstrument].Units); t.CurrentUnit >= l { @@ -229,6 +228,16 @@ func (t *Tracker) AddInstrument() { t.sequencer.SetPatch(t.song.Patch) } +func (t *Tracker) SwapInstruments(i, j int) { + if i < 0 || j < 0 || i >= len(t.song.Patch.Instruments) || j >= len(t.song.Patch.Instruments) { + return + } + t.SaveUndo() + instruments := t.song.Patch.Instruments + instruments[i], instruments[j] = instruments[j], instruments[i] + t.sequencer.SetPatch(t.song.Patch) +} + func (t *Tracker) DeleteInstrument() { if len(t.song.Patch.Instruments) <= 1 { return @@ -460,7 +469,7 @@ func New(audioContext sointu.AudioContext, synthService sointu.SynthService) *Tr closer: make(chan struct{}), undoStack: []sointu.Song{}, redoStack: []sointu.Song{}, - InstrumentList: &layout.List{Axis: layout.Horizontal}, + InstrumentDragList: &DragList{List: &layout.List{Axis: layout.Horizontal}}, TopHorizontalSplit: new(Split), BottomHorizontalSplit: new(Split), VerticalSplit: new(Split), diff --git a/tracker/uniteditor.go b/tracker/uniteditor.go index 649ac95..d0c9ab5 100644 --- a/tracker/uniteditor.go +++ b/tracker/uniteditor.go @@ -15,59 +15,55 @@ import ( "golang.org/x/exp/shiny/materialdesign/icons" ) -func (t *Tracker) layoutUnitEditor() layout.Widget { +func (t *Tracker) layoutUnitEditor(gtx C) D { 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()), - ) - } + 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...) +func (t *Tracker) layoutUnitSliders(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) } - 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...) + 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 { @@ -104,28 +100,26 @@ func (t *Tracker) layoutUnitFooter() layout.Widget { } } -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() { - t.SetUnit(allUnits[i]) - } - 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)) +func (t *Tracker) layoutUnitTypeChooser(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() { + t.SetUnit(allUnits[i]) } - return t.ChooseUnitTypeList.Layout(gtx, len(allUnits), listElem) + 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) }