Files
benilla-octo/crates/benilla/assets/ui/GameMenuFrame.xml
T
SamandClaude Fable 5 c887170b37 the social window, the escape menu, and items that work from the bar
- The social window: friends, ignore and `/who`, in the reference's own two-level
  arrangement with its inner tabs and per-row right-click menus. The lists ride the
  real wire — guid-keyed with no names, resolved through the name cache, and capped
  at 50 friends / 25 ignores because those are the client's own caps — and the
  ignore list does its actual job: an ignored speaker's chat is dropped silently, a
  whisper additionally tells the sender, and a duel challenge is auto-declined.
  Guild ships as a disabled tab, which is what a guildless character sees.
- The escape menu: ESC's last rung finally opens something. The game menu lands at
  the reference's own size and ladder, and leaving works — logout and quit with the
  server owning the clock (instant at an inn, refused in combat, or the 20-second
  countdown), narrated by the real camp/quit dialogs. While the menu holds the
  center nothing else opens, and the bag bar greys out under it.
- Items on the action bar act like items. An equipped trinket was a dead button;
  the reference's use law is two stages — not equippable means use, equippable and
  already worn means use in place — over its exact search order (equipment first,
  then the bags, then the backpack, then the keyring; never the bank). What can go
  on the bar is now the reference's own two filters, byte-read: an item with an
  on-use spell or one you can equip, and no passive spells — and dropping a passive
  there speaks the real client's own line, "You can't put a passive ability in the
  action bar."
- A quest-starter item opens its quest instead of "The item was not found." The
  question goes to the item's own guid, never through item-use — which the server
  refuses for every one of the 215 quest-starter items — and refusing the quest
  says what the client says, then closes the window.
- For the toolbox: a pixel pick that reports everything under the cursor all the
  way back, a lens that says which render phase a draw landed in, and switches that
  separate "discarded" from "lost depth" from "never drawn" — built while cornering
  a flickering tent, whose fix ships in the next snapshot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 19:19:06 +07:00

307 lines
18 KiB
XML

