From 2ae3611d7fe4b5f7a915b85d1ebb93bcf429f26a Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 31 Mar 2020 23:29:43 +0100 Subject: [PATCH] Allow menu records to be processed hierarchically by the UI driver Nothing is actually processed in this way yet, but there is a new assertion forbidding certain types of records from having children. Because of this new assertion, our menutype tweaks must be moved up a layer into internal/menus. They fit better there anyway. --- internal/menus/menus.go | 23 ++++++++++++++++++++- internal/ui/buttons.go | 17 ++++++--------- internal/ui/driver.go | 39 ++++++++++++++++++++++------------- internal/ui/noninteractive.go | 10 ++++----- internal/ui/selectors.go | 6 +++--- 5 files changed, 61 insertions(+), 34 deletions(-) diff --git a/internal/menus/menus.go b/internal/menus/menus.go index e625e40..67dd138 100644 --- a/internal/menus/menus.go +++ b/internal/menus/menus.go @@ -23,7 +23,7 @@ const ( TypeDoorHotspot MenuType = 30 // Like a button I guess? "FONTTYPE is animation speed" TypeDoorHotspot2 MenuType = 31 // Seems like a duplicate of the above? What's different? TypeLineKbd MenuType = 40 - TypeThumb MenuType = 45 + TypeThumb MenuType = 45 // A "thumb" appears to be a vertical slider TypeLineBriefing MenuType = 41 TypeInvokeButton MenuType = 50 TypeDoorHotspot3 MenuType = 60 // Maybe? Appears in Arrange.mnu @@ -67,6 +67,20 @@ var TextOverrides = map[string]map[string]string{ }, } +// FIXME: The menu is specified as type 2 (button) in these cases, which is +// weird. Make it a menu for now. +var TypeOverrides = map[string]map[string]MenuType{ + "levelply": { + "2": TypeMenu, + }, + "savegame": { + "2": TypeMenu, + }, + "loadgame": { + "2": TypeMenu, + }, +} + type Record struct { Menu *Menu Parent *Record @@ -262,6 +276,13 @@ func setProperty(r *Record, k, v string) { r.Id = vInt case "MENUTYPE", "SUBMENUTYPE": r.Type = MenuType(vInt) + + // FIXME: Type override. Note that MENUID is specified first, so this works + if overrides, ok := TypeOverrides[r.Menu.Name]; ok { + if newType, ok := overrides[r.Path()]; ok { + r.Type = newType + } + } case "ACTIVE": r.Active = (vInt != 0) case "SPRITEID": diff --git a/internal/ui/buttons.go b/internal/ui/buttons.go index cc79da5..83ac746 100644 --- a/internal/ui/buttons.go +++ b/internal/ui/buttons.go @@ -8,12 +8,12 @@ import ( ) func init() { - registerBuilder(menus.TypeSimpleButton, registerSimpleButton) - registerBuilder(menus.TypeInvokeButton, registerInvokeButton) - registerBuilder(menus.TypeMainButton, registerMainButton) - registerBuilder(menus.TypeDoorHotspot, registerDoorHotspot) - registerBuilder(menus.TypeDoorHotspot2, registerDoorHotspot) - registerBuilder(menus.TypeDoorHotspot3, registerDoorHotspot) + registerBuilder(menus.TypeSimpleButton, noChildren(registerSimpleButton)) + registerBuilder(menus.TypeInvokeButton, noChildren(registerInvokeButton)) + registerBuilder(menus.TypeMainButton, noChildren(registerMainButton)) + registerBuilder(menus.TypeDoorHotspot, noChildren(registerDoorHotspot)) + registerBuilder(menus.TypeDoorHotspot2, noChildren(registerDoorHotspot)) + registerBuilder(menus.TypeDoorHotspot3, noChildren(registerDoorHotspot)) } // A button without hover animation @@ -39,11 +39,6 @@ type mainButton struct { } func registerSimpleButton(d *Driver, r *menus.Record) error { - // FIXME: LevelPly.mnu specifies a menu oddly. This tweak gets us past it - if d.Name == "levelply" && r.Id == 2 { - return nil - } - return registerButton(d, r, r.SpriteId[0]) } diff --git a/internal/ui/driver.go b/internal/ui/driver.go index 77f007f..f694c47 100644 --- a/internal/ui/driver.go +++ b/internal/ui/driver.go @@ -56,18 +56,26 @@ var ( ) // Used to add widgets to a driver -type builderFunc func(d *Driver, r *menus.Record) error +type builderFunc func(d *Driver, r *menus.Record) (children []*menus.Record, err error) func registerDebug(reason string, onward builderFunc) builderFunc { - return func(d *Driver, r *menus.Record) error { + return func(d *Driver, r *menus.Record) ([]*menus.Record, error) { log.Printf("%v: %#+v", reason, r) if onward == nil { - return registerStatic(d, r) - } else { - return onward(d, r) + return r.Children, nil } - return nil + return onward(d, r) + } +} + +func noChildren(f func(d *Driver, r *menus.Record) error) builderFunc { + return func(d *Driver, r *menus.Record) ([]*menus.Record, error) { + if len(r.Children) > 0 { + return nil, fmt.Errorf("Children in record %v:%v (%#+v)", r.Menu.Name, r.Path(), r) + } + + return nil, f(d, r) } } @@ -132,7 +140,7 @@ func (d *Driver) Value(id string, into *string) error { } } - return fmt.Errorf("Couldn't find valueable widget %q", id) + return fmt.Errorf("Couldn't find valueable widget %v:%v", d.menu.Name, id) } func (d *Driver) SetValue(id, value string) error { @@ -143,7 +151,7 @@ func (d *Driver) SetValue(id, value string) error { } } - return fmt.Errorf("Couldn't find valueable widget %q", id) + return fmt.Errorf("Couldn't find valueable widget %v:%v", d.menu.Name, id) } func (d *Driver) ValueBool(id string, into *bool) error { @@ -173,7 +181,7 @@ func (d *Driver) SetFreeze(id string, value bool) error { } } - return fmt.Errorf("Couldn't find clickable widget %q", id) + return fmt.Errorf("Couldn't find clickable widget %v:%v", d.menu.Name, id) } func (d *Driver) OnClick(id string, f func()) error { @@ -184,7 +192,7 @@ func (d *Driver) OnClick(id string, f func()) error { } } - return fmt.Errorf("Couldn't find clickable widget %q", id) + return fmt.Errorf("Couldn't find clickable widget %v:%v", d.menu.Name, id) } // FIXME: HURK. Surely I'm missing something? steps is value:offset @@ -197,7 +205,7 @@ func (d *Driver) ConfigureSlider(id string, steps map[int]int) error { } } - return fmt.Errorf("Couldn't find slider %q", id) + return fmt.Errorf("Couldn't find slider %v:%v", d.menu.Name, id) } func (d *Driver) ValueInt(id string, into *int) error { @@ -306,6 +314,7 @@ func (d *Driver) Draw(screen *ebiten.Image) error { func (d *Driver) addRecord(record *menus.Record) error { //log.Printf("Adding record: %#+v", record) + children := record.Children handler, ok := widgetBuilders[record.Type] if !ok { @@ -313,13 +322,15 @@ func (d *Driver) addRecord(record *menus.Record) error { } if handler != nil { - if err := handler(d, record); err != nil { + var err error + children, err = handler(d, record) + if err != nil { return err } } - // Recursively add all children of this record - for _, record := range record.Children { + // Recursively add all remaining children of this record + for _, record := range children { if err := d.addRecord(record); err != nil { return err } diff --git a/internal/ui/noninteractive.go b/internal/ui/noninteractive.go index 2ba66a7..4067e3e 100644 --- a/internal/ui/noninteractive.go +++ b/internal/ui/noninteractive.go @@ -10,11 +10,11 @@ import ( ) func init() { - registerBuilder(menus.TypeStatic, registerStatic) - registerBuilder(menus.TypeHypertext, registerHypertext) - registerBuilder(menus.TypeOverlay, registerOverlay) - registerBuilder(menus.TypeAnimationSample, registerAnimation) - registerBuilder(menus.TypeAnimationHover, registerAnimationHover) + registerBuilder(menus.TypeStatic, noChildren(registerStatic)) + registerBuilder(menus.TypeHypertext, noChildren(registerHypertext)) + registerBuilder(menus.TypeOverlay, noChildren(registerOverlay)) + registerBuilder(menus.TypeAnimationSample, noChildren(registerAnimation)) + registerBuilder(menus.TypeAnimationHover, noChildren(registerAnimationHover)) } // A non-interactive element is not a widget; it merely displays some pixels and diff --git a/internal/ui/selectors.go b/internal/ui/selectors.go index be8d4eb..c6dce25 100644 --- a/internal/ui/selectors.go +++ b/internal/ui/selectors.go @@ -10,9 +10,9 @@ import ( ) func init() { - registerBuilder(menus.TypeCheckbox, registerCheckbox) - registerBuilder(menus.TypeSlider, registerSlider) - registerBuilder(menus.TypeInventorySelect, registerInventorySelect) + registerBuilder(menus.TypeCheckbox, noChildren(registerCheckbox)) + registerBuilder(menus.TypeSlider, noChildren(registerSlider)) + registerBuilder(menus.TypeInventorySelect, noChildren(registerInventorySelect)) } // A checkbox can be a fancy button