Noah Petherbridge
4de0126b19
Adds support for Xbox and Nintendo style game controllers. The gamepad controls are documented on the README and in the game's Settings window. The buttons are not customizable yet, except that the player can choose between two button styles: * X Style (default): "A" button is on the bottom and "B" on the right. * N Style: swaps the A/B and the X/Y buttons to use a Nintendo-style layout instead of an Xbox-style.
279 lines
5.6 KiB
Go
279 lines
5.6 KiB
Go
// Package magicform helps create simple form layouts with go/ui.
|
|
package magicform
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/log"
|
|
"git.kirsle.net/go/render"
|
|
"git.kirsle.net/go/ui"
|
|
"git.kirsle.net/go/ui/style"
|
|
)
|
|
|
|
type Type int
|
|
|
|
const (
|
|
Auto Type = iota
|
|
Text // free, wide Label row
|
|
Frame // custom frame from the caller
|
|
Button // Single button with a label
|
|
Textbox
|
|
Checkbox
|
|
Radiobox
|
|
Selectbox
|
|
)
|
|
|
|
// Form configuration.
|
|
type Form struct {
|
|
Supervisor *ui.Supervisor // Required for most useful forms
|
|
Engine render.Engine
|
|
|
|
// For vertical forms.
|
|
Vertical bool
|
|
LabelWidth int // size of left frame for labels.
|
|
}
|
|
|
|
/*
|
|
Field for your form (or form-aligned label sections, etc.)
|
|
|
|
The type of Form control to render is inferred based on bound
|
|
variables and other configuration.
|
|
*/
|
|
type Field struct {
|
|
// Type may be inferred by presence of other params.
|
|
Type Type
|
|
|
|
// Set a text string and font for simple labels or paragraphs.
|
|
Label string
|
|
Font render.Text
|
|
|
|
// Easy button row: make Buttons an array of Button fields
|
|
Buttons []Field
|
|
ButtonStyle *style.Button
|
|
|
|
// Easy Paginator. DO NOT SUPERVISE, let the Create do so!
|
|
Pager *ui.Pager
|
|
|
|
// If you send a *ui.Frame to insert, the Type is inferred
|
|
// to be Frame.
|
|
Frame *ui.Frame
|
|
|
|
// Variable bindings, the type may infer to be:
|
|
BoolVariable *bool // Checkbox
|
|
TextVariable *string // Textbox
|
|
Options []Option // Selectbox
|
|
SelectValue interface{} // Selectbox default choice
|
|
|
|
// Tooltip to add to a form control.
|
|
// Checkbox only for now.
|
|
Tooltip ui.Tooltip // config for the tooltip only
|
|
|
|
// Handlers you can configure
|
|
OnSelect func(value interface{}) // Selectbox
|
|
OnClick func() // Button
|
|
}
|
|
|
|
// Option used in Selectbox or Radiobox fields.
|
|
type Option struct {
|
|
Value interface{}
|
|
Label string
|
|
}
|
|
|
|
/*
|
|
Create the form field and populate it into the given Frame.
|
|
|
|
Renders the form vertically.
|
|
*/
|
|
func (form Form) Create(into *ui.Frame, fields []Field) {
|
|
for n, row := range fields {
|
|
row := row
|
|
|
|
if row.Frame != nil {
|
|
into.Pack(row.Frame, ui.Pack{
|
|
Side: ui.N,
|
|
FillX: true,
|
|
})
|
|
continue
|
|
}
|
|
|
|
frame := ui.NewFrame(fmt.Sprintf("Line %d", n))
|
|
into.Pack(frame, ui.Pack{
|
|
Side: ui.N,
|
|
FillX: true,
|
|
})
|
|
|
|
// Pager row?
|
|
if row.Pager != nil {
|
|
row.Pager.Compute(form.Engine)
|
|
form.Supervisor.Add(row.Pager)
|
|
frame.Pack(row.Pager, ui.Pack{
|
|
Side: ui.W,
|
|
Expand: true,
|
|
})
|
|
|
|
}
|
|
|
|
// Buttons row?
|
|
if row.Buttons != nil && len(row.Buttons) > 0 {
|
|
for _, row := range row.Buttons {
|
|
row := row
|
|
|
|
btn := ui.NewButton(row.Label, ui.NewLabel(ui.Label{
|
|
Text: row.Label,
|
|
Font: row.Font,
|
|
}))
|
|
if row.ButtonStyle != nil {
|
|
btn.SetStyle(row.ButtonStyle)
|
|
}
|
|
|
|
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
|
if row.OnClick != nil {
|
|
row.OnClick()
|
|
} else {
|
|
log.Error("No OnClick handler for button %s", row.Label)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
btn.Compute(form.Engine)
|
|
form.Supervisor.Add(btn)
|
|
|
|
frame.Pack(btn, ui.Pack{
|
|
Side: ui.W,
|
|
PadX: 4,
|
|
PadY: 2,
|
|
})
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
// Infer the type of the form field.
|
|
if row.Type == Auto {
|
|
row.Type = row.Infer()
|
|
if row.Type == Auto {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Is there a label frame to the left?
|
|
// - Checkbox gets a full row.
|
|
if row.Label != "" && row.Type != Checkbox {
|
|
labFrame := ui.NewFrame("Label Frame")
|
|
labFrame.Configure(ui.Config{
|
|
Width: form.LabelWidth,
|
|
})
|
|
frame.Pack(labFrame, ui.Pack{
|
|
Side: ui.W,
|
|
})
|
|
|
|
// Draw the label text into it.
|
|
label := ui.NewLabel(ui.Label{
|
|
Text: row.Label,
|
|
Font: row.Font,
|
|
})
|
|
labFrame.Pack(label, ui.Pack{
|
|
Side: ui.W,
|
|
})
|
|
}
|
|
|
|
// Checkbox?
|
|
if row.Type == Checkbox {
|
|
cb := ui.NewCheckbox("Checkbox", row.BoolVariable, ui.NewLabel(ui.Label{
|
|
Text: row.Label,
|
|
Font: row.Font,
|
|
}))
|
|
cb.Supervise(form.Supervisor)
|
|
frame.Pack(cb, ui.Pack{
|
|
Side: ui.W,
|
|
FillX: true,
|
|
})
|
|
|
|
// Tooltip? TODO - make nicer.
|
|
if row.Tooltip.Text != "" || row.Tooltip.TextVariable != nil {
|
|
ui.NewTooltip(cb, row.Tooltip)
|
|
}
|
|
|
|
// Handlers
|
|
cb.Handle(ui.Click, func(ed ui.EventData) error {
|
|
if row.OnClick != nil {
|
|
row.OnClick()
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Selectbox? also Radiobox for now.
|
|
if row.Type == Selectbox || row.Type == Radiobox {
|
|
btn := ui.NewSelectBox("Select", ui.Label{
|
|
Font: row.Font,
|
|
})
|
|
frame.Pack(btn, ui.Pack{
|
|
Side: ui.W,
|
|
FillX: true,
|
|
Expand: true,
|
|
})
|
|
|
|
if row.Options != nil {
|
|
for _, option := range row.Options {
|
|
btn.AddItem(option.Label, option.Value, func() {})
|
|
}
|
|
}
|
|
|
|
if row.SelectValue != nil {
|
|
btn.SetValue(row.SelectValue)
|
|
}
|
|
|
|
btn.Handle(ui.Change, func(ed ui.EventData) error {
|
|
if selection, ok := btn.GetValue(); ok {
|
|
if row.OnSelect != nil {
|
|
row.OnSelect(selection.Value)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
btn.Supervise(form.Supervisor)
|
|
form.Supervisor.Add(btn)
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
Infer the type if the field was of type Auto.
|
|
|
|
Returns the first Type inferred from the field by checking in
|
|
this order:
|
|
|
|
- Frame if the field has a *Frame
|
|
- Checkbox if there is a *BoolVariable
|
|
- Selectbox if there are Options
|
|
- Textbox if there is a *TextVariable
|
|
- Text if there is a Label
|
|
|
|
May return Auto if none of the above and be ignored.
|
|
*/
|
|
func (field Field) Infer() Type {
|
|
if field.Frame != nil {
|
|
return Frame
|
|
}
|
|
|
|
if field.BoolVariable != nil {
|
|
return Checkbox
|
|
}
|
|
|
|
if field.Options != nil && len(field.Options) > 0 {
|
|
return Selectbox
|
|
}
|
|
|
|
if field.TextVariable != nil {
|
|
return Textbox
|
|
}
|
|
|
|
if field.Label != "" {
|
|
return Text
|
|
}
|
|
|
|
return Auto
|
|
}
|