diff --git a/internal/flow/flow.go b/internal/flow/flow.go index e2ee8f8..74d6f2f 100644 --- a/internal/flow/flow.go +++ b/internal/flow/flow.go @@ -241,9 +241,37 @@ func (f *Flow) playNextScenario(from driverName) func() { } } +func (f *Flow) setActive(driver driverName, id string, value bool) func() { + return func() { + if f.exit != nil { + return + } + + f.exit = maybeErr(driver, f.setActiveNow(driver, id, value)) + } +} + +func (f *Flow) setActiveNow(driver driverName, id string, value bool) error { + return f.drivers[driver].SetActive(locator(driver, id), value) +} + +func (f *Flow) toggleActive(driver driverName, id string) func() { + return func() { + if f.exit != nil { + return + } + + f.exit = maybeErr(driver, f.drivers[driver].ToggleActive(locator(driver, id))) + } +} + func (f *Flow) showDialogue(driver driverName, id string) func() { return func() { - f.drivers[driver].ShowDialogue(locator(driver, id)) + if f.exit != nil { + return + } + + f.exit = maybeErr(driver, f.drivers[driver].ShowDialogue(locator(driver, id))) } } diff --git a/internal/flow/main_game.go b/internal/flow/main_game.go index 3e237ed..2b72581 100644 --- a/internal/flow/main_game.go +++ b/internal/flow/main_game.go @@ -4,13 +4,34 @@ package flow // to duplicate everything for both? func (f *Flow) linkMainGame() { - // 3: Action menu + // 3: Action menu. These are mostly predicated on selected character state + // 3.1: Aimed shot + // 3.2: Shooting + // 3.3: Walk + // 3.4: Run + // 3.5: Crouch/Stand + // 3.6: Hand to hand (commented out) + // 3.7: Retrieve + // 3.8: Door + // 3.9: Switch + // 3.10: Overwatch + // 3.11: Rally/Formation + // 3.12: Board/Disembark + // FIXME: for now, this is "end scenario", for convenience + f.onClick(mainGame, "3.13", func() { // End turn button. + f.scenario = nil + f.returnToLastDriverNow(mainGame) + }) + // 3.14: Special action heal + // 3.15: Special action techmarine + // 3.16: Special action jump pack + // 3.17: Special action spell // 4: Interface options menu f.onClick(mainGame, "4.1", f.setReturningDriver(mainGame, options)) // Options button - // 4.2: Map button + f.onClick(mainGame, "4.2", f.toggleActive(mainGame, "14")) // Map button // 4.3: Mission objectives button - // 4.4: Inventory + f.onClick(mainGame, "4.4", f.showDialogue(mainGame, "12")) // Inventory // 4.5: Next man // 4.6: Next enemy // 4.7: Total enemy text @@ -22,31 +43,31 @@ func (f *Flow) linkMainGame() { // 9: Visible enemy menu // 10: Friendly squad menu // 11: Psyker spell dialogue - - // FIXME: lots and lots and lots of wiring up to do. - // For now, just link all the exit buttons to go back to the bridge - f.onClick(mainGame, "11.6", func() { - f.scenario = nil - f.returnToLastDriverNow(mainGame) - }) - // 12: Inventory dialogue - f.onClick(mainGame, "12.21", func() { - f.scenario = nil - f.returnToLastDriverNow(mainGame) - }) - - // 13: Exchange menu - - f.onClick(mainGame, "13.1", func() { - f.scenario = nil - f.returnToLastDriverNow(mainGame) - }) + f.onClick(mainGame, "12.21", f.hideDialogue(mainGame)) // Exit + // 13: exchange menu // 14: Map + // 14.1: MAP_SPRITE + // 14.2: Multiplier button (2x) + f.onClick(mainGame, "14.3", f.setActive(mainGame, "14", false)) + // 14.4: Area - // 15: Interface wing left - // 16: Interface wing right + // FIXME: the display of left and right interface buttons is hidden by these + // sprites, because we draw in strict numeric order. Just hide them for now. + // + // FIXME: The child element is already set to hidden, while the menu itself + // is set to active, so maybe this is a hint that menus shouldn't be drawn? + // + // FIXME: the approach taken by the original binary in resolutions greater + // than 640x480 is to draw the menu elements *unscaled*. They are centered, + // and the dead space is filled by the "interface wing" sprites in the + // background. Should we replicate this, or keep with the current scaling + // behaviour? Which is better? + // + // FIXME: the menu bar should be at the bottom, not top, of the screen + f.exit = maybeErr(mainGame, f.setActiveNow(mainGame, "15", false)) // Interface wing left + f.exit = maybeErr(mainGame, f.setActiveNow(mainGame, "16", false)) // Interface wing right // 17: Grenade dialogue // 18: Info dialogue // 19: Turn start dialogue diff --git a/internal/ui/buttons.go b/internal/ui/buttons.go index ac6e845..ddbdb72 100644 --- a/internal/ui/buttons.go +++ b/internal/ui/buttons.go @@ -44,6 +44,7 @@ func (d *Driver) buildButton(p *menus.Properties) (*button, *Widget, error) { } widget := &Widget{ + Active: p.Active, ownClickables: []clickable{btn}, ownFreezables: []freezable{btn}, ownHoverables: []hoverable{btn}, @@ -76,6 +77,7 @@ func (d *Driver) buildMainButton(p *menus.Properties) (*mainButton, *Widget, err } widget := &Widget{ + Active: p.Active, ownClickables: []clickable{btn}, ownFreezables: []freezable{btn}, ownHoverables: []hoverable{btn}, @@ -100,6 +102,7 @@ func (d *Driver) buildDoorHotspot(p *menus.Properties) (*button, *Widget, error) } widget := &Widget{ + Active: p.Active, ownClickables: []clickable{btn}, ownFreezables: []freezable{btn}, ownHoverables: []hoverable{btn}, diff --git a/internal/ui/dialogues.go b/internal/ui/dialogues.go index 9089b89..86e7dbe 100644 --- a/internal/ui/dialogues.go +++ b/internal/ui/dialogues.go @@ -19,6 +19,7 @@ func (d *Driver) ShowDialogue(locator string) error { if dialogue.Locator == locator { // FIXME: we should unhover and mouseup the non-dialogue elements + dialogue.Active = true d.activeDialogue = dialogue return nil @@ -28,5 +29,9 @@ func (d *Driver) ShowDialogue(locator string) error { } func (d *Driver) HideDialogue() { + if d.activeDialogue != nil { + d.activeDialogue.Active = false + } + d.activeDialogue = nil } diff --git a/internal/ui/driver.go b/internal/ui/driver.go index ab9481e..63964cb 100644 --- a/internal/ui/driver.go +++ b/internal/ui/driver.go @@ -169,11 +169,11 @@ func (d *Driver) allClickables() []clickable { var out []clickable for _, widget := range d.widgets { - out = append(out, widget.clickables()...) + out = append(out, widget.allClickables()...) } for _, widget := range d.dialogues { - out = append(out, widget.clickables()...) + out = append(out, widget.allClickables()...) } return out @@ -182,11 +182,11 @@ func (d *Driver) allClickables() []clickable { func (d *Driver) allFreezables() []freezable { var out []freezable for _, widget := range d.widgets { - out = append(out, widget.freezables()...) + out = append(out, widget.allFreezables()...) } for _, widget := range d.dialogues { - out = append(out, widget.freezables()...) + out = append(out, widget.allFreezables()...) } return out @@ -196,11 +196,11 @@ func (d *Driver) allValueables() []valueable { var out []valueable for _, widget := range d.widgets { - out = append(out, widget.valueables()...) + out = append(out, widget.allValueables()...) } for _, widget := range d.dialogues { - out = append(out, widget.valueables()...) + out = append(out, widget.allValueables()...) } return out @@ -208,12 +208,12 @@ func (d *Driver) allValueables() []valueable { func (d *Driver) activeClickables() []clickable { if d.activeDialogue != nil { - return d.activeDialogue.clickables() + return d.activeDialogue.activeClickables() } var out []clickable for _, widget := range d.widgets { - out = append(out, widget.clickables()...) + out = append(out, widget.activeClickables()...) } return out @@ -221,12 +221,12 @@ func (d *Driver) activeClickables() []clickable { func (d *Driver) activeHoverables() []hoverable { if d.activeDialogue != nil { - return d.activeDialogue.hoverables() + return d.activeDialogue.activeHoverables() } var out []hoverable for _, widget := range d.widgets { - out = append(out, widget.hoverables()...) + out = append(out, widget.activeHoverables()...) } return out @@ -234,12 +234,12 @@ func (d *Driver) activeHoverables() []hoverable { func (d *Driver) activeMouseables() []mouseable { if d.activeDialogue != nil { - return d.activeDialogue.mouseables() + return d.activeDialogue.activeMouseables() } var out []mouseable for _, widget := range d.widgets { - out = append(out, widget.mouseables()...) + out = append(out, widget.activeMouseables()...) } return out @@ -249,12 +249,23 @@ func (d *Driver) activePaintables() []paintable { var out []paintable for _, widget := range d.widgets { - out = append(out, widget.paintables()...) + out = append(out, widget.activePaintables()...) } if d.activeDialogue != nil { - out = append(out, d.activeDialogue.paintables()...) + out = append(out, d.activeDialogue.activePaintables()...) } return out } + +func (d *Driver) findWidget(locator string) *Widget { + toplevels := append(d.widgets, d.dialogues...) + for _, widget := range toplevels { + if w := widget.findWidget(locator); w != nil { + return w + } + } + + return nil +} diff --git a/internal/ui/group.go b/internal/ui/group.go index 37306bd..96d9591 100644 --- a/internal/ui/group.go +++ b/internal/ui/group.go @@ -28,7 +28,10 @@ func (d *Driver) registerGroup(group *menus.Group) error { return err } } else { - groupWidget = &Widget{Locator: group.Locator} + groupWidget = &Widget{ + Locator: group.Locator, + Active: group.Active, + } } if dialogue { @@ -127,6 +130,7 @@ func (d *Driver) maybeBuildInventorySelect(group *menus.Group, records []*menus. elements := make([]*inventorySelect, len(touched)) widget := &Widget{ Locator: group.Locator, + Active: group.Active, } for i, record := range touched { diff --git a/internal/ui/inventory_select.go b/internal/ui/inventory_select.go index bb2da4d..4d0fe5e 100644 --- a/internal/ui/inventory_select.go +++ b/internal/ui/inventory_select.go @@ -27,6 +27,7 @@ func (d *Driver) buildInventorySelect(p *menus.Properties) (*inventorySelect, *W element := &inventorySelect{checkbox: *c} widget := &Widget{ + Active: p.Active, ownClickables: []clickable{element}, ownFreezables: []freezable{element}, ownHoverables: []hoverable{element}, diff --git a/internal/ui/list_box.go b/internal/ui/list_box.go index 50a996a..42a4ba4 100644 --- a/internal/ui/list_box.go +++ b/internal/ui/list_box.go @@ -77,6 +77,7 @@ func (d *Driver) buildListBox(group *menus.Group, up, down, thumb *menus.Record, // mostly self-registered at the moment. widget := &Widget{ Children: []*Widget{upWidget, downWidget}, + Active: group.Active, // FIXME: children have their own active state ownPaintables: []paintable{element}, ownValueables: []valueable{element}, } diff --git a/internal/ui/noninteractive.go b/internal/ui/noninteractive.go index 9a06acf..0eac12f 100644 --- a/internal/ui/noninteractive.go +++ b/internal/ui/noninteractive.go @@ -83,6 +83,7 @@ func (d *Driver) buildStatic(p *menus.Properties) (*noninteractive, *Widget, err widget := &Widget{ Locator: ni.locator, + Active: p.Active, ownClickables: []clickable{ni}, // FIXME: credits background needs to be clickable ownHoverables: []hoverable{ni}, ownPaintables: []paintable{ni}, @@ -100,6 +101,7 @@ func (d *Driver) buildHypertext(p *menus.Properties) (*noninteractive, *Widget, // FIXME: check if this is still needed on the bridge -> briefing transition widget := &Widget{ Locator: ni.locator, + Active: p.Active, ownClickables: []clickable{ni}, ownHoverables: []hoverable{ni}, } @@ -116,6 +118,7 @@ func (d *Driver) buildOverlay(p *menus.Properties) (*noninteractive, *Widget, er widget := &Widget{ Locator: ni.locator, + Active: p.Active, ownPaintables: []paintable{ni}, } @@ -155,6 +158,7 @@ func (d *Driver) buildAnimationSample(p *menus.Properties) (*noninteractive, *Wi } widget := &Widget{ + Active: p.Active, ownHoverables: []hoverable{ani}, ownPaintables: []paintable{ani}, } @@ -190,6 +194,7 @@ func (d *Driver) buildAnimationHover(p *menus.Properties) (*animationHover, *Wid } widget := &Widget{ + Active: p.Active, ownHoverables: []hoverable{ani}, ownPaintables: []paintable{ani}, } diff --git a/internal/ui/selectors.go b/internal/ui/selectors.go index f80943b..cbbe6eb 100644 --- a/internal/ui/selectors.go +++ b/internal/ui/selectors.go @@ -51,6 +51,8 @@ func (d *Driver) buildCheckbox(p *menus.Properties) (*checkbox, *Widget, error) } widget := &Widget{ + Locator: p.Locator, + Active: p.Active, ownClickables: []clickable{checkbox}, ownFreezables: []freezable{checkbox}, ownHoverables: []hoverable{checkbox}, @@ -76,6 +78,8 @@ func (d *Driver) buildSlider(p *menus.Properties) (*slider, *Widget, error) { } widget := &Widget{ + Locator: p.Locator, + Active: p.Active, ownClickables: []clickable{slider}, ownMouseables: []mouseable{slider}, ownPaintables: []paintable{slider}, diff --git a/internal/ui/value.go b/internal/ui/value.go index 366bc5f..a08ef37 100644 --- a/internal/ui/value.go +++ b/internal/ui/value.go @@ -61,6 +61,26 @@ func (d *Driver) SetFreeze(id string, value bool) error { return fmt.Errorf("Couldn't find clickable widget %v:%v", d.menu.Name, id) } +func (d *Driver) ToggleActive(locator string) error { + if widget := d.findWidget(locator); widget != nil { + widget.Active = !widget.Active + + return nil + } + + return fmt.Errorf("Couldn't find activatable widget %v to toggle", locator) +} + +func (d *Driver) SetActive(locator string, value bool) error { + if widget := d.findWidget(locator); widget != nil { + widget.Active = value + + return nil + } + + return fmt.Errorf("Couldn't find activeatable widget %v to set to %v", locator, value) +} + func (d *Driver) OnClick(id string, f func()) error { for _, clickable := range d.allClickables() { if clickable.id() == d.realId(id) { diff --git a/internal/ui/widget.go b/internal/ui/widget.go index 0dfad0e..4b8822b 100644 --- a/internal/ui/widget.go +++ b/internal/ui/widget.go @@ -3,6 +3,7 @@ package ui type Widget struct { Locator string Children []*Widget + Active bool ownClickables []clickable ownFreezables []freezable @@ -12,62 +13,130 @@ type Widget struct { ownValueables []valueable } -func (w *Widget) clickables() []clickable { +func (w *Widget) allClickables() []clickable { out := w.ownClickables for _, widget := range w.Children { - out = append(out, widget.clickables()...) + out = append(out, widget.allClickables()...) } return out } -func (w *Widget) freezables() []freezable { +func (w *Widget) allFreezables() []freezable { out := w.ownFreezables for _, widget := range w.Children { - out = append(out, widget.freezables()...) + out = append(out, widget.allFreezables()...) } return out } -func (w *Widget) hoverables() []hoverable { - out := w.ownHoverables - - for _, widget := range w.Children { - out = append(out, widget.hoverables()...) - } - - return out -} - -func (w *Widget) mouseables() []mouseable { - out := w.ownMouseables - - for _, widget := range w.Children { - out = append(out, widget.mouseables()...) - } - - return out -} - -func (w *Widget) paintables() []paintable { - out := w.ownPaintables - - for _, widget := range w.Children { - out = append(out, widget.paintables()...) - } - - return out -} - -func (w *Widget) valueables() []valueable { +func (w *Widget) allValueables() []valueable { out := w.ownValueables for _, widget := range w.Children { - out = append(out, widget.valueables()...) + out = append(out, widget.allValueables()...) } return out } + +func (w *Widget) activeClickables() []clickable { + if !w.Active { + return nil + } + + out := w.ownClickables + + for _, widget := range w.Children { + out = append(out, widget.activeClickables()...) + } + + return out +} + +func (w *Widget) activeFreezables() []freezable { + if !w.Active { + return nil + } + + out := w.ownFreezables + + for _, widget := range w.Children { + out = append(out, widget.activeFreezables()...) + } + + return out +} + +func (w *Widget) activeHoverables() []hoverable { + if !w.Active { + return nil + } + + out := w.ownHoverables + + for _, widget := range w.Children { + out = append(out, widget.activeHoverables()...) + } + + return out +} + +func (w *Widget) activeMouseables() []mouseable { + if !w.Active { + return nil + } + + out := w.ownMouseables + + for _, widget := range w.Children { + out = append(out, widget.activeMouseables()...) + } + + return out +} + +func (w *Widget) activePaintables() []paintable { + if !w.Active { + return nil + } + + out := w.ownPaintables + + for _, widget := range w.Children { + out = append(out, widget.activePaintables()...) + } + + return out +} + +func (w *Widget) activeValueables() []valueable { + if !w.Active { + return nil + } + + out := w.ownValueables + + for _, widget := range w.Children { + out = append(out, widget.activeValueables()...) + } + + return out +} + +func (w *Widget) findWidget(locator string) *Widget { + if w.Locator == locator { + return w + } + + for _, child := range w.Children { + if found := child.findWidget(locator); found != nil { + return found + } + } + + return nil +}