diff --git a/README.md b/README.md index 4cdc191..e65cdcc 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,7 @@ type DialogueManager = Manager[Dialogue] type ScriptManager = Manager[Script] type AssetManager = Manager[Asset] type VerbManager = Manager[Verb] -type UIManager = Manager[Widget] +type WidgetManager = Manager[Widget] type ThemeManager = Manager[Theme] ``` @@ -242,7 +242,7 @@ type Game struct { ScriptManager *ScriptManager AssetManager *AssetManager VerbManager *VerbManager - UIManager *UIManager + WidgetManager *WidgetManager ThemeManager *ThemeManager // exits @@ -273,7 +273,7 @@ Initialises every manager (empty), registers the default SCUMM verb set (`look`, `use`, `talk`, `take`), installs all four preset themes, and selects `classic-scumm` as the active theme. Widgets are **not** auto-registered — call `RegisterDefaultUI(g)` (or one of its siblings) -explicitly, or let `Run` install the default set if `UIManager` is empty. +explicitly, or let `Run` install the default set if `WidgetManager` is empty. The manager fields are ordinary `*Manager` values, so a domain that keeps its own registries can **assign them onto the Game** instead of copying every entry @@ -1002,7 +1002,7 @@ could be used by triggers once those land. ## 10. The widget system -The HUD is a tree of `Widget`s, each registered into `g.UIManager`. The +The HUD is a tree of `Widget`s, each registered into `g.WidgetManager`. The library ships built-in widgets for every classic adventure UI piece; domain code can register arbitrary new ones — chat panels, minimaps, hotbars — without touching the library. @@ -1352,7 +1352,7 @@ func (m *Minimap) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { // ... render scene thumbnail, mark NPCs, etc. } -g.UIManager.Register(&Minimap{ +g.WidgetManager.Register(&Minimap{ Name: "minimap", Bounds: inkwell.Rect(220, 4, 96, 56), }) @@ -1554,7 +1554,7 @@ func Run(g *Game) error // toplevel — same as g.Run() 1. `g.Audio.attach(g.AssetManager)` — wire the audio player to whichever asset registry the game is carrying by now. 2. `g.Validate()` — cross-check name references between managers. -3. If `UIManager` is empty, call `RegisterDefaultUI(g)`. +3. If `WidgetManager` is empty, call `RegisterDefaultUI(g)`. 4. Place the start scene directly (no transition), bump `State.NoteVisit`, position registered actors, kick off music. 5. Compose `Seq(scene.OnEnter, OnStart script, OnFinale script)` and queue @@ -1575,7 +1575,7 @@ if scriptRunner != nil: return clear hoverLabel -for w in reversed(UIManager): +for w in reversed(WidgetManager): w.Tick(uictx) // widgets consume input top-down handleSceneInput() // hotspot resolution + right-click reset @@ -1589,7 +1589,7 @@ fill Theme.SceneBackdrop (if any) draw scene background image for c in characters sorted by Y: drawCharacter(c) -for w in UIManager (registration order): +for w in WidgetManager (registration order): w.Draw(screen, uictx) draw transition overlay ``` @@ -1845,7 +1845,7 @@ inkwell/ # module git.teletypegames.org/games/inkwell ├── input.def.go # Input (consume-on-use) │ ├── ui.widget.go # Widget interface, UICtx, Size, Align -├── ui.manager.go # UIManager alias + reversed/ordered iterators +├── ui.manager.go # WidgetManager alias + reversed/ordered iterators ├── ui.theme.go # Theme + ThemeManager ├── ui.theme_presets.go # 4 preset themes ├── ui.defaults.go # RegisterDefaultUI/RadialVerbUI/RichUI diff --git a/core.dsl.go b/core.dsl.go index 691c145..4ad2a8a 100644 --- a/core.dsl.go +++ b/core.dsl.go @@ -16,7 +16,7 @@ func Run(g *Game) error { } // If the domain didn't register any widgets, fall back to the SCUMM // preset so the game is still playable. - if g.UIManager.Len() == 0 { + if g.WidgetManager.Len() == 0 { RegisterDefaultUI(g) } diff --git a/core.engine.go b/core.engine.go index 7e90a96..7a194ec 100644 --- a/core.engine.go +++ b/core.engine.go @@ -61,7 +61,7 @@ func (e *engine) Update() error { // Top-down input: the widget drawn last (= registered last) gets the // click first, then the next-to-last, etc. A widget signals "I took it" // via g.Input.ConsumeLeft / ConsumeRight. - for _, w := range reversedWidgets(g.UIManager) { + for _, w := range reversedWidgets(g.WidgetManager) { w.Tick(uictx) } @@ -192,7 +192,7 @@ func (e *engine) Draw(screen *ebiten.Image) { // widgets in registration order uictx := &UICtx{Game: g, DT: 1.0 / 60.0} - for _, w := range orderedWidgets(g.UIManager) { + for _, w := range orderedWidgets(g.WidgetManager) { w.Draw(screen, uictx) } diff --git a/core.game.go b/core.game.go index f8d0971..33c93dd 100644 --- a/core.game.go +++ b/core.game.go @@ -21,7 +21,7 @@ type Game struct { ScriptManager *ScriptManager AssetManager *AssetManager VerbManager *VerbManager - UIManager *UIManager + WidgetManager *WidgetManager ThemeManager *ThemeManager // SceneRect is the part of the window the picture occupies. Zero means @@ -131,7 +131,7 @@ func NewGame(title string, w, h int) *Game { ScriptManager: NewManager[Script](), AssetManager: NewManager[Asset](), VerbManager: NewManager[Verb](), - UIManager: NewManager[Widget](), + WidgetManager: NewManager[Widget](), ThemeManager: NewManager[Theme](), State: NewState(), Inventory: NewInventory(), diff --git a/ui.defaults.go b/ui.defaults.go index 7d7a9b0..a246041 100644 --- a/ui.defaults.go +++ b/ui.defaults.go @@ -1,20 +1,20 @@ package inkwell -// RegisterDefaultUI installs the SCUMM-style HUD widgets into g.UIManager +// RegisterDefaultUI installs the SCUMM-style HUD widgets into g.WidgetManager // in the conventional Z-order (HotspotDebug at the back, Cursor on top). // Domains that want a different layout call this and then either tweak // the registered widgets in place or replace them entirely. // // Layout assumes the default 320×200 internal resolution. func RegisterDefaultUI(g *Game) { - g.UIManager.Register(&HotspotDebug{Name: "hotspot_debug"}) - g.UIManager.Register(&VerbBar{ + g.WidgetManager.Register(&HotspotDebug{Name: "hotspot_debug"}) + g.WidgetManager.Register(&VerbBar{ Name: "verbs", Origin: Point{X: 4, Y: 152}, Cols: 2, Button: Size{W: 60, H: 14}, }) - g.UIManager.Register(&InventoryBar{ + g.WidgetManager.Register(&InventoryBar{ Name: "inventory", Origin: Point{X: 132, Y: 152}, Slots: 8, @@ -22,11 +22,11 @@ func RegisterDefaultUI(g *Game) { SlotSize: 22, Gap: 2, }) - g.UIManager.Register(&StatusLine{Name: "status", Y: 142, Align: AlignCenter}) - g.UIManager.Register(&SpeechBubble{Name: "speech", MaxWidth: 200, Padding: 3, OffsetY: 4, FallbackY: 14}) - g.UIManager.Register(&DialogBox{Name: "dialog", Bounds: Rect(0, 140, float64(g.Width), 60), LineHeight: 14, Padding: 6}) - g.UIManager.Register(&EndCard{Name: "endcard"}) - g.UIManager.Register(&Cursor{Name: "cursor"}) + g.WidgetManager.Register(&StatusLine{Name: "status", Y: 142, Align: AlignCenter}) + g.WidgetManager.Register(&SpeechBubble{Name: "speech", MaxWidth: 200, Padding: 3, OffsetY: 4, FallbackY: 14}) + g.WidgetManager.Register(&DialogBox{Name: "dialog", Bounds: Rect(0, 140, float64(g.Width), 60), LineHeight: 14, Padding: 6}) + g.WidgetManager.Register(&EndCard{Name: "endcard"}) + g.WidgetManager.Register(&Cursor{Name: "cursor"}) } // RegisterRichUI installs a "story-rich" HUD that matches the layout of @@ -39,15 +39,15 @@ func RegisterDefaultUI(g *Game) { // PlayerName / NPCName correspond to registered characters. Pass "" for // NPCName to skip the NPC panel. func RegisterRichUI(g *Game, playerName, npcName string) { - g.UIManager.Register(&TopBar{ + g.WidgetManager.Register(&TopBar{ Name: "topbar", Height: 12, ScoreVar: "score", ScoreMax: 100, TimeVar: "time", }) - g.UIManager.Register(&HotspotDebug{Name: "hotspot_debug"}) + g.WidgetManager.Register(&HotspotDebug{Name: "hotspot_debug"}) if playerName != "" { - g.UIManager.Register(&CharacterPanel{ + g.WidgetManager.Register(&CharacterPanel{ Name: "panel_player", Bounds: Rect(2, 14, 92, 36), Character: playerName, @@ -59,7 +59,7 @@ func RegisterRichUI(g *Game, playerName, npcName string) { }) } if npcName != "" { - g.UIManager.Register(&CharacterPanel{ + g.WidgetManager.Register(&CharacterPanel{ Name: "panel_npc", Bounds: Rect(180, 14, 92, 36), Character: npcName, @@ -70,7 +70,7 @@ func RegisterRichUI(g *Game, playerName, npcName string) { }, }) } - g.UIManager.Register(&InventoryBar{ + g.WidgetManager.Register(&InventoryBar{ Name: "inventory", Origin: Point{X: 4, Y: 188}, Slots: 8, @@ -78,14 +78,14 @@ func RegisterRichUI(g *Game, playerName, npcName string) { SlotSize: 10, Gap: 1, }) - g.UIManager.Register(&ChatLog{ + g.WidgetManager.Register(&ChatLog{ Name: "chat", Bounds: Rect(2, 148, 280, 38), LineHeight: 9, Padding: 3, ShowBorder: true, }) - g.UIManager.Register(&RadialVerbs{ + g.WidgetManager.Register(&RadialVerbs{ Name: "verbs", AlwaysVisible: true, Center: Point{X: 282, Y: 90}, @@ -97,18 +97,18 @@ func RegisterRichUI(g *Game, playerName, npcName string) { "take": "Vedd", }, }) - g.UIManager.Register(&SpeechBubble{Name: "speech", MaxWidth: 200, Padding: 3, OffsetY: 4, FallbackY: 18}) - g.UIManager.Register(&DialogBox{Name: "dialog", Bounds: Rect(0, 90, float64(g.Width), 56), LineHeight: 12, Padding: 6}) - g.UIManager.Register(&EndCard{Name: "endcard"}) - g.UIManager.Register(&Cursor{Name: "cursor"}) + g.WidgetManager.Register(&SpeechBubble{Name: "speech", MaxWidth: 200, Padding: 3, OffsetY: 4, FallbackY: 18}) + g.WidgetManager.Register(&DialogBox{Name: "dialog", Bounds: Rect(0, 90, float64(g.Width), 56), LineHeight: 12, Padding: 6}) + g.WidgetManager.Register(&EndCard{Name: "endcard"}) + g.WidgetManager.Register(&Cursor{Name: "cursor"}) } // RegisterRadialVerbUI installs an alternative HUD that swaps the // permanent verb-bar for a verb-coin (right-click radial menu). Inventory, // dialog, speech and cursor stay the same. func RegisterRadialVerbUI(g *Game) { - g.UIManager.Register(&HotspotDebug{Name: "hotspot_debug"}) - g.UIManager.Register(&InventoryBar{ + g.WidgetManager.Register(&HotspotDebug{Name: "hotspot_debug"}) + g.WidgetManager.Register(&InventoryBar{ Name: "inventory", Origin: Point{X: 4, Y: 178}, Slots: 14, @@ -116,10 +116,10 @@ func RegisterRadialVerbUI(g *Game) { SlotSize: 22, Gap: 0, }) - g.UIManager.Register(&StatusLine{Name: "status", Y: 168, Align: AlignCenter}) - g.UIManager.Register(&SpeechBubble{Name: "speech", MaxWidth: 200, Padding: 3, OffsetY: 4, FallbackY: 14}) - g.UIManager.Register(&DialogBox{Name: "dialog", Bounds: Rect(0, 140, float64(g.Width), 60), LineHeight: 14, Padding: 6}) - g.UIManager.Register(&RadialVerbs{Name: "verbs", Trigger: MouseButtonRight, Radius: 40}) - g.UIManager.Register(&EndCard{Name: "endcard"}) - g.UIManager.Register(&Cursor{Name: "cursor"}) + g.WidgetManager.Register(&StatusLine{Name: "status", Y: 168, Align: AlignCenter}) + g.WidgetManager.Register(&SpeechBubble{Name: "speech", MaxWidth: 200, Padding: 3, OffsetY: 4, FallbackY: 14}) + g.WidgetManager.Register(&DialogBox{Name: "dialog", Bounds: Rect(0, 140, float64(g.Width), 60), LineHeight: 14, Padding: 6}) + g.WidgetManager.Register(&RadialVerbs{Name: "verbs", Trigger: MouseButtonRight, Radius: 40}) + g.WidgetManager.Register(&EndCard{Name: "endcard"}) + g.WidgetManager.Register(&Cursor{Name: "cursor"}) } diff --git a/ui.manager.go b/ui.manager.go index 65f9889..4c538d5 100644 --- a/ui.manager.go +++ b/ui.manager.go @@ -1,12 +1,12 @@ package inkwell -// UIManager registers Widget instances. Same shape as every other manager. -type UIManager = Manager[Widget] +// WidgetManager registers Widget instances. Same shape as every other manager. +type WidgetManager = Manager[Widget] // reversedWidgets iterates a manager's contents in reverse registration // order — used by the engine for top-down input dispatch (the widget // drawn on top gets the click first). -func reversedWidgets(m *UIManager) []Widget { +func reversedWidgets(m *WidgetManager) []Widget { all := m.All() out := make([]Widget, len(all)) for i, w := range all { @@ -16,4 +16,4 @@ func reversedWidgets(m *UIManager) []Widget { } // orderedWidgets iterates in registration order — bottom-up draw. -func orderedWidgets(m *UIManager) []Widget { return m.All() } +func orderedWidgets(m *WidgetManager) []Widget { return m.All() } diff --git a/ui.verb_radial.go b/ui.verb_radial.go index b41e5a1..dfb183a 100644 --- a/ui.verb_radial.go +++ b/ui.verb_radial.go @@ -140,11 +140,11 @@ func (r *RadialVerbs) consumeTrigger(g *Game) { // whether its area covers the cursor — so the radial menu doesn't open // over an InventoryBar that would also want this click. func (r *RadialVerbs) blockedAt(ctx *UICtx, p Point) bool { - for _, name := range ctx.Game.UIManager.Names() { + for _, name := range ctx.Game.WidgetManager.Names() { if name == r.Name { continue } - w := ctx.Game.UIManager.MustGet(name) + w := ctx.Game.WidgetManager.MustGet(name) // DialogBox advertises its bounds via clickBlocker even when no // dialog is open, so the verb-coin would refuse to pop up over // scenery that happens to sit under the dialog rect. Skip the