- a corpse is a body you can hover, cursor and click: it enters the same ray pass as units with the winner switched on type at publish (the reference's own shape — one trace over every object), the cursor classifier is transcribed whole (lootable-by-me → the pickup cursor, grayed by range; else the insignia leg behind the learned-skill latch; your own body falls through to the clear), and right-click is "not mounted AND lootable" → CMSG_LOOT; CORPSE_FIELD_FLAGS bit 5 is the PvP insignia flag, not "reclaimable" as an old note had it; the widening also caught that a GameObject anywhere on screen could steal the click from a nearer corpse, and that a left-click on a body cleared your target as if you had clicked ground
- the cinematic sweep closes eleven holes: a shot plays its sequence band's WIDTH, not its end — two shipped fly-bys band away from zero and ran long with their opening frames frozen (the Night Elf intro measured 102333 → 102000 ms); CMSG_NEXT_CINEMATIC_CAMERA announces every shot, the first included — the old "between shots" reading meant it was sent NEVER, since every 1.12 sequence row carries exactly one camera; the ack invariant is stated once (one CMSG_COMPLETE_CINEMATIC per accepted trigger, over-acking tolerated, under-acking never — a dropped ack despawns the world around you until relog); the hardware cursor hides for the shot; and the music cut lands in the frame that starts it — it was one frame late, exactly long enough for a first login's zone fanfare to start under the narration and be stamped as played
- RunBinding exists, and a fly-by stops handing the world its mouse: CinematicFrame's OnKeyDown hands the SCREENSHOT chord back to its binding through RunBinding, a global we never had, so every press during a fly-by raised and took no picture; and the mouse was fully live under a shot — right-drag turned the character, clicks picked targets through the flying camera, both buttons walked the body — so the pointer now reads as over the UI while a shot plays
- AdvanceTime and ReplaceIconTexture are carved off the bytes: AdvanceTime really is a no-op in the reference (its one reachable call is mov eax,1; ret), ReplaceIconTexture is a material swap over a model's type-14 texture slots that stores nothing on the widget, and SetModel(nil) RAISES — the "documented clear" our binding claimed was invented, not read; and the addon roster now says WHOSE package is short when a .toc names files the folder doesn't hold — nine of the corpus's twenty-nine load failures are the addon's own manifest, which the real client also shrugs off ("Couldn't open %s", carry on), so they stop reading as our defects
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2101 lines
123 KiB
XML
2101 lines
123 KiB
XML
<Ui>
|
||
<!--
|
||
The UIPanel slot manager (decision 0084 §2) — benilla's own implementation of the
|
||
panel-window plumbing the reference keeps in UIParent.lua. Loaded FIRST in the shipped-XML
|
||
list (ui_script.rs) so `UIPanelWindows`/`ShowUIPanel`/`HideUIPanel` exist before any panel
|
||
frame's OnLoad/OnEvent references them.
|
||
|
||
Specification: the 1.12 `UIParent.lua` extract (patch-2.MPQ), cited by line number
|
||
throughout so every rule below can be checked against it. What is quoted VERBATIM is the
|
||
**data** — each `UIPanelWindows` row's `area`/`pushable`/`whileDead`, which IS the
|
||
reference's window arbitration (a different number is a different window order), and the
|
||
`UISpecialFrames` seeds. The code around that data is ours.
|
||
|
||
Scope: all four areas are live — `left`, `center`, `full` and `doublewide` — and so is
|
||
`UIChildWindows` (decision 1507: all four of the reference's child windows ship now).
|
||
`doublewide` was the last one out, on the honest grounds that its one 1.12 tenant is the
|
||
auction house and benilla didn't ship one; decision 1511 is the arc that does, so the slice
|
||
came with it rather than the window working around it. **`UISpecialFrames` is not ours to
|
||
write into** (decision 1206): it is the table an ADDON puts its own window in, and 34 corpus
|
||
call sites do.
|
||
|
||
Rows are keyed by the frame names we actually ship. Most of those ARE the reference's own
|
||
(`GossipFrame`, `MerchantFrame`, `LootFrame`); where ours differs, the row is `Benilla*` and
|
||
its comment names the reference row it stands in for.
|
||
-->
|
||
<Script><![CDATA[
|
||
-- UIParent.lua l.14-42: UIPanelWindows["FrameName"] = { area, pushable, whileDead }. Only
|
||
-- the rows benilla ships today; the ref rows with no benilla window yet (Tabard,
|
||
-- Battlefield, WorldStateScore, Minigame) join as their windows land (audited
|
||
-- row-by-row against the bytes, decision 1507). Four have since left that list: the
|
||
-- doublewide AuctionFrame with decision 1511, PetStableFrame with 1676, HelpFrame with
|
||
-- 1673, and Petition + GuildRegistrar with 1672 — all of them are rows below.
|
||
UIPanelWindows = {}
|
||
UIPanelWindows["GossipFrame"] = { area = "left", pushable = 0 }
|
||
UIPanelWindows["MerchantFrame"] = { area = "left", pushable = 0 }
|
||
-- The item-text reader (decision 0572, ItemTextFrame.xml): the ref's own l.20 verbatim
|
||
-- (`UIPanelWindows["ItemTextFrame"] = { area = "left", pushable = 0 }`). The missing row
|
||
-- WAS bug B288 (decision 1507): the reader already opened through ShowUIPanel, but
|
||
-- unregistered that call is a bare Show() in no slot — so a quest giver's gossip seated
|
||
-- itself at the left slot straight over the open note, page text and greeting interleaved.
|
||
-- Registered, the two are ordinary left-slot rivals (both pushable 0): one replaces the
|
||
-- other, and the replaced reader's own OnHide sends its CloseItemText().
|
||
UIPanelWindows["ItemTextFrame"] = { area = "left", pushable = 0 }
|
||
-- The mail window (decision 0544): the reference UIParent.lua l.37 registers MailFrame
|
||
-- area="left", pushable=0. The OpenMailFrame is NOT a panel — it is a UIChildWindow
|
||
-- (UIParent.lua l.46; 0544 misread that line as UISpecialFrames, 1507 corrects it): shown
|
||
-- through ShowUIPanel's bare-Show unregistered branch exactly as the ref shows it
|
||
-- (MailFrame.lua l.206), hidden by SetCenterFrame's child-window loop when a frame takes
|
||
-- the center seat, and closed with its parent by MailFrame's own OnHide. ESC still closes
|
||
-- it on our own hard-listed chain in ToggleGameMenu below — never via UISpecialFrames,
|
||
-- the ADDONS' list (1206), where an addon's `tremove` could break the client's own ESC.
|
||
UIPanelWindows["MailFrame"] = { area = "left", pushable = 0 }
|
||
-- The script error log (decision 1495) — OURS, with no reference row to transcribe. It is
|
||
-- registered here for exactly one reason: an unregistered frame takes ShowUIPanel's bare
|
||
-- `frame:Show()` branch (no left-slot placement, and `CloseAllWindows` never sees it), so
|
||
-- without this row the window would flip IsShown() without landing on screen and ESC would
|
||
-- not close it. `pushable = 9` puts it behind everything already registered: a diagnostic
|
||
-- must never shove a player's real window out of the slot it wanted. whileDead is ours by
|
||
-- the same logic (1507): a Lua error in the death flow is exactly one you need to read.
|
||
UIPanelWindows["BenillaScriptLogFrame"] = { area = "left", pushable = 9, whileDead = 1 }
|
||
-- The trade window (decision 0592 P1, TradeFrame.xml): the ref's own UIParent.lua l.27
|
||
-- registration (`UIPanelWindows["TradeFrame"] = { area = "left", pushable = 1 }`). Without
|
||
-- this row ShowUIPanel takes its unregistered branch — a bare frame:Show() with no left-slot
|
||
-- placement — so the window flips IsShown() but never lands on screen (the bug: OPEN_WINDOW
|
||
-- arrived on both clients, yet no window). pushable=1 lets a pushable=0 panel (a merchant)
|
||
-- shove it to center, the reference's browse-while-trading arbitration.
|
||
UIPanelWindows["TradeFrame"] = { area = "left", pushable = 1 }
|
||
-- The trainer window (decision 0237 phase 3, TrainerFrame.xml): an NPC session exactly like
|
||
-- Gossip/Merchant above (area "left", pushable 0) — competes for/replaces them at the slot.
|
||
UIPanelWindows["BenillaTrainerFrame"] = { area = "left", pushable = 0 }
|
||
-- The bank window (decision 0604 phase 4, BankFrame.xml): the ref's own UIParent.lua l.28
|
||
-- registration (`UIPanelWindows["BankFrame"] = { area = "left", pushable = 6 }`), quoted
|
||
-- verbatim — the same left-slot competition as the other NPC-session windows above.
|
||
UIPanelWindows["BankFrame"] = { area = "left", pushable = 6 }
|
||
-- The taxi-map window (decision 0484 phase 2, TaxiFrame.xml): the ref's own UIParent.lua
|
||
-- registration line (`UIPanelWindows["TaxiFrame"] = { area = "left", pushable = 0 }`),
|
||
-- quoted verbatim — same left-slot competition as Gossip/Merchant/Trainer above.
|
||
UIPanelWindows["TaxiFrame"] = { area = "left", pushable = 0 }
|
||
-- The stable-master window (decision 1676, PetStable.xml): the ref's own UIParent.lua l.39
|
||
-- registration line (`UIPanelWindows["PetStableFrame"] = { area = "left", pushable = 0 }`),
|
||
-- quoted verbatim — the same left-slot competition as Gossip/Merchant/Trainer/Taxi above,
|
||
-- which is what makes the stable master's gossip window give way to the stable it opens.
|
||
-- The reference lists it between MailFrame (l.37) and DressUpFrame (l.41), which is where
|
||
-- it sits here too, inside the NPC-session cluster those rows are grouped into.
|
||
UIPanelWindows["PetStableFrame"] = { area = "left", pushable = 0 }
|
||
-- The auction house (decision 1511, AuctionFrame.xml): the reference's own registration,
|
||
-- quoted verbatim from `Blizzard_AuctionUI.lua` l.13 rather than UIParent.lua — the window
|
||
-- ships as a loadable addon there, so it registers its own row. **The one and only
|
||
-- `doublewide` row in all of 1.12**: an 832-wide window takes the left AND centre seats at
|
||
-- once, which is why the slice above exists at all. Its `pushable = 0` is quoted and inert
|
||
-- — ShowUIPanel's doublewide arm never consults it (l.392-397).
|
||
UIPanelWindows["AuctionFrame"] = { area = "doublewide", pushable = 0 }
|
||
-- The dressing room (decision 1060, DressUpFrame.xml): the ref's own UIParent.lua l.41
|
||
-- registration (`UIPanelWindows["DressUpFrame"] = { area = "left", pushable = 2 }`), quoted
|
||
-- verbatim — pushable=2 lets the NPC-session windows (pushable=0) shove it aside, which is
|
||
-- what makes "ctrl-click a merchant's item" show both.
|
||
UIPanelWindows["DressUpFrame"] = { area = "left", pushable = 2 }
|
||
UIPanelWindows["QuestFrame"] = { area = "left", pushable = 0 }
|
||
-- The ref's own l.25 verbatim — whileDead carried since 1507: `UnitIsDead` is a live unit
|
||
-- binding now, so the flag decides, and checking your quest log while dead is 1.12's call.
|
||
UIPanelWindows["QuestLogFrame"] = { area = "left", pushable = 0, whileDead = 1 }
|
||
UIPanelWindows["LootFrame"] = { area = "left", pushable = 7 }
|
||
-- The character window (decision 0208 phase 1a, CharacterFrame.xml): the ref's own row is
|
||
-- `{ area = "left", pushable = 2 , whileDead = 1}` (UIParent.lua l.19, spacing and all) —
|
||
-- NOT the pushable=0 this line carried from 0208 until 1507 re-read the bytes. The 2 is
|
||
-- load-bearing arbitration: an NPC session (pushable 0) opening under an open character
|
||
-- sheet slides it to the center seat — vendor left, your equipment beside it — instead of
|
||
-- replacing it; and whileDead=1 lets you look your corpse's gear over while dead.
|
||
UIPanelWindows["CharacterFrame"] = { area = "left", pushable = 2, whileDead = 1 }
|
||
-- The inspect window (decision 0631, InspectFrame.xml): the ref's own InspectFrame row
|
||
-- (`Blizzard_InspectUI.lua:4` — area "left", pushable 0), so inspecting swaps out whichever
|
||
-- left-slot window was open, exactly as the character window does.
|
||
UIPanelWindows["BenillaInspectFrame"] = { area = "left", pushable = 0 }
|
||
-- The spellbook (decision 0216 §8, SpellBookFrame.xml): the ref's own SpellBookFrame row
|
||
-- verbatim (`{ area = "left", pushable = 0, whileDead = 1 }`, UIParent.lua l.21; whileDead
|
||
-- carried since 1507) — same left-slot competition as every window above.
|
||
UIPanelWindows["SpellBookFrame"] = { area = "left", pushable = 0, whileDead = 1 }
|
||
-- The talent window (decision 0304 §4, TalentFrame.xml): the ref's own TalentFrame row
|
||
-- verbatim (`{ area = "left", pushable = 6, whileDead = 1 }`, Blizzard_TalentUI.lua:71) —
|
||
-- pushable 6 one tier above the plain NPC-session/player-state windows' 0. whileDead was
|
||
-- omitted here while `UnitIsDead` didn't exist; it is a live unit binding now, so 1507
|
||
-- carries the ref's flag on every row that has one.
|
||
UIPanelWindows["BenillaTalentFrame"] = { area = "left", pushable = 6, whileDead = 1 }
|
||
-- The social window (decision 0668) — the ref's own registration line verbatim
|
||
-- (`UIPanelWindows["FriendsFrame"] = { area = "left", pushable = 0, whileDead = 1 }`,
|
||
-- UIParent.lua l.29). whileDead: a corpse run is exactly when you want to whisper someone.
|
||
UIPanelWindows["FriendsFrame"] = { area = "left", pushable = 0, whileDead = 1 }
|
||
-- The two guild-charter windows (decision 1672), the ref's own UIParent.lua l.33-34
|
||
-- verbatim. They are ordinary left-slot rivals of each other and of the gossip menu, all
|
||
-- pushable 0 — which is exactly right: selecting the registrar's gossip row makes the
|
||
-- server close the menu and push the charter list, so the registrar replacing the gossip
|
||
-- window in the same slot IS the handoff. Both must be registered or ShowUIPanel degrades
|
||
-- to a bare Show() in no slot, which is what left the item-text reader interleaved with a
|
||
-- gossip window in B288 (1507).
|
||
UIPanelWindows["GuildRegistrarFrame"] = { area = "left", pushable = 0 }
|
||
UIPanelWindows["PetitionFrame"] = { area = "left", pushable = 0 }
|
||
-- The macro window (decision 0983, MacroFrame.xml) — the ref's own registration line
|
||
-- verbatim (`UIPanelWindows["MacroFrame"] = { area = "left", pushable = 5, whileDead = 1 }`,
|
||
-- Blizzard_MacroUI.lua:7). whileDead is the ref's and it earns its keep: writing a macro
|
||
-- during a corpse run is a real thing people do.
|
||
UIPanelWindows["BenillaMacroFrame"] = { area = "left", pushable = 5, whileDead = 1 }
|
||
-- The crafting window (decision 0437 phase 2, TradeSkillFrame.xml): NOT an NPC session (it
|
||
-- opens off your own cast, TradeSkillFrame.xml's own header comment) — the ref Lua's own
|
||
-- registration line (Blizzard_TradeSkillUI.lua:13) is `{ area = "left", pushable = 3 }`,
|
||
-- quoted verbatim.
|
||
UIPanelWindows["BenillaTradeSkillFrame"] = { area = "left", pushable = 3 }
|
||
-- The Enchanting window (decision 0437 phase 3, CraftFrame.xml): the ref Lua's own
|
||
-- registration line (`Blizzard_CraftUI.lua:24`), quoted verbatim — `pushable = 4`, NOT `3`
|
||
-- (grepped both ref files directly to confirm: `3` is TradeSkillFrame's own line-13 value,
|
||
-- a different window/depth — CraftFrame.xml's own header comment has the same note).
|
||
UIPanelWindows["BenillaCraftFrame"] = { area = "left", pushable = 4 }
|
||
-- The game menu (decision 0674, GameMenuFrame.xml) — the ref's own registration line
|
||
-- verbatim (`UIPanelWindows["GameMenuFrame"] = { area = "center", pushable = 0,
|
||
-- whileDead = 1 }`, UIParent.lua l.15), and the FIRST **center**-area row benilla ships.
|
||
-- "center" is not a position here, it is a MODE: a native-center frame takes the center
|
||
-- slot keeping its own anchor (the menu is screen-CENTER by its own XML), closes every
|
||
-- other window on the way in, and — while it holds the slot — makes ShowUIPanel refuse
|
||
-- every non-center panel (`CanOpenPanels` below). That is the rule behind "you can't open
|
||
-- anything with the ESC menu up". whileDead: dying is exactly when you want to log out.
|
||
UIPanelWindows["GameMenuFrame"] = { area = "center", pushable = 0, whileDead = 1 }
|
||
-- The options window (decision 0950, OptionsFrame.xml) — the ref's own registration
|
||
-- verbatim (`UIPanelWindows["OptionsFrame"] = { area = "center", pushable = 0,
|
||
-- whileDead = 1 }`, UIParent.lua l.16): the second native-center row. Same MODE as the
|
||
-- menu above — it takes the screen, and dying is exactly when you might want to retune
|
||
-- the client. (The ref's l.17-18 register SoundOptionsFrame/UIOptionsFrame the same way;
|
||
-- those panels are this one window's category rows now, so the rows have no frames.)
|
||
UIPanelWindows["OptionsFrame"] = { area = "center", pushable = 0, whileDead = 1 }
|
||
-- The GM help window (HelpFrame.xml) — the ref's own registration verbatim
|
||
-- (`UIPanelWindows["HelpFrame"] = { area = "center", pushable = 0, whileDead = 1 }`,
|
||
-- UIParent.lua l.35): the THIRD native-center row, and the first one that is not part of
|
||
-- the settings shell. Same MODE as the two above — it keeps its own screen-CENTER anchor,
|
||
-- closes every other window on the way in, and while it holds the slot ShowUIPanel refuses
|
||
-- every non-center panel (`CanOpenPanels` below). That is deliberate here rather than
|
||
-- incidental: petitioning a GM is a full stop, not something you do beside your bags.
|
||
-- whileDead, because being dead somewhere you cannot leave is one of the things you page a
|
||
-- GM about — and it is what lets the ticket toast's HELP_TICKET dialog reach the window
|
||
-- from a corpse. (The ref's l.40 WorldStateScoreFrame row is center too; no frame here.)
|
||
UIPanelWindows["HelpFrame"] = { area = "center", pushable = 0, whileDead = 1 }
|
||
-- (0997's KeyBindingFrame row is gone — decision 1008 folded key bindings into the
|
||
-- options window above, the 1.15 layout's own placement; KeyBindingsPage.xml.)
|
||
-- The world map (decision 0203 phase 2) — the ref's own line verbatim (UIParent.lua l.30)
|
||
-- and the first **full** row. It was a plain toplevel until now, showing itself with a bare
|
||
-- `WorldMapFrame:Show()` and so bypassing this manager entirely: with the game menu up it
|
||
-- was the ONE window that still opened (director-reported). Routing its toggle through
|
||
-- ShowUIPanel is what subjects it to the same rules as everything else.
|
||
UIPanelWindows["WorldMapFrame"] = { area = "full", pushable = 0, whileDead = 1 }
|
||
-- The cinematic letterbox (CinematicFrame.xml) — the ref's own line verbatim
|
||
-- (UIParent.lua l.31). The row is what makes CINEMATIC_START's `ShowUIPanel(this, 1)`
|
||
-- actually seat the frame: unregistered, ShowUIPanel takes its bare-`Show()` branch and
|
||
-- the bars would appear without the panel manager ever closing what they cover. `full`
|
||
-- because a fly-by owns the screen, and no `whileDead` — the ref gives it none, and a
|
||
-- cinematic is not something a corpse needs.
|
||
UIPanelWindows["CinematicFrame"] = { area = "full", pushable = 0 }
|
||
|
||
-- UIChildWindows — the ref's own table verbatim (UIParent.lua l.44-50; its comment:
|
||
-- windows that "rely on a parent frame to be open. If the parent closes or a pushable
|
||
-- frame overlaps them they must be hidden."). Dropped as "none exist yet" until 1507 —
|
||
-- all four ship now: the open letter (MailFrame.xml) and the guild pane's three
|
||
-- satellites (FriendsFrame.xml). The parent-close half is each parent's own OnHide
|
||
-- (MailFrame hides the letter, FriendsFrame_OnHide hides the satellites); this table
|
||
-- serves the OVERLAP half — SetCenterFrame's hide loop below — because the child popups
|
||
-- sit exactly where the center seat puts a window.
|
||
UIChildWindows = {
|
||
"OpenMailFrame", "GuildControlPopupFrame",
|
||
"GuildMemberDetailFrame", "GuildInfoFrame",
|
||
}
|
||
|
||
-- The real client stores `.left`/`.center`/`.doublewide`/`.fullscreen` as fields directly on
|
||
-- the `UIParent` frame table (UIParent.lua l.789-885). benilla has no `UIParent` frame object
|
||
-- yet (a later decision's job) — so the two slots this v1 tracks live in a private chunk-local
|
||
-- table instead of squatting the `UIParent` global name; a future `CreateFrame("Frame",
|
||
-- "UIParent")` then lands cleanly on an empty global rather than silently coexisting with our
|
||
-- state. Every function below is defined in this same chunk, so they all close over it.
|
||
local slots = { left = nil, center = nil, fullscreen = nil, doublewide = nil }
|
||
|
||
-- SetLeftFrame(frame) — UIParent.lua l.809-824. Hides the old left occupant, SetPoints +
|
||
-- Shows the new one at the left slot's screen anchor (verified: TOPLEFT UIParent, 0, -104).
|
||
-- "UIParent" as the relativeTo name doesn't resolve to a real frame yet (none exists) — the
|
||
-- engine's SetPoint falls back to the frame's own parent-or-screen default, which for our
|
||
-- top-level panel frames IS the screen root, so this already anchors correctly; the string is
|
||
-- kept for fidelity/forward-compat with the day a real UIParent frame lands.
|
||
function SetLeftFrame(frame)
|
||
local oldFrame = slots.left
|
||
slots.left = frame
|
||
if oldFrame then
|
||
oldFrame:Hide()
|
||
end
|
||
if frame then
|
||
frame:SetPoint("TOPLEFT", "UIParent", "TOPLEFT", 0, -104)
|
||
frame:Show()
|
||
end
|
||
end
|
||
|
||
-- SetCenterFrame(frame, skipSetPoint) — UIParent.lua l.826-850, the `UIChildWindows` hide
|
||
-- loop (l.839-846) carried since 1507: a frame ARRIVING at the center seat lands exactly
|
||
-- where the child popups sit, so they are put away first. Arriving is the ref's exact
|
||
-- trigger — MovePanelToCenter's slide re-seats by direct assignment after a
|
||
-- SetCenterFrame(nil), so a PUSHED panel does not run the loop; kept as transcribed.
|
||
-- (The ref's `for index, value in UIChildWindows do` is Lua 5.0's iterator-less
|
||
-- generic-for, which our VM cannot run — decision 1202; `ipairs` is the same walk.)
|
||
function SetCenterFrame(frame, skipSetPoint)
|
||
local oldFrame = slots.center
|
||
slots.center = frame
|
||
if oldFrame then
|
||
oldFrame:Hide()
|
||
end
|
||
if frame then
|
||
frame:Show()
|
||
if not skipSetPoint then
|
||
frame:SetPoint("TOPLEFT", "UIParent", "TOPLEFT", 384, -104)
|
||
end
|
||
for _, name in ipairs(UIChildWindows) do
|
||
local childWindow = getglobal(name)
|
||
if childWindow then childWindow:Hide() end
|
||
end
|
||
end
|
||
end
|
||
|
||
-- MovePanelToLeft() — UIParent.lua l.868-875: the center occupant slides back to the left slot
|
||
-- (used when the left slot vacates and a "left-area" frame is holding center).
|
||
function MovePanelToLeft()
|
||
if slots.center then
|
||
SetLeftFrame(nil)
|
||
slots.center:SetPoint("TOPLEFT", "UIParent", "TOPLEFT", 0, -104)
|
||
slots.left = slots.center
|
||
slots.center = nil
|
||
end
|
||
end
|
||
|
||
-- MovePanelToCenter() — UIParent.lua l.877-885: the left occupant slides to the center slot
|
||
-- (used when a higher-`pushable` frame wants the left spot). `:Raise()` (l.881) has no benilla
|
||
-- frame-level equivalent yet — guarded, same posture as the pin's `UnitIsDead` guard below.
|
||
function MovePanelToCenter()
|
||
if slots.left then
|
||
SetCenterFrame(nil)
|
||
slots.left:SetPoint("TOPLEFT", "UIParent", "TOPLEFT", 384, -104)
|
||
if slots.left.Raise then
|
||
slots.left:Raise()
|
||
end
|
||
slots.center = slots.left
|
||
slots.left = nil
|
||
end
|
||
end
|
||
|
||
-- GetLeftFrame / GetCenterFrame — UIParent.lua l.916-922.
|
||
function GetLeftFrame()
|
||
return slots.left
|
||
end
|
||
|
||
function GetCenterFrame()
|
||
return slots.center
|
||
end
|
||
|
||
-- SetFullScreenFrame(frame) — UIParent.lua l.852-866. ONE line dropped: the reference also
|
||
-- does `UIParent:Hide()`/`:Show()` around the swap, hiding the whole game UI behind the
|
||
-- fullscreen frame.
|
||
--
|
||
-- BOTH of the reasons this note used to give are now FALSE (decision 1724). A real
|
||
-- `UIParent` frame exists (UIParent.xml), and the world map is no longer the only `full`
|
||
-- row: `CinematicFrame` is a second one, and it paints nothing by design. So this dropped
|
||
-- line is exactly what makes a cinematic hide the HUD through `crate::ui_hide::UiHidden`
|
||
-- instead of through here — and `UiHidden` also means "the UI takes no mouse", which is
|
||
-- ALT-Z's meaning and the reason the world took every click of a fly-by.
|
||
--
|
||
-- Restoring it needs the HUD parented to UIParent first (the 32 dropped `parent="UIParent"`
|
||
-- declarations 1724 measured); hiding UIParent today would reach almost nothing.
|
||
function SetFullScreenFrame(frame)
|
||
local oldFrame = slots.fullscreen
|
||
slots.fullscreen = frame
|
||
if oldFrame then
|
||
oldFrame:Hide()
|
||
end
|
||
if frame then
|
||
frame:Show()
|
||
end
|
||
end
|
||
|
||
function GetFullScreenFrame()
|
||
return slots.fullscreen
|
||
end
|
||
|
||
-- SetDoublewideFrame(frame) — UIParent.lua l.788-808. One frame takes the left AND center
|
||
-- seats at once: both incumbents are dropped (nil'd first, then hidden — the order matters,
|
||
-- since an OnHide that re-enters this manager must not find itself still seated), and the
|
||
-- new frame lands at the left slot's own screen anchor, because a doublewide window simply
|
||
-- starts where left starts and is wide enough to cover both.
|
||
--
|
||
-- Note what the reference does NOT do: `HideUIPanel` has no doublewide branch (l.754-786),
|
||
-- so nothing ever clears this seat — `UIParent.doublewide` keeps pointing at the auction
|
||
-- window forever once it has been opened, and every later ShowUIPanel re-hides an
|
||
-- already-hidden frame. We reproduce that rather than inventing a clear: `Hide()` on a
|
||
-- hidden frame fires nothing, so the difference is unobservable, and a "fix" here would be
|
||
-- a silent divergence in the one function every window's placement runs through.
|
||
function SetDoublewideFrame(frame)
|
||
local oldLeft = slots.left
|
||
local oldCenter = slots.center
|
||
slots.doublewide = frame
|
||
slots.left = nil
|
||
slots.center = nil
|
||
if oldLeft then
|
||
oldLeft:Hide()
|
||
end
|
||
if oldCenter then
|
||
oldCenter:Hide()
|
||
end
|
||
if frame then
|
||
frame:SetPoint("TOPLEFT", "UIParent", "TOPLEFT", 0, -104)
|
||
frame:Show()
|
||
end
|
||
end
|
||
|
||
-- GetDoublewideFrame — UIParent.lua l.924-926.
|
||
function GetDoublewideFrame()
|
||
return slots.doublewide
|
||
end
|
||
|
||
-- The area a frame is REGISTERED for — or nil, which is equally the answer for no frame at
|
||
-- all and for a frame this manager has never heard of. Four rules below turn on exactly
|
||
-- that question ("is a native-center window holding the screen?", "is the frame parked at
|
||
-- center a left-area one that should slide back?"), and all four want an absent frame and
|
||
-- an unregistered one to answer the same way. The reference open-codes the pair of nil
|
||
-- checks at each site, which is four chances to leave one of them out.
|
||
local function AreaOf(frame)
|
||
local info = frame and UIPanelWindows[frame:GetName()]
|
||
return info and info.area
|
||
end
|
||
|
||
-- CanOpenPanels() — UIParent.lua l.887-910. Its dead-player arms are commented out in the
|
||
-- reference itself (both of them, with the note that the fear/out-of-control gate was
|
||
-- "previously"); what survives is one rule: **a native-center frame open ⇒ nothing else
|
||
-- opens**. The game menu is the only such frame (decision 0674), so this reads: with the
|
||
-- ESC menu up, no window opens — not from a keybind, not from an NPC's OPEN_WINDOW.
|
||
function CanOpenPanels()
|
||
if AreaOf(GetCenterFrame()) == "center" then
|
||
return nil
|
||
end
|
||
return 1
|
||
end
|
||
|
||
-- Everything that can REFUSE an open, asked in the reference's own order (UIParent.lua
|
||
-- l.663-689). Answers false when the frame must not open. Not a pure predicate, and that
|
||
-- is the point: `force` is precisely the right to throw the incumbent out instead of being
|
||
-- turned away, so each refusal and its eviction are one decision and are written as one.
|
||
local function AdmitPanel(info, force)
|
||
-- A dead player gets only the windows that asked for it (l.663-666) — and hears why:
|
||
-- NotWhileDeadError is the binary's 0x48d340 (wow-re §5 cross-check, 2026-08-21 —
|
||
-- decision 1507's open item): push id 0x7e into the message catalog, whose row is
|
||
-- ERR_PLAYER_DEAD → UI_ERROR_MESSAGE, sound "NONE". Our binding (ui_errors.rs)
|
||
-- pushes the same key onto the engine's client-local error queue, which the app
|
||
-- resolves against GlobalStrings and fires as the same event. The binding is a pure
|
||
-- display trigger — this line, like the reference's, decides WHEN.
|
||
if UnitIsDead("player") and not info.whileDead then
|
||
NotWhileDeadError()
|
||
return false
|
||
end
|
||
-- l.668-675: nothing opens behind a full-screen frame. The world map is the only
|
||
-- `full` row we register, so this reads: nothing opens behind the map.
|
||
if GetFullScreenFrame() and info.area ~= "full" then
|
||
if not force then
|
||
return false
|
||
end
|
||
SetFullScreenFrame(nil)
|
||
end
|
||
-- l.677-689: with a native-center frame open, only another center request gets
|
||
-- through. Redundant with the caller's CanOpenPanels gate for anything that doesn't
|
||
-- force (the reference double-checks it too); kept because it is the ONLY path that
|
||
-- can displace the menu, and dropping it would silently turn `force` into a no-op.
|
||
-- No benilla caller passes `force` yet.
|
||
if AreaOf(GetCenterFrame()) == "center" and info.area ~= "center" then
|
||
if not force then
|
||
return false
|
||
end
|
||
SetCenterFrame(nil)
|
||
end
|
||
return true
|
||
end
|
||
|
||
-- Seating a **left-area** panel (l.719-751). The left slot is what it wants; `pushable`
|
||
-- settles what happens to whoever is already sitting there. Read the comparisons as "the
|
||
-- higher number is the one that gets moved along" — and two zeroes will not move for each
|
||
-- other at all, which is what makes one NPC window replace another outright.
|
||
local function SeatLeftAreaPanel(frame, info)
|
||
local leftFrame = GetLeftFrame()
|
||
if not leftFrame then
|
||
SetLeftFrame(frame)
|
||
return
|
||
end
|
||
|
||
local leftInfo = UIPanelWindows[leftFrame:GetName()]
|
||
local centerFrame = GetCenterFrame()
|
||
if centerFrame then
|
||
-- Both seats taken (l.744-751): outrank the center occupant and it slides back to
|
||
-- the left while we take center; otherwise settle for replacing the left one.
|
||
if info.pushable > UIPanelWindows[centerFrame:GetName()].pushable then
|
||
MovePanelToLeft()
|
||
SetCenterFrame(frame)
|
||
else
|
||
SetLeftFrame(frame)
|
||
end
|
||
elseif leftInfo.pushable == 0 and info.pushable == 0 then
|
||
-- Neither will budge — two NPC sessions — so we replace it outright (l.729-732,
|
||
-- the gossip<->merchant case).
|
||
SetLeftFrame(frame)
|
||
elseif leftInfo.pushable > info.pushable then
|
||
-- The incumbent outranks us, so it is promoted to center and we take the left slot
|
||
-- it vacates (l.735-741 — highest priority goes to center).
|
||
MovePanelToCenter()
|
||
SetLeftFrame(frame)
|
||
else
|
||
SetCenterFrame(frame)
|
||
end
|
||
end
|
||
|
||
-- ShowUIPanel(frame) — UIParent.lua l.649-751, now the WHOLE slice: left, center, full
|
||
-- and doublewide (pin §3 steps 1,3-5, plus the native-center mode decision 0674 needed and
|
||
-- the `full` slot the world map took in 0203/0674). `doublewide` (l.706-716) arrived with
|
||
-- the auction house, decision 1511 — the last arm below is now genuinely unreachable for a
|
||
-- registered row and stays only as the defensive fallback its comment says it is.
|
||
--
|
||
-- Two questions, in order: may it open at all, and where does it sit.
|
||
function ShowUIPanel(frame, force)
|
||
if not frame or frame:IsVisible() then
|
||
return
|
||
end
|
||
|
||
local info = UIPanelWindows[frame:GetName()]
|
||
-- l.655-657. Note the reference's own precedence: an UNREGISTERED frame is not gated
|
||
-- (the `info and` term), so a bare toplevel still shows with the menu up. The gate is
|
||
-- asked unconditionally, because addons replace CanOpenPanels and expect to be asked.
|
||
local panelsAllowed = CanOpenPanels()
|
||
if not panelsAllowed and info and info.area ~= "center" then
|
||
return
|
||
end
|
||
if not info then
|
||
frame:Show()
|
||
return
|
||
end
|
||
if not AdmitPanel(info, force) then
|
||
return
|
||
end
|
||
|
||
if info.area == "full" then
|
||
-- l.692-696: full-screen frames just replace each other, and they take everything
|
||
-- else with them on the way in.
|
||
CloseAllWindows()
|
||
SetFullScreenFrame(frame)
|
||
elseif info.area == "center" then
|
||
-- l.697-702: native-center frames likewise replace each other and take the
|
||
-- screen. `skipSetPoint` is the load-bearing argument here — the menu keeps its
|
||
-- own CENTER anchor instead of being dropped at the left-area seat.
|
||
CloseWindows()
|
||
CloseAllBags()
|
||
SetCenterFrame(frame, 1)
|
||
elseif info.area == "doublewide" then
|
||
-- l.706-710: it wants both seats, so it takes both and nothing else is consulted
|
||
-- — including its own `pushable`, which is why the auction house's registered 0
|
||
-- never means anything.
|
||
SetDoublewideFrame(frame)
|
||
elseif info.area == "left" then
|
||
-- l.712-716: a doublewide incumbent is squatting the seat we are about to use, so
|
||
-- it goes first. In the reference this is a bare statement between the branches
|
||
-- rather than part of one, but every earlier arm has already returned by then, so
|
||
-- the LEFT path is the only one that reaches it — which is why it lives here. And
|
||
-- since `HideUIPanel` never clears the slot, this is a plain Hide of a frame that
|
||
-- may well already be hidden — see SetDoublewideFrame's note.
|
||
local doublewide = GetDoublewideFrame()
|
||
if doublewide then
|
||
doublewide:Hide()
|
||
end
|
||
SeatLeftAreaPanel(frame, info)
|
||
else
|
||
-- An area string this manager does not know: defensive fallback only.
|
||
frame:Show()
|
||
end
|
||
end
|
||
|
||
-- HideUIPanel(frame) — UIParent.lua l.754-786. Which seat is the frame sitting in? That is
|
||
-- the whole function: vacate that one. A frame in no seat at all was never this manager's
|
||
-- to place, so it is simply hidden.
|
||
function HideUIPanel(frame)
|
||
if not frame or not frame:IsShown() then
|
||
return
|
||
end
|
||
|
||
if frame == GetFullScreenFrame() then
|
||
SetFullScreenFrame(nil)
|
||
elseif frame == GetCenterFrame() then
|
||
SetCenterFrame(nil)
|
||
elseif frame == GetLeftFrame() then
|
||
-- l.777-782: a LEFT-area panel parked at center (it was pushed there) slides back
|
||
-- into the seat we are giving up, rather than being stranded beside an empty left
|
||
-- slot. A native-center frame stays where it is.
|
||
if AreaOf(GetCenterFrame()) == "left" then
|
||
MovePanelToLeft()
|
||
else
|
||
SetLeftFrame(nil)
|
||
end
|
||
else
|
||
-- Seated nowhere: a plain toplevel, or an addon's frame we never placed.
|
||
frame:Hide()
|
||
end
|
||
end
|
||
|
||
-- UISpecialFrames (UIParent.lua l.52-55) — **the addon's own ESC list**, and the reason
|
||
-- this stopped being "dropped: none exist yet" (decision 1206).
|
||
--
|
||
-- `tinsert(UISpecialFrames, "MyFrame")` is THE 1.12 idiom for "ESC should close my
|
||
-- window", and it is 34 call sites across the corpus. With the table absent that line is
|
||
-- `bad argument #1 to 'tinsert' (table expected, got nil)` — which killed 11 addons at
|
||
-- load, the second-largest wall in the survey after the Lua-5.0 generic-for.
|
||
--
|
||
-- Seeded with the reference's own two entries — **both** of them now. 1206 seeded only
|
||
-- `ItemRefTooltip`, and its Rejected section wrote down why, verbatim: *"Listing
|
||
-- `ColorPickerFrame`. The reference seeds it; we do not ship one, and a name that resolves
|
||
-- to nothing is skipped harmlessly — but seeding a frame we lack would be writing a claim
|
||
-- we cannot honour."* ColorPickerFrame.xml ships it, so the claim is honourable now, and
|
||
-- the entry is the reference's own rather than one of ours.
|
||
--
|
||
-- Note it does NOT contradict the paragraph above (our own escapable toplevels stay
|
||
-- hard-listed in the ESC chain). The concern there is an addon's `tremove` breaking the
|
||
-- CLIENT's own ESC; here the ESC chain carries the colour picker on its own rung — and has
|
||
-- to, because ESC on this window must CANCEL (restore the colour), which this walk's plain
|
||
-- `Hide` cannot do. So the table entry is not what makes ESC work; it is what makes
|
||
-- `CloseWindows` put the picker away when something ELSE closes the world's windows —
|
||
-- opening the game menu, a `CloseAllWindows()` — which is exactly the reference's own
|
||
-- behaviour on that path, cancel-less Hide and all.
|
||
UISpecialFrames = { "ItemRefTooltip", "ColorPickerFrame" }
|
||
|
||
-- CloseWindows(ignoreCenter) — UIParent.lua l.928-957. Both once-dropped slices are now
|
||
-- carried: the UISpecialFrames loop at l.947-954, and the doublewide seat (decision 1511).
|
||
function CloseWindows(ignoreCenter)
|
||
local fullScreenFrame = GetFullScreenFrame()
|
||
local doublewideFrame = GetDoublewideFrame()
|
||
local centerFrame = GetCenterFrame()
|
||
local leftFrame = GetLeftFrame()
|
||
|
||
HideUIPanel(leftFrame)
|
||
HideUIPanel(fullScreenFrame)
|
||
-- l.938. HideUIPanel has no doublewide branch, so this lands on its "seated nowhere"
|
||
-- arm and is a plain Hide; the seat itself is never vacated (SetDoublewideFrame's note).
|
||
HideUIPanel(doublewideFrame)
|
||
-- l.934, quoted as-is: `found` deliberately does NOT consider the doublewide frame,
|
||
-- even though the line above closes it. Reproduced rather than corrected — ESC's "did
|
||
-- I close anything?" answer is this value, and changing it would change which key
|
||
-- press opens the game menu. (Computed off the locals read before any Hide landed.)
|
||
local found = leftFrame or centerFrame or fullScreenFrame
|
||
-- `ignoreCenter` spares a NATIVE-center frame and nothing else: a left-area panel
|
||
-- merely parked at the center seat closes with the rest of them.
|
||
if centerFrame and not (ignoreCenter and AreaOf(centerFrame) == "center") then
|
||
HideUIPanel(centerFrame)
|
||
end
|
||
|
||
-- The special frames, the reference's own loop (l.947-954): a plain Hide, never
|
||
-- HideUIPanel, because these hold no panel slot. Its `for index, value in
|
||
-- UISpecialFrames do` is Lua 5.0's iterator-less generic-for, which our VM cannot run
|
||
-- (decision 1202) — `ipairs` is the same walk in the dialect we have.
|
||
for _, name in ipairs(UISpecialFrames) do
|
||
local frame = getglobal(name)
|
||
if frame and frame:IsVisible() then
|
||
frame:Hide()
|
||
found = 1
|
||
end
|
||
end
|
||
|
||
return found
|
||
end
|
||
|
||
-- CloseAllWindows(ignoreCenter) — UIParent.lua l.970-982. The real fn first loops
|
||
-- ContainerFrame1..NUM_CONTAINER_FRAMES to hide any open bags, then calls CloseWindows() for
|
||
-- the panel slots, returning whether anything closed. benilla ships a single backpack window
|
||
-- (BenillaBagFrame, decision 0068 T2), so that loop collapses to hiding it. The bag is NOT a
|
||
-- UIPanel (no slot registration) — it must be closed here explicitly, exactly as the real
|
||
-- client closes its ContainerFrames outside the slot system. Naming BenillaBagFrame here
|
||
-- mirrors UIParent.lua naming ContainerFrame in the very same function.
|
||
-- The five bag windows (the real fn's ContainerFrame1..NUM_CONTAINER_FRAMES): the backpack
|
||
-- plus the four equipped-bag windows. Chunk-LOCAL rather than BagFrame.xml's own
|
||
-- BENILLA_BAG_WINDOWS global, so every user below stays nil-safe on a surface without the
|
||
-- bag file (a headless panel test) — each still resolves per window through `getglobal`.
|
||
-- (It was three identical inline copies until the game menu needed a fourth.)
|
||
local BAG_WINDOWS = { "BenillaBagFrame", "BenillaBagFrame1", "BenillaBagFrame2",
|
||
"BenillaBagFrame3", "BenillaBagFrame4" }
|
||
-- ESC closes the KEYRING too, but nothing else here does. The ref's split, exactly: its
|
||
-- CloseAllWindows loops every ContainerFrame and hides whatever is shown (the keyring
|
||
-- included), while CloseAllBags/OpenBackpack/CloseBackpack are id-driven over the real bags
|
||
-- and never see container -2 (decision 0765). So the keyring is appended for the ESC path
|
||
-- alone — a vendor opening your bags must not open your keyring with them.
|
||
local ESC_CLOSES = { "BenillaBagFrame", "BenillaBagFrame1", "BenillaBagFrame2",
|
||
"BenillaBagFrame3", "BenillaBagFrame4", "BenillaKeyRingFrame" }
|
||
|
||
function CloseAllWindows(ignoreCenter)
|
||
local bagsVisible = nil
|
||
-- Hide every open bag window. Now that 'B' opens all bags at once (BagFrame.xml
|
||
-- BenillaBagToggle_OnClick), ESC must close all of them, not just the backpack. The bag
|
||
-- windows are NOT UIPanels (no slot registration) — they must be closed here explicitly,
|
||
-- exactly as the real client closes its ContainerFrames outside the slot system.
|
||
for _, name in ipairs(ESC_CLOSES) do
|
||
local bag = getglobal(name)
|
||
if bag and bag:IsShown() then
|
||
bag:Hide()
|
||
bagsVisible = 1
|
||
end
|
||
end
|
||
local windowsVisible = CloseWindows(ignoreCenter)
|
||
return bagsVisible or windowsVisible
|
||
end
|
||
|
||
-- CloseAllBags() — the ref ContainerFrame.lua l.702-707 (`CloseBackpack()` + `CloseBag(1..N)`),
|
||
-- called by ShowUIPanel's native-center branch above. Distinct from CloseBackpack below on
|
||
-- purpose: this is the UNCONDITIONAL close (the ref's CloseBag ignores the was-open memory),
|
||
-- because a center frame takes the whole screen — nothing is being "restored afterwards"
|
||
-- the way a vendor window restores the bags it opened.
|
||
function CloseAllBags()
|
||
for _, name in ipairs(BAG_WINDOWS) do
|
||
local bag = getglobal(name)
|
||
if bag and bag:IsShown() then
|
||
bag:Hide()
|
||
end
|
||
end
|
||
end
|
||
|
||
-- OpenBackpack / CloseBackpack — the real ContainerFrame.lua globals (l.187-217) the
|
||
-- vendor/bank/mail/trade windows call from their OnShow/OnHide so your bags open with them
|
||
-- (playing igBackPackOpen/Close alongside the window's own panel sound — the two "go
|
||
-- together"), and you can sell/deposit from the open bag. Homed here in benilla's panel-glue
|
||
-- file (rather than the bag's own, as in the real client) so every window that opens can call
|
||
-- them regardless of FrameXML load order — the windows are looked up by name. The real fn
|
||
-- tracks `backpackWasOpen` so a bag you already had open is LEFT open on the window's close;
|
||
-- benilla stores that on each frame (`wasOpen`). benilla DIVERGES from the ref here on
|
||
-- purpose: the real OpenBackpack touches only the backpack, but ours opens EVERY equipped
|
||
-- bag with the window (director's call — decision 0561; its stated reason was matching the
|
||
-- 'B' all-bags toggle, which 1494 split apart — the call itself stands on its own),
|
||
-- so the names keep the ref's caller contract while the body loops the five windows. Every
|
||
-- step is guarded, so a surface without the bag file — a headless panel test — no-ops
|
||
-- rather than erroring.
|
||
function OpenBackpack()
|
||
for bagId = 0, 4 do
|
||
-- The backpack (bag 0) always exists; an equipped-bag window only when a bag is in
|
||
-- the slot (the same existence test as BagFrame.xml's OpenAllBags).
|
||
local exists = bagId == 0
|
||
or (GetContainerNumSlots and GetContainerNumSlots(bagId) > 0)
|
||
local bag = exists and getglobal(BAG_WINDOWS[bagId + 1])
|
||
if bag then
|
||
if bag:IsShown() then
|
||
bag.wasOpen = 1 -- already open: leave it, and don't close it on window hide
|
||
else
|
||
bag.wasOpen = nil
|
||
BenillaBagWindow_Repaint(bag)
|
||
bag:Show() -- OnShow → igBackPackOpen
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
function CloseBackpack()
|
||
for _, name in ipairs(BAG_WINDOWS) do
|
||
local bag = getglobal(name)
|
||
if bag and bag:IsShown() and not bag.wasOpen then
|
||
bag:Hide() -- OnHide → igBackPackClose
|
||
end
|
||
end
|
||
end
|
||
|
||
-- ToggleGameMenu(clicked) — UIParent.lua l.1465-1497, the ESCAPE binding's action
|
||
-- (TOGGLEGAMEMENU), run by the host (crates/benilla ui_script/input.rs) when no EditBox
|
||
-- consumed the key, and by the Main Menu micro button with `clicked = 1`. It was
|
||
-- benilla's own `BenillaOnEscape` while there was no menu to open (decision 0674 retires
|
||
-- that name — this IS the reference function now, and an addon-visible one).
|
||
--
|
||
-- TWO behaviours in one function, which is the reference's own shape:
|
||
-- · `clicked` (the micro button, l.1466-1480) — a TOGGLE. Menu up ⇒ close it; menu down ⇒
|
||
-- CloseMenus() + CloseAllWindows() first, then open. It never cancels a cast or drops a
|
||
-- target: clicking a button is not "get me out of this".
|
||
-- · no argument (the ESC key, l.1482-1496) — the LADDER, one eater per press, in order:
|
||
-- if ( StaticPopup_EscapePressed() ) then
|
||
-- elseif ( OptionsFrame:IsVisible() ) then OptionsFrameCancel:Click()
|
||
-- elseif ( GameMenuFrame:IsVisible() ) then PlaySound("igMainMenuQuit"); HideUIPanel(GameMenuFrame)
|
||
-- elseif ( CloseMenus() ) then
|
||
-- elseif ( SpellStopCasting() ) then
|
||
-- elseif ( SpellStopTargeting() ) then
|
||
-- elseif ( CloseAllWindows() ) then -- l.1491
|
||
-- elseif ( ClearTarget() ) then
|
||
-- else PlaySound("igMainMenuOpen"); ShowUIPanel(GameMenuFrame) -- l.1494-1495
|
||
-- The ORDER is the law (the director's two-press report, decision 0449): ESC mid-cast
|
||
-- cancels the cast and ONLY the cast; the target drops on a later press once nothing
|
||
-- else claims it; and only a press with NOTHING left to eat opens the menu.
|
||
--
|
||
-- Every reference branch is present now (the OptionsFrame rung landed with decision
|
||
-- 0950 — ours hides rather than cancel-clicks; its own comment below says why). ONE
|
||
-- benilla ADDITION, ahead of everything: a held cursor item is
|
||
-- dropped (ClearCursor). The cursor-item model (decision 0068 T2) has no real-client ESC
|
||
-- handling to mirror — vanilla leaves the item on the cursor — so this is a deliberate
|
||
-- benilla convenience (ESC as the universal "get me out" key). Guarded so it stays inert
|
||
-- were the cursor slice ever absent. Three further benilla rungs sit between the popup and
|
||
-- CloseMenus (stack split, world map, open mail); each is commented at its own line with
|
||
-- the reference mechanism it stands in for.
|
||
function ToggleGameMenu(clicked)
|
||
-- Looked up rather than indexed off the global: a headless panel test loads this file
|
||
-- without GameMenuFrame.xml behind it, and the reference's bare `GameMenuFrame:` would
|
||
-- abort the whole chain there (the `CloseMenus and` guard two rungs down, same reason).
|
||
local menu = getglobal("GameMenuFrame")
|
||
|
||
-- The micro button's toggle (l.1466-1480). Its own first move is the ref's too
|
||
-- (l.1467-1469): an open options window is put away before the menu toggles.
|
||
if clicked then
|
||
local options = getglobal("OptionsFrame")
|
||
if options and options:IsVisible() then
|
||
HideUIPanel(options)
|
||
end
|
||
if menu and menu:IsVisible() then
|
||
PlaySound("igMainMenuQuit")
|
||
HideUIPanel(menu)
|
||
elseif menu then
|
||
if CloseMenus then CloseMenus() end
|
||
CloseAllWindows()
|
||
PlaySound("igMainMenuOpen")
|
||
ShowUIPanel(menu)
|
||
end
|
||
return
|
||
end
|
||
|
||
if CursorHasItem and CursorHasItem() then
|
||
ClearCursor()
|
||
end
|
||
-- An open escapable dialog eats the ESC first (the ref's StaticPopup_EscapePressed —
|
||
-- defined with the popup engine below): OnCancel(data, "clicked") + hide, but ONLY for
|
||
-- hideOnEscape entries, so e.g. the DEATH release popup ignores ESC like the ref's.
|
||
if StaticPopup_EscapePressed() then
|
||
return
|
||
end
|
||
-- The colour picker next (ColorPickerFrame.xml). The reference hooks ESC on the frame
|
||
-- itself (`<OnKeyDown>` l.177-184, whose ESCAPE arm is `HideUIPanel(this)` +
|
||
-- `cancelFunc(previousValues)`); that frame does not transcribe its OnKeyDown (see its
|
||
-- own header — the engine can deliver one since 1319; the deferral is now about the
|
||
-- existence gate, not the engine), so it routes through this shared chain like the
|
||
-- world map does. **Cancel-CLICK, not hide** — the ref's own arm restores the previous colour, and
|
||
-- clicking the real button is how the options rung below already spells this (the ref's
|
||
-- `OptionsFrameCancel:Click()`), so there is one cancel path, not two.
|
||
local picker = getglobal("ColorPickerFrame")
|
||
if picker and picker:IsVisible() then
|
||
ColorPickerCancelButton:Click()
|
||
return
|
||
end
|
||
-- The options window next (l.1483-1484, live since decision 0950 — this rung sat
|
||
-- unfilled while GameMenuFrame.xml's disabled buttons were the gap). The ref
|
||
-- cancel-clicks (`OptionsFrameCancel:Click()`); ours hides: benilla applies changes
|
||
-- live, there is no pending-change model for a Cancel to roll back, so putting the
|
||
-- window away IS the whole gesture — and since 1008 that covers the Keybindings
|
||
-- page too (live-commit; its OnHide hook disarms a straggling bind capture, and
|
||
-- while a capsule IS armed the host's capture seam swallows ESC before this chain
|
||
-- ever exists — the press becomes a BIND, 1.12's law). Looked up like the menu
|
||
-- above (a headless panel test loads this file without OptionsFrame.xml behind it).
|
||
local options = getglobal("OptionsFrame")
|
||
if options and options:IsVisible() then
|
||
HideUIPanel(options)
|
||
return
|
||
end
|
||
-- The menu itself closes next (l.1485-1487) — before every close/cancel rung below,
|
||
-- so ESC with the menu up only puts the menu away. Note the sound: closing by ESC
|
||
-- plays igMainMenuQuit, the same kit the Exit Game button uses — the reference's
|
||
-- choice, not a copy/paste slip here.
|
||
if menu and menu:IsVisible() then
|
||
PlaySound("igMainMenuQuit")
|
||
HideUIPanel(menu)
|
||
return
|
||
end
|
||
-- The stack-split spinner next (decision 0216 §6/slice 2, StackSplit.xml): the real
|
||
-- client's own StackSplitFrame hooks ESC directly (its OnKeyDown, `enableKeyboard="true"`)
|
||
-- — this engine has no plain-frame keyboard capture (StackSplit.xml's own header comment),
|
||
-- so it routes through this shared chain instead. Just hides (StackSplitFrame_OnHide
|
||
-- clears the owner's hasStackSplit flag) — no accept, matching a real Cancel click.
|
||
local split = getglobal("StackSplitFrame")
|
||
if split and split:IsVisible() then
|
||
split:Hide()
|
||
return
|
||
end
|
||
-- The fullscreen world map next (the ref's OnKeyDown ESCAPE arm — WorldMapFrame.xml
|
||
-- l.628; ours routes through this chain rather than transcribing that frame's own
|
||
-- OnKeyDown, which the engine could deliver since 1319).
|
||
local worldmap = getglobal("WorldMapFrame")
|
||
if worldmap and worldmap:IsVisible() then
|
||
-- HideUIPanel, not a bare Hide: the map holds the full-screen slot (decision 0203's
|
||
-- `full` row, wired 0674), and hiding it behind the manager's back would leave the
|
||
-- slot pointing at an invisible frame — which then refuses every future panel.
|
||
HideUIPanel(worldmap)
|
||
return
|
||
end
|
||
-- The open-letter toplevel (decision 0544): the ref registers OpenMailFrame in
|
||
-- UISpecialFrames (UIParent.lua l.46), so ESC closes it BEFORE the mail panel itself —
|
||
-- routed through this chain since this v1 drops the UISpecialFrames table. Its OnHide
|
||
-- runs the taken-letter auto-delete + close sound (MailFrame.lua OpenMailFrame_OnHide).
|
||
local openmail = getglobal("OpenMailFrame")
|
||
if openmail and openmail:IsVisible() then
|
||
HideUIPanel(openmail)
|
||
return
|
||
end
|
||
-- The ref chain's CloseMenus leg (UIParent.lua l.1488, defined with the dropdown
|
||
-- engine): an open right-click/dropdown menu closes and eats the press — BEFORE the
|
||
-- cast cancel, so ESC with a menu up spares a running cast, exactly the ref order.
|
||
-- Guarded like CursorHasItem above: inert when the dropdown slice isn't loaded.
|
||
if CloseMenus and CloseMenus() then
|
||
return
|
||
end
|
||
-- The ref chain's SpellStopCasting leg (UIParent.lua l.1489): ESC stops the FIRST
|
||
-- of {running auto-repeat, in-flight cast} and eats the press — windows close on
|
||
-- the NEXT press, exactly the reference order. A CHANNEL doesn't answer (nil): the
|
||
-- ref's 0x6e6e80 can't stop one — the vanilla "/stopcasting can't stop a channel"
|
||
-- quirk (wow-re esc-stopcasting.md, decision 0454) — so mid-channel the press
|
||
-- falls through. Truthy only when something was actually stopped (the host's
|
||
-- stoppable mirror), so an idle ESC still falls through to CloseAllWindows.
|
||
if SpellStopCasting() then
|
||
return
|
||
end
|
||
-- The ref chain's SpellStopTargeting leg (UIParent.lua l.1490, decision 0792): ESC
|
||
-- with the ground-targeting cursor up cancels the targeting — word cleared, no
|
||
-- packet — and eats the press. After the cast rung, exactly the artifact's order
|
||
-- (while merely targeting nothing is in flight, so SpellStopCasting answered nil).
|
||
if SpellStopTargeting() then
|
||
return
|
||
end
|
||
-- CloseAllWindows eats the press when it closed anything (l.1491); only a press with
|
||
-- nothing left to close reaches ClearTarget (l.1492) — the ref's last eater, and the
|
||
-- ONLY route to the target drop: the raw-key Esc clear the app used to run beside
|
||
-- this chain double-fired on the first press (decision 0449 retires it).
|
||
if CloseAllWindows() then
|
||
return
|
||
end
|
||
if ClearTarget() then
|
||
return
|
||
end
|
||
-- Nothing left to eat (l.1493-1495): the press opens the menu. ClearTarget answering
|
||
-- nil is what gets us here — which is why its `1`/nil return is load-bearing, and why
|
||
-- the ladder above must never return an unconditional truthy.
|
||
if menu then
|
||
PlaySound("igMainMenuOpen")
|
||
ShowUIPanel(menu)
|
||
end
|
||
end
|
||
|
||
-- ── The inspect-cursor pair (ref UIParent.lua l.1805-1817) ────────────────────────────
|
||
-- `CursorUpdate` is the ctrl-hover "can I inspect this?" cursor swap, and `CursorOnUpdate`
|
||
-- is the OnUpdate wrapper that only re-runs it while the tooltip is still ours. Both are
|
||
-- **FrameXML, not engine** (`reference/1.12-globals.tsv` marks them `framexml`, while
|
||
-- `ResetCursor` and `ShowInspectCursor` beside them are `engine`) — so they are ours to
|
||
-- write, and every piece they reach for already resolves here: `IsControlKeyDown`,
|
||
-- `ShowInspectCursor`, `ResetCursor` and `GameTooltip:IsOwned` are all present.
|
||
--
|
||
-- Two corpus addons call `CursorUpdate` (AdvancedTradeSkillWindow, WhoHas) and one calls
|
||
-- `CursorOnUpdate`. It is also a blocker — one of several — for two reference templates
|
||
-- addons inherit (`GroupLootFrameTemplate`, `ContainerFrameItemButtonTemplate`), and it is
|
||
-- called by the SOURCED `ContainerFrame.lua`'s keyring fork.
|
||
--
|
||
-- `this.hasItem` is read, not required: the reference tests it as a plain truthy field the
|
||
-- hovering frame sets for itself, so a frame that never sets it simply takes the
|
||
-- `ResetCursor` arm — which is why this is safe to ship before anything sets it.
|
||
function CursorUpdate()
|
||
if ( IsControlKeyDown() and this.hasItem ) then
|
||
ShowInspectCursor()
|
||
else
|
||
ResetCursor()
|
||
end
|
||
end
|
||
|
||
function CursorOnUpdate()
|
||
if ( GameTooltip:IsOwned(this) ) then
|
||
CursorUpdate()
|
||
end
|
||
end
|
||
|
||
-- SetDesaturation(texture, desaturation) — ref UIParent.lua l.1499-1510, plus two guards
|
||
-- it does not have: a nil texture, and a texture carrying no `SetDesaturated` method at
|
||
-- all (the reference calls it unconditionally, l.1501; ours keeps the guard for a region
|
||
-- leaf that never grew the verb). Since decision 1327 the engine HAS a desaturating
|
||
-- shader, so `shaderSupported` is true and the greyscale arm is the one taken — the
|
||
-- vertex-colour branch below is now the ref's own no-shader fallback, kept because it is
|
||
-- the ref's and because a texture without the method still has to grey somehow. Written as
|
||
-- the wrapper rather than open-coded at each call site precisely so that flip was one
|
||
-- function. First caller here is the game menu's bag-bar greying (decision 0674);
|
||
-- PartyFrame.xml's disconnected-member branch has been CALLING this global since decision
|
||
-- 0434 with nothing defining it — an error thrown every time a party member went offline,
|
||
-- which is why it went unnoticed.
|
||
function SetDesaturation(texture, desaturation)
|
||
if not texture then return end
|
||
local shaderSupported = texture.SetDesaturated and texture:SetDesaturated(desaturation)
|
||
if not shaderSupported then
|
||
if desaturation then
|
||
texture:SetVertexColor(0.5, 0.5, 0.5)
|
||
else
|
||
texture:SetVertexColor(1.0, 1.0, 1.0)
|
||
end
|
||
end
|
||
end
|
||
|
||
-- ── The shared dialog IS the ref's StaticPopup engine (decision 0308 §3) ─────────────────
|
||
-- Re-expressed from the patch-chain StaticPopup.lua (mpqx extraction, decision 0246):
|
||
-- the StaticPopupDialogs registry + StaticPopup_Show(which, arg1, arg2) + the OnUpdate
|
||
-- countdown/StartDelay machinery — the death arc's dialogs exercise nearly all of it, and
|
||
-- StaticPopup_Show/StaticPopupDialogs are 1.14 addon-API surface (0068). v1 scope:
|
||
-- · TWO dialog instances (STATICPOPUP_NUMDIALOGS = 2; the ref runs 4) — the deepest
|
||
-- nesting exercised so far is decline-res re-showing DEATH (Show precedes the click's
|
||
-- hide, so a free second instance is required). Grow toward 4 when a third coexists.
|
||
-- · info fields honored: text, button1, button2, DisplayButton2, OnShow(data),
|
||
-- OnHide(data), OnAccept(data, data2) (truthy return = keep open — RECOVER_CORPSE
|
||
-- waits for the server, XP_LOSS's first Accept re-asks), OnCancel(data, reason)
|
||
-- (reason ∈ "clicked"|"timeout"|"override"), OnUpdate(elapsed, dialog), timeout,
|
||
-- StartDelay, delayText, sound, whileDead, hideOnEscape, cancels, showAlert (the
|
||
-- 420 widen + DialogAlertIcon, decision 1068).
|
||
-- · hasEditBox + maxLetters + EditBoxOnEnterPressed are live (decision 0668's
|
||
-- ADD_FRIEND/ADD_IGNORE are the first customers): the box shows, caps its length,
|
||
-- opens empty, and re-anchors the buttons under itself.
|
||
-- · hasWideEditBox is live too (the guild arc's SET_GUILDMOTD / SET_GUILDPLAYERNOTE /
|
||
-- SET_GUILDOFFICERNOTE, registered in FriendsFrame.xml): the 350x64 centred box
|
||
-- replaces the narrow one, and the dialog widens to 420. See the widen arm below for
|
||
-- the reference's own oddity — the widen is keyed on `which`, not on hasWideEditBox.
|
||
-- · hasMoneyFrame is live (decision 1580's CONFIRM_TALENT_WIPE is the first customer,
|
||
-- and the reference's other five are all price confirms): a SmallMoneyFrameTemplate
|
||
-- child under the text, shown per entry, with the buttons dropping to -24 to clear it
|
||
-- and the frame growing 16px. The AMOUNT is not the engine's — the entry's own
|
||
-- OnShow/the event arm calls MoneyFrame_Update, exactly as the reference does.
|
||
-- · interruptCinematic is LIVE as of the cinematic arc: a dialog without it is
|
||
-- refused while one plays, the ref's own l.1454 gate (it was inert only while
|
||
-- 0196 acked every cinematic instantly, so nothing could be playing).
|
||
-- · Accepted but v1-inert: exclusive, notClosableByLogout, preferredIndex.
|
||
-- · text_arg1/2 stash on the DIALOG frame (the ref stashes them on the Text region;
|
||
-- our regions don't carry Lua fields).
|
||
-- · GetText("SECONDS"/"MINUTES") localization collapses to English singular/plural.
|
||
-- · Button widths stay the template's fixed 120 (the ref widens past 120/110 via
|
||
-- GetTextWidth; the widen law joins when a longer label first needs it).
|
||
-- · Resize runs per tick while shown, not only on text swaps: GetHeight's measure has a
|
||
-- frame of latency (the measured-width convention below), so the height self-corrects
|
||
-- the frame after any text change.
|
||
STATICPOPUP_NUMDIALOGS = 2
|
||
StaticPopupDialogs = {}
|
||
|
||
local function StaticPopupTimeUnit(n, minutes)
|
||
if minutes then
|
||
if n == 1 then return "minute" end
|
||
return "minutes"
|
||
end
|
||
if n == 1 then return "second" end
|
||
return "seconds"
|
||
end
|
||
|
||
function StaticPopup_FindVisible(which)
|
||
for index = 1, STATICPOPUP_NUMDIALOGS do
|
||
local frame = getglobal("StaticPopup" .. index)
|
||
if frame:IsShown() and frame.which == which then
|
||
return frame
|
||
end
|
||
end
|
||
return nil
|
||
end
|
||
|
||
-- Ref l.1870-1877 (returns the frame NAME — the ref's contract).
|
||
function StaticPopup_Visible(which)
|
||
local frame = StaticPopup_FindVisible(which)
|
||
if frame then
|
||
return frame:GetName()
|
||
end
|
||
return nil
|
||
end
|
||
|
||
function StaticPopup_Hide(which)
|
||
for index = 1, STATICPOPUP_NUMDIALOGS do
|
||
local frame = getglobal("StaticPopup" .. index)
|
||
if frame:IsShown() and frame.which == which then
|
||
frame:Hide()
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Ref l.1428-1438, all three legs.
|
||
--
|
||
-- Note the ref measures the NARROW `EditBox` for the height even when the shown box is the
|
||
-- wide one — the wide box is 64 tall, anchored CENTER, and floats over a frame sized for a
|
||
-- 32-tall box. Transcribed as written: the guild MOTD dialog's look is that arithmetic.
|
||
--
|
||
-- The money leg does NOT measure the coin row either: it is the plain height plus a flat
|
||
-- 16, which is the room the -24 button drop below leaves for it. Same arithmetic, same
|
||
-- reason — the ref sizes these from constants, not from what is in the frame.
|
||
function StaticPopup_Resize(dialog, which)
|
||
local text = getglobal(dialog:GetName() .. "Text")
|
||
local button1 = getglobal(dialog:GetName() .. "Button1")
|
||
local editBox = getglobal(dialog:GetName() .. "EditBox")
|
||
local info = StaticPopupDialogs[which]
|
||
if info and info.hasEditBox then
|
||
dialog:SetHeight(16 + text:GetHeight() + 8 + editBox:GetHeight() + 8
|
||
+ button1:GetHeight() + 16)
|
||
elseif info and info.hasMoneyFrame then
|
||
dialog:SetHeight(16 + text:GetHeight() + 8 + button1:GetHeight() + 32)
|
||
else
|
||
dialog:SetHeight(16 + text:GetHeight() + 8 + button1:GetHeight() + 16)
|
||
end
|
||
end
|
||
|
||
-- Ref l.1441-1700. Returns the dialog, or nil when refused (whileDead gate / no free
|
||
-- instance) — calling info.OnCancel() on refusal exactly as the ref does.
|
||
function StaticPopup_Show(which, text_arg1, text_arg2, data)
|
||
local info = StaticPopupDialogs[which]
|
||
if not info then
|
||
return nil
|
||
end
|
||
|
||
-- The whileDead gate (ref l.1447-1452). UnitIsDeadOrGhost lands with the death arc's
|
||
-- ghost predicate — guarded like CursorHasItem in ToggleGameMenu until then.
|
||
if UnitIsDeadOrGhost and UnitIsDeadOrGhost("player") and not info.whileDead then
|
||
if info.OnCancel then
|
||
info.OnCancel()
|
||
end
|
||
return nil
|
||
end
|
||
|
||
-- The cinematic gate (ref l.1454-1459), the whileDead gate's twin: a fly-by owns the
|
||
-- screen, so a dialog that has not declared itself worth interrupting one is cancelled
|
||
-- rather than raised over it. `InCinematic` is guarded like the predicate above so a
|
||
-- VM without the binding behaves as it did before.
|
||
if InCinematic and InCinematic() and not info.interruptCinematic then
|
||
if info.OnCancel then
|
||
info.OnCancel()
|
||
end
|
||
return nil
|
||
end
|
||
|
||
-- Showing X hides a visible X.cancels with an "override" cancel (ref l.1470-1485).
|
||
if info.cancels then
|
||
local frame = StaticPopup_FindVisible(info.cancels)
|
||
if frame then
|
||
frame:Hide()
|
||
local OnCancel = StaticPopupDialogs[frame.which].OnCancel
|
||
if OnCancel then
|
||
OnCancel(frame.data, "override")
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Showing DEATH sweeps every visible non-whileDead dialog (ref l.1501-1512).
|
||
if which == "DEATH" then
|
||
for index = 1, STATICPOPUP_NUMDIALOGS do
|
||
local frame = getglobal("StaticPopup" .. index)
|
||
if frame:IsShown() and not StaticPopupDialogs[frame.which].whileDead then
|
||
frame:Hide()
|
||
local OnCancel = StaticPopupDialogs[frame.which].OnCancel
|
||
if OnCancel then
|
||
OnCancel(frame.data, "override")
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
-- A visible same-type dialog is overridden in place; else take a free instance
|
||
-- (ref l.1515-1553).
|
||
local dialog = StaticPopup_FindVisible(which)
|
||
if dialog then
|
||
if info.OnCancel then
|
||
info.OnCancel(dialog.data, "override")
|
||
end
|
||
dialog:Hide()
|
||
end
|
||
if not dialog then
|
||
for i = 1, STATICPOPUP_NUMDIALOGS do
|
||
local frame = getglobal("StaticPopup" .. i)
|
||
if not frame:IsShown() then
|
||
dialog = frame
|
||
break
|
||
end
|
||
end
|
||
end
|
||
if not dialog then
|
||
if info.OnCancel then
|
||
info.OnCancel()
|
||
end
|
||
return nil
|
||
end
|
||
|
||
-- The text. Countdown/delay dialogs start blank — their OnUpdate writes it
|
||
-- (ref l.1556-1574's which-list, verbatim).
|
||
local text = getglobal(dialog:GetName() .. "Text")
|
||
if (which == "DEATH") or (which == "CAMP") or (which == "QUIT")
|
||
or (which == "DUEL_OUTOFBOUNDS") or (which == "RECOVER_CORPSE")
|
||
or (which == "RESURRECT") or (which == "RESURRECT_NO_SICKNESS")
|
||
or (which == "INSTANCE_BOOT") or (which == "CONFIRM_SUMMON")
|
||
or (which == "AREA_SPIRIT_HEAL") then
|
||
text:SetText(" ")
|
||
dialog.text_arg1 = text_arg1
|
||
dialog.text_arg2 = text_arg2
|
||
else
|
||
text:SetText(format(info.text, text_arg1, text_arg2))
|
||
end
|
||
|
||
-- The width/alert dress (ref l.1576-1589), an if/elseif in the ref's own order: every
|
||
-- Show resets width 320 / icon hidden; a GUILD MESSAGE dialog widens to 420 with NO
|
||
-- icon; otherwise a showAlert entry widens to 420 and shows the icon. HELP_TICKET's
|
||
-- 350 arm waits for that dialog.
|
||
--
|
||
-- **The widen is keyed on `which`, not on `hasWideEditBox`** — that is the reference's
|
||
-- own shape, transcribed rather than improved: a fourth wide-box dialog would get the
|
||
-- 350x64 box inside an un-widened 320 frame until its name joins this list. (The ref's
|
||
-- list also names SET_GUILDPLAYERNOTE twice, l.1582; the duplicate is inert.)
|
||
local alertIcon = getglobal(dialog:GetName() .. "AlertIcon")
|
||
alertIcon:Hide()
|
||
dialog:SetWidth(320)
|
||
if (which == "SET_GUILDMOTD") or (which == "SET_GUILDPLAYERNOTE")
|
||
or (which == "SET_GUILDOFFICERNOTE") then
|
||
dialog:SetWidth(420)
|
||
elseif info.showAlert then
|
||
dialog:SetWidth(420)
|
||
alertIcon:Show()
|
||
end
|
||
|
||
-- The edit box (ref l.1599-1624): shown only for a hasEditBox entry, capped at the
|
||
-- entry's maxLetters, and always empty on open — a name left over from the last time
|
||
-- the dialog ran would be sent by a reflexive Enter. `hasWideEditBox` picks the 350x64
|
||
-- centred box INSTEAD of the narrow one; exactly one of the two is ever shown, and
|
||
-- neither is when the entry has no box at all (the ref's three-way, verbatim).
|
||
local editBox = getglobal(dialog:GetName() .. "EditBox")
|
||
local wideEditBox = getglobal(dialog:GetName() .. "WideEditBox")
|
||
if info.hasEditBox then
|
||
if info.hasWideEditBox then
|
||
wideEditBox:SetMaxLetters(info.maxLetters or 0)
|
||
wideEditBox:SetText("")
|
||
wideEditBox:Show()
|
||
editBox:Hide()
|
||
else
|
||
editBox:SetMaxLetters(info.maxLetters or 0)
|
||
editBox:SetText("")
|
||
editBox:Show()
|
||
wideEditBox:Hide()
|
||
end
|
||
else
|
||
editBox:Hide()
|
||
wideEditBox:Hide()
|
||
end
|
||
|
||
-- The money frame (ref l.1627-1632): shown for a hasMoneyFrame entry, hidden
|
||
-- otherwise. The engine never fills it — the AMOUNT belongs to whoever raised the
|
||
-- dialog, who calls MoneyFrame_Update on it (the ref's own division: its
|
||
-- CONFIRM_TALENT_WIPE arm in UIParent.lua does exactly that with the event's arg1).
|
||
getglobal(dialog:GetName() .. "MoneyFrame"):Hide()
|
||
if info.hasMoneyFrame then
|
||
getglobal(dialog:GetName() .. "MoneyFrame"):Show()
|
||
end
|
||
|
||
-- The buttons (ref l.1634-1676; fixed 120 widths — header note). Button2 shows only
|
||
-- when the entry names it AND DisplayButton2 (when present) allows it; a solo button1
|
||
-- re-seats centered under the text. With an edit box the pair hangs off the BOX's
|
||
-- bottom instead of the text's (ref l.1641-1643), which is what leaves room for it;
|
||
-- with a money frame the pair stays on the TEXT and drops 24 instead of 8, which is
|
||
-- what leaves room for the coins (ref l.1644-1646).
|
||
local buttonAnchor = info.hasEditBox and (dialog:GetName() .. "EditBox")
|
||
or (dialog:GetName() .. "Text")
|
||
local buttonDrop = info.hasMoneyFrame and -24 or -8
|
||
local button1 = getglobal(dialog:GetName() .. "Button1")
|
||
local button2 = getglobal(dialog:GetName() .. "Button2")
|
||
if info.button2 and (not info.DisplayButton2 or info.DisplayButton2()) then
|
||
button1:ClearAllPoints()
|
||
button2:ClearAllPoints()
|
||
button1:SetPoint("TOPRIGHT", buttonAnchor, "BOTTOM", -6, buttonDrop)
|
||
button2:SetPoint("LEFT", dialog:GetName() .. "Button1", "RIGHT", 13, 0)
|
||
button2:SetText(info.button2)
|
||
button2:Show()
|
||
else
|
||
button1:ClearAllPoints()
|
||
button1:SetPoint("TOP", buttonAnchor, "BOTTOM", 0, -8)
|
||
button2:Hide()
|
||
end
|
||
if info.button1 then
|
||
button1:SetText(info.button1)
|
||
button1:Show()
|
||
else
|
||
button1:Hide()
|
||
end
|
||
|
||
-- Misc vars (ref l.1677-1690). `which` is set before Show so OnShow reads it.
|
||
dialog.which = which
|
||
dialog.timeleft = info.timeout or 0
|
||
dialog.hideOnEscape = info.hideOnEscape
|
||
dialog.data = data
|
||
|
||
if info.StartDelay then
|
||
dialog.startDelay = info.StartDelay()
|
||
button1:Disable()
|
||
else
|
||
dialog.startDelay = nil
|
||
button1:Enable()
|
||
end
|
||
|
||
dialog:Show()
|
||
StaticPopup_Resize(dialog, which)
|
||
|
||
if info.sound then
|
||
PlaySound(info.sound)
|
||
end
|
||
return dialog
|
||
end
|
||
|
||
-- Ref l.1850-1868: a truthy OnAccept return keeps the dialog open.
|
||
function StaticPopup_OnClick(dialog, index)
|
||
local which = dialog.which
|
||
local info = StaticPopupDialogs[which]
|
||
if not info then
|
||
return
|
||
end
|
||
local dontHide = nil
|
||
if index == 1 then
|
||
if info.OnAccept then
|
||
dontHide = info.OnAccept(dialog.data, dialog.data2)
|
||
end
|
||
else
|
||
if info.OnCancel then
|
||
info.OnCancel(dialog.data, "clicked")
|
||
end
|
||
end
|
||
if not dontHide and (which == dialog.which) then
|
||
dialog:Hide()
|
||
end
|
||
end
|
||
|
||
-- The edit box's two keys (ref StaticPopup.xml's template scripts). Enter runs the
|
||
-- entry's EditBoxOnEnterPressed hook, which by convention does the accept work and hides
|
||
-- the dialog itself; Escape dismisses the dialog through the ordinary cancel path so a
|
||
-- half-typed name never acts.
|
||
function StaticPopup_EditBoxOnEnterPressed(editBox)
|
||
local dialog = editBox:GetParent()
|
||
local info = StaticPopupDialogs[dialog.which]
|
||
if info and info.EditBoxOnEnterPressed then
|
||
info.EditBoxOnEnterPressed(dialog.data)
|
||
end
|
||
end
|
||
|
||
function StaticPopup_EditBoxOnEscapePressed(editBox)
|
||
local dialog = editBox:GetParent()
|
||
editBox:ClearFocus()
|
||
dialog:Hide()
|
||
end
|
||
|
||
-- Ref l.1832-1848: the open/close pair sounds on EVERY path, then the entry's own hook.
|
||
function StaticPopup_OnShow()
|
||
PlaySound("igMainMenuOpen")
|
||
local info = StaticPopupDialogs[this.which]
|
||
if info and info.OnShow then
|
||
info.OnShow(this.data)
|
||
end
|
||
end
|
||
|
||
function StaticPopup_OnHide()
|
||
PlaySound("igMainMenuClose")
|
||
local info = StaticPopupDialogs[this.which]
|
||
if info and info.OnHide then
|
||
info.OnHide(this.data)
|
||
end
|
||
end
|
||
|
||
-- Ref l.1712-1808: the timeout countdown (expiry → OnCancel "timeout" + hide), the
|
||
-- per-tick countdown text for the timer dialogs, the StartDelay gate (button1 disabled,
|
||
-- delayText counts down, then the real text swaps in and the button enables), then the
|
||
-- entry's own OnUpdate. The INSTANCE_BOOT/CONFIRM_SUMMON special text formats join with
|
||
-- their dialogs; their which-list slots stay so the gate is the ref's.
|
||
function StaticPopup_OnUpdate(dialog, elapsed)
|
||
local info = StaticPopupDialogs[dialog.which]
|
||
if not info then
|
||
return
|
||
end
|
||
if (dialog.timeleft or 0) > 0 then
|
||
local which = dialog.which
|
||
local timeleft = dialog.timeleft - elapsed
|
||
if timeleft <= 0 then
|
||
dialog.timeleft = 0
|
||
if info.OnCancel then
|
||
info.OnCancel(dialog.data, "timeout")
|
||
end
|
||
dialog:Hide()
|
||
return
|
||
end
|
||
dialog.timeleft = timeleft
|
||
|
||
if (which == "DEATH") or (which == "CAMP") or (which == "QUIT")
|
||
or (which == "DUEL_OUTOFBOUNDS") or (which == "INSTANCE_BOOT")
|
||
or (which == "CONFIRM_SUMMON") or (which == "AREA_SPIRIT_HEAL") then
|
||
local text = getglobal(dialog:GetName() .. "Text")
|
||
timeleft = ceil(timeleft)
|
||
if timeleft < 60 then
|
||
text:SetText(format(info.text, timeleft, StaticPopupTimeUnit(timeleft, false)))
|
||
else
|
||
local m = ceil(timeleft / 60)
|
||
text:SetText(format(info.text, m, StaticPopupTimeUnit(m, true)))
|
||
end
|
||
end
|
||
end
|
||
if dialog.startDelay then
|
||
local which = dialog.which
|
||
local timeleft = dialog.startDelay - elapsed
|
||
if timeleft <= 0 then
|
||
dialog.startDelay = nil
|
||
local text = getglobal(dialog:GetName() .. "Text")
|
||
text:SetText(format(info.text, dialog.text_arg1, dialog.text_arg2))
|
||
getglobal(dialog:GetName() .. "Button1"):Enable()
|
||
return
|
||
end
|
||
dialog.startDelay = timeleft
|
||
|
||
if (which == "RECOVER_CORPSE") or (which == "RESURRECT")
|
||
or (which == "RESURRECT_NO_SICKNESS") then
|
||
local text = getglobal(dialog:GetName() .. "Text")
|
||
timeleft = ceil(timeleft)
|
||
if (which == "RESURRECT") or (which == "RESURRECT_NO_SICKNESS") then
|
||
if timeleft < 60 then
|
||
text:SetText(format(info.delayText, dialog.text_arg1, timeleft,
|
||
StaticPopupTimeUnit(timeleft, false)))
|
||
else
|
||
local m = ceil(timeleft / 60)
|
||
text:SetText(format(info.delayText, dialog.text_arg1, m,
|
||
StaticPopupTimeUnit(m, true)))
|
||
end
|
||
else
|
||
if timeleft < 60 then
|
||
text:SetText(format(info.delayText, timeleft,
|
||
StaticPopupTimeUnit(timeleft, false)))
|
||
else
|
||
local m = ceil(timeleft / 60)
|
||
text:SetText(format(info.delayText, m,
|
||
StaticPopupTimeUnit(m, true)))
|
||
end
|
||
end
|
||
end
|
||
end
|
||
StaticPopup_Resize(dialog, dialog.which)
|
||
if info.OnUpdate then
|
||
info.OnUpdate(elapsed, dialog)
|
||
end
|
||
end
|
||
|
||
-- Ref l.1879-1893: only hideOnEscape dialogs respond — a shown DEATH popup deliberately
|
||
-- does NOT close on ESC. Returns truthy when something closed (the caller's chain stops).
|
||
function StaticPopup_EscapePressed()
|
||
local closed = nil
|
||
for index = 1, STATICPOPUP_NUMDIALOGS do
|
||
local frame = getglobal("StaticPopup" .. index)
|
||
if frame:IsShown() and frame.hideOnEscape then
|
||
local OnCancel = StaticPopupDialogs[frame.which].OnCancel
|
||
if OnCancel then
|
||
OnCancel(frame.data, "clicked")
|
||
end
|
||
frame:Hide()
|
||
closed = 1
|
||
end
|
||
end
|
||
return closed
|
||
end
|
||
|
||
-- Delete-item confirm strings — the real 1.12 GlobalStrings.lua (patch-2.MPQ,
|
||
-- Interface\FrameXML\GlobalStrings.lua), quoted verbatim (extracted via benilla-extract).
|
||
local DELETE_ITEM_TEXT = "Do you want to destroy %s?" -- DELETE_ITEM; %s = the item name
|
||
local YES_TEXT = "Yes" -- YES
|
||
local NO_TEXT = "No" -- No
|
||
|
||
-- Ref StaticPopup.lua l.659-679, verbatim — its own OnUpdate poll (cursor emptied by any
|
||
-- other path → hide) replaces the old bespoke `deleteItemFlow` CURSOR_UPDATE driver.
|
||
-- exclusive is v1-inert (engine header note); showAlert is live since 1068 — the ref's
|
||
-- own "display the error image" case is this very dialog.
|
||
StaticPopupDialogs["DELETE_ITEM"] = {
|
||
text = DELETE_ITEM_TEXT,
|
||
button1 = YES_TEXT,
|
||
button2 = NO_TEXT,
|
||
OnAccept = function()
|
||
DeleteCursorItem()
|
||
end,
|
||
OnCancel = function()
|
||
ClearCursor()
|
||
end,
|
||
OnUpdate = function()
|
||
if not CursorHasItem() then
|
||
StaticPopup_Hide("DELETE_ITEM")
|
||
end
|
||
end,
|
||
timeout = 0,
|
||
whileDead = 1,
|
||
exclusive = 1,
|
||
showAlert = 1,
|
||
hideOnEscape = 1,
|
||
}
|
||
|
||
-- BenillaDeleteItemConfirmDriver's OnEvent — DELETE_ITEM_CONFIRM(name, quality) is the
|
||
-- engine's world-drop of an item payload (decision 0216 §3, cursor.rs::world_drop) →
|
||
-- ref UIParent.lua:344-352. DEFERRED (0216 §3): the ref splits quality >= 3 to
|
||
-- DELETE_GOOD_ITEM (the typed-confirmation editbox variant) — no editbox variant yet, so
|
||
-- every quality (arg2) shows DELETE_ITEM for now.
|
||
function BenillaDeleteItemConfirmDriver_OnEvent(frame)
|
||
if event == "DELETE_ITEM_CONFIRM" then
|
||
StaticPopup_Show("DELETE_ITEM", arg1)
|
||
end
|
||
end
|
||
|
||
-- ── The shared frame-fade kit — ref UIParent.lua:1103-1219 is the specification (the
|
||
-- function names are its published API and the arithmetic is the same lerp); the code is
|
||
-- ours. FADEFRAMES is a plain ARRAY walked by index rather than by `pairs`, because
|
||
-- UIFrameFadeUpdate removes from it mid-walk. The ref drives UIFrameFadeUpdate from
|
||
-- UIParent's OnUpdate; our driver is the BenillaFadeDriver frame below. Callers: the quest
|
||
-- detail write-on (QuestFrame.xml) fades TextAlphaDependentFrame in over
|
||
-- QUESTINFO_FADE_IN. ─────────────────────────────────────────────────────────────────────
|
||
FADEFRAMES = {}
|
||
|
||
-- Where `frame` sits in the fade list, searching from `from` onward, or nil. Three of the
|
||
-- verbs below ask exactly this question, and the reference spells the index walk out
|
||
-- longhand inside each of them.
|
||
local function FadeIndexOf(frame, from)
|
||
local index = from or 1
|
||
while FADEFRAMES[index] do
|
||
if FADEFRAMES[index] == frame then
|
||
return index
|
||
end
|
||
index = index + 1
|
||
end
|
||
return nil
|
||
end
|
||
|
||
function UIFrameFadeRemoveFrame(frame)
|
||
local index = FadeIndexOf(frame)
|
||
while index do
|
||
table.remove(FADEFRAMES, index)
|
||
-- Resume from the hole rather than from the start: a frame listed twice goes twice.
|
||
index = FadeIndexOf(frame, index)
|
||
end
|
||
end
|
||
|
||
-- Where each mode starts and ends when the caller doesn't say. The tests below are
|
||
-- `not fadeInfo.startAlpha`, so an explicit 0 — falsey in most languages, TRUTHY in Lua —
|
||
-- is the caller's and stands.
|
||
local FADE_ENDPOINTS = {
|
||
IN = { startAlpha = 0, endAlpha = 1.0 },
|
||
OUT = { startAlpha = 1.0, endAlpha = 0 },
|
||
}
|
||
|
||
function UIFrameFade(frame, fadeInfo)
|
||
if not fadeInfo.mode then
|
||
fadeInfo.mode = "IN"
|
||
end
|
||
local endpoints = FADE_ENDPOINTS[fadeInfo.mode]
|
||
if endpoints then
|
||
if not fadeInfo.startAlpha then fadeInfo.startAlpha = endpoints.startAlpha end
|
||
if not fadeInfo.endAlpha then fadeInfo.endAlpha = endpoints.endAlpha end
|
||
end
|
||
frame:SetAlpha(fadeInfo.startAlpha)
|
||
frame.fadeInfo = fadeInfo
|
||
-- Already in the list? The new info is installed above and takes over on the next
|
||
-- tick; the frame keeps its place instead of being listed twice (the ref's early
|
||
-- return).
|
||
if FadeIndexOf(frame) then
|
||
return
|
||
end
|
||
frame:Show()
|
||
table.insert(FADEFRAMES, frame)
|
||
-- Wake the driver: it hides itself the moment the list empties (its OnUpdate gate),
|
||
-- so the ONE insertion point is where it is shown again. Every other verb only
|
||
-- removes or rewrites entries.
|
||
BenillaFadeDriver:Show()
|
||
end
|
||
|
||
-- The two directions are one call with the mode flipped; whatever endpoints the caller
|
||
-- leaves out, UIFrameFade fills in above.
|
||
local function FadeToMode(mode, frame, timeToFade, startAlpha, endAlpha)
|
||
UIFrameFade(frame, {
|
||
mode = mode,
|
||
timeToFade = timeToFade,
|
||
startAlpha = startAlpha,
|
||
endAlpha = endAlpha,
|
||
})
|
||
end
|
||
|
||
function UIFrameFadeIn(frame, timeToFade, startAlpha, endAlpha)
|
||
FadeToMode("IN", frame, timeToFade, startAlpha, endAlpha)
|
||
end
|
||
|
||
function UIFrameFadeOut(frame, timeToFade, startAlpha, endAlpha)
|
||
FadeToMode("OUT", frame, timeToFade, startAlpha, endAlpha)
|
||
end
|
||
|
||
function UIFrameFadeUpdate(elapsed)
|
||
local index = 1
|
||
while FADEFRAMES[index] do
|
||
local frame = FADEFRAMES[index]
|
||
local fadeInfo = frame.fadeInfo
|
||
if not fadeInfo.fadeTimer then fadeInfo.fadeTimer = 0 end
|
||
fadeInfo.fadeTimer = fadeInfo.fadeTimer + elapsed
|
||
if fadeInfo.fadeTimer < fadeInfo.timeToFade then
|
||
if fadeInfo.mode == "IN" then
|
||
frame:SetAlpha((fadeInfo.fadeTimer / fadeInfo.timeToFade)
|
||
* (fadeInfo.endAlpha - fadeInfo.startAlpha) + fadeInfo.startAlpha)
|
||
elseif fadeInfo.mode == "OUT" then
|
||
frame:SetAlpha(((fadeInfo.timeToFade - fadeInfo.fadeTimer) / fadeInfo.timeToFade)
|
||
* (fadeInfo.startAlpha - fadeInfo.endAlpha) + fadeInfo.endAlpha)
|
||
end
|
||
else
|
||
frame:SetAlpha(fadeInfo.endAlpha)
|
||
if fadeInfo.fadeHoldTime and fadeInfo.fadeHoldTime > 0 then
|
||
fadeInfo.fadeHoldTime = fadeInfo.fadeHoldTime - elapsed
|
||
else
|
||
UIFrameFadeRemoveFrame(frame)
|
||
if fadeInfo.finishedFunc then
|
||
fadeInfo.finishedFunc(fadeInfo.finishedArg1, fadeInfo.finishedArg2,
|
||
fadeInfo.finishedArg3, fadeInfo.finishedArg4)
|
||
fadeInfo.finishedFunc = nil
|
||
end
|
||
end
|
||
end
|
||
index = index + 1
|
||
end
|
||
end
|
||
|
||
function UIFrameIsFading(frame)
|
||
if FadeIndexOf(frame) then
|
||
return 1
|
||
end
|
||
return nil
|
||
end
|
||
|
||
-- ── The tab kit (ref UIPanelTemplates.lua l.1-29/90-133, transcribed VERBATIM shape) ──
|
||
-- One selected tab per group: the frame carries numTabs/selectedTab, tabs are named
|
||
-- <frame>Tab1..N (CharacterFrameTabButtonTemplate instances — the template lives in this
|
||
-- file). SelectTab swaps the InActive art for the Active art and disables the button (a
|
||
-- selected tab doesn't re-click); DeselectTab reverses it. Omitted vs ref: the
|
||
-- isDisabled/SetDisabledTabState arm (no window has a disabled tab yet) and the SelectTab
|
||
-- tab-tooltip hide (our tabs set no tooltip). First consumer: the merchant's
|
||
-- Merchant/Buyback pair.
|
||
-- The kit's XML-facing entry point (ref l.2-4), and the one member of it this client never
|
||
-- had. Not an omission anyone decided: our own windows wire each tab's OnClick to their own
|
||
-- handler (the merchant's Merchant/Buyback pair, CharacterFrame's pages), so nothing here
|
||
-- ever reached for the generic one. An ADDON does the opposite — it puts the reference's
|
||
-- name straight in its <OnClick> and lets the kit do the work:
|
||
--
|
||
-- Enchantrix EnchantrixBarker.xml:61 (Enchantrix_BarkerOptions_Frame)
|
||
-- Outfitter Outfitter.xml:7 (OutfitterFrame)
|
||
-- SimpleActionSets SimpleActionSets.xml:342 (this:GetParent())
|
||
-- TheoryCraft TheoryCraft.xml:163 (TheoryCraft)
|
||
--
|
||
-- Four addons, four separate files, so 1207's one-library caveat does not apply. A raw grep
|
||
-- says FIVE: QuestItemTemplates.xml:17 has the call commented out. Reading the line is what
|
||
-- tells those apart, and the survey's own count of 4 was the right one.
|
||
--
|
||
-- Missing, every one of them raises on each click of the tab, so the addon's options window
|
||
-- stays on whatever page it opened with and cannot be navigated at all. `this` is the
|
||
-- clicked tab button, whose id is its slot in the row; the frame is passed in separately
|
||
-- because the tab's parent is not always the frame owning the row — SimpleActionSets is the
|
||
-- case that proves it, passing `this:GetParent()` explicitly.
|
||
function PanelTemplates_Tab_OnClick(frame)
|
||
PanelTemplates_SetTab(frame, this:GetID())
|
||
end
|
||
|
||
function PanelTemplates_SetNumTabs(frame, numTabs)
|
||
frame.numTabs = numTabs
|
||
end
|
||
|
||
function PanelTemplates_SetTab(frame, id)
|
||
frame.selectedTab = id
|
||
PanelTemplates_UpdateTabs(frame)
|
||
end
|
||
|
||
function PanelTemplates_GetSelectedTab(frame)
|
||
return frame.selectedTab
|
||
end
|
||
|
||
function PanelTemplates_UpdateTabs(frame)
|
||
if not frame.selectedTab then return end
|
||
for i = 1, frame.numTabs do
|
||
local tab = getglobal(frame:GetName() .. "Tab" .. i)
|
||
if tab.isDisabled then
|
||
PanelTemplates_SetDisabledTabState(tab)
|
||
elseif i == frame.selectedTab then
|
||
PanelTemplates_SelectTab(tab)
|
||
else
|
||
PanelTemplates_DeselectTab(tab)
|
||
end
|
||
end
|
||
end
|
||
|
||
-- The disabled-tab arm (ref UIPanelTemplates.lua l.94-105 + l.135-148) — the piece this
|
||
-- kit deliberately omitted until a window needed it. First customer: the social window's
|
||
-- GUILD tab, which `InGuildCheck` disables for a character in no guild — i.e. permanently
|
||
-- until the guild arc exists (decision 0668). A disabled tab wears the INACTIVE
|
||
-- three-slice art (like an unselected tab) but is un-clickable with a greyed label, so it
|
||
-- reads as "there, not for you" rather than as the selected one.
|
||
function PanelTemplates_DisableTab(frame, index)
|
||
getglobal(frame:GetName() .. "Tab" .. index).isDisabled = 1
|
||
PanelTemplates_UpdateTabs(frame)
|
||
end
|
||
|
||
function PanelTemplates_EnableTab(frame, index)
|
||
local tab = getglobal(frame:GetName() .. "Tab" .. index)
|
||
tab.isDisabled = nil
|
||
tab:SetDisabledTextColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
|
||
PanelTemplates_UpdateTabs(frame)
|
||
end
|
||
|
||
function PanelTemplates_SetDisabledTabState(tab)
|
||
local name = tab:GetName()
|
||
getglobal(name .. "Left"):Show()
|
||
getglobal(name .. "Middle"):Show()
|
||
getglobal(name .. "Right"):Show()
|
||
tab:Disable()
|
||
tab.text = tab:GetText()
|
||
tab:SetDisabledTextColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b)
|
||
getglobal(name .. "LeftDisabled"):Hide()
|
||
getglobal(name .. "MiddleDisabled"):Hide()
|
||
getglobal(name .. "RightDisabled"):Hide()
|
||
end
|
||
|
||
function PanelTemplates_DeselectTab(tab)
|
||
local name = tab:GetName()
|
||
getglobal(name .. "Left"):Show()
|
||
getglobal(name .. "Middle"):Show()
|
||
getglobal(name .. "Right"):Show()
|
||
tab:Enable()
|
||
getglobal(name .. "LeftDisabled"):Hide()
|
||
getglobal(name .. "MiddleDisabled"):Hide()
|
||
getglobal(name .. "RightDisabled"):Hide()
|
||
end
|
||
|
||
function PanelTemplates_SelectTab(tab)
|
||
local name = tab:GetName()
|
||
getglobal(name .. "Left"):Hide()
|
||
getglobal(name .. "Middle"):Hide()
|
||
getglobal(name .. "Right"):Hide()
|
||
tab:Disable()
|
||
getglobal(name .. "LeftDisabled"):Show()
|
||
getglobal(name .. "MiddleDisabled"):Show()
|
||
getglobal(name .. "RightDisabled"):Show()
|
||
end
|
||
|
||
-- The text-fit (ref PanelTemplates_TabResize, UIPanelTemplates.lua l.31-88 — now the WHOLE
|
||
-- function, `(padding, tab, absoluteSize, maxWidth)`): the tab is text + padding wide, plus
|
||
-- the two end slices; both middle slices stretch to the text; the text box itself un-sizes
|
||
-- to natural. `maxWidth` CAPS it and pins the label's box to the cap instead — the arm that
|
||
-- keeps a long tab inside its window, which the first transcription dropped and the macro
|
||
-- window's character tab then overran (0983 → 0991). `absoluteSize` is the ref's third arm,
|
||
-- transcribed with it so the next caller has it.
|
||
--
|
||
-- One deviation: the ref reads the label's laid-out GetWidth (its C measure is synchronous);
|
||
-- ours reads GetStringWidth — the same number once the measure round-trip lands, which is
|
||
-- why the caller below re-checks from OnUpdate instead of OnShow.
|
||
function PanelTemplates_TabResize(padding, tab, absoluteSize, maxWidth)
|
||
local name = tab:GetName()
|
||
local text = getglobal(name .. "Text")
|
||
local sideWidths = 2 * getglobal(name .. "Left"):GetWidth()
|
||
local width, tabWidth
|
||
if absoluteSize then
|
||
if absoluteSize < sideWidths then
|
||
width, tabWidth = 1, sideWidths
|
||
else
|
||
width, tabWidth = absoluteSize - sideWidths, absoluteSize
|
||
end
|
||
text:SetWidth(width)
|
||
else
|
||
width = text:GetStringWidth() + (padding or 24)
|
||
if maxWidth and width > maxWidth then
|
||
width = maxWidth + (padding or 24)
|
||
text:SetWidth(width)
|
||
else
|
||
text:SetWidth(0)
|
||
end
|
||
tabWidth = width + sideWidths
|
||
end
|
||
getglobal(name .. "Middle"):SetWidth(width)
|
||
getglobal(name .. "MiddleDisabled"):SetWidth(width)
|
||
tab:SetWidth(tabWidth)
|
||
-- benilla addition: hand back the width just declared. `tab:GetWidth()` cannot serve
|
||
-- it — that reads the last RESOLVED rect, which inside a settle is this frame's stale
|
||
-- input, not the value one line above just set. A clamp that read it back saw the old
|
||
-- width, declined to fire, and the settle's gate then locked the un-clamped result in
|
||
-- for good (decision 1002).
|
||
return tabWidth
|
||
end
|
||
|
||
-- The tab template's per-frame settle: the ref does TabResize(0) once, in OnShow
|
||
-- (CharacterFrameTemplates.xml l.77-80), trusting a synchronous text measure. Our
|
||
-- GetStringWidth has a frame of latency, so the template's OnUpdate calls this until the
|
||
-- measured width settles (then it's a no-op compare per frame). The ref sizes the hover
|
||
-- highlight alongside it; this kit does not — both tab templates anchor their highlight to
|
||
-- their own side edges, so it is the tab by construction and no settle can desync it
|
||
-- (decision 1004).
|
||
--
|
||
-- The fit PARAMETERS live on the tab (`benillaTabPadding`/`benillaTabMaxWidth`, defaulting
|
||
-- to the +0 fit every window tab has always used) rather than at the call site, because
|
||
-- there is exactly one call site and it is this settle loop: an instance that ran its own
|
||
-- TabResize in OnLoad — as the reference's macro tabs do — would just be overwritten on the
|
||
-- next frame. Setting the two fields IS how an instance asks for the reference's own fit.
|
||
-- THE STRUCTURAL CLAMP — a tab may never grow past its window's drawn right edge, whatever
|
||
-- the instance asked for (decision 1002; the director's "fix this properly to prevent it
|
||
-- ever again"). The reference's own caps are numbers hand-tuned to ITS font: the macro
|
||
-- window's 150 is right for the enUS FRIZQT__ the 1.12 client rasterizes and for nothing
|
||
-- else in particular, and the label it guards carries a CHARACTER NAME — the one string
|
||
-- whose width no window author can know. So the instance's cap is a preference and the
|
||
-- window's edge is the law.
|
||
--
|
||
-- `benillaTabRightInset` is how a window declares how far its DRAWN plate stops short of
|
||
-- its frame rect, because no kit can introspect an art margin. The macro window's is 40:
|
||
-- its 384-unit frame is painted by a 256-wide slice plus a 128-wide one that carries a
|
||
-- transparent tail. Clamping to the frame rect instead — the first attempt — clamped to a
|
||
-- boundary the player cannot see, and the tab still walked out over the world.
|
||
--
|
||
-- The clamp is applied to the RESULT, through TabResize's own `absoluteSize` arm, rather
|
||
-- than folded into `maxWidth`: that parameter is compared against `text + padding` but
|
||
-- assigned as `maxWidth + padding`, so a room budget expressed through it is off by the
|
||
-- padding and silently fails to bind. `absoluteSize` sets the tab width outright, which is
|
||
-- exactly what a room budget is.
|
||
function BenillaTab_Room(tab)
|
||
local parent = tab:GetParent()
|
||
if not parent then return nil end
|
||
local left, right = tab:GetLeft(), parent:GetRight()
|
||
if not left or not right then return nil end
|
||
local room = (right - (tab.benillaTabRightInset or 0)) - left
|
||
if room < 1 then room = 1 end
|
||
return room
|
||
end
|
||
|
||
function BenillaTabButton_OnUpdate(tab)
|
||
local text = getglobal(tab:GetName() .. "Text")
|
||
local w = (text and text:GetStringWidth()) or 0
|
||
if w <= 0 then return end
|
||
local pad = tab.benillaTabPadding or 0
|
||
local room = BenillaTab_Room(tab)
|
||
if tab.benillaFitWidth == w and tab.benillaFitRoom == room then return end
|
||
tab.benillaFitWidth, tab.benillaFitRoom = w, room
|
||
local fitted = PanelTemplates_TabResize(pad, tab, nil, tab.benillaTabMaxWidth)
|
||
if room and fitted > room then
|
||
PanelTemplates_TabResize(pad, tab, room)
|
||
end
|
||
-- No highlight sizing here on purpose — BOTH tab templates anchor their highlight to
|
||
-- their own two side edges, so it IS the tab by construction (decision 1004).
|
||
end
|
||
]]></Script>
|
||
|
||
<!-- The fade kit's tick source — the ref hangs UIFrameFadeUpdate on UIParent's OnUpdate
|
||
(UIParent.xml l.16-22); benilla has no UIParent frame, so a dedicated 1x1 driver ticks it.
|
||
It runs only while FADEFRAMES has entries: the engine dispatches OnUpdate to visible
|
||
frames only (tick.rs), so hiding itself on an empty list stops the per-frame walk cold,
|
||
and UIFrameFade — the single inserter — shows it again (decision 1396's class). -->
|
||
<Frame name="BenillaFadeDriver">
|
||
<Size><AbsDimension x="1" y="1"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
||
<Scripts><OnUpdate>if not FADEFRAMES[1] then this:Hide() return end UIFrameFadeUpdate(arg1)</OnUpdate></Scripts>
|
||
</Frame>
|
||
|
||
<!--
|
||
The window tab button (ref CharacterFrameTabButtonTemplate — Interface\FrameXML\
|
||
CharacterFrameTemplates.xml in patch.MPQ, NOT the base FrameXML.toc set; extracted via
|
||
mpqx, transcribed VERBATIM: 115x32, the Active/InActive three-slice pair with the ref's
|
||
exact TexCoords, text CENTER +2, the ADD highlight inset 10px). "Disabled" IS "selected"
|
||
here — PanelTemplates_SelectTab (script kit above) disables the button and shows the
|
||
*Disabled (= Active) slices. The ref's OnShow text-fit (PanelTemplates_TabResize(0)) runs
|
||
from OnUpdate here instead — BenillaTabButton_OnUpdate settles once the async text measure
|
||
lands, then no-ops (the ref's C measure is synchronous; the 115px XML width is just the
|
||
pre-fit state). Its OnShow *also* sets the highlight width, which is dead code in the
|
||
reference and dropped here: this highlight is anchored LEFT/RIGHT, and two opposing anchors
|
||
resolve the size from the span without consulting the width at all (wow-re layout.md,
|
||
VERIFIED). The small tab now says the same thing the same way (1004). One deviation: the ref's OnLoad
|
||
SetFrameLevel(+4) raise (our loader has no cross-frame level raise wired into templates
|
||
yet; the tabs sit below the window art's hit-rect so nothing overlaps them). OnClick is
|
||
the instance's to define (the ref merchant overrides it too).
|
||
-->
|
||
<Button name="CharacterFrameTabButtonTemplate" virtual="true">
|
||
<Size><AbsDimension x="115" y="32"/></Size>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture name="$parentLeftDisabled" file="Interface\PaperDollInfoFrame\UI-Character-ActiveTab">
|
||
<Size><AbsDimension x="20" y="32"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="5"/></Offset></Anchor></Anchors>
|
||
<TexCoords left="0" right="0.15625" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture name="$parentMiddleDisabled" file="Interface\PaperDollInfoFrame\UI-Character-ActiveTab">
|
||
<Size><AbsDimension x="88" y="32"/></Size>
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentLeftDisabled" relativePoint="RIGHT"/></Anchors>
|
||
<TexCoords left="0.15625" right="0.84375" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture name="$parentRightDisabled" file="Interface\PaperDollInfoFrame\UI-Character-ActiveTab">
|
||
<Size><AbsDimension x="20" y="32"/></Size>
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentMiddleDisabled" relativePoint="RIGHT"/></Anchors>
|
||
<TexCoords left="0.84375" right="1.0" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture name="$parentLeft" file="Interface\PaperDollInfoFrame\UI-Character-InActiveTab">
|
||
<Size><AbsDimension x="20" y="32"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
||
<TexCoords left="0" right="0.15625" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture name="$parentMiddle" file="Interface\PaperDollInfoFrame\UI-Character-InActiveTab">
|
||
<Size><AbsDimension x="88" y="32"/></Size>
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentLeft" relativePoint="RIGHT"/></Anchors>
|
||
<TexCoords left="0.15625" right="0.84375" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture name="$parentRight" file="Interface\PaperDollInfoFrame\UI-Character-InActiveTab">
|
||
<Size><AbsDimension x="20" y="32"/></Size>
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentMiddle" relativePoint="RIGHT"/></Anchors>
|
||
<TexCoords left="0.84375" right="1.0" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<ButtonText name="$parentText">
|
||
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="2"/></Offset></Anchor></Anchors>
|
||
</ButtonText>
|
||
<NormalFont inherits="GameFontNormalSmall"/>
|
||
<HighlightFont inherits="GameFontHighlightSmall"/>
|
||
<DisabledFont inherits="GameFontHighlightSmall"/>
|
||
<HighlightTexture name="$parentHighlightTexture" file="Interface\PaperDollInfoFrame\UI-Character-Tab-Highlight" alphaMode="ADD">
|
||
<Anchors>
|
||
<Anchor point="LEFT"><Offset><AbsDimension x="10" y="2"/></Offset></Anchor>
|
||
<Anchor point="RIGHT"><Offset><AbsDimension x="-10" y="2"/></Offset></Anchor>
|
||
</Anchors>
|
||
</HighlightTexture>
|
||
<Scripts>
|
||
<OnUpdate>BenillaTabButton_OnUpdate(self)</OnUpdate>
|
||
</Scripts>
|
||
</Button>
|
||
|
||
<!--
|
||
The SMALL tab button (ref `TabButtonTemplate`, UIPanelTemplates.xml l.303-402, transcribed
|
||
VERBATIM: the HelpFrameTab Active/Inactive three-slice pair on 16/32/16 slices, text at
|
||
BOTTOM +5, the ADD highlight at +2,-8 — anchored to both side edges rather than sized in
|
||
Lua, the one departure, and why: see the highlight below). Not the same thing as
|
||
`CharacterFrameTabButtonTemplate` above — that one is the big *window* tab along a panel's
|
||
bottom edge; this is the compact tab a panel uses to switch a list INSIDE itself. The
|
||
social window's Friends/Ignore pair is the first customer (decision 0668); the reference
|
||
also uses it for the help frame and the raid pane.
|
||
|
||
Slice naming matches the big tab's, so the shared PanelTemplates_SelectTab/DeselectTab kit
|
||
drives both unchanged — "Disabled" is "selected" here too. Same OnUpdate text-fit for the
|
||
same reason (our GetStringWidth lands a frame late).
|
||
-->
|
||
<Button name="TabButtonTemplate" virtual="true">
|
||
<Size><AbsDimension x="115" y="32"/></Size>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture name="$parentLeftDisabled" file="Interface\HelpFrame\HelpFrameTab-Active">
|
||
<Size><AbsDimension x="16" y="32"/></Size>
|
||
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors>
|
||
<TexCoords left="0" right="0.25" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture name="$parentMiddleDisabled" file="Interface\HelpFrame\HelpFrameTab-Active">
|
||
<Size><AbsDimension x="32" y="32"/></Size>
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentLeftDisabled" relativePoint="RIGHT"/></Anchors>
|
||
<TexCoords left="0.25" right="0.75" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture name="$parentRightDisabled" file="Interface\HelpFrame\HelpFrameTab-Active">
|
||
<Size><AbsDimension x="16" y="32"/></Size>
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentMiddleDisabled" relativePoint="RIGHT"/></Anchors>
|
||
<TexCoords left="0.75" right="1.0" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture name="$parentLeft" file="Interface\HelpFrame\HelpFrameTab-Inactive">
|
||
<Size><AbsDimension x="16" y="32"/></Size>
|
||
<Anchors><Anchor point="BOTTOMLEFT"/></Anchors>
|
||
<TexCoords left="0" right="0.25" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture name="$parentMiddle" file="Interface\HelpFrame\HelpFrameTab-Inactive">
|
||
<Size><AbsDimension x="32" y="32"/></Size>
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentLeft" relativePoint="RIGHT"/></Anchors>
|
||
<TexCoords left="0.25" right="0.75" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture name="$parentRight" file="Interface\HelpFrame\HelpFrameTab-Inactive">
|
||
<Size><AbsDimension x="16" y="32"/></Size>
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentMiddle" relativePoint="RIGHT"/></Anchors>
|
||
<TexCoords left="0.75" right="1.0" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<ButtonText name="$parentText">
|
||
<Size><AbsDimension x="0" y="13"/></Size>
|
||
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="5"/></Offset></Anchor></Anchors>
|
||
</ButtonText>
|
||
<NormalFont inherits="GameFontNormalSmall"/>
|
||
<HighlightFont inherits="GameFontHighlightSmall"/>
|
||
<DisabledFont inherits="GameFontHighlightSmall"/>
|
||
<!-- The highlight spans the tab's own two side edges, so it tracks any fit for free. The
|
||
ref sizes it in Lua instead — `SetWidth(GetTextWidth() + 31)` at OnLoad, which on the
|
||
template's own +0 fit (tab = text + 32) lands at tab − 1: "the highlight is the tab",
|
||
said in arithmetic. It only stops meaning that when an instance passes a padding or a
|
||
cap, and the macro window's tabs pass both — which is how the director got a highlight
|
||
13 units proud of tab 1 and 35 short of tab 2 in the same screenshot (decision 1004).
|
||
The big tab template above already says it this way (LEFT/RIGHT, no Lua); this is the
|
||
same statement on 16-unit end slices. Two opposing anchors resolve the size from the
|
||
span and never consult the width, so the declared 5 is inert on x (wow-re layout.md,
|
||
VERIFIED); the y anchors are both BOTTOM, so 32 still sets the height. -->
|
||
<HighlightTexture name="$parentHighlightTexture" file="Interface\PaperDollInfoFrame\UI-Character-Tab-Highlight" alphaMode="ADD">
|
||
<Size><AbsDimension x="5" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="2" y="-8"/></Offset></Anchor>
|
||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="2" y="-8"/></Offset></Anchor>
|
||
</Anchors>
|
||
</HighlightTexture>
|
||
<Scripts>
|
||
<OnUpdate>BenillaTabButton_OnUpdate(self)</OnUpdate>
|
||
</Scripts>
|
||
</Button>
|
||
|
||
<!--
|
||
The shared dialog instance — the ref's StaticPopup1 (decision 0308 §3; the engine lives in
|
||
the script block above). Geometry/art transcribed VERBATIM from the real StaticPopup.xml
|
||
(mpqx extraction — template l.30-120: 320x72, the UI-DialogBox backdrop, text
|
||
GameFontHighlight 290 wide at TOP -16, 128x20 UI-DialogBox-Button buttons with the
|
||
gold/white/gray state-font trio; instance l.231-239: TOP of the screen at (0,-128)) and
|
||
StaticPopup.lua's no-editbox button seat (l.1645-1649: button1 TOPRIGHT of text's BOTTOM
|
||
at (-6,-8), button2 LEFT of button1 at (13,0)) — the XML anchors are the two-button seat;
|
||
StaticPopup_Show re-anchors per entry (a solo button1 re-seats centered).
|
||
-->
|
||
<Button name="StaticPopupButtonTemplate" virtual="true">
|
||
<Size><AbsDimension x="128" y="20"/></Size>
|
||
<NormalTexture file="Interface\Buttons\UI-DialogBox-Button-Up"><TexCoords left="0.0" right="1.0" top="0.0" bottom="0.625"/></NormalTexture>
|
||
<PushedTexture file="Interface\Buttons\UI-DialogBox-Button-Down"><TexCoords left="0.0" right="1.0" top="0.0" bottom="0.625"/></PushedTexture>
|
||
<DisabledTexture file="Interface\Buttons\UI-DialogBox-Button-Disabled"><TexCoords left="0.0" right="1.0" top="0.0" bottom="0.625"/></DisabledTexture>
|
||
<HighlightTexture file="Interface\Buttons\UI-DialogBox-Button-Highlight" alphaMode="ADD"><TexCoords left="0.0" right="1.0" top="0.0" bottom="0.625"/></HighlightTexture>
|
||
<NormalFont inherits="GameFontNormal"/>
|
||
<DisabledFont inherits="GameFontDisable"/>
|
||
<HighlightFont inherits="GameFontHighlight"/>
|
||
</Button>
|
||
<!-- The dialog template + 2 instances (the ref runs 4; two is what the flows built so far
|
||
need — the deepest live nesting is decline-a-res re-showing DEATH, where Show runs before
|
||
the click's hide so a second instance must be free; the ref's own StaticPopup2 case).
|
||
Extend toward 4 the day a third coexists. StaticPopup2 seats below 1 (the ref's
|
||
StaticPopup_SetUpPosition stack, simplified to a static anchor). -->
|
||
<!-- `toplevel` is the ref's (StaticPopup.xml l.3): a confirm popup must come to the front over
|
||
whatever DIALOG-strata window raised it (decision 1318). -->
|
||
<Frame name="StaticPopupTemplate" virtual="true" toplevel="true" enableMouse="true" frameStrata="DIALOG" hidden="true">
|
||
<Size><AbsDimension x="320" y="72"/></Size>
|
||
<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">
|
||
<FontString name="$parentText" inherits="GameFontHighlight">
|
||
<Size><AbsDimension x="290" y="0"/></Size>
|
||
<Anchors><Anchor point="TOP" relativePoint="TOP"><Offset><AbsDimension x="0" y="-16"/></Offset></Anchor></Anchors>
|
||
</FontString>
|
||
<!-- The showAlert icon (ref template l.58-67 verbatim: 64x64 DialogAlertIcon at
|
||
LEFT +12). Show resets it hidden and shows it only for a showAlert entry —
|
||
the ref keeps the text 290 wide and centered even at the 420 width, so the
|
||
icon owning the left margin IS the reference look, not an overlap bug. -->
|
||
<Texture name="$parentAlertIcon" file="Interface\DialogFrame\DialogAlertIcon">
|
||
<Size><AbsDimension x="64" y="64"/></Size>
|
||
<Anchors><Anchor point="LEFT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<Frames>
|
||
<Button name="$parentButton1" inherits="StaticPopupButtonTemplate" text="Yes">
|
||
<Anchors><Anchor point="TOPRIGHT" relativeTo="$parentText" relativePoint="BOTTOM"><Offset><AbsDimension x="-6" y="-8"/></Offset></Anchor></Anchors>
|
||
<Scripts><OnClick>StaticPopup_OnClick(self:GetParent(), 1)</OnClick></Scripts>
|
||
</Button>
|
||
<Button name="$parentButton2" inherits="StaticPopupButtonTemplate" text="No">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentButton1" relativePoint="RIGHT"><Offset><AbsDimension x="13" y="0"/></Offset></Anchor></Anchors>
|
||
<Scripts><OnClick>StaticPopup_OnClick(self:GetParent(), 2)</OnClick></Scripts>
|
||
</Button>
|
||
<!-- The name-entry box (ref StaticPopup.xml l.84-140): 130x32 at BOTTOM +45, the
|
||
chat-input three-slice border, hidden unless the entry sets hasEditBox. First
|
||
customer: the social window's ADD_FRIEND / ADD_IGNORE dialogs (decision 0668).
|
||
Enter runs the entry's own EditBoxOnEnterPressed (the ref's hook name) and Escape
|
||
dismisses the whole dialog, which is the ref's EditBoxOnEscapePressed default. -->
|
||
<EditBox name="$parentEditBox" historyLines="1" hidden="true" autoFocus="false">
|
||
<Size><AbsDimension x="130" y="32"/></Size>
|
||
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="45"/></Offset></Anchor></Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture file="Interface\ChatFrame\UI-ChatInputBorder-Left">
|
||
<Size><AbsDimension x="75" y="32"/></Size>
|
||
<Anchors><Anchor point="LEFT"><Offset><AbsDimension x="-10" y="0"/></Offset></Anchor></Anchors>
|
||
<TexCoords left="0" right="0.29296875" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture file="Interface\ChatFrame\UI-ChatInputBorder-Right">
|
||
<Size><AbsDimension x="75" y="32"/></Size>
|
||
<Anchors><Anchor point="RIGHT"><Offset><AbsDimension x="10" y="0"/></Offset></Anchor></Anchors>
|
||
<TexCoords left="0.70703125" right="1.0" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnEnterPressed>StaticPopup_EditBoxOnEnterPressed(self)</OnEnterPressed>
|
||
<OnEscapePressed>StaticPopup_EditBoxOnEscapePressed(self)</OnEscapePressed>
|
||
</Scripts>
|
||
<FontString inherits="ChatFontNormal"/>
|
||
</EditBox>
|
||
<!-- The coin row (ref StaticPopup.xml l.203-217, verbatim seat): a
|
||
SmallMoneyFrameTemplate under the text, hidden unless the entry sets
|
||
hasMoneyFrame. Type "STATIC" is the money kit's "a number somebody parked on the
|
||
frame" source, which is what MoneyFrame_Update(name, amount) leaves behind — so
|
||
the dialog shows a cost its raiser worked out, never a live purse. First customer:
|
||
the class trainer's CONFIRM_TALENT_WIPE (decision 1580). -->
|
||
<Frame name="$parentMoneyFrame" inherits="SmallMoneyFrameTemplate" hidden="true">
|
||
<Anchors>
|
||
<Anchor point="TOP" relativeTo="$parentText" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Scripts>
|
||
<OnLoad>SmallMoneyFrame_OnLoad(); MoneyFrame_SetType("STATIC")</OnLoad>
|
||
</Scripts>
|
||
</Frame>
|
||
<!-- The WIDE entry box (ref StaticPopup.xml l.138-202): 350x64 at CENTER, hidden
|
||
unless the entry sets hasWideEditBox. Its border is the same chat-input art
|
||
stretched over three slices — a 256 left cap, a 75 right cap, and a 5-wide middle
|
||
pinned between the two so it fills whatever is left. First customers: the guild
|
||
arc's SET_GUILDMOTD / SET_GUILDPLAYERNOTE / SET_GUILDOFFICERNOTE. It shares the
|
||
narrow box's two key handlers, which take the box as an argument for exactly this
|
||
reason. -->
|
||
<EditBox name="$parentWideEditBox" historyLines="1" hidden="true" autoFocus="false">
|
||
<Size><AbsDimension x="350" y="64"/></Size>
|
||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture name="$parentLeft" file="Interface\ChatFrame\UI-ChatInputBorder-Left">
|
||
<Size><AbsDimension x="256" y="32"/></Size>
|
||
<Anchors><Anchor point="LEFT"><Offset><AbsDimension x="-10" y="0"/></Offset></Anchor></Anchors>
|
||
<TexCoords left="0" right="1.0" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture name="$parentRight" file="Interface\ChatFrame\UI-ChatInputBorder-Right">
|
||
<Size><AbsDimension x="75" y="32"/></Size>
|
||
<Anchors><Anchor point="RIGHT"><Offset><AbsDimension x="10" y="0"/></Offset></Anchor></Anchors>
|
||
<TexCoords left="0.70703125" right="1.0" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture file="Interface\ChatFrame\UI-ChatInputBorder-Left">
|
||
<Size><AbsDimension x="5" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="LEFT" relativeTo="$parentLeft" relativePoint="RIGHT"/>
|
||
<Anchor point="RIGHT" relativeTo="$parentRight" relativePoint="LEFT"/>
|
||
</Anchors>
|
||
<TexCoords left="0.29296875" right="1.0" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnEnterPressed>StaticPopup_EditBoxOnEnterPressed(self)</OnEnterPressed>
|
||
<OnEscapePressed>StaticPopup_EditBoxOnEscapePressed(self)</OnEscapePressed>
|
||
</Scripts>
|
||
<FontString inherits="ChatFontNormal"/>
|
||
</EditBox>
|
||
</Frames>
|
||
<!-- The engine handlers (ref StaticPopup.xml template Scripts): open/close sounds ride
|
||
inside StaticPopup_OnShow/OnHide so every path sounds; OnUpdate drives the countdown/
|
||
StartDelay machinery (arg1 = elapsed, RF-0025). -->
|
||
<Scripts>
|
||
<OnShow>StaticPopup_OnShow()</OnShow>
|
||
<OnHide>StaticPopup_OnHide()</OnHide>
|
||
<OnUpdate>StaticPopup_OnUpdate(this, arg1)</OnUpdate>
|
||
</Scripts>
|
||
</Frame>
|
||
<Frame name="StaticPopup1" inherits="StaticPopupTemplate">
|
||
<Anchors><Anchor point="TOP" relativePoint="TOP"><Offset><AbsDimension x="0" y="-128"/></Offset></Anchor></Anchors>
|
||
</Frame>
|
||
<Frame name="StaticPopup2" inherits="StaticPopupTemplate">
|
||
<Anchors><Anchor point="TOP" relativeTo="StaticPopup1" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-8"/></Offset></Anchor></Anchors>
|
||
</Frame>
|
||
|
||
<!-- The delete-item confirm driver (decision 0216 §3): DELETE_ITEM_CONFIRM names no frame of
|
||
its own (the ref handles it in UIParent.lua's OnEvent), so this is a dedicated hidden
|
||
listener. The old CURSOR_UPDATE watch is gone — the DELETE_ITEM registry entry's own
|
||
OnUpdate poll (ref-verbatim) owns the auto-hide now. -->
|
||
<Frame name="BenillaDeleteItemConfirmDriver" hidden="true">
|
||
<Size><AbsDimension x="1" y="1"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
||
<Scripts>
|
||
<OnLoad>
|
||
self:RegisterEvent("DELETE_ITEM_CONFIRM")
|
||
</OnLoad>
|
||
<OnEvent>BenillaDeleteItemConfirmDriver_OnEvent(self)</OnEvent>
|
||
</Scripts>
|
||
</Frame>
|
||
</Ui>
|