widget manager refact

This commit is contained in:
2026-08-30 22:27:51 +02:00
parent 258cbf85c0
commit 3890edc946
26 changed files with 413 additions and 242 deletions
+46 -6
View File
@@ -49,7 +49,7 @@ All game code lives in one flat package, `inc`. There are no subdirectories: a
file's name carries the structure, in the form `[category].[name].go`.
- The category is always **singular**: `item`, `scene`, `background`,
`character`, `dialog`, `script`, `world`, `ui`, `theme`, `names`.
`character`, `dialog`, `script`, `world`, `widget`, `ui`, `theme`, `names`.
- `[category].manager.go` is the file that ties a category together — its
entity type and whatever else the category shares.
- Every other file in a category holds exactly one entity: one scene, one
@@ -65,9 +65,10 @@ inc/theme.manager.go the Theme alias and the colour helpers
inc/theme.realworld.go realworld-93
inc/theme.nokia_punk.go nokia-punk
inc/world.manager.go unsaved runtime state
inc/world.action.go custom actions and the action pump
inc/ui.manager.go HUD layout and widget registration
inc/ui.*.go custom widgets, coloured text
inc/world.action.go custom actions
inc/widget.manager.go the Widget alias, HUD layout, the visibility gate
inc/widget.*.go one widget per file, registering itself
inc/ui.text.go coloured text on a scratch image
inc/background.*.go one image asset per scene
inc/character.*.go the cast, tapes included
inc/item.*.go inventory
@@ -84,7 +85,7 @@ Every category that owns a collection of entities has a manager, and they are
all the engine's own `inkwell.Manager[T]` — the same registry type the `*Game`
hangs its content off. The game defines no registry of its own.
All seven are declared together, in `boot.go`, so the list of what the game
All eight are declared together, in `boot.go`, so the list of what the game
holds is one block rather than a line hidden in each category file:
```go
@@ -96,6 +97,7 @@ var (
SceneManager = inkwell.NewManager[Scene]()
ScriptManager = inkwell.NewManager[Script]()
ThemeManager = inkwell.NewManager[Theme]()
WidgetManager = inkwell.NewManager[Widget]()
)
```
@@ -146,6 +148,43 @@ into one.
Nothing may depend on it — including the order the arrow keys walk the scenes,
which is simply the order the files sit in.
### Widgets name their layer instead of their order
A widget is a file like any other entity — `widget.<name>.go`, one widget,
registering itself in an `init()` — and that includes the engine's own widgets,
which the game registers as literals the same way it registers a background:
```go
func init() {
WidgetManager.Register(&inkwell.Cursor{
Name: "cursor",
})
}
```
What a widget cannot take from alphabetical registration is its place in the
stack: the frame has to be drawn under the slots that sit on it, the cursor over
everything, and the use-with guard has to see a click after the HUD has had it.
So a widget declares a layer rather than inheriting one from the order it was
registered in — `inkwell.LayerScene`, `LayerPanel`, `LayerHUD`, `LayerSpeech`,
`LayerDialog`, `LayerMenu`, `LayerCurtain`, `LayerCursor`. The engine draws from
the bottom layer up and ticks from the top layer down, and registration order
only decides ties inside one layer:
```go
func (h *hudFrame) Layer() inkwell.Layer { return inkwell.LayerPanel }
```
A widget that says nothing sits on `LayerHUD`; ours all say it, because the
layer is the one thing about a widget the file cannot show. `gate`, the wrapper
that hides a widget outside `ModePlay`, returns `inkwell.LayerOf` of the widget
it wraps, so wrapping never moves anything.
Two widgets are worth knowing about because they are not decoration: `pump`
drives `World.PumpTick`, which is what runs every queued action, and
`usewith_guard` sits on `LayerScene` so that it ticks last and can answer a
use-with pair nobody authored.
### Handing a category to the engine
Nothing is copied. `inkwell.NewGame` builds its own empty managers, and `New`
@@ -159,6 +198,7 @@ g.DialogueManager = DialogManager
g.ItemManager = ItemManager
g.SceneManager = SceneManager
g.ScriptManager = ScriptManager
g.WidgetManager = WidgetManager
ThemeManager.Each(g.ThemeManager.Register)
```
@@ -221,7 +261,7 @@ constants in `names.manager.go`, the colour tokens, and the action constructors
(`TapeSay`, `Paused`, `SetMode`, `EnterScene`, `Back`, `Fn`).
Everything else is machinery and stays unexported: the HUD widgets
(`tapeSlots`, `letterbox`, `hudFrame`, …), `registerUI`, the runners,
(`tapeSlots`, `letterbox`, `hudFrame`, …), `gated`, the runners,
`prepareScene`, `sceneDefaults`, `fillSelectorPins`.
The word is **scene**, the engine's own. The wiki and the concept-art deck count
+22 -9
View File
@@ -109,9 +109,9 @@ Every pixel it gives back is a pixel of art.
InventoryBar TapeSlots TapeChannel
```
Every vertical measurement derives from `ui.LineH` (glyph cell + leading) and
from `ui.ScreenH`, not from the wireframe deck's ratios, so the layout cannot
drift out of step with the font — or with the art — again;
Every vertical measurement derives from `LineH` (glyph cell + leading) and from
`ScreenH`, both in `widget.manager.go`, not from the wireframe deck's ratios, so
the layout cannot drift out of step with the font — or with the art — again;
`TestLayoutFitsTheFont` guards it. The tape channel comes out at 4 rows of 38
columns, which is about one and a half of Dex's remarks on screen at once.
@@ -161,8 +161,10 @@ main.go flags + inkwell.Run
inc/boot.go the managers, and New(Opts)
inc/names.manager.go entity names and world-state keys
inc/theme.*.go realworld-93 + nokia-punk
inc/world.*.go unsaved runtime state, custom actions, action pump
inc/ui.*.go HUD: layout, custom widgets, coloured text
inc/world.*.go unsaved runtime state and custom actions
inc/widget.manager.go HUD layout and the widget registration list
inc/widget.*.go one widget per file
inc/ui.text.go coloured text
inc/<kind>.<name>.go one file per registered entity, by kind
```
@@ -195,7 +197,7 @@ Every category owns a manager, and they are all the engine's own
`inkwell.Manager[T]` — the same registry the `*Game` hangs its content off, kept
in registration order and addressed by name. There is no second registry type
here: the entity types are aliases of engine structs, so they already satisfy
`inkwell.Named` and need no help reading their own name. The seven of them are
`inkwell.Named` and need no help reading their own name. The eight of them are
declared as one block in `boot.go`, and a category's manager file is left
holding the alias:
@@ -215,6 +217,17 @@ func init() {
}
```
Widgets go the same way, the engine's own included: `widget.cursor.go` registers
an `inkwell.Cursor` exactly as `background.street.go` registers an image. What
they cannot take from alphabetical order is their place in the stack, so each
widget names a **layer**`LayerScene`, `LayerPanel`, `LayerHUD`, `LayerSpeech`,
`LayerDialog`, `LayerMenu`, `LayerCurtain`, `LayerCursor` — and inkwell draws
from the bottom layer up, ticks from the top layer down, and lets registration
order decide only ties inside one layer. That is why the HUD frame comes out
under the tape slots and the cursor over everything, whatever the file names
happen to be. The layer was added to inkwell for this; the alternative was a
registration list, which is the thing this package spent its life deleting.
So adding an entity is adding a file, and there is no second list to keep in
step. The price is that registration order is file-name order: the arrow keys
walk the scenes alphabetically rather than in the concept-art deck's order.
@@ -307,8 +320,8 @@ has to shadow where the player is.
*Fix:* move `drawText` to `text/v2` — no call site would change.
2. **`queueAction` is unexported**, so a domain widget cannot start an action.
*Workaround:* the pump in `world.action.go` drives its own `Runner` through
the exported `inkwell.Ctx`. *Caveat:* it runs alongside the engine's script
*Workaround:* the pump `widget.action_pump.go` calling `World.PumpTick`
drives its own `Runner` through the exported `inkwell.Ctx`. *Caveat:* it runs alongside the engine's script
runner, not instead of it.
3. **The `"Nem ehhez."` flash is hardcoded** in `core.engine.go` for an
@@ -319,7 +332,7 @@ has to shadow where the player is.
4. **Widgets have no `Visible` field and the `Manager` cannot unregister**, so
the built-in HUD cannot be hidden during a cutscene. *Workaround:* the
`gate` wrapper in `ui/ui.go` forwards `Tick`/`Draw`/`BlocksClickAt` only
`gate` wrapper in `widget.manager.go` forwards `Tick`/`Draw`/`BlocksClickAt` only
while the HUD is visible.
5. **`Run` hardcodes a 4× window** (`core.dsl.go`), which at 640×400 would be
+1 -1
View File
@@ -3,7 +3,7 @@ module git.teletypegames.org/games/realworld
go 1.26.3
require (
git.teletypegames.org/engines/inkwell v0.1.1-0.20260830092549-e26f7345c14d
git.teletypegames.org/engines/inkwell v0.1.1-0.20260830201917-2429ada0900f
github.com/hajimehoshi/ebiten/v2 v2.9.9
)
+4
View File
@@ -1,5 +1,9 @@
git.teletypegames.org/engines/inkwell v0.1.1-0.20260830092549-e26f7345c14d h1:4AGtmrWwfnHkJvZ90on1+9bUmZOGSrcIG4KaJFiy+xo=
git.teletypegames.org/engines/inkwell v0.1.1-0.20260830092549-e26f7345c14d/go.mod h1:/v6QtismTE8+e7kobbVR5B/CpTSrd4j2DYwJ8DvINhs=
git.teletypegames.org/engines/inkwell v0.1.1-0.20260830200555-53f4df04669c h1:GOKa6IbkfJXjB5LREr3eUw6YeeOeDICryfa0eKERWo0=
git.teletypegames.org/engines/inkwell v0.1.1-0.20260830200555-53f4df04669c/go.mod h1:/v6QtismTE8+e7kobbVR5B/CpTSrd4j2DYwJ8DvINhs=
git.teletypegames.org/engines/inkwell v0.1.1-0.20260830201917-2429ada0900f h1:WBIh7svmA2R7tbi1Xrafn5//yl8vnK2qbpBXDbbz6Aw=
git.teletypegames.org/engines/inkwell v0.1.1-0.20260830201917-2429ada0900f/go.mod h1:/v6QtismTE8+e7kobbVR5B/CpTSrd4j2DYwJ8DvINhs=
github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1 h1:+kz5iTT3L7uU+VhlMfTb8hHcxLO3TlaELlX8wa4XjA0=
github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1/go.mod h1:lKJoeixeJwnFmYsBny4vvCJGVFc3aYDalhuDsfZzWHI=
github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
+2 -1
View File
@@ -12,6 +12,7 @@ var (
SceneManager = inkwell.NewManager[Scene]()
ScriptManager = inkwell.NewManager[Script]()
ThemeManager = inkwell.NewManager[Theme]()
WidgetManager = inkwell.NewManager[Widget]()
)
type Opts struct {
@@ -43,11 +44,11 @@ func New(o Opts) *inkwell.Game {
g.ItemManager = ItemManager
g.SceneManager = SceneManager
g.ScriptManager = ScriptManager
g.WidgetManager = WidgetManager
ThemeManager.Each(g.ThemeManager.Register)
World.attach(g)
g.UseTheme(RealWorld)
registerUI()
g.StartAt(start)
g.OnStart(ScriptOpening)
-199
View File
@@ -1,199 +0,0 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
)
const (
ScreenW = 640
ScreenH = 380
LineH = glyphH + 2
Pad = 6
TopBarH = LineH + 2
DividerX = 394
HUDH = Pad + LineH + 4 + invSlot*2 + invGap + Pad
HUDTop = ScreenH - HUDH
statusY = HUDTop + Pad
slotsY = HUDTop + Pad + LineH
slotsH = LineH*2 + Pad*2
invY = slotsY + 4
invSlot = 40
invGap = 4
)
func registerUI() {
g := World.G
g.UIManager.Register(&useWithGuard{
Name: "usewith_guard",
})
g.UIManager.Register(&actionPump{
Name: "pump",
})
g.UIManager.Register(&sceneNav{
Name: "scene_nav",
})
g.UIManager.Register(&windowSizer{
Name: "window",
Scale: 2,
})
g.UIManager.Register(&inkwell.HotspotDebug{
Name: "hotspot_debug",
})
top := &inkwell.TopBar{
Name: "topbar",
Height: TopBarH,
TimeVar: VarArmillaStrip,
}
World.SetTopBar(top)
g.UIManager.Register(gated("topbar_gate", top))
g.UIManager.Register(&letterbox{
Name: "letterbox",
Bar: 36,
})
g.UIManager.Register(&hudFrame{
Name: "hudframe",
})
g.UIManager.Register(gated("status_gate", &inkwell.StatusLine{
Name: "status",
Y: statusY,
Align: inkwell.AlignLeft,
ScreenWidth: DividerX,
}))
g.UIManager.Register(gated("inventory_gate", &inkwell.InventoryBar{
Name: "inventory",
Origin: inkwell.Point{
X: Pad + 2,
Y: invY,
},
Slots: 8,
Cols: 4,
SlotSize: invSlot,
Gap: invGap,
}))
g.UIManager.Register(&tapeSlots{
Name: "tapes",
Bounds: inkwell.Rect(192, slotsY, 194, slotsH),
})
g.UIManager.Register(&tapeChannel{
Name: "channel",
Bounds: inkwell.Rect(DividerX+2, HUDTop+2, ScreenW-DividerX-4, ScreenH-HUDTop-4),
})
g.UIManager.Register(&inkwell.SpeechBubble{
Name: "speech",
MaxWidth: 360,
Padding: Pad,
OffsetY: 8,
FallbackY: TopBarH + Pad,
})
g.UIManager.Register(&inkwell.DialogBox{
Name: "dialog",
Bounds: inkwell.Rect(0, ScreenH-LineH*9, DividerX, LineH*9),
LineHeight: LineH,
Padding: Pad,
})
g.UIManager.Register(&inkwell.RadialVerbs{
Name: "verbs",
Trigger: inkwell.MouseButtonRight,
Radius: 58,
Labels: map[string]string{
"look": "Look", "use": "Use", "talk": "Talk", "take": "Take",
},
})
g.UIManager.Register(&inkwell.EndCard{
Name: "endcard",
})
g.UIManager.Register(&inkwell.Cursor{
Name: "cursor",
})
}
type windowSizer struct {
Name string
Scale int
done bool
}
func (s *windowSizer) GetName() string { return s.Name }
func (s *windowSizer) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {}
func (s *windowSizer) Tick(ctx *inkwell.UICtx) {
if s.done {
return
}
s.done = true
scale := s.Scale
if scale <= 0 {
scale = 2
}
ebiten.SetWindowSize(ctx.Game.Width*scale, ctx.Game.Height*scale)
}
type gate struct {
Name string
Inner inkwell.Widget
}
func gated(name string, inner inkwell.Widget) inkwell.Widget {
return &gate{
Name: name,
Inner: inner,
}
}
func (n *gate) GetName() string { return n.Name }
func (n *gate) Tick(ctx *inkwell.UICtx) {
if World.HUDVisible() {
n.Inner.Tick(ctx)
}
}
func (n *gate) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
if World.HUDVisible() {
n.Inner.Draw(dst, ctx)
}
}
func (n *gate) BlocksClickAt(p inkwell.Point) bool {
if !World.HUDVisible() {
return false
}
b, ok := n.Inner.(interface{ BlocksClickAt(inkwell.Point) bool })
return ok && b.BlocksClickAt(p)
}
type hudFrame struct {
Name string
}
func (h *hudFrame) GetName() string { return h.Name }
func (h *hudFrame) Tick(ctx *inkwell.UICtx) {}
func (h *hudFrame) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
if !World.HUDVisible() {
return
}
th := ctx.Game.Theme()
vector.DrawFilledRect(dst, 0, HUDTop+1, ScreenW, ScreenH-HUDTop-1, th.PanelBG, false)
vector.StrokeLine(dst, 0, HUDTop, ScreenW, HUDTop, 1, th.CharacterPanelBorder, false)
vector.StrokeLine(dst, DividerX, HUDTop+1, DividerX, ScreenH, 1, th.CharacterPanelBorder, false)
}
func (h *hudFrame) BlocksClickAt(p inkwell.Point) bool {
return World.HUDVisible() && p.Y >= HUDTop
}
+21
View File
@@ -0,0 +1,21 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
"github.com/hajimehoshi/ebiten/v2"
)
func init() {
WidgetManager.Register(&actionPump{
Name: "pump",
})
}
type actionPump struct {
Name string
}
func (p *actionPump) GetName() string { return p.Name }
func (p *actionPump) Layer() inkwell.Layer { return inkwell.LayerScene }
func (p *actionPump) Tick(ctx *inkwell.UICtx) { World.PumpTick(ctx.DT) }
func (p *actionPump) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {}
+11
View File
@@ -0,0 +1,11 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
)
func init() {
WidgetManager.Register(&inkwell.Cursor{
Name: "cursor",
})
}
+14
View File
@@ -0,0 +1,14 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
)
func init() {
WidgetManager.Register(&inkwell.DialogBox{
Name: "dialog",
Bounds: inkwell.Rect(0, ScreenH-LineH*9, DividerX, LineH*9),
LineHeight: LineH,
Padding: Pad,
})
}
+11
View File
@@ -0,0 +1,11 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
)
func init() {
WidgetManager.Register(&inkwell.EndCard{
Name: "endcard",
})
}
+11
View File
@@ -0,0 +1,11 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
)
func init() {
WidgetManager.Register(&inkwell.HotspotDebug{
Name: "hotspot_debug",
})
}
+37
View File
@@ -0,0 +1,37 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
)
func init() {
WidgetManager.Register(&hudFrame{
Name: "hudframe",
})
}
type hudFrame struct {
Name string
}
func (h *hudFrame) GetName() string { return h.Name }
func (h *hudFrame) Layer() inkwell.Layer { return inkwell.LayerPanel }
func (h *hudFrame) Tick(ctx *inkwell.UICtx) {}
func (h *hudFrame) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
if !World.HUDVisible() {
return
}
th := ctx.Game.Theme()
vector.DrawFilledRect(dst, 0, HUDTop+1, ScreenW, ScreenH-HUDTop-1, th.PanelBG, false)
vector.StrokeLine(dst, 0, HUDTop, ScreenW, HUDTop, 1, th.CharacterPanelBorder, false)
vector.StrokeLine(dst, DividerX, HUDTop+1, DividerX, ScreenH, 1, th.CharacterPanelBorder, false)
}
func (h *hudFrame) BlocksClickAt(p inkwell.Point) bool {
return World.HUDVisible() && p.Y >= HUDTop
}
+19
View File
@@ -0,0 +1,19 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
)
func init() {
WidgetManager.Register(gated("inventory", &inkwell.InventoryBar{
Name: "inventory",
Origin: inkwell.Point{
X: Pad + 2,
Y: invY,
},
Slots: 8,
Cols: 4,
SlotSize: invSlot,
Gap: invGap,
}))
}
@@ -7,12 +7,20 @@ import (
"github.com/hajimehoshi/ebiten/v2/vector"
)
func init() {
WidgetManager.Register(&letterbox{
Name: "letterbox",
Bar: 36,
})
}
type letterbox struct {
Name string
Bar float64
}
func (l *letterbox) GetName() string { return l.Name }
func (l *letterbox) GetName() string { return l.Name }
func (l *letterbox) Layer() inkwell.Layer { return inkwell.LayerScene }
func (l *letterbox) Tick(ctx *inkwell.UICtx) {
if World.Mode() != ModeCutscene || !World.TapeWaiting() {
+65
View File
@@ -0,0 +1,65 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
"github.com/hajimehoshi/ebiten/v2"
)
type Widget = inkwell.Widget
const (
ScreenW = 640
ScreenH = 380
LineH = glyphH + 2
Pad = 6
TopBarH = LineH + 2
DividerX = 394
HUDH = Pad + LineH + 4 + invSlot*2 + invGap + Pad
HUDTop = ScreenH - HUDH
statusY = HUDTop + Pad
slotsY = HUDTop + Pad + LineH
slotsH = LineH*2 + Pad*2
invY = slotsY + 4
invSlot = 40
invGap = 4
)
type gate struct {
Name string
Inner Widget
}
func gated(name string, inner Widget) Widget {
return &gate{
Name: name,
Inner: inner,
}
}
func (n *gate) GetName() string { return n.Name }
func (n *gate) Layer() inkwell.Layer { return inkwell.LayerOf(n.Inner) }
func (n *gate) Tick(ctx *inkwell.UICtx) {
if World.HUDVisible() {
n.Inner.Tick(ctx)
}
}
func (n *gate) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
if World.HUDVisible() {
n.Inner.Draw(dst, ctx)
}
}
func (n *gate) BlocksClickAt(p inkwell.Point) bool {
if !World.HUDVisible() {
return false
}
b, ok := n.Inner.(interface{ BlocksClickAt(inkwell.Point) bool })
return ok && b.BlocksClickAt(p)
}
+16
View File
@@ -0,0 +1,16 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
)
func init() {
WidgetManager.Register(&inkwell.RadialVerbs{
Name: "verbs",
Trigger: inkwell.MouseButtonRight,
Radius: 58,
Labels: map[string]string{
"look": "Look", "use": "Use", "talk": "Talk", "take": "Take",
},
})
}
@@ -6,11 +6,18 @@ import (
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
func init() {
WidgetManager.Register(&sceneNav{
Name: "scene_nav",
})
}
type sceneNav struct {
Name string
}
func (n *sceneNav) GetName() string { return n.Name }
func (n *sceneNav) Layer() inkwell.Layer { return inkwell.LayerScene }
func (n *sceneNav) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {}
func (n *sceneNav) Tick(ctx *inkwell.UICtx) {
+15
View File
@@ -0,0 +1,15 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
)
func init() {
WidgetManager.Register(&inkwell.SpeechBubble{
Name: "speech",
MaxWidth: 360,
Padding: Pad,
OffsetY: 8,
FallbackY: TopBarH + Pad,
})
}
+14
View File
@@ -0,0 +1,14 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
)
func init() {
WidgetManager.Register(gated("status", &inkwell.StatusLine{
Name: "status",
Y: statusY,
Align: inkwell.AlignLeft,
ScreenWidth: DividerX,
}))
}
@@ -8,12 +8,20 @@ import (
"github.com/hajimehoshi/ebiten/v2/vector"
)
func init() {
WidgetManager.Register(&tapeChannel{
Name: "channel",
Bounds: inkwell.Rect(DividerX+2, HUDTop+2, ScreenW-DividerX-4, ScreenH-HUDTop-4),
})
}
type tapeChannel struct {
Name string
Bounds inkwell.Rectangle
}
func (t *tapeChannel) GetName() string { return t.Name }
func (t *tapeChannel) GetName() string { return t.Name }
func (t *tapeChannel) Layer() inkwell.Layer { return inkwell.LayerHUD }
func (t *tapeChannel) Tick(ctx *inkwell.UICtx) {}
@@ -6,12 +6,20 @@ import (
"github.com/hajimehoshi/ebiten/v2/vector"
)
func init() {
WidgetManager.Register(&tapeSlots{
Name: "tapes",
Bounds: inkwell.Rect(192, slotsY, 194, slotsH),
})
}
type tapeSlots struct {
Name string
Bounds inkwell.Rectangle
}
func (t *tapeSlots) GetName() string { return t.Name }
func (t *tapeSlots) GetName() string { return t.Name }
func (t *tapeSlots) Layer() inkwell.Layer { return inkwell.LayerHUD }
func (t *tapeSlots) BlocksClickAt(p inkwell.Point) bool {
return World.HUDVisible() && t.Bounds.Contains(p)
+27
View File
@@ -0,0 +1,27 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
)
func init() {
WidgetManager.Register(gated("topbar", &titleBar{
TopBar: &inkwell.TopBar{
Name: "topbar",
Height: TopBarH,
TimeVar: VarArmillaStrip,
},
}))
}
type titleBar struct {
*inkwell.TopBar
}
func (t *titleBar) Tick(ctx *inkwell.UICtx) {
t.LeftText = World.SceneTitle()
if World.Paused() {
t.LeftText += " — paused"
}
t.TopBar.Tick(ctx)
}
@@ -7,11 +7,18 @@ import (
"github.com/hajimehoshi/ebiten/v2"
)
func init() {
WidgetManager.Register(&useWithGuard{
Name: "usewith_guard",
})
}
type useWithGuard struct {
Name string
}
func (u *useWithGuard) GetName() string { return u.Name }
func (u *useWithGuard) Layer() inkwell.Layer { return inkwell.LayerScene }
func (u *useWithGuard) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {}
func (u *useWithGuard) Tick(ctx *inkwell.UICtx) {
+35
View File
@@ -0,0 +1,35 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
"github.com/hajimehoshi/ebiten/v2"
)
func init() {
WidgetManager.Register(&windowSizer{
Name: "window",
Scale: 2,
})
}
type windowSizer struct {
Name string
Scale int
done bool
}
func (s *windowSizer) GetName() string { return s.Name }
func (s *windowSizer) Layer() inkwell.Layer { return inkwell.LayerScene }
func (s *windowSizer) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {}
func (s *windowSizer) Tick(ctx *inkwell.UICtx) {
if s.done {
return
}
s.done = true
scale := s.Scale
if scale <= 0 {
scale = 2
}
ebiten.SetWindowSize(ctx.Game.Width*scale, ctx.Game.Height*scale)
}
+1 -19
View File
@@ -2,7 +2,6 @@ package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
"github.com/hajimehoshi/ebiten/v2"
)
func (w *world) Do(a inkwell.Action) {
@@ -12,15 +11,6 @@ func (w *world) Do(a inkwell.Action) {
}
func (w *world) PumpTick(dt float64) {
if w.top != nil {
title := w.sceneTitle()
if w.paused {
title += " — paused"
}
w.top.LeftText = title
}
if w.running == nil {
if len(w.queue) == 0 {
return
@@ -40,7 +30,7 @@ func (w *world) PumpTick(dt float64) {
}
}
func (w *world) sceneTitle() string {
func (w *world) SceneTitle() string {
s, ok := w.G.SceneManager.Get(w.G.CurrentScene())
if !ok {
return ""
@@ -51,14 +41,6 @@ func (w *world) sceneTitle() string {
return s.Name
}
type actionPump struct {
Name string
}
func (p *actionPump) GetName() string { return p.Name }
func (p *actionPump) Tick(ctx *inkwell.UICtx) { World.PumpTick(ctx.DT) }
func (p *actionPump) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {}
type fnAction struct{ fn func(*inkwell.Ctx) }
func (a *fnAction) Start() inkwell.Runner {
-4
View File
@@ -21,7 +21,6 @@ type world struct {
pending string
queue []inkwell.Action
running inkwell.Runner
top *inkwell.TopBar
tapeWaiting bool
tapeLine inkwell.Action
@@ -36,7 +35,6 @@ func (w *world) attach(g *inkwell.Game) {
w.pending = ""
w.queue = nil
w.running = nil
w.top = nil
w.tapeWaiting = false
w.tapeLine = nil
}
@@ -49,8 +47,6 @@ func (w *world) HUDVisible() bool { return w.mode == ModePlay }
func (w *world) Paused() bool { return w.paused }
func (w *world) SetTopBar(t *inkwell.TopBar) { w.top = t }
func (w *world) Slot2() string {
if v, ok := w.G.State.Var(VarSlot2).(string); ok {
return v