widgets
This commit is contained in:
504
UIPLAN.md
Normal file
504
UIPLAN.md
Normal file
@@ -0,0 +1,504 @@
|
||||
# UIPLAN.md — dinamikus widget-alapú UI réteg
|
||||
|
||||
## Cél
|
||||
|
||||
A `pncdsl` jelenlegi kezelőfelülete hard-kódolt komponensek halmaza: van egy
|
||||
fix verb-bar, fix inventory-bar, fix dialog-box, fix speech-bubble. Cserélni,
|
||||
átalakítani őket csak az `ui.*.go` fájlok átírásával lehet.
|
||||
|
||||
Át kell térni egy **dinamikus widget-rendszerre**, ami:
|
||||
|
||||
- minden HUD-elemet egységesen, **`Widget` interfészként** kezel,
|
||||
- engedi a `domain`-nak hogy **saját widget-eket adjon hozzá** (pl. egy chat
|
||||
panel) anélkül, hogy a library kódjához nyúlna,
|
||||
- támogatja az alternatív input-módokat — pl. **verb-coin** (radiális menü
|
||||
másodlagos kattintásra) a SCUMM-szerű verb-bar helyett,
|
||||
- a jelenlegi „look and feel"-t **preset Theme-ekként** csomagolja, amik
|
||||
egyetlen sorral aktiválhatók,
|
||||
- mindezt a meglévő **`Manager.Register(...)` mintával** integrálja, hogy ne
|
||||
legyen idegen test a libraryban.
|
||||
|
||||
## Az alapfogalom: `Widget`
|
||||
|
||||
Egy widget bármi, ami:
|
||||
|
||||
1. névvel hivatkozható (lookup, ki/be-kapcsolás),
|
||||
2. frame-enként frissít magán állapotot és input-ot (Tick),
|
||||
3. frame-enként rajzol magát a képernyőre (Draw).
|
||||
|
||||
```go
|
||||
// pncdsl/ui.widget.go
|
||||
type Widget interface {
|
||||
Named // GetName() string
|
||||
Tick(ctx *UICtx) // állapot + input feldolgozás
|
||||
Draw(dst *ebiten.Image, ctx *UICtx)
|
||||
}
|
||||
|
||||
type UICtx struct {
|
||||
Game *Game
|
||||
DT float64
|
||||
}
|
||||
```
|
||||
|
||||
A widget **maga felel a saját geometriájáért** — nincs külső layout-rendszer,
|
||||
ami pozícionálja. Egy widget lehet:
|
||||
|
||||
- **statikus** (mindig ugyanazon a fix téglalapon van, pl. `VerbBar`),
|
||||
- **dinamikus** (a bounds-ot futás közben számolja, pl. a `RadialVerbs`
|
||||
a click-pozícióból, vagy a `SpeechBubble` a beszélő karakter feje fölött),
|
||||
- **modális** (csak bizonyos állapotban látszik / fogad inputot,
|
||||
pl. `DialogBox` amikor egy `Dialogue` aktív).
|
||||
|
||||
A bounds tehát **method, nem mező** — ha kellene, a widget egy `Bounds()`
|
||||
implementál belsőleg, de a library nem követeli meg. Ez a dynamic layout DSL
|
||||
lényege: nincs nagy `UILayout` struct fix mezőkkel; a layout **a widget-fa
|
||||
maga**.
|
||||
|
||||
## `UIManager`: a megszokott regiszter-minta
|
||||
|
||||
A többi entitáshoz hasonlóan a widgetek is egy generic Manager-ben élnek:
|
||||
|
||||
```go
|
||||
// pncdsl/ui.manager.go
|
||||
type UIManager = Manager[Widget]
|
||||
```
|
||||
|
||||
```go
|
||||
g.UIManager.Register(&pncdsl.VerbBar{Name: "verbs"})
|
||||
g.UIManager.Register(&pncdsl.InventoryBar{Name: "inventory"})
|
||||
g.UIManager.Register(&pncdsl.RadialVerbs{Name: "verb_coin", Trigger: pncdsl.MouseButtonRight})
|
||||
g.UIManager.Register(&domain.ChatPanel{Name: "chat", Bounds: pncdsl.Rect(220, 4, 96, 130)})
|
||||
```
|
||||
|
||||
Hideg újdonság a `Manager`-ben: a `Widget` egy **interfész**, nem konkrét
|
||||
struct. A `Manager[T Named]` ezt eddig is kezelte (T = `Widget`); csak most
|
||||
használjuk ki először.
|
||||
|
||||
**Z-order**: a regisztráció sorrendje. Korábban regisztrált widget hátrább van.
|
||||
|
||||
- `Draw` **regisztráció sorrendben** fut → későbbi widget rajzol felülre.
|
||||
- `Tick` **fordított sorrendben** fut → felül lévő widget kapja először az
|
||||
inputot. Ha a top-most widget `ctx.Game.input.ConsumeLeft()`-ot hív, az
|
||||
alatta lévők `input.LeftClicked()` hívása már `false`-ot ad — így a
|
||||
click „elnyelődik". Ez ugyanaz a consume-on-use pattern, mint amit a
|
||||
`Say` action és a hotspot-klikk-resolve használ.
|
||||
|
||||
## Beépített widget-ek
|
||||
|
||||
A library a következő `Widget` implementációkat szállítja (mindegyik egy-egy
|
||||
`ui.<név>.go` fájlban):
|
||||
|
||||
| Widget | Mit csinál |
|
||||
|-----------------|------------------------------------------------------------------|
|
||||
| `Panel` | Generic konténer — háttér-szín, opcionális keret. Csoportosításhoz. |
|
||||
| `StatusLine` | Hover-felirat + 2s-os flash üzenetek központozva. |
|
||||
| `VerbBar` | SCUMM-szerű verb-gomb grid (mai default). |
|
||||
| `RadialVerbs` | Verb-coin: másodlagos kattintásra felugró radiális ige-menü. |
|
||||
| `InventoryBar` | Item slotok, sorba/grid-be rendezve. |
|
||||
| `SpeechBubble` | A `Say` action által beállított beszéd buborék a beszélő fölött. |
|
||||
| `DialogBox` | Az aktív `Dialogue` választói + sorai (modális). |
|
||||
| `EndCard` | A `ShowEnd` által beállított befejező overlay (modális). |
|
||||
| `Cursor` | Egér-kurzor, kiválasztott item sprite-jával ha van. |
|
||||
| `HotspotDebug` | Hotspot-téglalapok overlay-je, fejlesztéshez (`Enabled` toggle). |
|
||||
|
||||
Mindegyik **konfigurálható mezőkkel** (struct literal) és **felülírható** —
|
||||
ha a domain saját `VerbBar`-t akar, nem regisztrálja a beépítettet, hanem a
|
||||
sajátját.
|
||||
|
||||
Példa egy beépített widget mezőire:
|
||||
|
||||
```go
|
||||
type VerbBar struct {
|
||||
Name string
|
||||
Origin Point
|
||||
Cols int
|
||||
Button Size
|
||||
Gap Point
|
||||
// belső állapot — nem kell érintened
|
||||
buttons []verbButton
|
||||
}
|
||||
func (v *VerbBar) GetName() string { return v.Name }
|
||||
func (v *VerbBar) Tick(ctx *UICtx) { /* hit-test + verb-szelekció */ }
|
||||
func (v *VerbBar) Draw(dst *ebiten.Image, c *UICtx) { /* gombrajzolás */ }
|
||||
```
|
||||
|
||||
### A `RadialVerbs` widget
|
||||
|
||||
Az új verb-coin működése:
|
||||
|
||||
```go
|
||||
type RadialVerbs struct {
|
||||
Name string
|
||||
Trigger MouseButton // alap: MouseButtonRight
|
||||
Radius float64 // alap: 40
|
||||
// belső állapot
|
||||
visible bool
|
||||
center Point
|
||||
}
|
||||
```
|
||||
|
||||
Lifecycle:
|
||||
|
||||
1. Tick-ben figyel a `Trigger`-re. Ha a játékos a scene-területen (nem UI-on)
|
||||
triggert nyom, megnyitja: `visible = true`, `center = mouse.Pos`. Konzumálja
|
||||
a kattintást, hogy ne menjen tovább a hotspot-resolve-ra.
|
||||
2. Amíg `visible`: minden frame megrajzolja a `VerbManager` ige-listáját
|
||||
`len(verbs)` szelet formájában a `center` körül `Radius` sugárral.
|
||||
3. Bal-klikk egy szeletre: kiválasztja az adott igét (`g.selectedVerb = ...`),
|
||||
`visible = false`. Klikk kívülre: csak bezár.
|
||||
4. ESC ugyanígy bezár.
|
||||
|
||||
A radiális menü tehát **dinamikus pozíciójú widget** — minden frame
|
||||
újraszámolt szelet-bounds-ok, a domain nem kell hogy pozícionálja.
|
||||
|
||||
## Custom widget — `ChatPanel` példa
|
||||
|
||||
A domain a `Widget` interfész implementálásával bármilyen Ebiten-eszközt be
|
||||
tud kötni. Egy chat-panel kb. 40 sorból:
|
||||
|
||||
```go
|
||||
// domain/widget.chat.go
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/vector"
|
||||
p "pncdsl/pncdsl"
|
||||
)
|
||||
|
||||
type ChatPanel struct {
|
||||
Name string
|
||||
Bounds p.Rectangle
|
||||
MaxLines int
|
||||
|
||||
lines []string
|
||||
}
|
||||
|
||||
func (c *ChatPanel) GetName() string { return c.Name }
|
||||
|
||||
func (c *ChatPanel) Push(line string) {
|
||||
c.lines = append(c.lines, line)
|
||||
if len(c.lines) > c.MaxLines {
|
||||
c.lines = c.lines[len(c.lines)-c.MaxLines:]
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ChatPanel) Tick(ctx *p.UICtx) {
|
||||
// pl. scrollozás egér-kerékkel — szabadon
|
||||
}
|
||||
|
||||
func (c *ChatPanel) Draw(dst *ebiten.Image, ctx *p.UICtx) {
|
||||
th := ctx.Game.Theme() // aktív téma
|
||||
vector.DrawFilledRect(dst,
|
||||
float32(c.Bounds.X), float32(c.Bounds.Y),
|
||||
float32(c.Bounds.W), float32(c.Bounds.H),
|
||||
th.PanelBG, false,
|
||||
)
|
||||
for i, ln := range c.lines {
|
||||
ctx.Game.DrawText(dst, ln,
|
||||
int(c.Bounds.X)+2,
|
||||
int(c.Bounds.Y)+2+i*14,
|
||||
th.StatusText,
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Bejegyzés a `domain/game.go` `Build()`-jébe:
|
||||
|
||||
```go
|
||||
g.UIManager.Register(&ChatPanel{
|
||||
Name: "chat",
|
||||
Bounds: p.Rect(220, 4, 96, 130),
|
||||
MaxLines: 8,
|
||||
})
|
||||
```
|
||||
|
||||
A chat tartalmát egy új action vagy a domain-kód push-olja:
|
||||
|
||||
```go
|
||||
g.UIManager.MustGet("chat").(*ChatPanel).Push("[server] Welcome!")
|
||||
```
|
||||
|
||||
Vagy egy action-konstruktor: `p.Custom(func(ctx) { ... })`.
|
||||
|
||||
## Theme rendszer
|
||||
|
||||
A jelenlegi színek/színskála a `Theme` struct-ba kerül. **A téma egy
|
||||
manager-be regisztrált entitás** — több téma lehet regisztrálva, és egyik
|
||||
aktív:
|
||||
|
||||
```go
|
||||
// pncdsl/ui.theme.go
|
||||
type Theme struct {
|
||||
Name string
|
||||
|
||||
PanelBG color.Color
|
||||
StatusText color.Color
|
||||
FlashText color.Color
|
||||
|
||||
VerbButtonBG color.Color
|
||||
VerbButtonSelectedBG color.Color
|
||||
VerbButtonText color.Color
|
||||
|
||||
InventorySlotBG color.Color
|
||||
InventorySlotSelectedBG color.Color
|
||||
|
||||
SpeechBubbleBG color.Color
|
||||
SpeechDefaultText color.Color
|
||||
|
||||
DialogBG color.Color
|
||||
DialogBorder color.Color
|
||||
DialogChoiceBG color.Color
|
||||
DialogChoiceHover color.Color
|
||||
DialogSpeaker color.Color
|
||||
DialogText color.Color
|
||||
|
||||
EndCardBG color.Color
|
||||
EndCardText color.Color
|
||||
|
||||
CursorColor color.Color
|
||||
HotspotOutline color.Color
|
||||
}
|
||||
|
||||
func (t Theme) GetName() string { return t.Name }
|
||||
func (t Theme) TypeLabel() string { return "theme" }
|
||||
|
||||
type ThemeManager = Manager[Theme]
|
||||
```
|
||||
|
||||
A `Game` mezője:
|
||||
|
||||
```go
|
||||
type Game struct {
|
||||
// ...
|
||||
ThemeManager *ThemeManager
|
||||
activeTheme string
|
||||
}
|
||||
|
||||
func (g *Game) Theme() Theme { return g.ThemeManager.MustGet(g.activeTheme) }
|
||||
func (g *Game) UseTheme(name string) { g.activeTheme = name }
|
||||
```
|
||||
|
||||
A widgetek a `ctx.Game.Theme()` hívásból olvassák ki a színeket — **minden
|
||||
frame friss**, így futás közbeni téma-csere zökkenőmentes.
|
||||
|
||||
### Szállított preset-ek
|
||||
|
||||
A library a következő témákat regisztrálja induláskor (vagy a domain hívja
|
||||
őket `pncdsl.RegisterPresetThemes(g)`-vel):
|
||||
|
||||
| Preset | Mit reprezentál |
|
||||
|-----------------------|------------------------------------------------------------------------|
|
||||
| `classic-scumm` | A jelenlegi sötét-kékes paletta, SCUMM-szerű, alapértelmezett. |
|
||||
| `sierra-coin` | Sötétebb, a verb-coin használathoz hangolt (nincs alsó verb-bar). |
|
||||
| `paper-notebook` | Világos, krémszínű, mint egy papír — modern indie-stílus. |
|
||||
| `terminal-green` | Klasszikus zöld-fekete terminál-look, retro hacker hangulat. |
|
||||
|
||||
A `classic-scumm` **pontosan a jelenlegi színekkel** indul, így a meglévő
|
||||
demo változtatás nélkül ugyanúgy néz ki, csak már a Theme-rendszeren át.
|
||||
|
||||
```go
|
||||
// pncdsl/ui.theme_presets.go — a built-in presetek
|
||||
func RegisterPresetThemes(g *Game) {
|
||||
g.ThemeManager.Register(Theme{Name: "classic-scumm", /* … */ })
|
||||
g.ThemeManager.Register(Theme{Name: "sierra-coin", /* … */ })
|
||||
// …
|
||||
}
|
||||
```
|
||||
|
||||
`NewGame()` automatikusan hívja, és `UseTheme("classic-scumm")`-ot állít be —
|
||||
backwards-compatible default.
|
||||
|
||||
## Lifecycle — pontosabban
|
||||
|
||||
```go
|
||||
// engine.Update
|
||||
func (e *engine) Update() error {
|
||||
g.input.poll()
|
||||
g.transition.update(dt)
|
||||
if g.transition.active && g.transition.out { return nil }
|
||||
if g.scriptRunner != nil { /* tick runner */ }
|
||||
|
||||
uictx := &UICtx{Game: g, DT: dt}
|
||||
|
||||
// 1) Top-down input feldolgozás. A felül lévő widget (reverse reg-order)
|
||||
// kapja először az inputot; a saját Tick-jében dönthet a Consume-ról.
|
||||
for _, w := range g.UIManager.reversed() {
|
||||
w.Tick(uictx)
|
||||
}
|
||||
|
||||
// 2) Ha az input még nincs konzumálva, a scene/hotspot kap esélyt:
|
||||
e.handleSceneInput()
|
||||
}
|
||||
|
||||
// engine.Draw
|
||||
func (e *engine) Draw(screen *ebiten.Image) {
|
||||
// háttér + karakterek
|
||||
g.UIManager.Each(func(w Widget) { w.Draw(screen, uictx) })
|
||||
g.transition.draw(screen, ...)
|
||||
}
|
||||
```
|
||||
|
||||
A `Cursor` widget mindig **utolsóként regisztrálva** — biztosan a tetejére
|
||||
rajzol és előbb kap inputot, mint bármi más, ha kell.
|
||||
|
||||
A modális widgetek (`DialogBox`, `EndCard`) belsőleg figyelik az aktív
|
||||
állapotot (`g.dialogueActive()`, `g.UI.endCard != ""`), és csak akkor
|
||||
fogadnak inputot / rajzolnak. Ha aktívak, **konzumálnak minden inputot**,
|
||||
hogy a háttér widgetek és a scene-input ne reagáljon.
|
||||
|
||||
## Mit kapcsol ki / mire vált ki
|
||||
|
||||
A jelenlegi `pncdsl/ui.def.go` központi `UI` struct **megszűnik mint
|
||||
adattároló** — minden ami benne volt (`hoverLabel`, `flash`, `speech`,
|
||||
`dialog`, `endCard`, `verbButtons`) **átköltözik az adott widgetbe** mint
|
||||
belső állapot.
|
||||
|
||||
A `Game.UI` mező marad mint convenience-handle (pl. `g.UI.FlashLine(...)`
|
||||
továbbra is hívható — átdelegál a `StatusLine` widgetnek a manager-en
|
||||
keresztül), de ez egy vékony adapter, nem központi state.
|
||||
|
||||
## Fájlok
|
||||
|
||||
```
|
||||
pncdsl/
|
||||
├── ui.widget.go # új — Widget interface, UICtx, MouseButton, Size
|
||||
├── ui.manager.go # új — UIManager alias, reversed() iterátor
|
||||
├── ui.theme.go # új — Theme struct, ThemeManager alias
|
||||
├── ui.theme_presets.go # új — classic-scumm, sierra-coin, paper-notebook, terminal-green
|
||||
│
|
||||
├── ui.panel.go # új — Panel widget (generic container)
|
||||
├── ui.status.go # új — StatusLine widget (volt: ui.def hover/flash)
|
||||
├── ui.verb_bar.go # átnevezve: ui.verb.go → ui.verb_bar.go (most widget)
|
||||
├── ui.verb_radial.go # új — RadialVerbs (verb-coin)
|
||||
├── ui.inventory.go # új — InventoryBar widget (volt: ui.def-ben)
|
||||
├── ui.speech.go # → SpeechBubble widget interface-é alakítva
|
||||
├── ui.dialog_box.go # átnevezve: dialog.box.go (most widget)
|
||||
├── ui.end_card.go # új — EndCard widget
|
||||
├── ui.cursor.go # → Cursor widget interface-é alakítva
|
||||
├── ui.hotspot_debug.go # új — HotspotDebug widget (toggle-elhető)
|
||||
│
|
||||
└── ui.def.go # zsugorodik egy vékony Game.UI adapterre vagy törölhető
|
||||
```
|
||||
|
||||
Az `ui.verb_manager.go` (verb-manager alias) **változatlan marad** — a
|
||||
`RadialVerbs` és a `VerbBar` is ugyanazt a `VerbManager`-t olvassa.
|
||||
|
||||
## Domain-oldali használat (a végén így néz ki)
|
||||
|
||||
```go
|
||||
// domain/game.go
|
||||
func Build() *p.Game {
|
||||
g := p.NewGame("Reggeli Kávé", 320, 200)
|
||||
|
||||
registerAssets(g)
|
||||
definePlayer(g); defineCat(g)
|
||||
// … itemek, dialógusok, scriptek, scene-ek …
|
||||
|
||||
// SCUMM-szerű alapértelmezett UI:
|
||||
g.UIManager.Register(&p.HotspotDebug{Name: "debug"}) // legalul (z=0)
|
||||
g.UIManager.Register(&p.VerbBar{Name: "verbs",
|
||||
Origin: p.Point{X: 4, Y: 152}, Cols: 2,
|
||||
Button: p.Size{W: 60, H: 14}})
|
||||
g.UIManager.Register(&p.InventoryBar{Name: "inventory",
|
||||
Origin: p.Point{X: 132, Y: 152}, Slots: 8, Cols: 8, SlotSize: 22, Gap: 2})
|
||||
g.UIManager.Register(&p.StatusLine{Name: "status", Y: 142})
|
||||
g.UIManager.Register(&p.SpeechBubble{Name: "speech"})
|
||||
g.UIManager.Register(&p.DialogBox{Name: "dialog"})
|
||||
g.UIManager.Register(&p.EndCard{Name: "endcard"})
|
||||
g.UIManager.Register(&p.Cursor{Name: "cursor"}) // legfelül
|
||||
|
||||
g.UseTheme("classic-scumm")
|
||||
g.StartAt("bedroom").OnStart(p.RunScript("intro"))
|
||||
return g
|
||||
}
|
||||
```
|
||||
|
||||
Ha verb-coin kell verb-bar helyett, ennyi a változás:
|
||||
|
||||
```go
|
||||
// töröld a VerbBar regisztrációt, helyette:
|
||||
g.UIManager.Register(&p.RadialVerbs{Name: "verbs", Radius: 40})
|
||||
g.UseTheme("sierra-coin")
|
||||
```
|
||||
|
||||
És ha chat is kell:
|
||||
|
||||
```go
|
||||
g.UIManager.Register(&ChatPanel{Name: "chat",
|
||||
Bounds: p.Rect(220, 4, 96, 130), MaxLines: 8})
|
||||
```
|
||||
|
||||
## A „minden alapból regisztrálva" rövidítés
|
||||
|
||||
A fenti boilerplate cseppet sok ahhoz, hogy minden domain újra leírja. A
|
||||
`pncdsl.RegisterDefaultUI(g)` egysoros hívás regisztrálja a SCUMM-szerű
|
||||
default készletet:
|
||||
|
||||
```go
|
||||
g := p.NewGame(...)
|
||||
p.RegisterDefaultUI(g) // verbs + inventory + status + speech + dialog + endcard + cursor
|
||||
|
||||
// a domain csak felülír / hozzáad:
|
||||
g.UIManager.MustGet("verbs").(*p.VerbBar).Cols = 4
|
||||
g.UIManager.Register(&ChatPanel{...})
|
||||
```
|
||||
|
||||
A `NewGame` **nem** hívja automatikusan — explicit kell, hogy a domain
|
||||
lássa hova kerülnek a default widgetek (és könnyen kivehessen onnan).
|
||||
|
||||
## Validáció
|
||||
|
||||
`Game.Validate()` kiegészül:
|
||||
|
||||
- Minden regisztrált `Widget.GetName()` egyedi (ezt amúgy a `Register`
|
||||
panic-cal kiszűri).
|
||||
- `ActiveTheme` létezik a `ThemeManager`-ben.
|
||||
- Legalább egy `SpeechBubble` és egy `DialogBox` regisztrálva, mert a
|
||||
`Say` és `RunDialogue` action ezekre számít. (Vagy: ezek a built-in actionök
|
||||
graceful no-op-pá válnak ha hiányoznak — eldöntendő.)
|
||||
|
||||
## Lépésenkénti implementáció
|
||||
|
||||
1. **Widget interface + UIManager + Theme + ThemeManager** — csontváz, még
|
||||
üres widget-ekkel. NewGame regisztrálja a manager-eket; a régi UI
|
||||
párhuzamosan él tovább.
|
||||
2. **`Panel`, `StatusLine`, `SpeechBubble`, `Cursor`** — refaktoráld át
|
||||
ezeket widgetekre. A régi `UI.SetSpeech`/`UI.FlashLine` delegál a
|
||||
widgetekhez.
|
||||
3. **`VerbBar` widget** — a régi `rebuildVerbButtons` + draw + hit-test
|
||||
kerül bele. Az engine `handleFreeInput` ezt használja.
|
||||
4. **`InventoryBar`, `DialogBox`, `EndCard`, `HotspotDebug`** — a maradék
|
||||
beépített widgetek.
|
||||
5. **`Theme` preset-ek** — minden hard-kódolt szín kikerül a Theme-be.
|
||||
`RegisterPresetThemes` regisztrálja a négyet.
|
||||
6. **`RadialVerbs`** — új widget, verb-coin működéssel.
|
||||
7. **`RegisterDefaultUI`** convenience + dokumentáció.
|
||||
8. **Demo átállítása** — `domain/game.go` használja az új API-t. Smoke test
|
||||
marad zöld.
|
||||
9. **Régi `ui.def.go` központi state kitörlése** vagy zsugorítása vékony
|
||||
adapterre.
|
||||
|
||||
Egy-egy lépés után a játék fut, a smoke test zöld — semmi atomi nagy commit.
|
||||
|
||||
## Out of scope (most)
|
||||
|
||||
- **Egyéni font** — a `Game.DrawText` továbbra is ebitenutil debug fontot
|
||||
használ. A Theme-ben lesz `Font` mező placeholder, de a swap később.
|
||||
- **Animált widgetek** — pl. tweenelt megjelenés a `DialogBox`-nak. Felület
|
||||
van rá (Tick), implementáció későbbre.
|
||||
- **Drag and drop az inventory-ban** — most csak klikk.
|
||||
- **Több aktív theme egyszerre** (pl. UI overlay külön téma). Egy aktív téma.
|
||||
- **Layout-szerver / declarative layout DSL** (mint a CSS flexbox). A widget
|
||||
maga felel a saját pozíciójáért. Ha kell csoportosítás, `Panel` widget +
|
||||
children API jöhet a 2. iterációban.
|
||||
|
||||
## Nyitott kérdés
|
||||
|
||||
A `Game.UI` (jelenlegi convenience-handle, `g.UI.FlashLine` stb.) maradjon,
|
||||
vagy minden hívás közvetlenül `g.UIManager.MustGet("status").(*StatusLine).Flash(...)`?
|
||||
|
||||
Javaslom: maradjon mint **shortcut**, de mögötte a widget-rendszer fut. A
|
||||
hivatkozást egy `(g *Game) Status() *StatusLine` típusú getter is megteheti
|
||||
a publikus API-n, hogy ne kelljen type-assertelni a domain kódjában.
|
||||
Reference in New Issue
Block a user