Add a partial listbox implementation

This commit is contained in:
2020-04-01 01:38:42 +01:00
parent 31da40e772
commit 7935f78acc
6 changed files with 241 additions and 53 deletions

158
internal/ui/list_box.go Normal file
View File

@@ -0,0 +1,158 @@
package ui
import (
"fmt"
"log"
"code.ur.gs/lupine/ordoor/internal/menus"
)
func init() {
registerBuilder(menus.TypeLineKbd, ownedByMenu)
registerBuilder(menus.TypeLineBriefing, ownedByMenu)
registerBuilder(menus.TypeThumb, ownedByMenu)
registerBuilder(menus.TypeListBoxUp, ownedByMenu)
registerBuilder(menus.TypeListBoxDown, ownedByMenu)
}
// 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 {
upBtn *button
downBtn *button
// TODO: can we reuse the slider element for the thumb?
base *noninteractive // The menu itself has a sprite to display
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 registerListBox(d *Driver, menu *menus.Record) ([]*menus.Record, error) {
var upBtn *menus.Record
var downBtn *menus.Record
var slider *menus.Record
var items []*menus.Record
for _, rec := range menu.Children {
switch rec.Type {
case menus.TypeListBoxUp:
if upBtn != nil {
return nil, fmt.Errorf("Duplicate up buttons in menu %v", menu.Locator())
}
upBtn = rec
case menus.TypeListBoxDown:
if downBtn != nil {
return nil, fmt.Errorf("Duplicate down buttons in menu %v", menu.Locator())
}
downBtn = rec
case menus.TypeLineKbd, menus.TypeLineBriefing:
items = append(items, rec)
case menus.TypeThumb:
if slider != nil {
return nil, fmt.Errorf("Duplicate thumbs in menu %v", menu.Locator())
}
slider = rec
default:
return nil, fmt.Errorf("Unrecognised child in listbox menu: %v", rec.Locator())
}
}
if len(items) == 0 || slider == nil || upBtn == nil || downBtn == nil {
return nil, fmt.Errorf("Missing items in menu %v", menu.Locator())
}
// Now build the wonderful thing
elemUp, err := registerButton(d, upBtn, upBtn.SpriteId[0])
if err != nil {
return nil, err
}
elemDown, err := registerButton(d, downBtn, downBtn.SpriteId[0])
if err != nil {
return nil, err
}
element := &listBox{
upBtn: elemUp,
downBtn: elemDown,
}
for _, rec := range items {
ni, err := registerNoninteractive(d, rec)
if err != nil {
return nil, err
}
element.lines = append(element.lines, ni)
}
// Internal wiring-up
elemUp.onClick(element.up)
elemDown.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
// FIXME: we should be able to freeze/unfreeze as a group. The buttons register themselves though...
return nil, nil
}
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.
ni.hoverImpl.text = ""
if len(l.strings) > l.offset+i {
ni.hoverImpl.text = l.strings[l.offset+i]
}
}
log.Printf("Listbox offset: %v", l.offset)
}