Covers the windows the client itself opens and closes. Distinct from widgets.md (generic tree + create/place/destroy), ui-chrome.md (
Window.Deco,IBox, theiresize/contarea/cszgeometry) and window-positions.md (where each window stands: thewndc-*store and the screen resize). The class + method name is the stable anchor.
GameUI: invwnd, equwnd, makewnd, srchwnd, iconwnd, chrwdg, zerg, opts, mapfile| What | Where |
|---|---|
Hidewnd — a Window whose close hides instead of destroying |
GameUI.Hidewnd — reqclose() { hide(); }. The contrast is what the close button means: Window.reqclose defaults to wdgmsg("close"), so a server-put-up container window’s close is a request the server answers with a widget destroy — the window, its Inventory and every GItem in it die through rdispose and never through remove (widgets.md) — while a Hidewnd answers itself and destroys nothing, so maininv’s items outlive every close |
| Inventory / equipment / character sheet: a close that hides, twice by wrapper and once by the window itself | GameUI.addchild place == "inv" — new Hidewnd(…, "Inventory"), add(maininv), pack(), hide(); "equ" is the same shape. "chr" has no wrapper and answers for itself: chrwdg = add((CharWnd)child, …), then chrwdg.reqclose(chrwdg::hide).hide() — so CharWnd and every tab under it (battr/BAttrWnd, sattr/SAttrWnd with its study inventory, fight/FightWnd) is put up once and outlives every close; only a server destroy ends it (GameUI.cdestroy nulls chrwdg) |
| Action search | place == "menu" — srchwnd, reqclose(srchwnd::hide).hide() |
| Crafting: an anonymous wrapper the CONTENT ends | place == "craft" — new Window(…, ((Makewindow)child).rcpnm), add(mkwdg), pack(). Its cdestroy(w) runs ui.destroy(this) and makewnd = null when w == mkwdg |
The crafting pair dies content-first, and only the content dies at once. The server destroys the
Makewindow, a plain Widget whose reqdestroy() is destroy(): it unlinks immediately and
hasparent(root) goes false on the same call. Its cdestroy then destroys the anonymous wrapper, and that
is a Window, whose reqdestroy starts a fade — so for a while the wrapper is reachable with nothing in it.
Ask the content widget whether a recipe is open, never the wrapper and never makewnd, which is one
field for whichever recipe is newest. See the two-branch liveness rule in widgets.md.
The wrappers are hidden from birth — maininv exists from login, the Hidewnd around it does not, so “put
the inventory back” is never a blind show(). And only the GRID is the server’s: the wrapper is
constructed here rather than sent, so Widget.wdgid() on it is -1 and it appears in no UI.rwidgets entry —
address the inventory by id through maininv (GameUI.equwnd’s Equipory likewise), and reserve the wrapper
for what it is, a frame the client hangs around it. TRAP — that wrapper PACKS AROUND ITS GRID and cannot be
resized from outside: it is anonymous, with cresize(ch) { pack(); }, and Widget.resize notifies
parent.cresize(this) — so resize2’s deco.iresize makes the deco call back and the window re-packs to its
content before your call returns (pack() = resize(contentsz())). equwnd has none, and resizes.
Hidepanels, and the plates they blit| What | Where |
|---|---|
| The panel | GameUI.Hidepanel — an id, an Indir<Coord> base and a g gravity; add(T) pack()s and move()s on every child added, so a panel is exactly its contents. move(double a) slides it off along g, show(boolean) is remembered as <id>-visible |
| The seven | built in the ctor, in order: blpanel, mapmenupanel, brpanel, menupanel, ulpanel, umpanel, urpanel. The two menu panels take an Indir base that chases the corner panel beside them |
The plates blitted in a widget’s own draw |
MainMenu.draw → menubg (gfx/hud/rbtn-bg), MapMenu.draw → mapmenubg (gfx/hud/lbtn-bg), NKeyBelt.draw → nkeybg (gfx/hud/hb-main), each a static final Tex and each g.image(…, Coord.z) before the rest of the draw |
The plates that are Img children |
gfx/hud/blframe in blpanel (the minimap’s frame), gfx/hud/csearch-bg and gfx/hud/brframe in brpanel. Each is a plain Img, so it is a widget in the tree rather than a blit |
| What sits on them | menugridc is brframe.c plus a constant and menubuttons places the search button at rbtnimg.c — the Img’s own box decides, once, at construction. minimapc beside them is a bare constant |
The size of a panel is the size of its plate: MainMenu and MapMenu both call super(<tex>.sz())
and Hidepanel.add packs around them. Nothing re-measures on a redraw, so a plate painted at another size
is drawn into the box the client’s art gave it.
⚠️ It re-packs on two events and re-anchors on one: add does pack() and move(), cresize (a
child’s resize) does sz = contentsz() and no move(), and a child moved, hidden or unlinked reaches
neither. move() derives c from sz, so a child resized after it was added leaves the panel anchored
to its old box — a gap at the screen edge — until the next add or fold. Size a widget before adding it.
⚠️ resetui() persists the visibility of all seven and every fold click runs it (updfold(reset)): it
loops p.cshow(p.tvis), and cshow writes <id>-visible before comparing. toggleui (kb_hide,
unbound) slides them all off setting tvis and writing nothing — so one fold click while the UI is toggled
off records all seven hidden, the next start comes up bare, and cycling toggleui back does not undo it.
⚠️ beltwdg is not in a panel, and there are two of it. It is added to GameUI itself and placed by
hand, in resize and on every frame in GameUI.draw (window-positions.md);
NKeyBelt (super(nkeybg.sz())) blits that plate, FKeyBelt — the Options belt setting’s other half —
draws no background, and both blit Inventory.invsq for their squares. The :belt command destroys
one and adds the other, so a reference to the belt outlives the swap pointing at a dead widget.
GameUI.draw paints three things no widget stands for, after its children: the : line or the last
notice, and the hidden chat’s newest lines, bottom left — see console.md. Being painted
after super.draw, they cover every child of the HUD.
A widget’s own ui says which session it belongs to; which HUD it belongs to is the upward
getparent walk, so what it is parented to is the whole of what that walk can find. GameUI.addchild
does not add most of them to GameUI.
| Widget | Its parent |
|---|---|
IMeter (place == "meter") |
ulpanel, not GameUI — GameUI.meters is a separate ordered record, not a child list |
Buff (place == "buff") |
GameUI.buffs, and Bufflist.addchild is a plain add — so a buff IS a direct child of the Bufflist |
BAttrWnd / SAttrWnd / SkillWnd / FightWnd / QuestWnd / WoundWnd |
the matching Tabs.Tab inside CharWnd (CharWnd.battrtab.add(...) and its siblings), not CharWnd itself |
| The study inventory | SAttrWnd — SAttrWnd.addchild’s place == "study" branch does add(child) on itself and then builds a StudyInfo beside it, handing the inventory in as a constructor argument |
⚠️ StudyInfo does not contain the study inventory it reports on — the two are siblings under
SAttrWnd, and StudyInfo.study is a reference. getparent(StudyInfo.class) from one of its GItems
finds nothing; the walk is up to SAttrWnd and back down through children(StudyInfo.class). The same
shape holds for CharWnd: a walk from BAttrWnd reaches CharWnd only because Tabs.Tab is a widget
in between, so the hop count is never one.
| What | Where |
|---|---|
| The toggle ← addon seam | GameUI.togglewnd(Window) — wnd.show(!wnd.visible()) then raise/fitwdg/setfocus |
| The tick ← addon seam | GameUI.wndstate(Window) — just wnd.visible() |
| The buttons + their bindings | MenuButton / MenuCheckBox — both setgkey(gkey) in the ctor; kb_inv (Tab), kb_equ, kb_chr, kb_bud, kb_opt, kb_map, kb_srch |
| The five main-menu checkboxes | MainMenu — .state(() -> wndstate(x)).click(() -> togglewnd(x)); plumbing is ACheckBox.state Supplier<Boolean> + click Runnable |
| The map menu | MapMenu — mapfile (also writes wndvis-map) and iconwnd (which does not use togglewnd: it creates/reqcloses) |
| The odd one out | menubuttons — srchwnd: focus it if visible-but-unfocused, else togglewnd |
| Key → the button’s own click | Widget.globtype matches kb_gkey.key() → gkeytype, which ACheckBox overrides to click() |
The key and the button are ONE click. setgkey(KeyBinding) (Widget) stores kb_gkey; a matching GlobKeyEvent reaches the widget’s own gkeytype, which for an ACheckBox calls click() — no separate keyboard path, so one edit covers all seven call sites.
state() is polled every frame, per checkbox (six) ⇒ allocation-free. show(boolean) returns its ARGUMENT, not whether anything changed.
Window’s visibility is a small state machine (it wraps show/hide around a fade Transition)| What | Where |
|---|---|
visible() — animation-aware |
— visible && ((animst == null) \|\| (animst == "show")) |
hide() does not clear visible |
— starts animst = "hide"; tick calls super.hide() when the anim ends |
reqdestroy() → animst = "dest"; trans set on attach |
— tick destroys at the end (the 030 fading corpse); added() → initanim(), and it also setfocuses a visible window |
The fade is NOT a Widget.Anim |
Window.anim + animst are private fields of Window, ticked in Window.tick and nulled when Animation.tick returns true — nothing of it is in Widget.anims/nanims. FadeAnim.time = 0.1 s. Fork: Window.animating() (// addon:) — anything asking “is this widget still moving?” must ask BOTH |
Window.draw is itself a render-to-texture |
— every frame it makes a GOut over gbasic() (a Texture2D gbuf + FragColor + Ortho2D, rebuilt only when sz changes), clears it, draws the window into it, then drawfin blits gbuf back — through anim.draw(g, buf) while fading. So a window drawn inside another render target is a nested one, and gbuf is a TexRaw(…, invert=true) |
A fading-out window already reads visible() == false, so a menu tick reads one directly, no debounce; and
RootWidget sets focusctl — why parent.setfocus(w) terminates.
| What | Where |
|---|---|
public String cap |
— set by the ctor from args[1] (create), so a window built with a caption has it before any child is placed |
chcap(String) — the only later write |
. Both paths funnel here: the server’s uimsg "cap" (which maps "" → null) and any client-side title change. No subclass overrides it, and no other class assigns cap (BAttrWnd’s and Polity’s cap are unrelated fields) |
It fires outside the ui monitor, and nothing else announces it |
uimsg is applied on a Loader thread and UI closes synchronized(this) before the addon tap, so anything hung here may only record — no widget read, no tree walk. There is no tree event for a caption; the fork’s // addon: line at the end of chcap is the seam |
cap is not the Deco-rendered Text — that is Deco.cap, re-rendered by
checkcap on cap.text != wnd.cap (ui-chrome.md).
All // addon: edits sit inside existing method bodies — no visibility change, no new call site — and go
through AddonWidgets, so haven keeps one file that knows the addon layer exists. togglewnd asks
toggleWnd(wnd) first (true = handled), wndstate asks wndState(wnd) (null = not owned); ownership is
029’s hide record (Addon.hiddenNative) by widget identity, which also disposes of the fading corpse from
widgets.md, and that record carries the view, so wndState answers view.visible().
035’s chrome(wnd) is in Window.tick (ui-chrome.md). The seams on where a window stands
and on GameUI.resize are window-positions.md.