mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-04 01:28:45 -04:00
feat(tracker): implement draggable list and make unit list use such
This commit is contained in:
parent
68fbb914cd
commit
207ae5195d
157
tracker/draglist.go
Normal file
157
tracker/draglist.go
Normal file
@ -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
|
||||||
|
}
|
@ -3,7 +3,6 @@ package tracker
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
|
||||||
|
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
@ -137,11 +136,32 @@ func (t *Tracker) layoutInstrumentEditor() layout.Widget {
|
|||||||
addUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(4))
|
addUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(4))
|
||||||
margin := layout.UniformInset(unit.Dp(2))
|
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 {
|
return func(gtx C) D {
|
||||||
|
t.UnitDragList.SelectedItem = t.CurrentUnit
|
||||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
return layout.Stack{Alignment: layout.SE}.Layout(gtx,
|
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 {
|
layout.Stacked(func(gtx C) D {
|
||||||
return margin.Layout(gtx, addUnitBtnStyle.Layout)
|
return margin.Layout(gtx, addUnitBtnStyle.Layout)
|
||||||
}))
|
}))
|
||||||
@ -149,46 +169,3 @@ func (t *Tracker) layoutInstrumentEditor() layout.Widget {
|
|||||||
layout.Rigid(t.layoutUnitEditor()))
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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 popupSurfaceColor = color.NRGBA{R: 50, G: 50, B: 51, A: 255}
|
||||||
var popupShadowColor = color.NRGBA{R: 0, G: 0, B: 0, A: 192}
|
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 dragListSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255}
|
||||||
var unitListSelectedColor = color.NRGBA{R: 55, G: 55, B: 61, A: 255}
|
var dragListSelectedColor = color.NRGBA{R: 55, G: 55, B: 61, A: 255}
|
||||||
var unitListHighlightColor = color.NRGBA{R: 42, G: 45, 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 unitSurfaceColor = color.NRGBA{R: 30, G: 30, B: 30, A: 255}
|
||||||
var unitTypeListHighlightColor = color.NRGBA{R: 42, G: 45, B: 61, A: 255}
|
var unitTypeListHighlightColor = color.NRGBA{R: 42, G: 45, B: 61, A: 255}
|
||||||
|
@ -48,8 +48,7 @@ type Tracker struct {
|
|||||||
FileMenuBtn *widget.Clickable
|
FileMenuBtn *widget.Clickable
|
||||||
FileMenuVisible bool
|
FileMenuVisible bool
|
||||||
ParameterSliders []*widget.Float
|
ParameterSliders []*widget.Float
|
||||||
UnitBtns []*widget.Clickable
|
UnitDragList *DragList
|
||||||
UnitList *layout.List
|
|
||||||
DeleteUnitBtn *widget.Clickable
|
DeleteUnitBtn *widget.Clickable
|
||||||
ClearUnitBtn *widget.Clickable
|
ClearUnitBtn *widget.Clickable
|
||||||
ChooseUnitTypeList *layout.List
|
ChooseUnitTypeList *layout.List
|
||||||
@ -347,6 +346,16 @@ func (t *Tracker) DeleteUnit() {
|
|||||||
t.sequencer.SetPatch(t.song.Patch)
|
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() {
|
func (t *Tracker) ClampPositions() {
|
||||||
t.PlayPosition.Clamp(t.song)
|
t.PlayPosition.Clamp(t.song)
|
||||||
t.Cursor.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),
|
AddUnitBtn: new(widget.Clickable),
|
||||||
DeleteUnitBtn: new(widget.Clickable),
|
DeleteUnitBtn: new(widget.Clickable),
|
||||||
ClearUnitBtn: 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),
|
setPlaying: make(chan bool),
|
||||||
rowJump: make(chan int),
|
rowJump: make(chan int),
|
||||||
patternJump: make(chan int),
|
patternJump: make(chan int),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user