From 5124e0bf740ab786e8a00b6daa73da7f1a417759 Mon Sep 17 00:00:00 2001 From: vsariola <5684185+vsariola@users.noreply.github.com> Date: Wed, 13 Jan 2021 18:37:20 +0200 Subject: [PATCH] feat(tracker): move song related buttons to top and make the panel sizes adjustable --- tracker/layout.go | 56 +++++++--------------- tracker/songpanel.go | 88 ++++++++++++++++++++++++++++++++++ tracker/split.go | 109 +++++++++++++++++++++++++++++++++++++++++++ tracker/theme.go | 2 + tracker/tracker.go | 7 +++ 5 files changed, 223 insertions(+), 39 deletions(-) create mode 100644 tracker/songpanel.go create mode 100644 tracker/split.go diff --git a/tracker/layout.go b/tracker/layout.go index 01ec951..59b126f 100644 --- a/tracker/layout.go +++ b/tracker/layout.go @@ -75,7 +75,6 @@ func (t *Tracker) Layout(gtx layout.Context) { layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx2 layout.Context) layout.Dimensions { return layout.Flex{Axis: layout.Vertical}.Layout(gtx2, layout.Rigid(t.layoutControls), - layout.Rigid(t.line(true, separatorLineColor)), layout.Flexed(1, t.layoutTracksAndPatterns)) }) t.updateInstrumentScroll() @@ -86,14 +85,16 @@ func (t *Tracker) layoutTracksAndPatterns(gtx layout.Context) layout.Dimensions if !t.Playing { playPat = -1 } - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Rigid(t.layoutPatterns( + return t.BottomSplit.Layout(gtx, + t.layoutPatterns( t.song.Tracks, t.ActiveTrack, t.DisplayPattern, t.CursorColumn, playPat, - )), layout.Flexed(1, t.layoutTracks)) + ), + t.layoutTracks, + ) } func (t *Tracker) layoutTracks(gtx layout.Context) layout.Dimensions { @@ -219,14 +220,6 @@ func (t *Tracker) layoutControls(gtx layout.Context) layout.Dimensions { } }() - for t.LoadSongFileBtn.Clicked() { - t.LoadSongFile() - } - - for t.SaveSongFileBtn.Clicked() { - t.SaveSongFile() - } - for t.SongLengthUpBtn.Clicked() { t.IncreaseSongLength() } @@ -235,34 +228,19 @@ func (t *Tracker) layoutControls(gtx layout.Context) layout.Dimensions { t.DecreaseSongLength() } - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return in.Layout(gtx, smallButton(material.IconButton(t.Theme, t.SongLengthUpBtn, upIcon)).Layout) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return in.Layout(gtx, enableButton(smallButton(material.IconButton(t.Theme, t.SongLengthDownBtn, downIcon)), t.song.SequenceLength() > 1).Layout) - }), - layout.Rigid(Label(fmt.Sprintf("BPM: %3v", t.song.BPM), white)), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return in.Layout(gtx, enableButton(smallButton(material.IconButton(t.Theme, t.BPMUpBtn, upIcon)), t.song.BPM < 999).Layout) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return in.Layout(gtx, enableButton(smallButton(material.IconButton(t.Theme, t.BPMDownBtn, downIcon)), t.song.BPM > 1).Layout) - }), - layout.Flexed(1, t.layoutInstruments()), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - iconBtn := enableButton(material.IconButton(t.Theme, t.NewInstrumentBtn, addIcon), t.song.Patch.TotalVoices() < 32) - return in.Layout(gtx, iconBtn.Layout) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - iconBtn := material.IconButton(t.Theme, t.LoadSongFileBtn, loadIcon) - return in.Layout(gtx, iconBtn.Layout) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - iconBtn := material.IconButton(t.Theme, t.SaveSongFileBtn, saveIcon) - return in.Layout(gtx, iconBtn.Layout) - }), + return t.TopSplit.Layout(gtx, + t.layoutSongPanel, + func(gtx C) D { + return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, + layout.Flexed(1, t.layoutInstruments()), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + iconBtn := enableButton(material.IconButton(t.Theme, t.NewInstrumentBtn, addIcon), t.song.Patch.TotalVoices() < 32) + return in.Layout(gtx, iconBtn.Layout) + }), + ) + }, ) + } func (t *Tracker) line(horizontal bool, color color.RGBA) layout.Widget { diff --git a/tracker/songpanel.go b/tracker/songpanel.go new file mode 100644 index 0000000..e0b587e --- /dev/null +++ b/tracker/songpanel.go @@ -0,0 +1,88 @@ +package tracker + +import ( + "fmt" + "image" + + "gioui.org/layout" + "gioui.org/op/clip" + "gioui.org/op/paint" + "gioui.org/unit" + "gioui.org/widget/material" +) + +func (t *Tracker) layoutSongPanel(gtx C) D { + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(t.layoutSongButtons), + layout.Flexed(1, t.layoutSongOptions), + ) +} + +func (t *Tracker) layoutSongButtons(gtx C) D { + gtx.Constraints.Max.Y = gtx.Px(unit.Dp(36)) + gtx.Constraints.Min.Y = gtx.Px(unit.Dp(36)) + + //paint.FillShape(gtx.Ops, primaryColorDark, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op()) + + for t.LoadSongFileBtn.Clicked() { + t.LoadSongFile() + } + + for t.SaveSongFileBtn.Clicked() { + t.SaveSongFile() + } + + newBtnStyle := material.IconButton(t.Theme, t.NewSongFileBtn, addIcon) + newBtnStyle.Background = transparent + newBtnStyle.Inset = layout.UniformInset(unit.Dp(6)) + newBtnStyle.Color = primaryColor + + loadBtnStyle := material.IconButton(t.Theme, t.LoadSongFileBtn, loadIcon) + loadBtnStyle.Background = transparent + loadBtnStyle.Inset = layout.UniformInset(unit.Dp(6)) + loadBtnStyle.Color = primaryColor + + saveBtnStyle := material.IconButton(t.Theme, t.SaveSongFileBtn, saveIcon) + saveBtnStyle.Background = transparent + saveBtnStyle.Inset = layout.UniformInset(unit.Dp(6)) + saveBtnStyle.Color = primaryColor + + layout.Flex{Axis: layout.Horizontal}.Layout(gtx, + layout.Rigid(newBtnStyle.Layout), + layout.Rigid(loadBtnStyle.Layout), + layout.Rigid(saveBtnStyle.Layout), + ) + + return layout.Dimensions{Size: gtx.Constraints.Max} +} + +func (t *Tracker) layoutSongOptions(gtx C) D { + paint.FillShape(gtx.Ops, songSurfaceColor, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op()) + + in := layout.UniformInset(unit.Dp(1)) + + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, + layout.Rigid(Label(fmt.Sprintf("LEN: %3v", t.song.SequenceLength()), white)), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return in.Layout(gtx, smallButton(material.IconButton(t.Theme, t.SongLengthUpBtn, upIcon)).Layout) + }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return in.Layout(gtx, enableButton(smallButton(material.IconButton(t.Theme, t.SongLengthDownBtn, downIcon)), t.song.SequenceLength() > 1).Layout) + }), + ) + }), + layout.Rigid(func(gtx C) D { + return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, + layout.Rigid(Label(fmt.Sprintf("BPM: %3v", t.song.BPM), white)), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return in.Layout(gtx, enableButton(smallButton(material.IconButton(t.Theme, t.BPMUpBtn, upIcon)), t.song.BPM < 999).Layout) + }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return in.Layout(gtx, enableButton(smallButton(material.IconButton(t.Theme, t.BPMDownBtn, downIcon)), t.song.BPM > 1).Layout) + }), + ) + }), + ) +} diff --git a/tracker/split.go b/tracker/split.go new file mode 100644 index 0000000..ad314f7 --- /dev/null +++ b/tracker/split.go @@ -0,0 +1,109 @@ +package tracker + +import ( + "image" + + "gioui.org/f32" + "gioui.org/io/pointer" + "gioui.org/layout" + "gioui.org/op" + "gioui.org/unit" +) + +type Split struct { + // Ratio keeps the current layout. + // 0 is center, -1 completely to the left, 1 completely to the right. + Ratio float32 + // Bar is the width for resizing the layout + Bar unit.Value + + drag bool + dragID pointer.ID + dragX float32 +} + +var defaultBarWidth = unit.Dp(10) + +func (s *Split) Layout(gtx layout.Context, left, right layout.Widget) layout.Dimensions { + bar := gtx.Px(s.Bar) + if bar <= 1 { + bar = gtx.Px(defaultBarWidth) + } + + proportion := (s.Ratio + 1) / 2 + leftsize := int(proportion*float32(gtx.Constraints.Max.X) - float32(bar)) + + rightoffset := leftsize + bar + rightsize := gtx.Constraints.Max.X - rightoffset + + { // handle input + // Avoid affecting the input tree with pointer events. + stack := op.Push(gtx.Ops) + + for _, ev := range gtx.Events(s) { + e, ok := ev.(pointer.Event) + if !ok { + continue + } + + switch e.Type { + case pointer.Press: + if s.drag { + break + } + + s.dragID = e.PointerID + s.dragX = e.Position.X + + case pointer.Drag: + if s.dragID != e.PointerID { + break + } + + deltaX := e.Position.X - s.dragX + s.dragX = e.Position.X + + deltaRatio := deltaX * 2 / float32(gtx.Constraints.Max.X) + s.Ratio += deltaRatio + + case pointer.Release: + fallthrough + case pointer.Cancel: + s.drag = false + } + } + + // register for input + barRect := image.Rect(leftsize, 0, rightoffset, gtx.Constraints.Max.X) + pointer.Rect(barRect).Add(gtx.Ops) + pointer.InputOp{Tag: s, + Types: pointer.Press | pointer.Drag | pointer.Release, + Grab: s.drag, + }.Add(gtx.Ops) + + stack.Pop() + } + + { + stack := op.Push(gtx.Ops) + + gtx := gtx + gtx.Constraints = layout.Exact(image.Pt(leftsize, gtx.Constraints.Max.Y)) + left(gtx) + + stack.Pop() + } + + { + stack := op.Push(gtx.Ops) + + op.Offset(f32.Pt(float32(rightoffset), 0)).Add(gtx.Ops) + gtx := gtx + gtx.Constraints = layout.Exact(image.Pt(rightsize, gtx.Constraints.Max.Y)) + right(gtx) + + stack.Pop() + } + + return layout.Dimensions{Size: gtx.Constraints.Max} +} diff --git a/tracker/theme.go b/tracker/theme.go index 4aa0f1d..457c4c5 100644 --- a/tracker/theme.go +++ b/tracker/theme.go @@ -82,3 +82,5 @@ var patternCursorColor = color.RGBA{R: 38, G: 79, B: 120, A: 64} var inactiveBtnColor = color.RGBA{R: 61, G: 55, B: 55, A: 255} var instrumentSurfaceColor = color.RGBA{R: 31, G: 31, B: 31, A: 31} + +var songSurfaceColor = color.RGBA{R: 31, G: 31, B: 31, A: 31} diff --git a/tracker/tracker.go b/tracker/tracker.go index 9e5d8cf..b095db2 100644 --- a/tracker/tracker.go +++ b/tracker/tracker.go @@ -37,6 +37,7 @@ type Tracker struct { NewTrackBtn *widget.Clickable NewInstrumentBtn *widget.Clickable LoadSongFileBtn *widget.Clickable + NewSongFileBtn *widget.Clickable SongLengthUpBtn *widget.Clickable SongLengthDownBtn *widget.Clickable SaveSongFileBtn *widget.Clickable @@ -46,6 +47,8 @@ type Tracker struct { InstrumentList *layout.List TrackHexCheckBoxes []*widget.Bool TrackShowHex []bool + TopSplit *Split + BottomSplit *Split sequencer *Sequencer ticked chan struct{} @@ -286,6 +289,7 @@ func New(audioContext sointu.AudioContext) *Tracker { BPMDownBtn: new(widget.Clickable), NewTrackBtn: new(widget.Clickable), NewInstrumentBtn: new(widget.Clickable), + NewSongFileBtn: new(widget.Clickable), LoadSongFileBtn: new(widget.Clickable), SaveSongFileBtn: new(widget.Clickable), SongLengthUpBtn: new(widget.Clickable), @@ -298,7 +302,10 @@ func New(audioContext sointu.AudioContext) *Tracker { undoStack: []sointu.Song{}, redoStack: []sointu.Song{}, InstrumentList: &layout.List{Axis: layout.Horizontal}, + TopSplit: new(Split), + BottomSplit: new(Split), } + t.BottomSplit.Ratio = -.5 t.Theme.Color.Primary = primaryColor t.Theme.Color.InvText = black go t.sequencerLoop(t.closer)