Files
ordoor/internal/ui/list_box.go

187 lines
4.2 KiB
Go

package ui
import (
"fmt"
"image"
"code.ur.gs/lupine/ordoor/internal/assetstore"
"code.ur.gs/lupine/ordoor/internal/menus"
)
// listBox is a TListBox in VCL terms. It has a number of lines of text, one of
// which may be selected, and a slider with up and down buttons to scroll if the
// options in the box exceed its viewing capacity.
//
// TODO: multi-select functionality? Is it needed?
type listBox struct {
locator string
upBtn *button
downBtn *button
// FIXME: can we share code between slider and this element?
thumbBase *assetstore.Sprite // Bounds are given by this
thumbImg *assetstore.Sprite // This is displayed at offset * (height / steps)
lines []*noninteractive // We display to these
// The list box acts as a window onto these
strings []string
// The start of our window
offset int
}
func (d *Driver) buildListBox(group *menus.Group, up, down, thumb *menus.Record, items ...*menus.Record) (*listBox, *Widget, error) {
upElem, upWidget, err := d.buildButton(up.Props())
if err != nil {
return nil, nil, err
}
downElem, downWidget, err := d.buildButton(down.Props())
if err != nil {
return nil, nil, err
}
thumbBaseSpr, err := d.menu.Sprite(thumb.ObjectIdx, thumb.Share)
if err != nil {
return nil, nil, err
}
thumbImgSpr, err := d.menu.Sprite(thumb.ObjectIdx, thumb.BaseSpriteID())
if err != nil {
return nil, nil, err
}
element := &listBox{
locator: group.Locator,
// TODO: upBtn needs to be frozen when offset == 0; downBtn when offset == max
upBtn: upElem,
downBtn: downElem,
// TODO: need to be able to drag the thumb
thumbBase: thumbBaseSpr,
thumbImg: thumbImgSpr,
}
// Internal wiring-up
upElem.onClick(element.up)
downElem.onClick(element.down)
// FIXME: Test data for now
for i := 0; i < 50; i++ {
element.strings = append(element.strings, fmt.Sprintf("FOO %v", i))
}
// Register everything. Since we're a composite of other controls, they are
// mostly self-registered at the moment.
widget := &Widget{
Children: []*Widget{upWidget, downWidget},
Active: group.Active, // FIXME: children have their own active state
ownPaintables: []paintable{element},
ownValueables: []valueable{element},
}
// FIXME: we should be able to freeze/unfreeze as a group.
// HURK: These need to be registered after the other elements so they are
// drawn in the correct order to be visible
for _, rec := range items {
ni, niWidget, err := d.buildStatic(rec.Props())
if err != nil {
return nil, nil, err
}
niWidget.ownClickables = append(niWidget.ownClickables, ni)
// TODO: pick the correct font
ni.label = &label{
align: AlignModeLeft,
font: d.menu.Font(0),
rect: ni.rect,
}
element.lines = append(element.lines, ni)
widget.Children = append(widget.Children, niWidget)
}
element.refresh()
return element, widget, nil
}
func (l *listBox) id() string {
return l.locator
}
func (l *listBox) value() string {
return ""
}
func (l *listBox) setValue(s string) {
}
func (l *listBox) SetStrings(to []string) {
if len(to) < len(l.strings) {
l.offset = 0 // FIXME: unconditional? Trim to max?
}
l.strings = to
l.refresh()
}
// TODO: Selected returns the index and value of the selected item
func (l *listBox) Selected() (int, string) {
return 0, ""
}
func (l *listBox) up() {
if l.offset <= 0 {
return
}
l.offset -= 1
l.refresh()
}
func (l *listBox) down() {
if l.offset > len(l.strings)-len(l.lines) {
return
}
l.offset += 1
l.refresh()
}
func (l *listBox) refresh() {
for i, ni := range l.lines {
// FIXME: noninteractive isn't set up for dynamic text yet. Need to
// generate textImg on demand instead of once at start.
if ni.label != nil {
ni.label.str = ""
if len(l.strings) > l.offset+i {
ni.label.str = l.strings[l.offset+i]
}
}
}
}
func (l *listBox) thumbPos() image.Point {
pos := l.thumbImg.Rect.Min
if len(l.strings) == 0 {
return pos
}
pixPerLine := (l.thumbBase.Rect.Dy()) / (len(l.strings) - len(l.lines))
pos.Y += pixPerLine * l.offset
return pos
}
func (l *listBox) regions(tick int) []region {
// Draw the slider at the appropriate point
out := oneRegion(l.thumbBase.Rect.Min, l.thumbBase.Image)
out = append(out, oneRegion(l.thumbPos(), l.thumbImg.Image)...)
return out
}