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

View File

@@ -41,44 +41,38 @@ const (
TypeSlider MenuType = 232 TypeSlider MenuType = 232
TypeStatusBar MenuType = 233 TypeStatusBar MenuType = 233
TypeDialogue MenuType = 300 TypeDialogue MenuType = 300
TypeListBoxUp MenuType = 400 // FIXME: these have multiple items in MENUTYPE
TypeListBoxDown MenuType = 405
) )
// FIXME: certain elements - especially overlays - don't have a DESC specified // FIXME: certain elements - especially overlays - don't have a DESC specified
// in the .mnu file, but display text specified with a number in i18n. The only // in the .mnu file, but display text specified with a number in i18n. The only
// conclusion I can draw is that they're hardcoded in the binary and set from // conclusion I can draw is that they're hardcoded in the binary and set from
// outside. So, do that here. // outside. So, do that here.
var DescOverrides = map[string]map[string]int{ var DescOverrides = map[string]int{
"main": { "main:2.6": 50992,
"2.6": 50992, "newgame:2.5": 50993,
}, "keyboard:3.3": 50995,
"newgame": { "levelply:2.6": 50996,
"2.5": 50993,
},
"levelply": {
"2.6": 50996,
},
} }
// FIXME: Same idea with text overrides, only these aren't mentioned in the .dta // FIXME: Same idea with text overrides, only these aren't mentioned in the .dta
// file at all! // file at all!
var TextOverrides = map[string]map[string]string{ var TextOverrides = map[string]string{
"main": { "main:2.7": "0.1-ordoor",
"2.7": "0.1-ordoor",
},
} }
// FIXME: The menu is specified as type 2 (button) in these cases, which is // FIXME: The menu is specified as type 2 (button) in these cases, which is
// weird. Make it a menu for now. // weird. Make it a menu for now.
var TypeOverrides = map[string]map[string]MenuType{ var TypeOverrides = map[string]MenuType{
"levelply": { "levelply:2": TypeMenu,
"2": TypeMenu, "savegame:2": TypeMenu,
}, "loadgame:2": TypeMenu,
"savegame": {
"2": TypeMenu, // ???
}, "configure_ultequip:7.5": TypeListBoxUp,
"loadgame": { "configure_ultequip:7.6": TypeListBoxDown,
"2": TypeMenu,
},
} }
type Record struct { type Record struct {
@@ -275,13 +269,15 @@ func setProperty(r *Record, k, v string) {
case "MENUID", "SUBMENUID": case "MENUID", "SUBMENUID":
r.Id = vInt r.Id = vInt
case "MENUTYPE", "SUBMENUTYPE": case "MENUTYPE", "SUBMENUTYPE":
if strings.Contains(v, ",") {
r.Type = MenuType(vSplitInt[0]) // FIXME: what are the other values in this case?
} else {
r.Type = MenuType(vInt) r.Type = MenuType(vInt)
}
// FIXME: Type override. Note that MENUID is specified first, so this works // FIXME: Type override. Note that MENUID is specified first, so this works
if overrides, ok := TypeOverrides[r.Menu.Name]; ok { if override, ok := TypeOverrides[r.Locator()]; ok {
if newType, ok := overrides[r.Path()]; ok { r.Type = override
r.Type = newType
}
} }
case "ACTIVE": case "ACTIVE":
r.Active = (vInt != 0) r.Active = (vInt != 0)
@@ -308,18 +304,14 @@ type Replacer interface {
} }
func (r *Record) Internationalize(replacer Replacer) { func (r *Record) Internationalize(replacer Replacer) {
if overrides, ok := TextOverrides[r.Menu.Name]; ok { if override, ok := TextOverrides[r.Locator()]; ok {
if override, ok := overrides[r.Path()]; ok {
delete(r.properties, "DESC") delete(r.properties, "DESC")
r.Text = override r.Text = override
} }
}
if overrides, ok := DescOverrides[r.Menu.Name]; ok { if override, ok := DescOverrides[r.Locator()]; ok {
if override, ok := overrides[r.Path()]; ok {
r.properties["DESC"] = strconv.Itoa(override) r.properties["DESC"] = strconv.Itoa(override)
} }
}
id, err := strconv.Atoi(r.properties["DESC"]) id, err := strconv.Atoi(r.properties["DESC"])
if err == nil { if err == nil {
@@ -348,3 +340,7 @@ func (r *Record) Path() string {
return strings.Join(path, ".") return strings.Join(path, ".")
} }
func (r *Record) Locator() string {
return fmt.Sprintf("%v:%v", r.Menu.Name, r.Path())
}

View File

@@ -39,11 +39,15 @@ type mainButton struct {
} }
func registerSimpleButton(d *Driver, r *menus.Record) error { func registerSimpleButton(d *Driver, r *menus.Record) error {
return registerButton(d, r, r.SpriteId[0]) _, err := registerButton(d, r, r.SpriteId[0])
return err
} }
func registerInvokeButton(d *Driver, r *menus.Record) error { func registerInvokeButton(d *Driver, r *menus.Record) error {
return registerButton(d, r, r.Share) _, err := registerButton(d, r, r.Share)
return err
} }
func registerMainButton(d *Driver, r *menus.Record) error { func registerMainButton(d *Driver, r *menus.Record) error {
@@ -99,10 +103,10 @@ func registerDoorHotspot(d *Driver, r *menus.Record) error {
} }
func registerButton(d *Driver, r *menus.Record, spriteId int) error { func registerButton(d *Driver, r *menus.Record, spriteId int) (*button, error) {
sprites, err := d.menu.Sprites(spriteId, 3) // base, pressed, disabled sprites, err := d.menu.Sprites(spriteId, 3) // base, pressed, disabled
if err != nil { if err != nil {
return err return nil, err
} }
btn := &button{ btn := &button{
@@ -118,7 +122,7 @@ func registerButton(d *Driver, r *menus.Record, spriteId int) error {
d.hoverables = append(d.hoverables, btn) d.hoverables = append(d.hoverables, btn)
d.paintables = append(d.paintables, btn) d.paintables = append(d.paintables, btn)
return nil return btn, nil
} }
func (b *button) id() string { func (b *button) id() string {

View File

@@ -18,14 +18,9 @@ func init() {
// FIXME: these need implementing // FIXME: these need implementing
// Needed for Keyboard.mnu (main -> options -> keyboard) // Needed for Keyboard.mnu (main -> options -> keyboard)
registerBuilder(menus.TypeLineKbd, registerDebug("Unimplemented LineKbd", nil))
registerBuilder(menus.TypeDialogue, registerDebug("Unimplemented Dialogue", nil)) registerBuilder(menus.TypeDialogue, registerDebug("Unimplemented Dialogue", nil))
// Needed for Briefing.mnu
registerBuilder(menus.TypeLineBriefing, registerDebug("Unimplemented LineBriefing", nil))
// Needed for ChaEquip.mnu // Needed for ChaEquip.mnu
registerBuilder(menus.TypeThumb, registerDebug("Unimplemented Thumb", nil))
// Needed for MainGameChaos.mnu // Needed for MainGameChaos.mnu
registerBuilder(menus.TypeStatusBar, registerDebug("Unimplemented StatusBar", nil)) registerBuilder(menus.TypeStatusBar, registerDebug("Unimplemented StatusBar", nil))

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)
}

View File

@@ -11,11 +11,42 @@ func init() {
} }
func registerMenu(d *Driver, r *menus.Record) ([]*menus.Record, error) { func registerMenu(d *Driver, r *menus.Record) ([]*menus.Record, error) {
childrenLeft, err := listBoxFromMenu(d, r, r.Children)
if err != nil {
return nil, err
}
childrenLeft, err = inventorySelectFromMenu(d, r, childrenLeft)
if err != nil {
return nil, err
}
// Return all the unhandled children to be processed further
return childrenLeft, nil
}
func listBoxFromMenu(d *Driver, menu *menus.Record, children []*menus.Record) ([]*menus.Record, error) {
ok := false
for _, rec := range children {
if rec.Type == menus.TypeThumb { // FIXME: we're using this to indicate a listbox
ok = true
break
}
}
if !ok {
return children, nil
}
return registerListBox(d, menu)
}
// Group all inventory selects that share a menu together // Group all inventory selects that share a menu together
func inventorySelectFromMenu(d *Driver, menu *menus.Record, children []*menus.Record) ([]*menus.Record, error) {
var childrenLeft []*menus.Record var childrenLeft []*menus.Record
var inventorySelects []*inventorySelect var inventorySelects []*inventorySelect
for _, child := range r.Children { for _, child := range children {
switch child.Type { switch child.Type {
case menus.TypeInventorySelect: case menus.TypeInventorySelect:
is, err := registerInventorySelect(d, child) is, err := registerInventorySelect(d, child)
@@ -37,6 +68,5 @@ func registerMenu(d *Driver, r *menus.Record) ([]*menus.Record, error) {
} }
} }
// Return all the unhandled children to be processed further
return childrenLeft, nil return childrenLeft, nil
} }

View File

@@ -46,6 +46,11 @@ type animationHover struct {
} }
func registerStatic(d *Driver, r *menus.Record) error { func registerStatic(d *Driver, r *menus.Record) error {
_, err := registerNoninteractive(d, r)
return err
}
func registerNoninteractive(d *Driver, r *menus.Record) (*noninteractive, error) {
// FIXME: SpriteID takes precedence over SHARE if present, but is that right? // FIXME: SpriteID takes precedence over SHARE if present, but is that right?
spriteId := r.Share spriteId := r.Share
if len(r.SpriteId) > 0 && r.SpriteId[0] != -1 { if len(r.SpriteId) > 0 && r.SpriteId[0] != -1 {
@@ -54,7 +59,7 @@ func registerStatic(d *Driver, r *menus.Record) error {
sprite, err := d.menu.Sprite(spriteId) sprite, err := d.menu.Sprite(spriteId)
if err != nil { if err != nil {
return err return nil, err
} }
ni := &noninteractive{ ni := &noninteractive{
@@ -67,7 +72,7 @@ func registerStatic(d *Driver, r *menus.Record) error {
d.hoverables = append(d.hoverables, ni) d.hoverables = append(d.hoverables, ni)
d.paintables = append(d.paintables, ni) d.paintables = append(d.paintables, ni)
return nil return ni, nil
} }
func registerHypertext(d *Driver, r *menus.Record) error { func registerHypertext(d *Driver, r *menus.Record) error {