It's a complete mess for now - many things are out of place or shown when they shouldn't be - and we can't move around the game map. But, it's a good start.
232 lines
5.3 KiB
Go
232 lines
5.3 KiB
Go
package ui
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
|
|
"code.ur.gs/lupine/ordoor/internal/assetstore"
|
|
"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
|
|
|
|
// 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)
|
|
|
|
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 thumb *menus.Record
|
|
var items []*menus.Record
|
|
var otherChildren []*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 thumb != nil {
|
|
return nil, fmt.Errorf("Duplicate thumbs in menu %v", menu.Locator())
|
|
}
|
|
thumb = rec
|
|
default:
|
|
// e.g. maingame:18.12 includes a button that is not part of the box
|
|
otherChildren = append(otherChildren, rec)
|
|
}
|
|
}
|
|
|
|
if len(items) == 0 || thumb == nil || upBtn == nil || downBtn == nil {
|
|
return nil, fmt.Errorf("Missing items in menu %v", menu.Locator())
|
|
}
|
|
|
|
// Now build the wonderful thing
|
|
baseElem, err := registerNoninteractive(d, menu)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
upSprId := upBtn.SpriteId[0]
|
|
if upSprId == -1 {
|
|
upSprId = upBtn.Share
|
|
}
|
|
|
|
elemUp, err := registerButton(d, upBtn, upSprId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dnSprId := downBtn.SpriteId[0]
|
|
if dnSprId == -1 {
|
|
dnSprId = downBtn.Share
|
|
}
|
|
elemDown, err := registerButton(d, downBtn, dnSprId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
thumbBaseSpr, err := d.menu.Sprite(thumb.Share)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
thumbSprId := thumb.SpriteId[0]
|
|
if thumbSprId == -1 {
|
|
thumbSprId = thumb.Share
|
|
}
|
|
|
|
thumbImgSpr, err := d.menu.Sprite(thumbSprId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
element := &listBox{
|
|
base: baseElem,
|
|
// TODO: upBtn needs to be frozen when offset == 0; downBtn when offset == max
|
|
upBtn: elemUp,
|
|
downBtn: elemDown,
|
|
|
|
// TODO: need to be able to drag the thumb
|
|
thumbBase: thumbBaseSpr,
|
|
thumbImg: thumbImgSpr,
|
|
}
|
|
|
|
// 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. Since we're a composite of other controls, they are
|
|
// mostly self-registered at the moment.
|
|
d.paintables = append(d.paintables, 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, err := registerNoninteractive(d, rec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// TODO: pick the correct font
|
|
ni.label = &label{
|
|
align: AlignModeLeft,
|
|
font: d.menu.Font(0),
|
|
rect: ni.rect,
|
|
}
|
|
|
|
element.lines = append(element.lines, ni)
|
|
}
|
|
|
|
element.refresh()
|
|
|
|
return otherChildren, 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.
|
|
if ni.label != nil {
|
|
ni.label.text = ""
|
|
if len(l.strings) > l.offset+i {
|
|
ni.label.text = 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
|
|
}
|