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(menu.ObjectIdx, thumb.Share) if err != nil { return nil, err } thumbSprId := thumb.SpriteId[0] if thumbSprId == -1 { thumbSprId = thumb.Share } thumbImgSpr, err := d.menu.Sprite(menu.ObjectIdx, 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 }