mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-20 05:54:34 -04:00
161 lines
4.7 KiB
Go
161 lines
4.7 KiB
Go
package gioui
|
|
|
|
import (
|
|
"fmt"
|
|
"image/color"
|
|
|
|
"gioui.org/io/key"
|
|
"gioui.org/layout"
|
|
"gioui.org/op/paint"
|
|
"gioui.org/widget"
|
|
"gioui.org/widget/material"
|
|
"github.com/vsariola/sointu/tracker"
|
|
)
|
|
|
|
const DIALOG_MAX_BTNS = 3
|
|
|
|
type (
|
|
// DialogState is the state that needs to be retained between frames
|
|
DialogState struct {
|
|
Clickables [DIALOG_MAX_BTNS]widget.Clickable
|
|
|
|
visible bool // this is used to control the visibility of the dialog
|
|
}
|
|
|
|
// DialogStyle is the style for a dialog that is store in the theme.yml
|
|
DialogStyle struct {
|
|
TitleInset layout.Inset
|
|
TextInset layout.Inset
|
|
ButtonStyle ButtonStyle
|
|
Title LabelStyle
|
|
Text LabelStyle
|
|
Bg color.NRGBA
|
|
Buttons ButtonStyle
|
|
}
|
|
|
|
// Dialog is the widget with a Layout method that can be used to display a dialog.
|
|
Dialog struct {
|
|
Theme *Theme
|
|
State *DialogState
|
|
Style *DialogStyle
|
|
Btns [DIALOG_MAX_BTNS]DialogButton
|
|
NumBtns int
|
|
Title string
|
|
Text string
|
|
}
|
|
|
|
DialogButton struct {
|
|
Text string
|
|
Action tracker.Action
|
|
}
|
|
)
|
|
|
|
func MakeDialog(th *Theme, d *DialogState, title, text string, btns ...DialogButton) Dialog {
|
|
ret := Dialog{
|
|
Theme: th,
|
|
Style: &th.Dialog,
|
|
State: d,
|
|
Title: title,
|
|
Text: text,
|
|
}
|
|
if len(btns) > DIALOG_MAX_BTNS {
|
|
panic(fmt.Sprintf("too many buttons for dialog: %d, max is %d", len(btns), DIALOG_MAX_BTNS))
|
|
}
|
|
copy(ret.Btns[:], btns)
|
|
ret.NumBtns = len(btns)
|
|
d.visible = true
|
|
return ret
|
|
}
|
|
|
|
func DialogBtn(text string, action tracker.Action) DialogButton {
|
|
return DialogButton{Text: text, Action: action}
|
|
}
|
|
|
|
func (d *Dialog) Layout(gtx C) D {
|
|
anyFocused := false
|
|
for i := 0; i < d.NumBtns; i++ {
|
|
anyFocused = anyFocused || gtx.Source.Focused(&d.State.Clickables[i])
|
|
}
|
|
if !anyFocused {
|
|
gtx.Execute(key.FocusCmd{Tag: &d.State.Clickables[d.NumBtns-1]})
|
|
}
|
|
d.handleKeys(gtx)
|
|
paint.Fill(gtx.Ops, d.Style.Bg)
|
|
dims := layout.Center.Layout(gtx, func(gtx C) D {
|
|
return Popup(d.Theme, &d.State.visible).Layout(gtx, func(gtx C) D {
|
|
return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx,
|
|
layout.Rigid(func(gtx C) D {
|
|
return d.Style.TitleInset.Layout(gtx, Label(d.Theme, &d.Style.Title, d.Title).Layout)
|
|
}),
|
|
layout.Rigid(func(gtx C) D {
|
|
return d.Style.TextInset.Layout(gtx, Label(d.Theme, &d.Style.Text, d.Text).Layout)
|
|
}),
|
|
layout.Rigid(func(gtx C) D {
|
|
return layout.E.Layout(gtx, func(gtx C) D {
|
|
var fcs [DIALOG_MAX_BTNS]layout.FlexChild
|
|
var actBtns [DIALOG_MAX_BTNS]material.ButtonStyle
|
|
for i := 0; i < d.NumBtns; i++ {
|
|
actBtns[i] = material.Button(&d.Theme.Material, &d.State.Clickables[i], d.Btns[i].Text)
|
|
actBtns[i].Background = d.Style.Buttons.Background
|
|
actBtns[i].Color = d.Style.Buttons.Color
|
|
actBtns[i].TextSize = d.Style.Buttons.TextSize
|
|
actBtns[i].Font = d.Style.Buttons.Font
|
|
actBtns[i].Inset = d.Style.Buttons.Inset
|
|
actBtns[i].CornerRadius = d.Style.Buttons.CornerRadius
|
|
}
|
|
// putting this inside these inside the for loop
|
|
// cause heap escapes, so that's why this ugliness;
|
|
// remember to update if you change the
|
|
// DIAOLG_MAX_BTNS constant
|
|
fcs[0] = layout.Rigid(actBtns[0].Layout)
|
|
fcs[1] = layout.Rigid(actBtns[1].Layout)
|
|
fcs[2] = layout.Rigid(actBtns[2].Layout)
|
|
gtx.Constraints.Min.Y = gtx.Dp(d.Style.Buttons.Height)
|
|
return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween}.Layout(gtx, fcs[:d.NumBtns]...)
|
|
})
|
|
}),
|
|
)
|
|
})
|
|
})
|
|
if !d.State.visible {
|
|
d.Btns[d.NumBtns-1].Action.Do()
|
|
}
|
|
return dims
|
|
}
|
|
|
|
func (d *Dialog) handleKeys(gtx C) {
|
|
for i := 0; i < d.NumBtns; i++ {
|
|
for d.State.Clickables[i].Clicked(gtx) {
|
|
d.Btns[i].Action.Do()
|
|
}
|
|
d.handleKeysForButton(gtx, (i+d.NumBtns-1)%d.NumBtns, i, (i+1)%d.NumBtns)
|
|
}
|
|
}
|
|
|
|
func (d *Dialog) handleKeysForButton(gtx C, prev, cur, next int) {
|
|
cPrev := &d.State.Clickables[prev]
|
|
cCur := &d.State.Clickables[cur]
|
|
cNext := &d.State.Clickables[next]
|
|
for {
|
|
e, ok := gtx.Event(
|
|
key.Filter{Focus: cCur, Name: key.NameLeftArrow},
|
|
key.Filter{Focus: cCur, Name: key.NameRightArrow},
|
|
key.Filter{Focus: cCur, Name: key.NameEscape},
|
|
key.Filter{Focus: cCur, Name: key.NameTab, Optional: key.ModShift},
|
|
)
|
|
if !ok {
|
|
break
|
|
}
|
|
if e, ok := e.(key.Event); ok && e.State == key.Press {
|
|
switch {
|
|
case e.Name == key.NameLeftArrow || (e.Name == key.NameTab && e.Modifiers.Contain(key.ModShift)):
|
|
gtx.Execute(key.FocusCmd{Tag: cPrev})
|
|
case e.Name == key.NameRightArrow || (e.Name == key.NameTab && !e.Modifiers.Contain(key.ModShift)):
|
|
gtx.Execute(key.FocusCmd{Tag: cNext})
|
|
case e.Name == key.NameEscape:
|
|
d.Btns[d.NumBtns-1].Action.Do() // last button is always the cancel button
|
|
}
|
|
}
|
|
}
|
|
}
|