7 changed files with 345 additions and 4 deletions
@ -0,0 +1,114 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"fmt" |
|||
|
|||
"git.kirsle.net/go/render" |
|||
"git.kirsle.net/go/render/sdl" |
|||
"git.kirsle.net/go/ui" |
|||
) |
|||
|
|||
func init() { |
|||
sdl.DefaultFontFilename = "../DejaVuSans.ttf" |
|||
} |
|||
|
|||
func main() { |
|||
mw, err := ui.NewMainWindow("Forms Test") |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
|
|||
mw.SetBackground(render.White) |
|||
|
|||
// Buttons row.
|
|||
{ |
|||
frame := ui.NewFrame("Frame 1") |
|||
mw.Pack(frame, ui.Pack{ |
|||
Side: ui.N, |
|||
FillX: true, |
|||
Padding: 4, |
|||
}) |
|||
label := ui.NewLabel(ui.Label{ |
|||
Text: "Buttons:", |
|||
}) |
|||
frame.Pack(label, ui.Pack{ |
|||
Side: ui.W, |
|||
}) |
|||
|
|||
// Buttons.
|
|||
btn := ui.NewButton("Button 1", ui.NewLabel(ui.Label{ |
|||
Text: "Click me!", |
|||
})) |
|||
btn.Handle(ui.Click, func(ed ui.EventData) error { |
|||
fmt.Println("Clicked!") |
|||
return nil |
|||
}) |
|||
frame.Pack(btn, ui.Pack{ |
|||
Side: ui.W, |
|||
PadX: 4, |
|||
}) |
|||
|
|||
mw.Supervisor().Add(btn) |
|||
} |
|||
|
|||
// Selectbox row.
|
|||
{ |
|||
frame := ui.NewFrame("Frame 2") |
|||
mw.Pack(frame, ui.Pack{ |
|||
Side: ui.N, |
|||
FillX: true, |
|||
Padding: 4, |
|||
}) |
|||
label := ui.NewLabel(ui.Label{ |
|||
Text: "Set window color:", |
|||
}) |
|||
frame.Pack(label, ui.Pack{ |
|||
Side: ui.W, |
|||
}) |
|||
|
|||
var colors = []struct{ |
|||
Label string |
|||
Value render.Color |
|||
}{ |
|||
{"White", render.White}, |
|||
{"Yellow", render.Yellow}, |
|||
{"Cyan", render.Cyan}, |
|||
{"Green", render.Green}, |
|||
{"Blue", render.RGBA(0, 153, 255, 255)}, |
|||
{"Pink", render.Pink}, |
|||
} |
|||
|
|||
// Create the SelectBox and populate its options.
|
|||
sel := ui.NewSelectBox("Select 1", ui.Label{}) |
|||
for _, option := range colors { |
|||
sel.AddItem(option.Label, option.Value, func() { |
|||
fmt.Printf("Picked option: %s\n", option.Value) |
|||
}) |
|||
} |
|||
|
|||
// On change: set the window BG color.
|
|||
sel.Handle(ui.Change, func(ed ui.EventData) error { |
|||
if val, ok := sel.GetValue(); ok { |
|||
if color, ok := val.Value.(render.Color); ok { |
|||
fmt.Printf("Set background to: %s\n", val.Label) |
|||
mw.SetBackground(color) |
|||
} else { |
|||
fmt.Println("Not a valid color!") |
|||
} |
|||
} else { |
|||
fmt.Println("Not a valid SelectBox value!") |
|||
} |
|||
return nil |
|||
}) |
|||
|
|||
frame.Pack(sel, ui.Pack{ |
|||
Side: ui.W, |
|||
PadX: 4, |
|||
}) |
|||
sel.Supervise(mw.Supervisor()) |
|||
mw.Supervisor().Add(sel) // TODO: ideally Supervise() is all that's needed,
|
|||
// but w/o this extra Add() the Button doesn't react.
|
|||
} |
|||
|
|||
mw.MainLoop() |
|||
} |
@ -0,0 +1,31 @@ |
|||
package ui |
|||
|
|||
/* |
|||
Glyph images as Base64 encoded PNGs. |
|||
*/ |
|||
|
|||
import ( |
|||
"encoding/base64" |
|||
"image" |
|||
"image/png" |
|||
"bytes" |
|||
) |
|||
|
|||
// List of available glyphs.
|
|||
const ( |
|||
// Downward pointed black arrow 9x9 pixels.
|
|||
GlyphDownArrow9x9 = `iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI |
|||
WXMAAC4jAAAuIwF4pT92AAAAKklEQVQY02NgoBZgZGBg+E+MIgYCChkZkTj/cRnCiCb4H4stWMF/ |
|||
BpoBAGQSBQOpAugRAAAAAElFTkSuQmCC` |
|||
) |
|||
|
|||
// GetGlyph loads a PNG image from a hard-coded glyph.
|
|||
func GetGlyph(b64 string) (image.Image, error) { |
|||
data, err := base64.StdEncoding.DecodeString(b64) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
scanner := bytes.NewReader(data) |
|||
return png.Decode(scanner) |
|||
} |
@ -0,0 +1,178 @@ |
|||
package ui |
|||
|
|||
import ( |
|||
"fmt" |
|||
|
|||
"git.kirsle.net/go/render" |
|||
"git.kirsle.net/go/ui/theme" |
|||
) |
|||
|
|||
// SelectBox is a kind of MenuButton which allows choosing a value from a list.
|
|||
type SelectBox struct { |
|||
MenuButton |
|||
|
|||
name string |
|||
|
|||
// Configurables after SelectBox creation.
|
|||
AlwaysChange bool // always call the Change event, even if selection not changed.
|
|||
|
|||
// Child widgets specific to the SelectBox.
|
|||
frame *Frame |
|||
label *Label |
|||
arrow *Image |
|||
|
|||
// Data storage.
|
|||
textVariable string |
|||
values []SelectValue |
|||
} |
|||
|
|||
// SelectValue holds a mapping between a text label for a SelectBox and
|
|||
// its underlying value (an arbitrary data type).
|
|||
type SelectValue struct { |
|||
Label string |
|||
Value interface{} |
|||
} |
|||
|
|||
// NewSelectBox creates a new SelectBox.
|
|||
//
|
|||
// The Label configuration passed in should be used to set font styles
|
|||
// and padding; the Text, TextVariable and IntVariable of the Label will
|
|||
// all be ignored, as SelectBox will handle the values internally.
|
|||
func NewSelectBox(name string, withLabel Label) *SelectBox { |
|||
w := &SelectBox{ |
|||
name: name, |
|||
textVariable: "Choose one", |
|||
values: []SelectValue{}, |
|||
} |
|||
|
|||
// Ensure the label has no text of its own.
|
|||
withLabel.Text = "" |
|||
withLabel.TextVariable = &w.textVariable |
|||
withLabel.IntVariable = nil |
|||
|
|||
w.frame = NewFrame(name + " Frame") |
|||
w.Button.child = w.frame |
|||
|
|||
w.label = NewLabel(withLabel) |
|||
w.frame.Pack(w.label, Pack{ |
|||
Side: W, |
|||
}) |
|||
|
|||
// arrow, _ := GetGlyph(GlyphDownArrow9x9)
|
|||
// w.image = ImageFromImage(arrow, )
|
|||
|
|||
// Configure the button's appearance.
|
|||
w.Button.Configure(Config{ |
|||
BorderSize: 2, |
|||
BorderStyle: BorderSunken, |
|||
Background: render.White, |
|||
}) |
|||
|
|||
// Set sensible default padding on the label.
|
|||
if w.label.Font.Padding == 0 && w.label.Font.PadX == 0 && w.label.Font.PadY == 0 { |
|||
w.label.Font.PadX = 4 |
|||
w.label.Font.PadY = 2 |
|||
} |
|||
|
|||
w.IDFunc(func() string { |
|||
return fmt.Sprintf("SelectBox<%s>", name) |
|||
}) |
|||
|
|||
w.setup() |
|||
return w |
|||
} |
|||
|
|||
// AddItem adds a new option to the SelectBox's menu.
|
|||
// The label is the text value to display.
|
|||
// The value is the underlying value (string or int) for the TextVariable or IntVariable.
|
|||
// The function callback runs when the option is picked.
|
|||
func (w *SelectBox) AddItem(label string, value interface{}, f func()) { |
|||
// Add this label and its value mapping to the SelectBox.
|
|||
w.values = append(w.values, SelectValue{ |
|||
Label: label, |
|||
Value: value, |
|||
}) |
|||
|
|||
// Call the inherited MenuButton.AddItem.
|
|||
w.MenuButton.AddItem(label, func() { |
|||
// Set the bound label.
|
|||
var changed = w.textVariable != label |
|||
w.textVariable = label |
|||
|
|||
if changed || w.AlwaysChange { |
|||
w.Event(Change, EventData{ |
|||
Supervisor: w.MenuButton.supervisor, |
|||
}) |
|||
} |
|||
}) |
|||
|
|||
// If the current text label isn't in the options, pick
|
|||
// the first option.
|
|||
if _, ok := w.GetValue(); !ok { |
|||
w.textVariable = w.values[0].Label |
|||
} |
|||
} |
|||
|
|||
// TODO: RemoveItem()
|
|||
|
|||
// Value returns the currently selected item in the SelectBox.
|
|||
//
|
|||
// Returns the SelectValue and true on success, and the Label or underlying Value
|
|||
// can be read from the SelectValue struct. If no valid option is selected, the
|
|||
// bool value returns false.
|
|||
func (w *SelectBox) GetValue() (SelectValue, bool) { |
|||
for _, row := range w.values { |
|||
if w.textVariable == row.Label { |
|||
return row, true |
|||
} |
|||
} |
|||
|
|||
return SelectValue{}, false |
|||
} |
|||
|
|||
// Compute to re-evaluate the button state (in the case of radio buttons where
|
|||
// a different button will affect the state of this one when clicked).
|
|||
func (w *SelectBox) Compute(e render.Engine) { |
|||
w.MenuButton.Compute(e) |
|||
} |
|||
|
|||
// setup the UI components and event handlers.
|
|||
func (w *SelectBox) setup() { |
|||
w.Configure(Config{ |
|||
BorderSize: 1, |
|||
BorderStyle: BorderSunken, |
|||
Background: theme.InputBackgroundColor, |
|||
}) |
|||
|
|||
w.Handle(MouseOver, func(ed EventData) error { |
|||
w.hovering = true |
|||
w.SetBackground(theme.ButtonHoverColor) |
|||
return nil |
|||
}) |
|||
w.Handle(MouseOut, func(ed EventData) error { |
|||
w.hovering = false |
|||
w.SetBackground(theme.InputBackgroundColor) |
|||
return nil |
|||
}) |
|||
|
|||
w.Handle(MouseDown, func(ed EventData) error { |
|||
w.clicked = true |
|||
w.SetBackground(theme.ButtonBackgroundColor) |
|||
return nil |
|||
}) |
|||
w.Handle(MouseUp, func(ed EventData) error { |
|||
w.clicked = false |
|||
w.SetBackground(theme.InputBackgroundColor) |
|||
return nil |
|||
}) |
|||
|
|||
w.Handle(Click, func(ed EventData) error { |
|||
// Are we properly configured?
|
|||
if w.supervisor != nil && w.menu != nil { |
|||
w.menu.Show() |
|||
w.supervisor.PushModal(w.menu) |
|||
} |
|||
return nil |
|||
}) |
|||
} |
|||
|
Loading…
Reference in new issue