diff --git a/tracker/popup.go b/tracker/popup.go new file mode 100644 index 0000000..8bdd3a4 --- /dev/null +++ b/tracker/popup.go @@ -0,0 +1,72 @@ +package tracker + +import ( + "image/color" + + "gioui.org/f32" + "gioui.org/io/pointer" + "gioui.org/layout" + "gioui.org/op" + "gioui.org/op/clip" + "gioui.org/op/paint" + "gioui.org/unit" +) + +type PopupStyle struct { + Visible *bool + Contents layout.Widget + SurfaceColor color.NRGBA + SE, SW, NW, NE unit.Value +} + +func Popup(visible *bool, contents layout.Widget) PopupStyle { + return PopupStyle{ + Visible: visible, + Contents: contents, + SurfaceColor: popupSurfaceColor, + SE: unit.Dp(6), + SW: unit.Dp(6), + NW: unit.Dp(6), + NE: unit.Dp(6), + } +} + +func (s PopupStyle) Layout(gtx C) D { + if !*s.Visible { + return D{} + } + for _, ev := range gtx.Events(s.Visible) { + e, ok := ev.(pointer.Event) + if !ok { + continue + } + + switch e.Type { + case pointer.Press: + *s.Visible = false + } + } + + bg := func(gtx C) D { + pointer.InputOp{Tag: s.Visible, + Types: pointer.Press, + }.Add(gtx.Ops) + rrect := clip.RRect{ + Rect: f32.Rectangle{Max: f32.Pt(float32(gtx.Constraints.Min.X), float32(gtx.Constraints.Min.Y))}, + SE: float32(gtx.Px(s.SE)), + SW: float32(gtx.Px(s.SW)), + NW: float32(gtx.Px(s.NW)), + NE: float32(gtx.Px(s.NE)), + } + paint.FillShape(gtx.Ops, s.SurfaceColor, rrect.Op(gtx.Ops)) + return D{Size: gtx.Constraints.Min} + } + macro := op.Record(gtx.Ops) + dims := layout.Stack{}.Layout(gtx, + layout.Expanded(bg), + layout.Stacked(s.Contents), + ) + callop := macro.Stop() + op.Defer(gtx.Ops, callop) + return dims +} diff --git a/tracker/songpanel.go b/tracker/songpanel.go index a2fd011..b633bb6 100644 --- a/tracker/songpanel.go +++ b/tracker/songpanel.go @@ -4,7 +4,9 @@ import ( "image" "math" + "gioui.org/f32" "gioui.org/layout" + "gioui.org/op" "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/unit" @@ -26,11 +28,13 @@ func (t *Tracker) layoutSongButtons(gtx C) D { //paint.FillShape(gtx.Ops, primaryColorDark, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op()) for t.NewSongFileBtn.Clicked() { - t.LoadSong(defaultSong) + t.LoadSong(defaultSong.Copy()) + t.FileMenuVisible = false } for t.LoadSongFileBtn.Clicked() { t.LoadSongFile() + t.FileMenuVisible = false } for t.SaveSongFileBtn.Clicked() { @@ -47,15 +51,42 @@ func (t *Tracker) layoutSongButtons(gtx C) D { loadBtnStyle.Inset = layout.UniformInset(unit.Dp(6)) loadBtnStyle.Color = primaryColor + menuContents := func(gtx C) D { + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(newBtnStyle.Layout), + layout.Rigid(loadBtnStyle.Layout), + ) + } + + fileMenu := Popup(&t.FileMenuVisible, menuContents) + saveBtnStyle := material.IconButton(t.Theme, t.SaveSongFileBtn, widgetForIcon(icons.ContentSave)) saveBtnStyle.Background = transparent saveBtnStyle.Inset = layout.UniformInset(unit.Dp(6)) saveBtnStyle.Color = primaryColor + fileMenuBtnStyle := material.IconButton(t.Theme, t.FileMenuBtn, widgetForIcon(icons.NavigationMoreVert)) + fileMenuBtnStyle.Background = transparent + fileMenuBtnStyle.Inset = layout.UniformInset(unit.Dp(6)) + fileMenuBtnStyle.Color = primaryColor + + for t.FileMenuBtn.Clicked() { + t.FileMenuVisible = true + } + + popupWidget := func(gtx C) D { + defer op.Save(gtx.Ops).Load() + dims := fileMenuBtnStyle.Layout(gtx) + op.Offset(f32.Pt(0, float32(dims.Size.Y))).Add(gtx.Ops) + gtx.Constraints.Max.X = 160 + gtx.Constraints.Max.Y = 300 + fileMenu.Layout(gtx) + return dims + } + layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Rigid(newBtnStyle.Layout), - layout.Rigid(loadBtnStyle.Layout), layout.Rigid(saveBtnStyle.Layout), + layout.Rigid(popupWidget), ) return layout.Dimensions{Size: gtx.Constraints.Max} diff --git a/tracker/theme.go b/tracker/theme.go index f8272db..5bafb8b 100644 --- a/tracker/theme.go +++ b/tracker/theme.go @@ -88,3 +88,5 @@ var inactiveBtnColor = color.NRGBA{R: 61, G: 55, B: 55, A: 255} var instrumentSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255} var songSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255} + +var popupSurfaceColor = color.NRGBA{R: 45, G: 45, B: 46, A: 255} diff --git a/tracker/tracker.go b/tracker/tracker.go index a1bb16f..2725587 100644 --- a/tracker/tracker.go +++ b/tracker/tracker.go @@ -42,6 +42,8 @@ type Tracker struct { SubtractOctaveBtn *widget.Clickable SongLength *NumberInput SaveSongFileBtn *widget.Clickable + FileMenuBtn *widget.Clickable + FileMenuVisible bool ParameterSliders []*widget.Float UnitBtns []*widget.Clickable InstrumentBtns []*widget.Clickable @@ -384,6 +386,7 @@ func New(audioContext sointu.AudioContext) *Tracker { NewInstrumentBtn: new(widget.Clickable), DeleteInstrumentBtn: new(widget.Clickable), NewSongFileBtn: new(widget.Clickable), + FileMenuBtn: new(widget.Clickable), LoadSongFileBtn: new(widget.Clickable), SaveSongFileBtn: new(widget.Clickable), AddSemitoneBtn: new(widget.Clickable), @@ -408,7 +411,7 @@ func New(audioContext sointu.AudioContext) *Tracker { t.Theme.Palette.Fg = primaryColor t.Theme.Palette.ContrastFg = black go t.sequencerLoop(t.closer) - if err := t.LoadSong(defaultSong); err != nil { + if err := t.LoadSong(defaultSong.Copy()); err != nil { panic(fmt.Errorf("cannot load default song: %w", err)) } return t