<Ui>
<!--
The GAME MENU — the frame ESC opens (decision 0674). The last hole in the game shell's
outermost layer: until now ESC's chain ran out of things to close and did nothing, and the
Main Menu micro button was drawn but dead (MicroMenu.xml's SCOPE note).
Ground truth, all quoted rather than re-derived:
· **The frame** — ref `GameMenuFrame.xml` (patch.MPQ, extracted): 195x246, `frameStrata`
DIALOG, the UI-DialogBox backdrop with the 11/12/12/11 insets and 32px tile/edge, the
256x64 UI-DialogBox-Header at TOP +12 with MAIN_MENU centered 14px down it, and the
eight-button ladder — first button CENTER on the frame's TOP at (0,-37), each next one
TOP-to-BOTTOM at (0,-1), and Return to Game hung (0,-16) below Exit Game (the one gap in
the stack, which is what makes the last entry read as separate).
· **The button** — ref `GameMenuButtonTemplate` (UIPanelTemplates.xml l.403-410): a 144x21
`UIPanelButtonTemplate` (the UI-Panel-Button Up/Down/Disabled/Highlight four-state art,
TexCoords 0..0.625 / 0..0.6875) whose label trio is overridden to
GameFontHighlight/GameFontHighlight/GameFontDisable — i.e. the menu's labels are WHITE,
not the gold a normal panel button wears, and grey when disabled.
· **The labels** — GlobalStrings.lua (patch-2.MPQ), verbatim: VIDEOOPTIONS_MENU "Video
Options", SOUNDOPTIONS_MENU "Sound Options", UIOPTIONS_MENU "Interface Options",
KEY_BINDINGS "Key Bindings", MACROS "Macros", LOGOUT "Logout", EXIT_GAME "Exit Game",
RETURN_TO_GAME "Return to Game", MAIN_MENU "Main Menu". Declared as the real globals
(they are addon-visible API, decision 0068) and read back into each button's label in its
OnLoad — the ref's `text="LOGOUT"` attribute IS a global lookup, which this engine's
literal `text=` is not.
· **The sounds** — the ref's own OnClick lines: igMainMenuOption (the five option buttons),
igMainMenuLogout, igMainMenuQuit, igMainMenuContinue. All four kits verified present in
SoundEntries.dbc (patch-2.MPQ) alongside the igMainMenuOpen/Close pair already in use.
· **The two logout dialogs** — ref `StaticPopup.lua` l.564-600 (CAMP + QUIT), transcribed
onto the shared popup engine (UiPanels.xml), whose countdown-text branch already lists
both `which` names. Their driver events are the reference's own (`re/events/event-catalog
.tsv`: 276 PLAYER_CAMPING, 277 PLAYER_QUITING, 278 LOGOUT_CANCEL), fired by
`crate::ui_logout` off `SMSG_LOGOUT_RESPONSE`.
NAMES are the reference's, unprefixed (`GameMenuFrame`, `GameMenuButton*`) — the same call
MicroMenu.xml made for its straight transcriptions, and required here besides: `ToggleGameMenu`
and `GameMenuFrame` are Era API surface an addon addresses by name.
SCOPE / divergences (noted, not bugs):
- **Five buttons ship DISABLED**: Video/Sound/Interface Options, Key Bindings, Macros have no
panel behind them and won't for a long time. The director's call (this session) is that they
stay in the ladder — the frame keeps the ref's exact 195x246 and its exact silhouette — wearing
the template's own `-Disabled` art and grey label rather than being cut or left as clickable
no-ops. Each goes live by deleting one `self:Disable()` and writing the ref OnClick that is
already quoted beside it. (MicroMenu.xml made the opposite call for its dead icons, and says
why: three grey icons mid-row read as a broken bar. A menu of labels with some greyed is the
ordinary idiom — it reads as "not yet", which is the truth.)
- The ref frame is `toplevel="true" movable="true" parent="UIParent"`. Dropped: this engine has
no cross-file `parent=` attachment (SkillFrame.xml's header comment), no frame-level raise,
and `movable` is inert in the ref too (GameMenuFrame defines no drag scripts, so it never
actually moves). `frameStrata="DIALOG"` is what puts it over the world UI, and that is kept.
- No `OptionsFrameCancel` rung in ToggleGameMenu (UiPanels.xml) — no OptionsFrame exists.
-->
<Script><![CDATA[
-- The reference GlobalStrings (patch-2.MPQ Interface\FrameXML\GlobalStrings.lua), quoted
-- verbatim. `X = X or "..."` so a later real strings table wins, the MicroMenu.xml idiom.
MAIN_MENU = MAIN_MENU or "Main Menu"
VIDEOOPTIONS_MENU = VIDEOOPTIONS_MENU or "Video Options"
SOUNDOPTIONS_MENU = SOUNDOPTIONS_MENU or "Sound Options"
UIOPTIONS_MENU = UIOPTIONS_MENU or "Interface Options"
KEY_BINDINGS = KEY_BINDINGS or "Key Bindings"
MACROS = MACROS or "Macros"
LOGOUT = LOGOUT or "Logout"
EXIT_GAME = EXIT_GAME or "Exit Game"
RETURN_TO_GAME = RETURN_TO_GAME or "Return to Game"
CAMP_TIMER = CAMP_TIMER or "%d %s until logout"
QUIT_TIMER = QUIT_TIMER or "%d %s until exit"
QUIT_NOW = QUIT_NOW or "Exit now"
CANCEL = CANCEL or "Cancel"
-- One label setter for the five that have nothing behind them yet: set the ref's text, then
-- disable (grey label + the template's -Disabled art). The whole divergence lives here, so
-- making one live is deleting its name from the call and writing its ref OnClick.
function GameMenuButton_Pending(button, text)
button:SetText(text)
button:Disable()
end
-- ── The two logout dialogs (ref StaticPopup.lua l.564-600) ──────────────────────────────
-- Both are countdown dialogs: their `text` is a format string the popup engine's per-tick
-- branch fills with the remaining time (StaticPopup_OnUpdate's which-list already names CAMP
-- and QUIT), so they open blank and count down. `timeout = 20` matches the server's own
-- logout timer (vmangos `WorldSession::LogoutRequest` → 20 s), which is what the countdown
-- is displaying — the client is not the authority here, it is narrating the server's clock.
--
-- The asymmetry between them is the reference's: CAMP offers only Cancel (the commented-out
-- CAMP_NOW/ForceLogout button is dead in 1.12 — "uncomment once forced logouts are completely
-- implemented"), while QUIT offers Exit now + Cancel, because a force-quit needs no server
-- round trip. Closing either one EARLY (button, ESC, or an override) cancels the logout;
-- letting it run out does not — the engine zeroes `timeleft` before hiding, which is exactly
-- what the ref's `if this.timeleft > 0` guard reads.
StaticPopupDialogs["CAMP"] = {
text = CAMP_TIMER,
button1 = CANCEL,
OnAccept = function()
CancelLogout()
end,
OnHide = function()
if this.timeleft > 0 then
CancelLogout()
end
end,
timeout = 20,
whileDead = 1,
hideOnEscape = 1,
}
StaticPopupDialogs["QUIT"] = {
text = QUIT_TIMER,
button1 = QUIT_NOW,
button2 = CANCEL,
OnAccept = function()
ForceQuit()
this.timeleft = 0
end,
OnHide = function()
if this.timeleft > 0 then
CancelLogout()
end
end,
timeout = 20,
whileDead = 1,
hideOnEscape = 1,
}
-- The camp/quit event driver. The ref handles these three in UIParent's own OnEvent
-- (UIParent.lua l.304-315); benilla has no UIParent frame, so this is a dedicated hidden
-- listener — the BenillaDeleteItemConfirmDriver shape.
function BenillaLogoutDriver_OnEvent()
if event == "PLAYER_CAMPING" then
StaticPopup_Show("CAMP")
elseif event == "PLAYER_QUITING" then
StaticPopup_Show("QUIT")
elseif event == "LOGOUT_CANCEL" then
StaticPopup_Hide("CAMP")
StaticPopup_Hide("QUIT")
end
end
]]></Script>
<!-- ref GameMenuButtonTemplate (UIPanelTemplates.xml l.403-410) = UIPanelButtonTemplate at
144x21 with a white/white/grey label trio. UIPanelButtonTemplate itself is not transcribed
as a shared virtual (no other benilla file inherits it — each open-codes the same four-state
art, e.g. QuestFrame.xml l.753); its textures are inlined here with the ref TexCoords. -->
<Button name="GameMenuButtonTemplate" virtual="true">
<Size><AbsDimension x="144" y="21"/></Size>
<ButtonText name="$parentText"/>
<NormalTexture file="Interface\Buttons\UI-Panel-Button-Up"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></NormalTexture>
<PushedTexture file="Interface\Buttons\UI-Panel-Button-Down"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></PushedTexture>
<DisabledTexture file="Interface\Buttons\UI-Panel-Button-Disabled"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></DisabledTexture>
<HighlightTexture file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></HighlightTexture>
<NormalFont inherits="GameFontHighlight"/>
<HighlightFont inherits="GameFontHighlight"/>
<DisabledFont inherits="GameFontDisable"/>
</Button>
<Frame name="GameMenuFrame" frameStrata="DIALOG" enableMouse="true" hidden="true">
<Size><AbsDimension x="195" y="246"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
<BackgroundInsets><AbsInset left="11" right="12" top="12" bottom="11"/></BackgroundInsets>
<TileSize><AbsValue val="32"/></TileSize>
<EdgeSize><AbsValue val="32"/></EdgeSize>
</Backdrop>
<Layers>
<Layer level="ARTWORK">
<Texture name="GameMenuFrameHeader" file="Interface\DialogFrame\UI-DialogBox-Header">
<Size><AbsDimension x="256" y="64"/></Size>
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="0" y="12"/></Offset></Anchor></Anchors>
</Texture>
<FontString name="GameMenuFrameTitle" inherits="GameFontNormal">
<Anchors>
<Anchor point="TOP" relativeTo="GameMenuFrameHeader"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The five with no panel behind them (header SCOPE note). Each keeps the ref's own
OnClick quoted above it, so going live is deleting the _Pending call and pasting it:
PlaySound("igMainMenuOption"); ShowUIPanel(OptionsFrame) -->
<Button name="GameMenuButtonOptions" inherits="GameMenuButtonTemplate">
<Anchors>
<Anchor point="CENTER" relativePoint="TOP"><Offset><AbsDimension x="0" y="-37"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>GameMenuButton_Pending(self, VIDEOOPTIONS_MENU)</OnLoad></Scripts>
</Button>
<!-- ref: PlaySound("igMainMenuOption"); ShowUIPanel(SoundOptionsFrame) -->
<Button name="GameMenuButtonSoundOptions" inherits="GameMenuButtonTemplate">
<Anchors>
<Anchor point="TOP" relativeTo="GameMenuButtonOptions" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>GameMenuButton_Pending(self, SOUNDOPTIONS_MENU)</OnLoad></Scripts>
</Button>
<!-- ref: PlaySound("igMainMenuOption"); ShowUIPanel(UIOptionsFrame) -->
<Button name="GameMenuButtonUIOptions" inherits="GameMenuButtonTemplate">
<Anchors>
<Anchor point="TOP" relativeTo="GameMenuButtonSoundOptions" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>GameMenuButton_Pending(self, UIOPTIONS_MENU)</OnLoad></Scripts>
</Button>
<!-- ref: PlaySound("igMainMenuOption"); KeyBindingFrame_LoadUI(); ShowUIPanel(KeyBindingFrame) -->
<Button name="GameMenuButtonKeybindings" inherits="GameMenuButtonTemplate">
<Anchors>
<Anchor point="TOP" relativeTo="GameMenuButtonUIOptions" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>GameMenuButton_Pending(self, KEY_BINDINGS)</OnLoad></Scripts>
</Button>
<!-- ref: PlaySound("igMainMenuOption"); HideUIPanel(GameMenuFrame); ShowMacroFrame() -->
<Button name="GameMenuButtonMacros" inherits="GameMenuButtonTemplate">
<Anchors>
<Anchor point="TOP" relativeTo="GameMenuButtonKeybindings" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>GameMenuButton_Pending(self, MACROS)</OnLoad></Scripts>
</Button>
<!-- The three that are live. The ref's OnShow on both Logout and Quit disables the
button while either logout dialog is already up (you cannot re-arm a running
countdown); it runs on the menu's own show, which is when a re-opened menu has to
re-read that state. -->
<Button name="GameMenuButtonLogout" inherits="GameMenuButtonTemplate">
<Anchors>
<Anchor point="TOP" relativeTo="GameMenuButtonMacros" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
</Anchors>
<Scripts>
<OnLoad>self:SetText(LOGOUT)</OnLoad>
<OnShow>
if not StaticPopup_Visible("CAMP") and not StaticPopup_Visible("QUIT") then
self:Enable()
else
self:Disable()
end
</OnShow>
<OnClick>
PlaySound("igMainMenuLogout")
Logout()
HideUIPanel(GameMenuFrame)
</OnClick>
</Scripts>
</Button>
<Button name="GameMenuButtonQuit" inherits="GameMenuButtonTemplate">
<Anchors>
<Anchor point="TOP" relativeTo="GameMenuButtonLogout" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
</Anchors>
<Scripts>
<OnLoad>self:SetText(EXIT_GAME)</OnLoad>
<OnShow>
if not StaticPopup_Visible("CAMP") and not StaticPopup_Visible("QUIT") then
self:Enable()
else
self:Disable()
end
</OnShow>
<OnClick>
PlaySound("igMainMenuQuit")
Quit()
HideUIPanel(GameMenuFrame)
</OnClick>
</Scripts>
</Button>
<Button name="GameMenuButtonContinue" inherits="GameMenuButtonTemplate">
<Anchors>
<Anchor point="TOP" relativeTo="GameMenuButtonQuit" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-16"/></Offset></Anchor>
</Anchors>
<Scripts>
<OnLoad>self:SetText(RETURN_TO_GAME)</OnLoad>
<OnClick>
PlaySound("igMainMenuContinue")
HideUIPanel(GameMenuFrame)
</OnClick>
</Scripts>
</Button>
</Frames>
<!-- ref GameMenuFrame OnShow/OnHide, verbatim: the micro row's Main Menu button pushes in
while the menu is up, and the bag bar greys out under it (you cannot open bags from
behind the menu — ShowUIPanel's center gate would refuse the window anyway, so the
greying is the visible half of a rule the panel manager already enforces). Guarded, the
UiPanels.xml `CloseMenus and CloseMenus()` posture: a headless test loads this file
without the action bar behind it. -->
<Scripts>
<OnLoad>GameMenuFrameTitle:SetText(MAIN_MENU)</OnLoad>
<OnShow>
if UpdateMicroButtons then UpdateMicroButtons() end
if Disable_BagButtons then Disable_BagButtons() end
</OnShow>
<OnHide>
if UpdateMicroButtons then UpdateMicroButtons() end
if Enable_BagButtons then Enable_BagButtons() end
</OnHide>
</Scripts>
</Frame>
<!-- The camp/quit event listener (the ref's UIParent OnEvent arms — script block above). -->
<Frame name="BenillaLogoutDriver" hidden="true">
<Size><AbsDimension x="1" y="1"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
<Scripts>
<OnLoad>
self:RegisterEvent("PLAYER_CAMPING")
self:RegisterEvent("PLAYER_QUITING")
self:RegisterEvent("LOGOUT_CANCEL")
</OnLoad>
<OnEvent>BenillaLogoutDriver_OnEvent()</OnEvent>
</Scripts>
</Frame>
</Ui>