- ground-targeted spells work end to end: press the button, get the targeting cursor, click the ground, and the cast ships on the wire the way the real client sends it - a reticle sized to the spell's real radius rides the cursor, and on the click the storm model, falling shards and area sound all land at the point - character screens: a newly created character comes back selected on the list, and the authored layout scales to small windows instead of clipping the bottom controls off - blob shadows ride their receiver's clip transform and the raster bias that kept them faint is measured and fixed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1528 lines
82 KiB
XML
1528 lines
82 KiB
XML
<Ui>
|
|
<!--
|
|
The UIPanel slot manager (decision 0084 §2) — benilla's own transcription of Blizzard's
|
|
UIParent.lua panel-window plumbing, script-only (no frames of its own). Loaded FIRST in the
|
|
shipped-XML list (ui_script.rs) so `UIPanelWindows`/`ShowUIPanel`/`HideUIPanel` exist before
|
|
any panel frame's OnLoad/OnEvent references them.
|
|
|
|
Ground truth: the 1.12 `UIParent.lua` extract (scratchpad/UIParent.lua, patch-2.MPQ), quoted
|
|
verbatim in the line-numbered comments below — never re-derived. v1 scope (pin §3): the
|
|
**left + center two-slot logic only**. The real client's `full`/`doublewide` areas, the
|
|
`CanOpenPanels`/full-screen/native-center gates, `UIChildWindows`, and `UISpecialFrames` are
|
|
all out of scope — no benilla panel uses them yet — but the registration table keeps their
|
|
exact shape (`area`, `pushable`, `whileDead`) so a later row drops in unchanged.
|
|
|
|
Frame names here are OUR shipped names (`Benilla*`); UIParent.lua's names (`GossipFrame`,
|
|
`MerchantFrame`, `LootFrame`) are the semantic reference the pin cites, not what we register.
|
|
-->
|
|
<Script><![CDATA[
|
|
-- UIParent.lua l.14-42: UIPanelWindows["FrameName"] = { area, pushable, whileDead }. Only the
|
|
-- rows benilla ships/anticipates today (pin §1): Gossip + Merchant are live; Loot registers
|
|
-- now (pushable=7) so the coming loot window lands on an already-wired system.
|
|
UIPanelWindows = {}
|
|
UIPanelWindows["BenillaGossipFrame"] = { area = "left", pushable = 0 }
|
|
UIPanelWindows["BenillaMerchantFrame"] = { 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 special-frame toplevel
|
|
-- (UIParent.lua l.46 UISpecialFrames), closed by ESC in ToggleGameMenu below (this v1 drops
|
|
-- the UISpecialFrames table, so escapable toplevels are hard-listed there, like WorldMapFrame).
|
|
UIPanelWindows["BenillaMailFrame"] = { area = "left", pushable = 0 }
|
|
-- 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["BenillaTradeFrame"] = { 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["BenillaBankFrame"] = { 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["BenillaTaxiFrame"] = { area = "left", pushable = 0 }
|
|
UIPanelWindows["BenillaQuestFrame"] = { area = "left", pushable = 0 }
|
|
UIPanelWindows["BenillaQuestLogFrame"] = { area = "left", pushable = 0 }
|
|
UIPanelWindows["BenillaLootFrame"] = { area = "left", pushable = 7 }
|
|
-- The character window (decision 0208 phase 1a, CharacterFrame.xml): the ref's own
|
|
-- CharacterFrame row (area "left", pushable 0) — competes for/replaces the other left-slot
|
|
-- windows exactly like Gossip/Merchant/Quest/QuestLog above.
|
|
UIPanelWindows["BenillaCharacterFrame"] = { area = "left", pushable = 0 }
|
|
-- 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
|
|
-- (area "left", pushable 0) — same left-slot competition as every window above.
|
|
UIPanelWindows["BenillaSpellBookFrame"] = { area = "left", pushable = 0 }
|
|
-- The talent window (decision 0304 §4, TalentFrame.xml): the ref's own TalentFrame row is
|
|
-- `{ area = "left", pushable = 6, whileDead = 1 }` — pushable 6 kept (one tier above the
|
|
-- plain NPC-session/player-state windows' 0, matching the ref exactly); `whileDead` is
|
|
-- OMITTED like every other row here (none of them model it, even though ShowUIPanel's own
|
|
-- guard already reads `info.whileDead` — ShowUIPanel's own comment: `UnitIsDead` doesn't
|
|
-- exist yet, so the guard is inert either way today).
|
|
UIPanelWindows["BenillaTalentFrame"] = { area = "left", pushable = 6 }
|
|
-- 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["BenillaFriendsFrame"] = { area = "left", pushable = 0, 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 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 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 }
|
|
|
|
-- 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.840-846) is dropped: no such frames exist in benilla yet (out of v1 scope).
|
|
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
|
|
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. benilla has no UIParent frame (the note on `slots` above), and the only
|
|
-- full frame we ship — the world map — already paints its own full-screen blackout
|
|
-- (WorldMapFrame.xml), which is what that Hide buys the reference.
|
|
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
|
|
|
|
-- 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()
|
|
local centerFrame = GetCenterFrame()
|
|
if not centerFrame then
|
|
return 1
|
|
end
|
|
local info = UIPanelWindows[centerFrame:GetName()]
|
|
if info and info.area == "center" then
|
|
return nil
|
|
end
|
|
return 1
|
|
end
|
|
|
|
-- ShowUIPanel(frame) — UIParent.lua l.649-751, the left+center slice (pin §3 steps 1,3-5)
|
|
-- plus the native-center mode decision 0674 needed. Still skipped by scope: full-screen
|
|
-- (l.668-675, l.692-696 — no `full` row is registered; benilla's world map is a plain
|
|
-- toplevel, not a panel) and `doublewide` (l.707-710 — likewise none).
|
|
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.
|
|
if not CanOpenPanels() and info and info.area ~= "center" then
|
|
return
|
|
end
|
|
if not info then
|
|
frame:Show()
|
|
return
|
|
end
|
|
|
|
-- UnitIsDead doesn't exist yet (pin §3) — guarded rather than assumed absent-forever.
|
|
if type(UnitIsDead) == "function" and UnitIsDead("player") and not info.whileDead then
|
|
return
|
|
end
|
|
|
|
-- l.668-675: a full-screen frame open ignores every non-full open request (`force`
|
|
-- evicts it instead). The world map is the only `full` row, so this reads: nothing opens
|
|
-- behind the map.
|
|
if GetFullScreenFrame() and info.area ~= "full" then
|
|
if force then
|
|
SetFullScreenFrame(nil)
|
|
else
|
|
return
|
|
end
|
|
end
|
|
|
|
-- l.677-689: with a native-center frame open, only another center request gets through
|
|
-- — `force` evicts it instead of refusing. Redundant with the CanOpenPanels gate above
|
|
-- for every caller 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.
|
|
local centerFrame = GetCenterFrame()
|
|
if centerFrame then
|
|
local centerInfo = UIPanelWindows[centerFrame:GetName()]
|
|
if centerInfo and centerInfo.area == "center" and info.area ~= "center" then
|
|
if force then
|
|
SetCenterFrame(nil)
|
|
else
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
-- l.692-696: full-screen frames just replace each other, and they take everything with
|
|
-- them on the way in.
|
|
if info.area == "full" then
|
|
CloseAllWindows()
|
|
SetFullScreenFrame(frame)
|
|
return
|
|
end
|
|
|
|
-- l.697-702: native-center frames just replace each other and take the screen —
|
|
-- everything else closes first. `skipSetPoint` is the load-bearing argument: the menu
|
|
-- keeps its own CENTER anchor instead of being dropped at the left-area seat.
|
|
if info.area == "center" then
|
|
CloseWindows()
|
|
CloseAllBags()
|
|
SetCenterFrame(frame, 1)
|
|
return
|
|
end
|
|
|
|
if info.area ~= "left" then
|
|
-- No full/doublewide row is registered — defensive fallback only.
|
|
frame:Show()
|
|
return
|
|
end
|
|
|
|
-- Try to put it on the left (l.719-723).
|
|
local leftFrame = GetLeftFrame()
|
|
if not leftFrame then
|
|
SetLeftFrame(frame)
|
|
return
|
|
end
|
|
|
|
local leftInfo = UIPanelWindows[leftFrame:GetName()]
|
|
local centerFrame = GetCenterFrame()
|
|
if not centerFrame then
|
|
-- If neither is pushable, replace (l.729-732 — the gossip<->merchant case).
|
|
if leftInfo.pushable == 0 and info.pushable == 0 then
|
|
SetLeftFrame(frame)
|
|
return
|
|
end
|
|
-- Highest priority goes to center (l.735-741).
|
|
if leftInfo.pushable > info.pushable then
|
|
MovePanelToCenter()
|
|
SetLeftFrame(frame)
|
|
else
|
|
SetCenterFrame(frame)
|
|
end
|
|
return
|
|
end
|
|
|
|
-- Center is already taken too (l.744-751).
|
|
local centerInfo = UIPanelWindows[centerFrame:GetName()]
|
|
if info.pushable > centerInfo.pushable then
|
|
MovePanelToLeft()
|
|
SetCenterFrame(frame)
|
|
else
|
|
SetLeftFrame(frame)
|
|
end
|
|
end
|
|
|
|
-- HideUIPanel(frame) — UIParent.lua l.754-786, v1 left+center slice (the real fn's
|
|
-- full-screen branch, l.759-763, is dropped: no full-screen row ships).
|
|
function HideUIPanel(frame)
|
|
if not frame or not frame:IsShown() then
|
|
return
|
|
end
|
|
|
|
-- l.759-763.
|
|
if frame == GetFullScreenFrame() then
|
|
SetFullScreenFrame(nil)
|
|
return
|
|
end
|
|
|
|
if frame == GetCenterFrame() then
|
|
SetCenterFrame(nil)
|
|
return
|
|
end
|
|
|
|
if frame == GetLeftFrame() then
|
|
local centerFrame = GetCenterFrame()
|
|
if centerFrame then
|
|
local info = UIPanelWindows[centerFrame:GetName()]
|
|
if info and info.area == "left" then
|
|
MovePanelToLeft()
|
|
return
|
|
end
|
|
end
|
|
SetLeftFrame(nil)
|
|
return
|
|
end
|
|
|
|
frame:Hide()
|
|
end
|
|
|
|
-- CloseWindows(ignoreCenter) — UIParent.lua l.928-957, v1 left+center slice (the
|
|
-- doublewide/full-screen/UISpecialFrames handling, l.932-954, is dropped: none exist yet).
|
|
-- Implemented per the pin so the function exists; wiring it to ESC is a later slice.
|
|
function CloseWindows(ignoreCenter)
|
|
local leftFrame = GetLeftFrame()
|
|
local centerFrame = GetCenterFrame()
|
|
local fullScreenFrame = GetFullScreenFrame()
|
|
local found = leftFrame or centerFrame or fullScreenFrame
|
|
|
|
HideUIPanel(leftFrame)
|
|
HideUIPanel(fullScreenFrame)
|
|
if centerFrame then
|
|
local info = UIPanelWindows[centerFrame:GetName()]
|
|
if not info or info.area ~= "center" or not ignoreCenter then
|
|
HideUIPanel(centerFrame)
|
|
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 — matching the 'B' all-bags toggle),
|
|
-- 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 BenillaOpenAllBags).
|
|
local exists = bagId == 0
|
|
or (C_Container and C_Container.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.
|
|
--
|
|
-- Absent branches, and why: no OptionsFrame (nothing to cancel — GameMenuFrame.xml's five
|
|
-- disabled buttons are exactly that gap). 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).
|
|
if clicked then
|
|
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 menu itself closes next (l.1485-1487) — before every close/cancel rung below, so
|
|
-- ESC with the menu up only puts the menu away. (The OptionsFrame rung the reference
|
|
-- checks between these two has no benilla frame; GameMenuFrame.xml's disabled buttons
|
|
-- ARE that gap.) 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("BenillaStackSplitFrame")
|
|
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 since frames get no raw key events).
|
|
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("BenillaOpenMailFrame")
|
|
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
|
|
|
|
-- SetDesaturation(texture, desaturation) — ref UIParent.lua l.1499-1510, verbatim. The
|
|
-- reference itself has a no-shader fallback, and OURS IS ALWAYS THAT PATH: this engine has
|
|
-- no SetDesaturated, so `shaderSupported` is false exactly as on a 1.12 card without the
|
|
-- desaturation shader, and the grey comes from the ref's own vertex-colour branch. Written
|
|
-- as the wrapper rather than open-coded at each call site so the day a real desaturation
|
|
-- lands, one function changes. 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) ─────────────────
|
|
-- Transcribed 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.
|
|
-- · 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.
|
|
-- · Accepted but v1-inert: exclusive, showAlert (no alert icon/420-width yet),
|
|
-- interruptCinematic (cinematics are acked instantly — 0196), notClosableByLogout,
|
|
-- preferredIndex, hasWideEditBox (guild MOTD/notes — the guild arc), and the money
|
|
-- variant.
|
|
-- · 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, the no-editbox/no-money leg only (v1 has neither variant).
|
|
function StaticPopup_Resize(dialog, which)
|
|
local text = getglobal(dialog:GetName() .. "Text")
|
|
local button1 = getglobal(dialog:GetName() .. "Button1")
|
|
local editBox = getglobal(dialog:GetName() .. "EditBox")
|
|
if StaticPopupDialogs[which] and StaticPopupDialogs[which].hasEditBox then
|
|
dialog:SetHeight(16 + text:GetHeight() + 8 + editBox:GetHeight() + 8
|
|
+ button1:GetHeight() + 16)
|
|
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
|
|
|
|
-- 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 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. The wide variant is guild-only
|
|
-- (MOTD/notes), so it waits for the guild arc.
|
|
local editBox = getglobal(dialog:GetName() .. "EditBox")
|
|
if info.hasEditBox then
|
|
editBox:SetMaxLetters(info.maxLetters or 0)
|
|
editBox:SetText("")
|
|
editBox:Show()
|
|
else
|
|
editBox:Hide()
|
|
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.
|
|
local buttonAnchor = info.hasEditBox and (dialog:GetName() .. "EditBox")
|
|
or (dialog:GetName() .. "Text")
|
|
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, -8)
|
|
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.
|
|
-- showAlert/exclusive are v1-inert (engine header note).
|
|
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, transcribed VERBATIM (the
|
|
-- while-index iteration included; tinsert/tDeleteItem spelled as table.insert / the
|
|
-- remove loop). 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 = {}
|
|
|
|
function UIFrameFadeRemoveFrame(frame)
|
|
local index = 1
|
|
while FADEFRAMES[index] do
|
|
if FADEFRAMES[index] == frame then
|
|
table.remove(FADEFRAMES, index)
|
|
else
|
|
index = index + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
function UIFrameFade(frame, fadeInfo)
|
|
if not fadeInfo.mode then fadeInfo.mode = "IN" end
|
|
if fadeInfo.mode == "IN" then
|
|
if not fadeInfo.startAlpha then fadeInfo.startAlpha = 0 end
|
|
if not fadeInfo.endAlpha then fadeInfo.endAlpha = 1.0 end
|
|
elseif fadeInfo.mode == "OUT" then
|
|
if not fadeInfo.startAlpha then fadeInfo.startAlpha = 1.0 end
|
|
if not fadeInfo.endAlpha then fadeInfo.endAlpha = 0 end
|
|
end
|
|
frame:SetAlpha(fadeInfo.startAlpha)
|
|
frame.fadeInfo = fadeInfo
|
|
-- Already fading? Leave the running fade alone (the ref's early return).
|
|
local index = 1
|
|
while FADEFRAMES[index] do
|
|
if FADEFRAMES[index] == frame then return end
|
|
index = index + 1
|
|
end
|
|
frame:Show()
|
|
table.insert(FADEFRAMES, frame)
|
|
end
|
|
|
|
function UIFrameFadeIn(frame, timeToFade, startAlpha, endAlpha)
|
|
local fadeInfo = {}
|
|
fadeInfo.mode = "IN"
|
|
fadeInfo.timeToFade = timeToFade
|
|
fadeInfo.startAlpha = startAlpha
|
|
fadeInfo.endAlpha = endAlpha
|
|
UIFrameFade(frame, fadeInfo)
|
|
end
|
|
|
|
function UIFrameFadeOut(frame, timeToFade, startAlpha, endAlpha)
|
|
local fadeInfo = {}
|
|
fadeInfo.mode = "OUT"
|
|
fadeInfo.timeToFade = timeToFade
|
|
fadeInfo.startAlpha = startAlpha
|
|
fadeInfo.endAlpha = endAlpha
|
|
UIFrameFade(frame, fadeInfo)
|
|
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)
|
|
local index = 1
|
|
while FADEFRAMES[index] do
|
|
if FADEFRAMES[index] == frame then return 1 end
|
|
index = index + 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.
|
|
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, the
|
|
-- padding-only branch the tab template's OnShow uses — TabResize(0) → tab width =
|
|
-- text + the two 20px end slices; both middle slices stretch to the text; the text box
|
|
-- itself un-sizes to natural). 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)
|
|
local name = tab:GetName()
|
|
local text = getglobal(name .. "Text")
|
|
local width = text:GetStringWidth() + (padding or 24)
|
|
text:SetWidth(0)
|
|
getglobal(name .. "Middle"):SetWidth(width)
|
|
getglobal(name .. "MiddleDisabled"):SetWidth(width)
|
|
local sideWidths = 2 * getglobal(name .. "Left"):GetWidth()
|
|
tab:SetWidth(width + sideWidths)
|
|
end
|
|
|
|
-- The tab template's per-frame settle: the ref does TabResize(0) + the highlight width
|
|
-- 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).
|
|
function BenillaTabButton_OnUpdate(tab)
|
|
local text = getglobal(tab:GetName() .. "Text")
|
|
local w = (text and text:GetStringWidth()) or 0
|
|
if w <= 0 or tab.benillaFitWidth == w then return end
|
|
tab.benillaFitWidth = w
|
|
PanelTemplates_TabResize(0, tab)
|
|
getglobal(tab:GetName() .. "HighlightTexture"):SetWidth(w + 30)
|
|
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. -->
|
|
<Frame name="BenillaFadeDriver">
|
|
<Size><AbsDimension x="1" y="1"/></Size>
|
|
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
|
<Scripts><OnUpdate>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) +
|
|
the highlight width) 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). One deliberate 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 BOTTOM +2,-8). 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"/>
|
|
<HighlightTexture name="$parentHighlightTexture" file="Interface\PaperDollInfoFrame\UI-Character-Tab-Highlight" alphaMode="ADD">
|
|
<Size><AbsDimension x="5" y="32"/></Size>
|
|
<Anchors><Anchor point="BOTTOM"><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). -->
|
|
<Frame name="StaticPopupTemplate" virtual="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>
|
|
</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>
|
|
</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>
|