From 40be82de4606e7f548715ca28b6e6f1abebfa5a0 Mon Sep 17 00:00:00 2001 From: "5684185+vsariola@users.noreply.github.com" <5684185+vsariola@users.noreply.github.com> Date: Sun, 27 Apr 2025 11:34:00 +0300 Subject: [PATCH] feat(tracker/gioui): refactor & rework playbar with the play buttons --- tracker/gioui/note_editor.go | 4 +- tracker/gioui/order_editor.go | 2 +- tracker/gioui/songpanel.go | 117 ++++++++++++++++++++-------------- tracker/gioui/surface.go | 67 ++++++++----------- tracker/gioui/theme.go | 2 +- tracker/gioui/tracker.go | 2 +- 6 files changed, 102 insertions(+), 92 deletions(-) diff --git a/tracker/gioui/note_editor.go b/tracker/gioui/note_editor.go index 4dcffa2..39173bd 100644 --- a/tracker/gioui/note_editor.go +++ b/tracker/gioui/note_editor.go @@ -149,7 +149,7 @@ func (te *NoteEditor) Layout(gtx layout.Context, t *Tracker) layout.Dimensions { } func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D { - return Surface{Gray: 37, Focus: te.scrollTable.Focused() || te.scrollTable.ChildFocused(), FitSize: true}.Layout(gtx, func(gtx C) D { + return Surface{Gray: 37, Focus: te.scrollTable.Focused() || te.scrollTable.ChildFocused()}.Layout(gtx, func(gtx C) D { addSemitoneBtnStyle := ActionButton(gtx, t.Theme, te.AddSemitoneBtn, "+1") subtractSemitoneBtnStyle := ActionButton(gtx, t.Theme, te.SubtractSemitoneBtn, "-1") addOctaveBtnStyle := ActionButton(gtx, t.Theme, te.AddOctaveBtn, "+12") @@ -240,7 +240,7 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D { } else if mod(j, beatMarkerDensity) == 0 { paint.FillShape(gtx.Ops, oneBeatHighlight, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, pxHeight)}.Op()) } - if t.SongPanel.PlayingBtn.Bool.Value() && j == playSongRow { + if t.Model.Playing().Value() && j == playSongRow { paint.FillShape(gtx.Ops, trackerPlayColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, pxHeight)}.Op()) } return D{} diff --git a/tracker/gioui/order_editor.go b/tracker/gioui/order_editor.go index 06b853d..679b145 100644 --- a/tracker/gioui/order_editor.go +++ b/tracker/gioui/order_editor.go @@ -79,7 +79,7 @@ func (oe *OrderEditor) Layout(gtx C, t *Tracker) D { } rowTitleBg := func(gtx C, j int) D { - if t.SongPanel.PlayingBtn.Bool.Value() && j == t.PlayPosition().OrderRow { + if t.Model.Playing().Value() && j == t.PlayPosition().OrderRow { paint.FillShape(gtx.Ops, patternPlayColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Dp(patternCellHeight))}.Op()) } return D{} diff --git a/tracker/gioui/songpanel.go b/tracker/gioui/songpanel.go index ad88952..d80d8df 100644 --- a/tracker/gioui/songpanel.go +++ b/tracker/gioui/songpanel.go @@ -23,14 +23,9 @@ type SongPanel struct { Step *NumberInput SongLength *NumberInput - RewindBtn *ActionClickable - PlayingBtn *BoolClickable - RecordBtn *BoolClickable - FollowBtn *BoolClickable - PanicBtn *BoolClickable - LoopBtn *BoolClickable - - Scope *Oscilloscope + PanicBtn *BoolClickable + Scope *Oscilloscope + PlayBar *PlayBar // File menu items fileMenuItems []MenuItem @@ -44,14 +39,7 @@ type SongPanel struct { // Edit menu items editMenuItems []MenuItem - // Hints - rewindHint string - playHint, stopHint string - recordHint, stopRecordHint string - followOnHint, followOffHint string - panicHint string - loopOffHint, loopOnHint string - + panicHint string // Midi menu items midiMenuItems []MenuItem } @@ -66,12 +54,8 @@ func NewSongPanel(model *tracker.Model) *SongPanel { Step: NewNumberInput(model.Step().Int()), SongLength: NewNumberInput(model.SongLength().Int()), PanicBtn: NewBoolClickable(model.Panic().Bool()), - LoopBtn: NewBoolClickable(model.LoopToggle().Bool()), - RecordBtn: NewBoolClickable(model.IsRecording().Bool()), - FollowBtn: NewBoolClickable(model.Follow().Bool()), - PlayingBtn: NewBoolClickable(model.Playing().Bool()), - RewindBtn: NewActionClickable(model.PlaySongStart()), Scope: NewOscilloscope(model), + PlayBar: NewPlayBar(model), } ret.fileMenuItems = []MenuItem{ {IconBytes: icons.ContentClear, Text: "New Song", ShortcutText: keyActionMap["NewSong"], Doer: model.NewSong()}, @@ -95,17 +79,7 @@ func NewSongPanel(model *tracker.Model) *SongPanel { Doer: model.SelectMidiInput(input), }) } - ret.rewindHint = makeHint("Rewind", "\n(%s)", "PlaySongStartUnfollow") - ret.playHint = makeHint("Play", " (%s)", "PlayCurrentPosUnfollow") - ret.stopHint = makeHint("Stop", " (%s)", "StopPlaying") ret.panicHint = makeHint("Panic", " (%s)", "PanicToggle") - ret.recordHint = makeHint("Record", " (%s)", "RecordingToggle") - ret.stopRecordHint = makeHint("Stop", " (%s)", "RecordingToggle") - ret.followOnHint = makeHint("Follow on", " (%s)", "FollowToggle") - ret.followOffHint = makeHint("Follow off", " (%s)", "FollowToggle") - ret.loopOffHint = makeHint("Loop off", " (%s)", "LoopToggle") - ret.loopOnHint = makeHint("Loop on", " (%s)", "LoopToggle") - return ret } @@ -147,16 +121,22 @@ func (t *SongPanel) layoutMenuBar(gtx C, tr *Tracker) D { func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D { paint.FillShape(gtx.Ops, songSurfaceColor, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op()) - rewindBtnStyle := ActionIcon(gtx, tr.Theme, t.RewindBtn, icons.AVFastRewind, t.rewindHint) - playBtnStyle := ToggleIcon(gtx, tr.Theme, t.PlayingBtn, icons.AVPlayArrow, icons.AVStop, t.playHint, t.stopHint) - recordBtnStyle := ToggleIcon(gtx, tr.Theme, t.RecordBtn, icons.AVFiberManualRecord, icons.AVFiberSmartRecord, t.recordHint, t.stopRecordHint) - noteTrackBtnStyle := ToggleIcon(gtx, tr.Theme, t.FollowBtn, icons.ActionSpeakerNotesOff, icons.ActionSpeakerNotes, t.followOffHint, t.followOnHint) - loopBtnStyle := ToggleIcon(gtx, tr.Theme, t.LoopBtn, icons.NavigationArrowForward, icons.AVLoop, t.loopOffHint, t.loopOnHint) - scopeStyle := LineOscilloscope(t.Scope, tr.SignalAnalyzer().Waveform(), tr.Theme) return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout), + layout.Rigid(func(gtx C) D { + return layout.Background{}.Layout(gtx, + func(gtx C) D { + // push defer clip op + defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)).Push(gtx.Ops).Pop() + paint.FillShape(gtx.Ops, songSurfaceColor, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op()) + return D{Size: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)} + }, + func(gtx C) D { + return t.PlayBar.Layout(gtx, tr.Theme) + }, + ) + }), layout.Rigid(func(gtx C) D { return layoutSongOptionRow(gtx, tr.Theme, "Song length", NumericUpDown(tr.Theme, t.SongLength, "Song Length").Layout) }), @@ -173,15 +153,6 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D { return layoutSongOptionRow(gtx, tr.Theme, "Cursor step", NumericUpDown(tr.Theme, t.Step, "Cursor step").Layout) }), layout.Rigid(VuMeter{Loudness: tr.Model.DetectorResult().Loudness[tracker.LoudnessShortTerm], Peak: tr.Model.DetectorResult().Peaks[tracker.PeakMomentary], Range: 100}.Layout), - layout.Rigid(func(gtx C) D { - return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(rewindBtnStyle.Layout), - layout.Rigid(playBtnStyle.Layout), - layout.Rigid(recordBtnStyle.Layout), - layout.Rigid(noteTrackBtnStyle.Layout), - layout.Rigid(loopBtnStyle.Layout), - ) - }), layout.Flexed(1, scopeStyle.Layout), layout.Rigid(func(gtx C) D { labelStyle := LabelStyle{Text: version.VersionOrHash, FontSize: unit.Sp(12), Color: mediumEmphasisTextColor, Shaper: tr.Theme.Shaper} @@ -202,3 +173,55 @@ func layoutSongOptionRow(gtx C, th *material.Theme, label string, widget layout. layout.Rigid(rightSpacer), ) } + +type PlayBar struct { + RewindBtn *ActionClickable + PlayingBtn *BoolClickable + RecordBtn *BoolClickable + FollowBtn *BoolClickable + LoopBtn *BoolClickable + // Hints + rewindHint string + playHint, stopHint string + recordHint, stopRecordHint string + followOnHint, followOffHint string + loopOffHint, loopOnHint string +} + +func NewPlayBar(model *tracker.Model) *PlayBar { + ret := &PlayBar{ + LoopBtn: NewBoolClickable(model.LoopToggle().Bool()), + RecordBtn: NewBoolClickable(model.IsRecording().Bool()), + FollowBtn: NewBoolClickable(model.Follow().Bool()), + PlayingBtn: NewBoolClickable(model.Playing().Bool()), + RewindBtn: NewActionClickable(model.PlaySongStart()), + } + ret.rewindHint = makeHint("Rewind", "\n(%s)", "PlaySongStartUnfollow") + ret.playHint = makeHint("Play", " (%s)", "PlayCurrentPosUnfollow") + ret.stopHint = makeHint("Stop", " (%s)", "StopPlaying") + ret.recordHint = makeHint("Record", " (%s)", "RecordingToggle") + ret.stopRecordHint = makeHint("Stop", " (%s)", "RecordingToggle") + ret.followOnHint = makeHint("Follow on", " (%s)", "FollowToggle") + ret.followOffHint = makeHint("Follow off", " (%s)", "FollowToggle") + ret.loopOffHint = makeHint("Loop off", " (%s)", "LoopToggle") + ret.loopOnHint = makeHint("Loop on", " (%s)", "LoopToggle") + return ret +} + +func (pb *PlayBar) Layout(gtx C, th *material.Theme) D { + rewindBtnStyle := ActionIcon(gtx, th, pb.RewindBtn, icons.AVFastRewind, pb.rewindHint) + playBtnStyle := ToggleIcon(gtx, th, pb.PlayingBtn, icons.AVPlayArrow, icons.AVStop, pb.playHint, pb.stopHint) + recordBtnStyle := ToggleIcon(gtx, th, pb.RecordBtn, icons.AVFiberManualRecord, icons.AVFiberSmartRecord, pb.recordHint, pb.stopRecordHint) + noteTrackBtnStyle := ToggleIcon(gtx, th, pb.FollowBtn, icons.ActionSpeakerNotesOff, icons.ActionSpeakerNotes, pb.followOffHint, pb.followOnHint) + loopBtnStyle := ToggleIcon(gtx, th, pb.LoopBtn, icons.NavigationArrowForward, icons.AVLoop, pb.loopOffHint, pb.loopOnHint) + + return Surface{Gray: 37}.Layout(gtx, func(gtx C) D { + return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, + layout.Flexed(1, playBtnStyle.Layout), + layout.Rigid(rewindBtnStyle.Layout), + layout.Rigid(recordBtnStyle.Layout), + layout.Rigid(noteTrackBtnStyle.Layout), + layout.Rigid(loopBtnStyle.Layout), + ) + }) +} diff --git a/tracker/gioui/surface.go b/tracker/gioui/surface.go index ee0ba7a..ad84917 100644 --- a/tracker/gioui/surface.go +++ b/tracker/gioui/surface.go @@ -4,52 +4,39 @@ import ( "image/color" "gioui.org/layout" - "gioui.org/op" "gioui.org/op/clip" "gioui.org/op/paint" ) type Surface struct { - Gray int - Inset layout.Inset - FitSize bool - Focus bool + Gray int + Inset layout.Inset + Focus bool } func (s Surface) Layout(gtx C, widget layout.Widget) D { - bg := func(gtx C) D { - grayInt := s.Gray - if s.Focus { - grayInt += 8 - } - var grayUint8 uint8 - if grayInt < 0 { - grayUint8 = 0 - } else if grayInt > 255 { - grayUint8 = 255 - } else { - grayUint8 = uint8(grayInt) - } - color := color.NRGBA{R: grayUint8, G: grayUint8, B: grayUint8, A: 255} - paint.FillShape(gtx.Ops, color, clip.Rect{ - Max: gtx.Constraints.Min, - }.Op()) - return D{Size: gtx.Constraints.Min} - } - fg := func(gtx C) D { - return s.Inset.Layout(gtx, widget) - } - if s.FitSize { - macro := op.Record(gtx.Ops) - dims := fg(gtx) - call := macro.Stop() - gtx.Constraints = layout.Exact(dims.Size) - bg(gtx) - call.Add(gtx.Ops) - return dims - } - gtxbg := gtx - gtxbg.Constraints.Min = gtxbg.Constraints.Max - bg(gtxbg) - return fg(gtx) + return layout.Background{}.Layout(gtx, + func(gtx C) D { + grayInt := s.Gray + if s.Focus { + grayInt += 8 + } + var grayUint8 uint8 + if grayInt < 0 { + grayUint8 = 0 + } else if grayInt > 255 { + grayUint8 = 255 + } else { + grayUint8 = uint8(grayInt) + } + color := color.NRGBA{R: grayUint8, G: grayUint8, B: grayUint8, A: 255} + paint.FillShape(gtx.Ops, color, clip.Rect{ + Max: gtx.Constraints.Min, + }.Op()) + return D{Size: gtx.Constraints.Min} + }, + func(gtx C) D { + return s.Inset.Layout(gtx, widget) + }, + ) } diff --git a/tracker/gioui/theme.go b/tracker/gioui/theme.go index 063414d..4c6b901 100644 --- a/tracker/gioui/theme.go +++ b/tracker/gioui/theme.go @@ -46,7 +46,7 @@ var loopMarkerColor = color.NRGBA{R: 252, G: 186, B: 3, A: 255} var instrumentHoverColor = color.NRGBA{R: 30, G: 31, B: 38, A: 255} var instrumentNameHintColor = color.NRGBA{R: 200, G: 200, B: 200, A: 255} -var songSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255} +var songSurfaceColor = color.NRGBA{R: 24, G: 24, B: 24, 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} diff --git a/tracker/gioui/tracker.go b/tracker/gioui/tracker.go index d41879e..dcea444 100644 --- a/tracker/gioui/tracker.go +++ b/tracker/gioui/tracker.go @@ -146,7 +146,7 @@ func (t *Tracker) Main() { } } gtx := app.NewContext(&ops, e) - if t.SongPanel.PlayingBtn.Bool.Value() && t.SongPanel.FollowBtn.Bool.Value() { + if t.Playing().Value() && t.Follow().Value() { t.TrackEditor.scrollTable.RowTitleList.CenterOn(t.PlaySongRow()) } t.Layout(gtx, w)