247 lines
5.2 KiB
Go
247 lines
5.2 KiB
Go
package menu
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/juju/errors"
|
|
"github.com/veandco/go-sdl2/sdl"
|
|
)
|
|
|
|
type menuItem struct {
|
|
ID string `json:"id"`
|
|
Label *string `json:"label"`
|
|
LabelCommand *command `json:"label_cmd"`
|
|
ActionKey string `json:"key"`
|
|
ActionCommand *command `json:"action_cmd"`
|
|
Toggle *toggle `json:"switch"`
|
|
Items []menuItem `json:"items"`
|
|
Invalidates []string `json:"invalidates"`
|
|
|
|
active bool
|
|
}
|
|
|
|
func (mi *menuItem) prepare() {
|
|
if mi.LabelCommand != nil {
|
|
mi.LabelCommand.keepUpdated()
|
|
}
|
|
if mi.Toggle != nil {
|
|
mi.Toggle.StateCommand.keepUpdated()
|
|
}
|
|
for i := range mi.Items {
|
|
mi.Items[i].prepare()
|
|
}
|
|
}
|
|
|
|
func (mi *menuItem) trigger() {
|
|
if mi.ActionCommand != nil && !mi.ActionCommand.busy {
|
|
mi.ActionCommand.exec()
|
|
}
|
|
if mi.Toggle != nil {
|
|
mi.Toggle.trigger()
|
|
}
|
|
}
|
|
|
|
func (mi *menuItem) label() string {
|
|
if cmd := mi.LabelCommand; cmd != nil {
|
|
// pretty.Println(cmd)
|
|
if cmd.error != nil {
|
|
return cmd.error.Error()
|
|
}
|
|
if cmd.out != nil {
|
|
return *cmd.out
|
|
}
|
|
}
|
|
if cmd := mi.ActionCommand; cmd != nil {
|
|
if cmd.error != nil {
|
|
return cmd.error.Error()
|
|
}
|
|
}
|
|
if mi.Toggle != nil {
|
|
return mi.Toggle.label()
|
|
}
|
|
if mi.Label != nil {
|
|
return *mi.Label
|
|
}
|
|
return "No name"
|
|
}
|
|
|
|
func (mi *menuItem) busy() bool {
|
|
if mi.LabelCommand != nil && mi.LabelCommand.busy {
|
|
return true
|
|
}
|
|
if mi.ActionCommand != nil && mi.ActionCommand.busy {
|
|
return true
|
|
}
|
|
if mi.Toggle != nil && mi.Toggle.StateCommand.busy {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (mi *menuItem) render(r *sdl.Renderer, offsetY int32) error {
|
|
if mi.ActionKey != "" {
|
|
if err := drawItemBackground(r, offsetY, mi.active); err != nil {
|
|
return errors.Annotate(err, "draw item background")
|
|
}
|
|
}
|
|
if err := drawItemLabel(r, offsetY, *mi); err != nil {
|
|
return errors.Annotate(err, "draw item label")
|
|
}
|
|
if err := drawActionKeyLabel(r, offsetY, *mi); err != nil {
|
|
return errors.Annotate(err, "draw item label")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type toggle struct {
|
|
StateCommand command `json:"state_cmd"`
|
|
States map[string]menuItem `json:"states"`
|
|
}
|
|
|
|
func (t *toggle) label() string {
|
|
if t.StateCommand.error != nil {
|
|
return t.StateCommand.error.Error()
|
|
}
|
|
if t.StateCommand.out != nil {
|
|
if opt, ok := t.States[*t.StateCommand.out]; ok {
|
|
return opt.label()
|
|
}
|
|
}
|
|
return "No name"
|
|
}
|
|
|
|
func (t *toggle) trigger() {
|
|
if t.StateCommand.busy {
|
|
return
|
|
}
|
|
|
|
if t.StateCommand.out != nil {
|
|
if opt, ok := t.States[*t.StateCommand.out]; ok {
|
|
opt.trigger()
|
|
t.StateCommand.resetTimer()
|
|
}
|
|
}
|
|
}
|
|
|
|
func drawItemBackground(r *sdl.Renderer, offsetY int32, active bool) error {
|
|
if err := setDrawColor(r, theme.ItemBackground); err != nil {
|
|
return errors.Annotate(err, "set item background color")
|
|
}
|
|
|
|
actionKeyFrame := &sdl.Rect{
|
|
X: int32(paddingX + borderWidth),
|
|
Y: offsetY,
|
|
W: int32(itemHeight()),
|
|
H: int32(itemHeight()),
|
|
}
|
|
var err error
|
|
if active {
|
|
err = r.DrawRect(actionKeyFrame)
|
|
} else {
|
|
err = r.FillRect(actionKeyFrame)
|
|
}
|
|
if err != nil {
|
|
return errors.Annotate(err, "draw item")
|
|
}
|
|
|
|
itemBackground := &sdl.Rect{
|
|
X: int32(borderWidth + paddingX + itemHeight() + paddingX),
|
|
Y: offsetY,
|
|
W: int32(windowWidth - 3*paddingX - itemHeight() - 2*borderWidth),
|
|
H: int32(itemHeight()),
|
|
}
|
|
|
|
if active {
|
|
err = r.DrawRect(itemBackground)
|
|
} else {
|
|
err = r.FillRect(itemBackground)
|
|
}
|
|
if err != nil {
|
|
return errors.Annotate(err, "draw item")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func drawItemLabel(r *sdl.Renderer, offsetY int32, mi menuItem) error {
|
|
label := mi.label()
|
|
color := theme.ItemText
|
|
if mi.busy() && mi.ActionKey != "" {
|
|
label += " [busy]"
|
|
color = theme.TextBusy
|
|
}
|
|
if label == "" {
|
|
return nil
|
|
}
|
|
|
|
labelTexture, err := renderText(r, label, color)
|
|
if err != nil {
|
|
return errors.Annotate(err, "draw item label")
|
|
}
|
|
defer labelTexture.Destroy()
|
|
lw, lh, err := font.SizeUTF8(label)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
const magicNumber = 3
|
|
itemLabel := &sdl.Rect{
|
|
X: int32(paddingX*2+itemHeight()+(itemHeight()-lh)/2) + magicNumber,
|
|
Y: offsetY + int32(itemHeight()-lh)/2,
|
|
W: int32(lw),
|
|
H: int32(lh),
|
|
}
|
|
if err := r.Copy(labelTexture, nil, itemLabel); err != nil {
|
|
return errors.Annotate(err, "render item label")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func drawActionKeyLabel(r *sdl.Renderer, offsetY int32, mi menuItem) error {
|
|
label := strings.ToUpper(mi.ActionKey)
|
|
color := theme.ItemText
|
|
if mi.busy() && mi.ActionKey != "" {
|
|
color = theme.TextBusy
|
|
}
|
|
if label == "" {
|
|
return nil
|
|
}
|
|
|
|
labelTexture, err := renderText(r, label, color)
|
|
if err != nil {
|
|
return errors.Annotate(err, "draw item label")
|
|
}
|
|
defer labelTexture.Destroy()
|
|
lw, lh, err := font.SizeUTF8(label)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
itemLabel := &sdl.Rect{
|
|
X: int32(paddingX + (itemHeight()-lw)/2),
|
|
Y: offsetY + int32(itemHeight()-lh)/2,
|
|
W: int32(lw),
|
|
H: int32(lh),
|
|
}
|
|
if err := r.Copy(labelTexture, nil, itemLabel); err != nil {
|
|
return errors.Annotate(err, "render item label")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func renderText(r *sdl.Renderer, text string, c sdl.Color) (t *sdl.Texture, err error) {
|
|
fs, err := font.RenderUTF8Blended(text, c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer fs.Free()
|
|
|
|
t, err = r.CreateTextureFromSurface(fs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return
|
|
}
|