From 207ae5195d45b0d91f94a54f1a8a6acb371d11d1 Mon Sep 17 00:00:00 2001 From: vsariola <5684185+vsariola@users.noreply.github.com> Date: Sat, 6 Feb 2021 16:28:32 +0200 Subject: [PATCH] feat(tracker): implement draggable list and make unit list use such --- tracker/draglist.go | 157 +++++++++++++++++++++++++++++++++++++++++ tracker/instruments.go | 67 ++++++------------ tracker/theme.go | 6 +- tracker/tracker.go | 15 +++- 4 files changed, 194 insertions(+), 51 deletions(-) create mode 100644 tracker/draglist.go diff --git a/tracker/draglist.go b/tracker/draglist.go new file mode 100644 index 0000000..3a71ad2 --- /dev/null +++ b/tracker/draglist.go @@ -0,0 +1,157 @@ +package tracker + +import ( + "image" + "image/color" + + "gioui.org/io/pointer" + "gioui.org/layout" + "gioui.org/op" + "gioui.org/op/clip" + "gioui.org/op/paint" + "gioui.org/widget/material" +) + +type DragList struct { + SelectedItem int + HoverItem int + List *layout.List + drag bool + dragID pointer.ID + tags []bool + swapped bool +} + +type FilledDragListStyle struct { + dragList *DragList + SurfaceColor color.NRGBA + HoverColor color.NRGBA + SelectedColor color.NRGBA + Count int + element func(gtx C, i int) D + swap func(i, j int) +} + +func FilledDragList(th *material.Theme, dragList *DragList, count int, element func(gtx C, i int) D, swap func(i, j int)) FilledDragListStyle { + return FilledDragListStyle{ + dragList: dragList, + element: element, + swap: swap, + Count: count, + SurfaceColor: dragListSurfaceColor, + HoverColor: dragListHoverColor, + SelectedColor: dragListSelectedColor, + } +} + +func (s *FilledDragListStyle) Layout(gtx C) D { + swap := 0 + + paint.FillShape(gtx.Ops, s.SurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op()) + defer op.Save(gtx.Ops).Load() + + if s.dragList.List.Axis == layout.Horizontal { + gtx.Constraints.Min.X = gtx.Constraints.Max.X + } else { + gtx.Constraints.Min.Y = gtx.Constraints.Max.Y + } + + listElem := func(gtx C, index int) D { + for len(s.dragList.tags) <= index { + 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 + } else if s.dragList.HoverItem == index { + color = s.HoverColor + } + 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} + } + inputFg := func(gtx C) D { + defer op.Save(gtx.Ops).Load() + for _, ev := range gtx.Events(&s.dragList.tags[index]) { + e, ok := ev.(pointer.Event) + if !ok { + continue + } + switch e.Type { + case pointer.Enter: + s.dragList.HoverItem = index + case pointer.Leave: + if s.dragList.HoverItem == index { + s.dragList.HoverItem = -1 + } + case pointer.Press: + if s.dragList.drag { + break + } + s.dragList.SelectedItem = index + } + } + rect := image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y) + pointer.Rect(rect).Add(gtx.Ops) + pointer.InputOp{Tag: &s.dragList.tags[index], + Types: pointer.Press | pointer.Enter | pointer.Leave, + }.Add(gtx.Ops) + if index == s.dragList.SelectedItem { + for _, ev := range gtx.Events(s.dragList) { + e, ok := ev.(pointer.Event) + if !ok { + continue + } + switch e.Type { + case pointer.Press: + s.dragList.dragID = e.PointerID + case pointer.Drag: + if s.dragList.dragID != e.PointerID { + break + } + if s.dragList.List.Axis == layout.Horizontal { + if e.Position.X < 0 { + swap = -1 + } + if e.Position.X > float32(gtx.Constraints.Min.X) { + swap = 1 + } + } else { + if e.Position.Y < 0 { + swap = -1 + } + if e.Position.Y > float32(gtx.Constraints.Min.Y) { + swap = 1 + } + } + case pointer.Release: + fallthrough + case pointer.Cancel: + s.dragList.drag = false + } + } + pointer.InputOp{Tag: s.dragList, + Types: pointer.Drag | pointer.Press | pointer.Release, + Grab: s.dragList.drag, + }.Add(gtx.Ops) + } + return layout.Dimensions{Size: gtx.Constraints.Min} + } + return layout.Stack{Alignment: layout.W}.Layout(gtx, + layout.Expanded(bg), + layout.Stacked(func(gtx C) D { + return s.element(gtx, index) + }), + layout.Expanded(inputFg)) + } + dims := s.dragList.List.Layout(gtx, s.Count, listElem) + if !s.dragList.swapped && swap != 0 && s.dragList.SelectedItem+swap >= 0 && s.dragList.SelectedItem+swap < s.Count { + s.swap(s.dragList.SelectedItem, s.dragList.SelectedItem+swap) + s.dragList.SelectedItem += swap + s.dragList.swapped = true + } else { + s.dragList.swapped = false + } + return dims +} diff --git a/tracker/instruments.go b/tracker/instruments.go index ab8ae9f..80bf95d 100644 --- a/tracker/instruments.go +++ b/tracker/instruments.go @@ -3,7 +3,6 @@ package tracker import ( "fmt" "image" - "image/color" "gioui.org/layout" "gioui.org/op" @@ -137,11 +136,32 @@ func (t *Tracker) layoutInstrumentEditor() layout.Widget { addUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(4)) margin := layout.UniformInset(unit.Dp(2)) + element := func(gtx C, i int) D { + gtx.Constraints = layout.Exact(image.Pt(gtx.Px(unit.Dp(120)), gtx.Px(unit.Dp(20)))) + u := t.song.Patch.Instruments[t.CurrentInstrument].Units[i] + labelStyle := LabelStyle{Text: u.Type, ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)} + if labelStyle.Text == "" { + labelStyle.Text = "---" + labelStyle.Alignment = layout.Center + } + return labelStyle.Layout(gtx) + } + + 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(t.layoutUnitList()), + 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) })) @@ -149,46 +169,3 @@ func (t *Tracker) layoutInstrumentEditor() layout.Widget { 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) - for len(t.UnitBtns) < count { - t.UnitBtns = append(t.UnitBtns, new(widget.Clickable)) - } - - listElem := func(gtx C, i int) D { - for t.UnitBtns[i].Clicked() { - t.CurrentUnit = i - op.InvalidateOp{}.Add(gtx.Ops) - } - u := t.song.Patch.Instruments[t.CurrentInstrument].Units[i] - labelStyle := LabelStyle{Text: u.Type, ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)} - 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 t.UnitList.Layout(gtx, len(t.song.Patch.Instruments[t.CurrentInstrument].Units), listElem) - } -} diff --git a/tracker/theme.go b/tracker/theme.go index f0a9283..d15abb7 100644 --- a/tracker/theme.go +++ b/tracker/theme.go @@ -92,9 +92,9 @@ var songSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, 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 dragListSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255} +var dragListSelectedColor = color.NRGBA{R: 55, G: 55, B: 61, A: 255} +var dragListHoverColor = 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} diff --git a/tracker/tracker.go b/tracker/tracker.go index f720e66..09291c8 100644 --- a/tracker/tracker.go +++ b/tracker/tracker.go @@ -48,8 +48,7 @@ type Tracker struct { FileMenuBtn *widget.Clickable FileMenuVisible bool ParameterSliders []*widget.Float - UnitBtns []*widget.Clickable - UnitList *layout.List + UnitDragList *DragList DeleteUnitBtn *widget.Clickable ClearUnitBtn *widget.Clickable ChooseUnitTypeList *layout.List @@ -347,6 +346,16 @@ func (t *Tracker) DeleteUnit() { t.sequencer.SetPatch(t.song.Patch) } +func (t *Tracker) SwapUnits(i, j int) { + if i < 0 || j < 0 || i >= len(t.song.Patch.Instruments[t.CurrentInstrument].Units) || j >= len(t.song.Patch.Instruments[t.CurrentInstrument].Units) { + return + } + t.SaveUndo() + units := t.song.Patch.Instruments[t.CurrentInstrument].Units + units[i], units[j] = units[j], units[i] + t.sequencer.SetPatch(t.song.Patch) +} + func (t *Tracker) ClampPositions() { t.PlayPosition.Clamp(t.song) t.Cursor.Clamp(t.song) @@ -437,7 +446,7 @@ func New(audioContext sointu.AudioContext, synthService sointu.SynthService) *Tr AddUnitBtn: new(widget.Clickable), DeleteUnitBtn: new(widget.Clickable), ClearUnitBtn: new(widget.Clickable), - UnitList: &layout.List{Axis: layout.Vertical}, + UnitDragList: &DragList{List: &layout.List{Axis: layout.Vertical}}, setPlaying: make(chan bool), rowJump: make(chan int), patternJump: make(chan int),