ui/selectbox.go

229 lines
5.5 KiB
Go

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
showImage *Image // User override show 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
}
// SetImage sets the selectbox button to show the image instead of its
// normal text label. If the image corresponds with an option in the selectbox,
// it is up to the caller to call SetImage on change and set the right image here.
//
// Provide a nil value to remove the image and show the text labels instead.
func (w *SelectBox) SetImage(img *Image) {
// Get rid of the current image.
if w.showImage != nil {
w.showImage.Hide()
w.frame.Unpack(w.showImage)
}
// Are we getting a new one?
if img != nil {
w.label.Hide()
w.frame.Pack(img, Pack{
Side: W,
})
} else {
w.label.Show()
}
w.showImage = img
if w.showImage != nil {
w.showImage.Show()
}
}
// 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()
// GetValue 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
}
// SetValueByLabel sets the currently selected option to the given label.
func (w *SelectBox) SetValueByLabel(label string) bool {
for _, option := range w.values {
if option.Label == label {
w.textVariable = option.Label
return true
}
}
return false
}
// SetValue sets the currently selected option to the given value.
func (w *SelectBox) SetValue(value interface{}) bool {
for _, option := range w.values {
if option.Value == value {
w.textVariable = option.Label
return true
}
}
return 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
})
}