- spells shake the camera: `SpellEffectCameraShakes.dbc` loads beside `CameraShakes.dbc` as the group table, `SpellVisualKit` field 14 is the shake column (58 of 1772 kits carry one — War Stomp, Thunder Clap, Kodo Stomp, the ~50 Knockdown/Slam/Charge impacts, every summon, Sapper Charge, Earthquake, Sapphiron's death), and the kit shake fires exactly once per play from the first effect node's one-time arm pass (`0x620e11`), the same leg that plays the kit sound one field over — the older "fires only if nothing else did" reading would have silenced every kit with a populated effect slot; `$SHK` markers fire from GameObject and DynamicObject hosts only (the titan chest, the bomb wagon); one deliberate deviation — the reference reads a bone-attached node's position twelve instructions before it is written, so 26 of its 58 shake kits shake at the world origin and are culled by the 80 yd cut, which is an uninitialised field, not a design; 21 of 24 presets now have a live producer, up from 6 - the sound keeps playing when the window loses focus, if you want it to: 1.12 mutes everything on `WM_ACTIVATE` and offers no way out, so benilla ships that as the default (`Sound_EnableSoundWhenGameIsInBG` "0") and puts an Audio-page checkbox on it — a gate appended LAST in the main-track chain, downstream of every tap, so an unattended capture still records; wow-re's round settled that music is not exempt and that it is a mute, not a pause - a building's far side no longer draws through its own walls: the camera-inside leg has three passes and we had one and a half — Pass 2 admits an exterior-shell group only against a frustum clipped to ONE deferred window (we marked every such group visible against the full view, under a comment calling it a benign over-draw), and Pass 3, which we had none of, draws the 24 `0x10000` groups (14 in Stormwind, 6 across Dire Maul, Razorfen's four 450–520 yd shells) from anywhere, frustum-tested on both legs by byte-identical setup; there is no antiportal branch in 5875 and no such flag in its data (815/815 roots and 5220/5220 groups censused, no bit above 18 ever set) - the macro window is the reference's own `Blizzard_MacroUI`, and `GlobalStrings.lua` — line 1 of the reference's own toc — was missing from our manifest entirely (the app loaded it out of band at startup, so the client was right and every harness that loaded the manifest was not); exactly one `<OnLoad>` runs, the most-derived, because `SetScript` releases the slot before it looks at the new body — which is why the reference re-states the template's width line in tab 1 and not tab 2 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1021 lines
62 KiB
XML
1021 lines
62 KiB
XML
<Ui>
|
|
<!--
|
|
UIParent itself (ref UIParent.xml l.5, quoted): the real, full-screen, invisible root
|
|
frame the reference UI hangs everything on. Until now benilla shipped only this file's
|
|
Lua (the manage pass below) and no frame - so the Lua global `UIParent` was nil, and
|
|
every ref-quoted `SetOwner(UIParent, ...)` / `GameTooltip_SetDefaultAnchor(this,
|
|
UIParent)` died before it could place anything (the world tooltip stayed parked on its
|
|
load-time anchor - the "tooltip on my character" bug). String anchors ("UIParent" in
|
|
SetPoint) had silently fallen back to the screen root, which has the same rect, so only
|
|
the *global* was ever missing. No enableMouse: the hit test gates on it (pointer.rs), so
|
|
the full-screen plate swallows nothing.
|
|
|
|
The ref's UIParent_OnLoad registers a long list of globally-routed events and its OnEvent
|
|
is a chain of `if event == ...` arms; ours ships exactly the ONE arm that has a consumer
|
|
(the extra bars' login seed, below) and grows an arm at a time as consumers appear. A
|
|
transcription of the whole routing table would be a list of no-ops that reads as coverage.
|
|
|
|
Known seam, stated: the real client PARENTS every FrameXML frame under UIParent (scale/
|
|
visibility cascade). benilla is PART WAY there - 41 of our top-level frames declare
|
|
parent="UIParent" where the reference declares 104, and 32 frames the reference parents
|
|
(MainMenuBar, PlayerFrame, TargetFrame, the four MultiBars, UIErrorsFrame, the panels)
|
|
still attach to the screen root because the transcription dropped the attribute. Same
|
|
rect, so anchors agree.
|
|
|
|
The cascade consumer this note was waiting for HAS appeared (decision 1724): a cinematic
|
|
hides the HUD by doing `UIParent:Hide()` in the reference, and benilla substitutes an
|
|
engine-side draw-kill that also switches the mouse feed off - which is how the world came
|
|
to take every click of a race intro. Restoring the dropped parents is the fix; it is a
|
|
whole-UI arc (draw order, strata, ALT-Z, the world map's own full-screen row) and so is
|
|
proposed rather than taken.
|
|
-->
|
|
<Frame name="UIParent" setAllPoints="true" frameStrata="MEDIUM">
|
|
<Scripts>
|
|
<OnLoad>
|
|
this:RegisterEvent("VARIABLES_LOADED")
|
|
this:RegisterEvent("MIRROR_TIMER_START")
|
|
this:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
this:RegisterEvent("READY_CHECK")
|
|
this:RegisterEvent("START_LOOT_ROLL")
|
|
this:RegisterEvent("CONFIRM_LOOT_ROLL")
|
|
</OnLoad>
|
|
<OnEvent>UIParent_OnEvent(event)</OnEvent>
|
|
</Scripts>
|
|
</Frame>
|
|
|
|
<!--
|
|
WorldFrame — the reference's full-screen, bottom-of-the-strata frame that the 3D world
|
|
renders into. **Ours renders nothing**: benilla draws the world through Bevy's render
|
|
graph, not through a FrameScript frame, so this is a NAMED HANDLE and nothing more. That
|
|
is a disclosed divergence, not a pretence — and it is what makes the two things addons
|
|
actually do with it work: `WorldFrame:GetWidth()`/`GetHeight()` (the screen rect, which
|
|
this frame really has) and `frame:SetParent(WorldFrame)` / `SetPoint(..., WorldFrame,
|
|
...)` (parking a tooltip or an overlay under everything). Measured demand: 29 of the 218
|
|
vanilla-corpus addons die on their first load with `attempt to index global 'WorldFrame'`
|
|
without it (the corpus harness, `benilla_app::addon_harness`).
|
|
|
|
**No `enableMouse`, deliberately** — the same reason UIParent above ships without it, and
|
|
here it is load-bearing rather than incidental. benilla's `PointerOverUi` is "is the
|
|
topmost mouse-enabled frame under the cursor a UI frame", and it gates camera look, world
|
|
clicks and hover targeting (`player/camera.rs`, `target/hover.rs`); a full-screen
|
|
mouse-enabled plate would answer yes for every world pixel and kill all three. The
|
|
reference gets away with it because *its* WorldFrame IS the world, so a click landing
|
|
there is a world click by construction. Reproducing that here means teaching the arbiter
|
|
that a WorldFrame hit is not a UI hit — engine work with its own tests, not a rename — so
|
|
until then the addon-facing `OnMouseDown`/`OnMouseUp` hooks on this frame never fire.
|
|
-->
|
|
<Frame name="WorldFrame" setAllPoints="true" frameStrata="BACKGROUND"/>
|
|
|
|
<!--
|
|
The UIParent bottom-stack position manager - UIPARENT_MANAGED_FRAME_POSITIONS and
|
|
UIParent_ManageFramePositions(), transcribed from the extracted 1.12 UIParent.lua
|
|
l.1560-1697 (patch-2.MPQ via mpqx, byte-diffed against this install's own chain;
|
|
decision 0272). The reference never leaves a bottom-anchored HUD frame on its static
|
|
XML anchor: every layout pass re-anchors it to a base offset plus per-bar deltas -
|
|
either bottom multibar, the pet/stance bar, the reputation watch bar - fired from those
|
|
bars' OnShow/OnHide (ref BonusActionBarFrame.xml:350-355 et al.) and applied once at
|
|
startup. The loader's post-load call (ui_script/mod.rs) is our startup seam; the stance
|
|
bar's OnShow/OnHide (StanceBar.xml) is the one live re-fire today.
|
|
|
|
Departures, each named:
|
|
- Lua 5.1 dialect (the 1.12 client ran 5.0): pairs() iteration, table.insert for
|
|
tinsert, _G[name] = v for setglobal, and the ref's undeclared `hasBottomRight`
|
|
declared local (it is never read).
|
|
- The ref gates the bottom flags on its interface-option globals
|
|
SHOW_MULTI_ACTIONBAR_1/2; benilla's bottom bars are always-on with no options state
|
|
(decision 0270), so the flags read the bars' own visibility - identical values, no
|
|
parallel global to drift.
|
|
- Frame names: our stance bar, pet bar and bottom multibars wear the reference's own
|
|
names (ShapeshiftBarFrame, PetActionBarFrame, MultiBarBottomLeft/Right), so their
|
|
rows and flags in the table below are LIVE, not the no-ops they were while those
|
|
frames carried a `Benilla` prefix. That is the payoff the bare names buy — the same
|
|
one `group_loot_tests::managed_positions_engage_for_the_bare_frame_name` pins for
|
|
GroupLootFrame1 — and it costs nothing on screen: each frame's static XML anchor was
|
|
already written to the value the row computes (the stance bar's XML (30,45) is the
|
|
row's xOffset 30 + baseY 0 + bottomLeft 45, and MultiBarBottomLeft's XML (0,17) is
|
|
its row's baseY 17), so the pass now re-derives what the XML asserted. TutorialFrameParent
|
|
and FramerateLabel still have no frame here and no-op through the ref's `if ( frame )`.
|
|
- Every frame these flags read now exists, so every flag is a real read — the nil guards
|
|
stay because the ref reads them bare only because it always creates them, and our
|
|
per-window test harnesses load file SUBSETS. `reputation` fires the moment a watched
|
|
faction raises ReputationWatchBar over a shown MainMenuExpBar (decision 1258; it could
|
|
not fire at all until that page landed, and `reputation_frame_tests` pins the 9px it
|
|
moves the bottom stack by). `maxLevel` fires exactly when the level-60 rail replaces the
|
|
XP bar — which is also why the two never fire together: at 60 the watch bar REPLACES
|
|
MainMenuExpBar rather than stacking above it, so `MainMenuExpBar:IsShown()` is false and
|
|
the stack stays put. Both are the ref's own arithmetic.
|
|
- No IsUserPlaced/IsObjectType/SIMPLE_CHAT legs on the SetPoint: benilla has no
|
|
user-placed frames and no simple-chat option, so the ref's guarded SetPoint
|
|
collapses to the plain one.
|
|
- Of the ref function's trailing custom positioning, the pet-bar half IS ours (the
|
|
sliding shelf art + the PETACTIONBAR_XPOS/YPOS re-seat, at the end of the pass,
|
|
decision 0988). BattlefieldMinimapTab and the shapeshift-bar art swap stay out of
|
|
scope - neither frame exists here, our stance bar shipping no end-caps at all (0270).
|
|
- The isVar globals (CONTAINER_OFFSET_*, PETACTIONBAR_YPOS, BATTLEFIELD_TAB_OFFSET_Y)
|
|
are pre-declared to their base values below - the ref declares them in
|
|
ContainerFrame.lua/PetActionBarFrame.lua, which we don't ship - so the ref's
|
|
`if ( frame )` guard (getglobal on a var row yields the number) behaves identically.
|
|
Nothing consumes them yet; they are set for the day the bag anchors do.
|
|
-->
|
|
<Script><![CDATA[
|
|
-- Declared by the ref's ContainerFrame.lua / PetActionBarFrame.lua / WorldStateFrame.lua
|
|
-- (files we don't ship); the manage pass below overwrites them every run.
|
|
CONTAINER_OFFSET_X = 0
|
|
CONTAINER_OFFSET_Y = 70
|
|
BATTLEFIELD_TAB_OFFSET_Y = 210
|
|
PETACTIONBAR_YPOS = 97
|
|
-- ref PetActionBarFrame.lua:3. Not a managed row (the manage pass computes only the Y);
|
|
-- the custom block at the end of the pass recomputes it, for the reason recorded there.
|
|
PETACTIONBAR_XPOS = 36
|
|
|
|
-- ref UIParent.lua:1 — the tooltip/range re-check cadence. It lived in our ActionBar.xml,
|
|
-- beside the range timer that reads it; the reference's own `BuffFrame.lua:32` reads it too
|
|
-- and, now that BuffFrame is the reference's own file (1751), a kit can want the buff row
|
|
-- without wanting the action bars. So it moves to the reference's home, one line above the
|
|
-- constant below it.
|
|
TOOLTIP_UPDATE_TIME = 0.2
|
|
|
|
-- ref UIParent.lua:2 — the model panes' held-button spin rate (half a turn per second),
|
|
-- read by every `Model_OnUpdate` transcription (the character window's + inspect's).
|
|
ROTATIONS_PER_SECOND = .5
|
|
|
|
-- ref SecondsToTime (UIParent.lua:1004-1031) and its GlobalStrings, in the ref's own home
|
|
-- file — two consumers now (the quest-timer window and the quest log's "Time Remaining:"
|
|
-- row, decision 1150), so it lives here rather than being redefined per file the way
|
|
-- BuffFrame.xml had to for SecondsToTimeAbbrev when it was the only caller.
|
|
--
|
|
-- Departures, both named:
|
|
-- - `GetText(TOKEN, nil, n)` is the ref's plural-aware GlobalString lookup, a C binding we
|
|
-- don't ship. Its enUS rule is the singular token for exactly 1 and the `_P1` twin
|
|
-- otherwise — the same rule already codified in `tooltip_item/render.rs` and MailFrame.xml
|
|
-- — so it collapses to the local `plural()` below and the four shipped string pairs
|
|
-- (GlobalStrings.lua:895-896, 2296-2297, 2693-2694, 3404-3405).
|
|
-- - Lua 5.1 dialect: `math.mod`/`math.floor` for the 5.0 bare `mod`/`floor`.
|
|
--
|
|
-- Everything else is verbatim, INCLUDING the two behaviours that look like bugs and are
|
|
-- not: the thresholds are strict `>` (86400 s reads as hours, not "1 Day"), and `count < 2`
|
|
-- stops after two units, so a 3-day quest reads "3 Days 4 Hrs" and never mentions minutes.
|
|
DAYS_ABBR = DAYS_ABBR or "Day"
|
|
DAYS_ABBR_P1 = DAYS_ABBR_P1 or "Days"
|
|
HOURS_ABBR = HOURS_ABBR or "Hr"
|
|
HOURS_ABBR_P1 = HOURS_ABBR_P1 or "Hrs"
|
|
MINUTES_ABBR = MINUTES_ABBR or "Min"
|
|
MINUTES_ABBR_P1 = MINUTES_ABBR_P1 or "Mins"
|
|
SECONDS_ABBR = SECONDS_ABBR or "Sec"
|
|
SECONDS_ABBR_P1 = SECONDS_ABBR_P1 or "Secs"
|
|
|
|
if not SecondsToTime then
|
|
local function plural(singular, pluralForm, n)
|
|
if n == 1 then return singular end
|
|
return pluralForm
|
|
end
|
|
|
|
function SecondsToTime(seconds, noSeconds)
|
|
local time = ""
|
|
local count = 0
|
|
local tempTime
|
|
seconds = math.floor(seconds)
|
|
if seconds > 86400 then
|
|
tempTime = math.floor(seconds / 86400)
|
|
time = tempTime .. " " .. plural(DAYS_ABBR, DAYS_ABBR_P1, tempTime) .. " "
|
|
seconds = math.mod(seconds, 86400)
|
|
count = count + 1
|
|
end
|
|
if seconds > 3600 then
|
|
tempTime = math.floor(seconds / 3600)
|
|
time = time .. tempTime .. " " .. plural(HOURS_ABBR, HOURS_ABBR_P1, tempTime) .. " "
|
|
seconds = math.mod(seconds, 3600)
|
|
count = count + 1
|
|
end
|
|
if count < 2 and seconds >= 60 then
|
|
tempTime = math.floor(seconds / 60)
|
|
time = time .. tempTime .. " " .. plural(MINUTES_ABBR, MINUTES_ABBR_P1, tempTime) .. " "
|
|
seconds = math.mod(seconds, 60)
|
|
count = count + 1
|
|
end
|
|
if count < 2 and seconds > 0 and not noSeconds then
|
|
time = time .. seconds .. " " .. plural(SECONDS_ABBR, SECONDS_ABBR_P1, seconds) .. " "
|
|
end
|
|
return time
|
|
end
|
|
end
|
|
|
|
-- ── The login seed for the extra action bars ──────────────────────────────────────────
|
|
--
|
|
-- The four extra bars' visibility is stored SERVER-SIDE, in `PLAYER_FIELD_BYTES` byte 2,
|
|
-- and the client never writes that byte itself — `SetActionBarToggles` only posts
|
|
-- `CMSG_SET_ACTIONBAR_TOGGLES` and waits for the field to come back (wow-re
|
|
-- `system/ui/scratch/action-bar-toggles.md` §5: the setter's every store is into its own
|
|
-- stack frame, and nothing in the binary fires on a change to that byte). So the live UI
|
|
-- state is the four Lua globals `MultiBars.xml` owns, and `GetActionBarToggles()` is read
|
|
-- exactly ONCE, here, to seed them. Reading it again later would fight the optimistic
|
|
-- value the options rows just set and lose a round trip's worth of clicks.
|
|
--
|
|
-- This is the reference's own seam, arm for arm (its UIParent_OnEvent does the same two
|
|
-- statements on the same event). `MultiActionBar_Update` is existence-guarded because the
|
|
-- ~25 per-window harnesses that load this file load no bars; the binding itself is engine
|
|
-- level and always installed. Decision 1500.
|
|
-- ref UIParent.lua:223, whose slot this is — immediately above `UIParent_OnEvent`, the
|
|
-- same neighbour it has there. It moved here from our own InspectFrame.xml when 1832
|
|
-- retired that file: the name belongs to UIParent in the reference, and the window it
|
|
-- opens is now the stock LoadOnDemand `Blizzard_InspectUI` off the player's chain.
|
|
--
|
|
-- The reference's body is `InspectFrame_LoadUI()` and then a guarded `InspectFrame_Show`.
|
|
-- We have no load-on-demand — benilla.toc loads that addon with everything else — so the
|
|
-- LoadUI call has nothing to do and is left out rather than stubbed (1203). The guard is
|
|
-- kept exactly as the reference writes it, and it is not decoration: it is what makes this
|
|
-- safe in the ~25 per-window harnesses that load UIParent.xml without the inspect window.
|
|
function InspectUnit(unit)
|
|
if ( InspectFrame_Show ) then
|
|
InspectFrame_Show(unit);
|
|
end
|
|
end
|
|
|
|
-- ref UIParent.lua's own `ToggleTalentFrame`, moved here from our TalentFrame.xml when
|
|
-- 1833 retired that file — the same move `InspectUnit` made just above, for the same
|
|
-- reason: the reference keeps the opener in UIParent and the window in a LoadOnDemand
|
|
-- addon, so deleting our window file would have taken the name with it.
|
|
--
|
|
-- The LEVEL GATE is the reference's, and it is real behaviour rather than a guard: below
|
|
-- level 10 the key and the micro button do nothing at all. `TalentFrame_LoadUI` is left
|
|
-- out (we load the addon eagerly, 1203 — build it or leave it out, never stub it); the
|
|
-- `TalentFrame_Toggle` guard is kept verbatim, and it is what lets the ~25 harnesses that
|
|
-- load UIParent.xml without the talent window do so safely.
|
|
function ToggleTalentFrame()
|
|
if ( UnitLevel("player") < 10 ) then
|
|
return;
|
|
end
|
|
if ( TalentFrame_Toggle ) then
|
|
TalentFrame_Toggle();
|
|
end
|
|
end
|
|
|
|
-- ref UIParent.lua's own `ShowMacroFrame`, moved here from our MacroFrame.xml when 1848
|
|
-- retired that file — the third such squatter after `InspectUnit` (1832) and
|
|
-- `ToggleTalentFrame` (1833), and by now the expected shape.
|
|
--
|
|
-- `MacroFrame_LoadUI` is left out: the addon loads eagerly from benilla.toc, so there is
|
|
-- nothing for it to do, and 1203 says leave it out rather than stub it. The
|
|
-- `MacroFrame_Show` guard is the reference's own.
|
|
function ShowMacroFrame()
|
|
if ( MacroFrame_Show ) then
|
|
MacroFrame_Show();
|
|
end
|
|
end
|
|
|
|
function UIParent_OnEvent(event)
|
|
-- The reference's own first arm (UIParent.lua l.230-235). `LocalizeFrames` comes from
|
|
-- the sourced `Interface\FrameXML\Localization.xml` (1751 window 5) and is the file
|
|
-- that sets `TwentyFourHourTime` and the locale's frame adjustments; nothing called it
|
|
-- here before, which is why GameTime.xml used to set that global itself and why
|
|
-- HelpFrame.xml's header records a family of strings that never arrived. Guarded like
|
|
-- every other cross-file reach in this function: the per-window harnesses load
|
|
-- UIParent.xml with no chain behind it. `UpdateNameplates` is the ref's other call in
|
|
-- this arm and is not bound here — left absent rather than stubbed (1203).
|
|
if ( event == "VARIABLES_LOADED" ) then
|
|
if ( LocalizeFrames ) then
|
|
LocalizeFrames()
|
|
end
|
|
return
|
|
end
|
|
-- The mirror bars' START driver (ref UIParent.lua l.97 + l.374-377). It lived on a
|
|
-- hidden `MirrorTimerDriver` frame in our own MirrorTimer.xml until 1751 window 7,
|
|
-- on the reasoning that "our UIParent.xml is script-only — it has no frame of its own
|
|
-- to register events on". That stopped being true when this file grew a real UIParent
|
|
-- frame with a Scripts block, so the driver came home and the extra frame is gone.
|
|
-- `MirrorTimer_Show` is the sourced `Interface\FrameXML\MirrorTimer.lua`'s.
|
|
if ( event == "MIRROR_TIMER_START" ) then
|
|
if ( MirrorTimer_Show ) then
|
|
MirrorTimer_Show(arg1, arg2, arg3, arg4, arg5, arg6)
|
|
end
|
|
return
|
|
end
|
|
if ( event == "PLAYER_ENTERING_WORLD" ) then
|
|
SHOW_MULTI_ACTIONBAR_1, SHOW_MULTI_ACTIONBAR_2, SHOW_MULTI_ACTIONBAR_3,
|
|
SHOW_MULTI_ACTIONBAR_4 = GetActionBarToggles()
|
|
if ( MultiActionBar_Update ) then
|
|
MultiActionBar_Update()
|
|
end
|
|
end
|
|
-- The group-roll opener, ref UIParent.lua l.513-515 verbatim. This arm is why our
|
|
-- own GroupLootFrame.xml existed at all: that file's header explained it was carrying
|
|
-- a dedicated hidden driver frame *because* "this engine doesn't ship" UIParent's
|
|
-- monolithic OnEvent. It does now, so the router comes home to the reference's own
|
|
-- slot and the driver goes with the file (decision 1838).
|
|
--
|
|
-- `GroupLootFrame_OpenNewFrame` and the four frames it walks are the CHAIN's, out of
|
|
-- `Interface\FrameXML\LootFrame.xml` and its `.lua` — already on the manifest, and
|
|
-- already declaring every name our copy re-declared. Existence-guarded for
|
|
-- `MultiActionBar_Update`'s reason, not the reference's: the per-window harnesses load
|
|
-- this file with no loot window behind it.
|
|
if ( event == "START_LOOT_ROLL" ) then
|
|
if ( GroupLootFrame_OpenNewFrame ) then
|
|
GroupLootFrame_OpenNewFrame(arg1, arg2)
|
|
end
|
|
return
|
|
end
|
|
-- ref UIParent.lua l.517-524, the arm directly below the one above and the second
|
|
-- router our GroupLootFrame.xml was carrying. The dialog it raises now lives in
|
|
-- UiPanels.xml beside DELETE_ITEM; `arg1`/`arg2` are the roll id and roll type, and
|
|
-- `StaticPopup_Show` answering nil (the popup pool being full) is the reference's own
|
|
-- guarded case, not ours.
|
|
if ( event == "CONFIRM_LOOT_ROLL" ) then
|
|
local dialog = StaticPopup_Show("CONFIRM_LOOT_ROLL")
|
|
if ( dialog ) then
|
|
dialog.data = arg1
|
|
dialog.data2 = arg2
|
|
end
|
|
return
|
|
end
|
|
-- The ready-check popup's opener (decision 1549), ref UIParent.lua l.570-572. The
|
|
-- event is the engine's own `0x218`, fired by the `MSG_RAID_READY_CHECK` open
|
|
-- handler; the frame and `ShowReadyCheck` live in RaidFrame.xml, so this is the one
|
|
-- place the two meet. Existence-guarded for `MultiActionBar_Update`'s reason: the
|
|
-- per-window harnesses load this file without the raid pane.
|
|
if ( event == "READY_CHECK" ) then
|
|
if ( ShowReadyCheck ) then
|
|
ShowReadyCheck()
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ── Everything that has to move when the bottom of the screen fills up ──────────────────
|
|
--
|
|
-- One row per thing whose seat depends on which bars are raised. A row reads:
|
|
--
|
|
-- baseY where it sits on the y axis with nothing in the way
|
|
-- baseX / xOffset the same for the x axis, under two names: the rows that write a var
|
|
-- spell it `baseX`, the rows that seat a frame spell it `xOffset`, and
|
|
-- baseX wins if a row ever names both
|
|
-- bottomLeft, bottomRight, bottomEither, pet, reputation, maxLevel (y axis)
|
|
-- rightLeft, rightRight (x axis)
|
|
-- what this row owes each raised bar, ADDED for every flag standing
|
|
-- anchorTo / point / rpoint who to hang off; defaulted to UIParent BOTTOM-to-BOTTOM
|
|
-- isVar the row is not a frame at all: it names a GLOBAL, and the pass writes
|
|
-- the computed offset into it for other files to read
|
|
--
|
|
-- The KEY is the global name — a frame to seat, or the var to write — and other windows'
|
|
-- OnShow/OnHide drive the pass by it, so both the keys and the fields are a published
|
|
-- shape. Written as one constructor rather than a dozen index assignments: it is a
|
|
-- constant, and a constant reads better as a table than as a program that builds one.
|
|
UIPARENT_MANAGED_FRAME_POSITIONS = {
|
|
-- Frames to seat
|
|
MultiBarBottomLeft = {baseY = 17, reputation = 9, maxLevel = -5, anchorTo = "ActionButton1", point = "BOTTOMLEFT", rpoint = "TOPLEFT"},
|
|
GroupLootFrame1 = {baseY = 60, bottomEither = 42, pet = 42, reputation = 9},
|
|
TutorialFrameParent = {baseY = 55, bottomEither = 47, pet = 42, reputation = 9},
|
|
FramerateLabel = {baseY = 64, bottomEither = 42, pet = 42, reputation = 9},
|
|
CastingBarFrame = {baseY = 60, bottomEither = 40, pet = 40, reputation = 9},
|
|
ChatFrame1 = {baseY = 85, bottomLeft = 17, pet = 17, reputation = 9, maxLevel = -5, anchorTo = "UIParent", point = "BOTTOMLEFT", rpoint = "BOTTOMLEFT", xOffset = 32},
|
|
-- ChatFrame2 is NOT managed here, and its absence is the fix for the latent
|
|
-- decision 1499 flagged in the ClearAllPoints comment below. The reference's row
|
|
-- seats an UNDOCKED ChatFrame2 against the screen's bottom-right; ours is DOCKED to
|
|
-- ChatFrame1, and a docked frame takes its rect from the dock, never from this pass.
|
|
-- With the row present the pass cleared ChatFrame2's authored anchors and re-seated it
|
|
-- with a single BOTTOMRIGHT point and no size — an unresolvable rect, so the window
|
|
-- rendered nothing at all. That was invisible while ChatFrame2 had no tab; the moment
|
|
-- one existed (1575) it became "the Combat Log tab shows an empty screen".
|
|
-- Re-add this row the day windows can be undocked, which is chat *settings* (0288 §2).
|
|
ShapeshiftBarFrame = {baseY = 0, bottomLeft = 45, reputation = 9, maxLevel = -5, anchorTo = "MainMenuBar", point = "BOTTOMLEFT", rpoint = "TOPLEFT", xOffset = 30},
|
|
|
|
-- Globals to write
|
|
CONTAINER_OFFSET_X = {baseX = 0, rightLeft = 90, rightRight = 45, isVar = "xAxis"},
|
|
CONTAINER_OFFSET_Y = {baseY = 70, bottomEither = 27, bottomRight = 0, reputation = 9, isVar = "yAxis", pet = 23},
|
|
BATTLEFIELD_TAB_OFFSET_Y = {baseY = 210, bottomRight = 40, reputation = 9, isVar = "yAxis"},
|
|
PETACTIONBAR_YPOS = {baseY = 97, bottomLeft = 43, reputation = 9, maxLevel = -5, isVar = "yAxis"},
|
|
}
|
|
|
|
-- ── Consumers of the VAR rows ──────────────────────────────────────────────────────────
|
|
-- A row with `isVar` leaves a NUMBER in a global and anchors nothing, so whoever seats
|
|
-- itself from that number has to be told when the number changes — otherwise it keeps the
|
|
-- seat it computed the first time and the bars grow underneath it. That is not a
|
|
-- hypothetical: the open-bag stack read a file-local copy of CONTAINER_OFFSET_X/Y and sat
|
|
-- at the no-bars corner forever, so a bottom multibar drew straight through the lowest bag
|
|
-- window (director-caught, decision 1499).
|
|
--
|
|
-- The reference closes the same loop by hard-calling `updateContainerFrameAnchors()` from
|
|
-- the end of its own pass (UIParent.lua:1774). This is that call generalized: a consumer
|
|
-- registers itself and the pass calls it after every recompute, so the NEXT var consumer
|
|
-- does not have to edit this file to be noticed. Order of registration is the order of
|
|
-- call; listeners are independent (nothing here reads what another writes).
|
|
--
|
|
-- The law, stated once so it can be pointed at: **nothing outside this file may keep its
|
|
-- own copy of a managed offset.** Read the global fresh at use, and if you cache a SEAT
|
|
-- derived from it, register here. `bottom_hud_tests.rs` enforces both halves.
|
|
UIPARENT_MANAGED_POSITION_LISTENERS = {}
|
|
|
|
function UIParent_RegisterManagedPositionListener(fn)
|
|
table.insert(UIPARENT_MANAGED_POSITION_LISTENERS, fn)
|
|
end
|
|
|
|
-- ── The model-pane rotation handlers (ref UIParent.lua l.1421-1462) ──────────────────
|
|
--
|
|
-- `Model_OnLoad` / `Model_RotateLeft` / `Model_RotateRight` / `Model_OnUpdate` — the four
|
|
-- the reference's dress-up room, pet paper doll, stable and character sheet all drive their
|
|
-- turntable with. Verbatim, in this file because this file is our `UIParent.lua`.
|
|
--
|
|
-- Everything they reach for is already here: `ROTATIONS_PER_SECOND` at the top of this
|
|
-- file (the reference puts it on its own line 2), `PI` as an engine global, and
|
|
-- `SetRotation` on the model panes that chain through `PlayerModel`
|
|
-- (`script/modelframe.rs` — a plain `<Model>` does NOT acquire it, and neither does the
|
|
-- reference's).
|
|
--
|
|
-- **What this does and does not buy, said plainly.** It removes these four names from the
|
|
-- gap list of `DressUpFrame.xml`, `PetPaperDollFrame.xml`, `PetStable.xml` and
|
|
-- `PaperDollFrame.xml`, and it makes the rotation STATE correct — the angle the API reads
|
|
-- back is the angle the reference would have. It does not make a model appear: this engine
|
|
-- holds the model pane's scene and draws no pixels into it, which is the one gap those four
|
|
-- windows still stand behind. Correct state under an unrendered pane is worth having on its
|
|
-- own — it is what an addon reading `model.rotation` gets — and it is not a stub, because
|
|
-- nothing here pretends the pane is drawn.
|
|
--
|
|
-- The two button-driven arms read `<name>RotateLeftButton`/`RotateRightButton` by
|
|
-- concatenation, so they work for any pane that follows the reference's naming and raise
|
|
-- loudly for one that does not. That is the reference's own coupling, not ours.
|
|
function Model_OnLoad()
|
|
this.rotation = 0.61
|
|
this:SetRotation(this.rotation)
|
|
end
|
|
|
|
function Model_RotateLeft(model, rotationIncrement)
|
|
if ( not rotationIncrement ) then
|
|
rotationIncrement = 0.03
|
|
end
|
|
model.rotation = model.rotation - rotationIncrement
|
|
model:SetRotation(model.rotation)
|
|
PlaySound("igInventoryRotateCharacter")
|
|
end
|
|
|
|
function Model_RotateRight(model, rotationIncrement)
|
|
if ( not rotationIncrement ) then
|
|
rotationIncrement = 0.03
|
|
end
|
|
model.rotation = model.rotation + rotationIncrement
|
|
model:SetRotation(model.rotation)
|
|
PlaySound("igInventoryRotateCharacter")
|
|
end
|
|
|
|
function Model_OnUpdate(elapsedTime, model, rotationsPerSecond)
|
|
if ( not rotationsPerSecond ) then
|
|
rotationsPerSecond = ROTATIONS_PER_SECOND
|
|
end
|
|
if ( getglobal(model:GetName() .. "RotateLeftButton"):GetButtonState() == "PUSHED" ) then
|
|
model.rotation = model.rotation + (elapsedTime * 2 * PI * rotationsPerSecond)
|
|
if ( model.rotation < 0 ) then
|
|
model.rotation = model.rotation + (2 * PI)
|
|
end
|
|
model:SetRotation(model.rotation)
|
|
end
|
|
if ( getglobal(model:GetName() .. "RotateRightButton"):GetButtonState() == "PUSHED" ) then
|
|
model.rotation = model.rotation - (elapsedTime * 2 * PI * rotationsPerSecond)
|
|
if ( model.rotation > (2 * PI) ) then
|
|
model.rotation = model.rotation - (2 * PI)
|
|
end
|
|
model:SetRotation(model.rotation)
|
|
end
|
|
end
|
|
|
|
-- ── SecondsToTimeAbbrev (ref UIParent.lua l.1034-1049) ───────────────────────────────
|
|
--
|
|
-- "2 m", "45 s" — the one-letter duration the buff bar prints under a timed aura. It lived
|
|
-- in our BuffFrame.xml behind an `if not SecondsToTimeAbbrev` guard, with the four
|
|
-- GlobalStrings it formats defined there too, on the reasoning that "we load no
|
|
-- UIParent/GlobalStrings" — which stopped being true a long time ago and is why the guard
|
|
-- reads as a leftover rather than a decision. It belongs in this file because it belongs
|
|
-- in `UIParent.lua`, and 1751's eighteenth window made the buff bar the reference's own,
|
|
-- which reaches for it by name and defines nothing.
|
|
--
|
|
-- Verbatim, including the `>` rather than `>=` on all three boundaries: exactly 60 seconds
|
|
-- prints "60 s", not "1 m". That is the reference's own arithmetic.
|
|
function SecondsToTimeAbbrev(seconds)
|
|
local tempTime
|
|
if ( seconds > 86400 ) then
|
|
tempTime = ceil(seconds / 86400)
|
|
return format(DAY_ONELETTER_ABBR, tempTime)
|
|
end
|
|
if ( seconds > 3600 ) then
|
|
tempTime = ceil(seconds / 3600)
|
|
return format(HOUR_ONELETTER_ABBR, tempTime)
|
|
end
|
|
if ( seconds > 60 ) then
|
|
tempTime = ceil(seconds / 60)
|
|
return format(MINUTE_ONELETTER_ABBR, tempTime)
|
|
end
|
|
return format(SECOND_ONELETTER_ABBR, seconds)
|
|
end
|
|
|
|
-- ── GetMaterialTextColors (ref UIParent.lua l.1880-1888) ─────────────────────────────
|
|
--
|
|
-- The page ink and the title ink for a readable's material. Transcribed here rather than
|
|
-- left absent because the sourced `Interface\FrameXML\ItemTextFrame.xml` (1751 window 9)
|
|
-- calls it, and it belongs in this file because it belongs in `UIParent.lua`. The two
|
|
-- tables it reads are the reference's own, in the reference's own home — our Fonts.xml,
|
|
-- which grew them with the same window.
|
|
--
|
|
-- The fallback is BOTH-or-neither on purpose (the reference's own `not(a and b)`), so an
|
|
-- unrecognised material never gets one table's colour and the other's default.
|
|
function GetMaterialTextColors(material)
|
|
local textColor = MATERIAL_TEXT_COLOR_TABLE[material]
|
|
local titleColor = MATERIAL_TITLETEXT_COLOR_TABLE[material]
|
|
if ( not (textColor and titleColor) ) then
|
|
textColor = MATERIAL_TEXT_COLOR_TABLE["Default"]
|
|
titleColor = MATERIAL_TITLETEXT_COLOR_TABLE["Default"]
|
|
end
|
|
return textColor, titleColor
|
|
end
|
|
|
|
-- ── ValidateFramePosition (ref UIParent.lua l.1513-1557) ─────────────────────────────
|
|
--
|
|
-- Shove a frame the player has dragged mostly off the screen back on. Transcribed here
|
|
-- rather than left absent because its one caller in 1.12 is the sourced
|
|
-- `Interface\FrameXML\ItemRef.xml`'s OnDragStop (l.48) — so with that window migrated
|
|
-- (1751, window 6) a player dragging a chat-link tooltip would otherwise hit a nil global,
|
|
-- which is a raise on an ordinary gesture rather than a loud missing feature. It belongs
|
|
-- in this file because it belongs in `UIParent.lua`, which this file is our copy of.
|
|
--
|
|
-- Verbatim, quirks included. Two are worth naming so nobody "fixes" them later:
|
|
-- · the two axes are exclusive, not additive — a frame off BOTH corners takes the
|
|
-- `not newAnchorX` branch and re-derives x from `left`, so only one axis moves per
|
|
-- drop. That is the reference's own control flow (l.1544-1548), not a transcription
|
|
-- slip.
|
|
-- · the bottom test is against `MainMenuBar:GetHeight()`, so this raises with no action
|
|
-- bar loaded. The reference has the same dependency; the harnesses that drive
|
|
-- ItemRefTooltip load ActionBar.xml for it.
|
|
function ValidateFramePosition(frame, offscreenPadding, returnOffscreen)
|
|
if ( not frame ) then
|
|
return
|
|
end
|
|
local left = frame:GetLeft()
|
|
local right = frame:GetRight()
|
|
local top = frame:GetTop()
|
|
local bottom = frame:GetBottom()
|
|
local newAnchorX, newAnchorY
|
|
if ( not offscreenPadding ) then
|
|
offscreenPadding = 15
|
|
end
|
|
if ( top < (0 + MainMenuBar:GetHeight() + offscreenPadding) ) then
|
|
-- Off the bottom of the screen
|
|
newAnchorY = MainMenuBar:GetHeight() + frame:GetHeight() - GetScreenHeight()
|
|
elseif ( bottom > GetScreenHeight() ) then
|
|
-- Off the top of the screen
|
|
newAnchorY = 0
|
|
end
|
|
if ( right < 0 ) then
|
|
-- Off the left of the screen
|
|
newAnchorX = 0
|
|
elseif ( left > GetScreenWidth() ) then
|
|
-- Off the right of the screen
|
|
newAnchorX = GetScreenWidth() - frame:GetWidth()
|
|
end
|
|
if ( newAnchorX or newAnchorY ) then
|
|
if ( returnOffscreen ) then
|
|
return 1
|
|
else
|
|
if ( not newAnchorX ) then
|
|
newAnchorX = left
|
|
elseif ( not newAnchorY ) then
|
|
newAnchorY = top - GetScreenHeight()
|
|
end
|
|
frame:ClearAllPoints()
|
|
frame:SetPoint("TOPLEFT", nil, "TOPLEFT", newAnchorX, newAnchorY)
|
|
end
|
|
else
|
|
if ( returnOffscreen ) then
|
|
return nil
|
|
end
|
|
end
|
|
end
|
|
|
|
function UIParent_ManageFramePositions()
|
|
-- Frames that affect offsets in y axis
|
|
local yOffsetFrames = {}
|
|
-- Frames that affect offsets in x axis
|
|
local xOffsetFrames = {}
|
|
|
|
-- Set up flags. Departure: the bottom flags read the always-on bars' visibility,
|
|
-- not the ref's SHOW_MULTI_ACTIONBAR_1/2 option globals (0270 ships no such state).
|
|
local bottomLeftShown = MultiBarBottomLeft and MultiBarBottomLeft:IsShown()
|
|
local bottomRightShown = MultiBarBottomRight and MultiBarBottomRight:IsShown()
|
|
if ( bottomLeftShown or bottomRightShown ) then
|
|
table.insert(yOffsetFrames, "bottomEither")
|
|
end
|
|
if ( bottomRightShown ) then
|
|
table.insert(yOffsetFrames, "bottomRight")
|
|
end
|
|
if ( bottomLeftShown ) then
|
|
table.insert(yOffsetFrames, "bottomLeft")
|
|
end
|
|
|
|
if ( MultiBarLeft and MultiBarLeft:IsShown() ) then
|
|
table.insert(xOffsetFrames, "rightLeft")
|
|
elseif ( MultiBarRight and MultiBarRight:IsShown() ) then
|
|
table.insert(xOffsetFrames, "rightRight")
|
|
end
|
|
|
|
if ( ( PetActionBarFrame and PetActionBarFrame:IsShown() ) or ( ShapeshiftBarFrame and ShapeshiftBarFrame:IsShown() ) ) then
|
|
table.insert(yOffsetFrames, "pet")
|
|
end
|
|
if ( ReputationWatchBar and MainMenuExpBar and ReputationWatchBar:IsShown() and MainMenuExpBar:IsShown() ) then
|
|
table.insert(yOffsetFrames, "reputation")
|
|
end
|
|
if ( MainMenuBarMaxLevelBar and MainMenuBarMaxLevelBar:IsShown() ) then
|
|
table.insert(yOffsetFrames, "maxLevel")
|
|
end
|
|
|
|
-- Walk the rows and settle each one against the flags now standing. Rows are
|
|
-- independent — nothing here reads a var another row writes — so the arbitrary order
|
|
-- `pairs` hands back is not a hazard.
|
|
for index, row in pairs(UIPARENT_MANAGED_FRAME_POSITIONS) do
|
|
local frame = getglobal(index)
|
|
if ( frame ) then
|
|
-- Start from where the row sits with nothing in its way. A zero base is a real
|
|
-- base, not an absent one, which is why `or` chains do the right thing here.
|
|
local xOffset = row.baseX or row.xOffset or 0
|
|
local yOffset = row.baseY or 0
|
|
|
|
-- Then add what the row owes each raised bar, keeping note of which ones it
|
|
-- actually paid — two of them interact below.
|
|
local paid = {}
|
|
for _, flag in pairs(yOffsetFrames) do
|
|
if ( row[flag] ) then
|
|
yOffset = yOffset + row[flag]
|
|
paid[flag] = true
|
|
end
|
|
end
|
|
for _, flag in pairs(xOffsetFrames) do
|
|
if ( row[flag] ) then
|
|
xOffset = xOffset + row[flag]
|
|
end
|
|
end
|
|
|
|
-- The bottom-left bar and the pet/stance row do not simply stack: clearing both
|
|
-- costs 23px more than the two figures added together. The reference carries the
|
|
-- same correction (l.1657-1658) with no derivation, and it is a fact about the
|
|
-- bars' art rather than arithmetic we can recompute.
|
|
if ( paid.bottomLeft and paid.pet ) then
|
|
yOffset = yOffset + 23
|
|
end
|
|
|
|
-- A row either NAMES A GLOBAL to leave the number in, or is a frame to seat.
|
|
if ( row.isVar == "xAxis" ) then
|
|
_G[index] = xOffset
|
|
elseif ( row.isVar ) then
|
|
_G[index] = yOffset
|
|
elseif ( frame.IsUserPlaced and frame:IsUserPlaced() ) then
|
|
-- **A frame the PLAYER placed is not this pass's to seat.** benilla's rule,
|
|
-- not the reference's: 1.12 has no *movable* managed frame — its ChatFrame1
|
|
-- is docked and its tab click refuses to drag a docked window — so the case
|
|
-- cannot arise there. Ours does, since an unlocked chat window drags and
|
|
-- resizes, and without this the next pass would `ClearAllPoints` the window
|
|
-- and drop it back on the bottom-stack seat. That is not a rare path: the
|
|
-- stance bar fires this on every show and hide.
|
|
--
|
|
-- `IsUserPlaced` is exactly the right question rather than a chat-specific
|
|
-- one: the engine sets that bit at the drag that moved the frame, and the
|
|
-- layout cache (`benilla_app::ui_layout`) sets it again for every frame it
|
|
-- seats out of the player's file — so "the player owns this frame's
|
|
-- position" and "do not manage it" are the same fact.
|
|
--
|
|
-- The `isVar` rows above are untouched: they write a NUMBER and seat no
|
|
-- frame, so there is no position for a player to own.
|
|
else
|
|
-- ClearAllPoints first: the row states the frame's WHOLE seat, so the pass
|
|
-- must own it outright. `SetPoint` only replaces the same-NAMED point, so
|
|
-- any other anchor the frame was authored with survives and fights this
|
|
-- one — ChatFrame2 is declared in XML with both a TOPLEFT and a
|
|
-- BOTTOMRIGHT onto ChatFrame1, and the row's BOTTOMRIGHT replaced only
|
|
-- the second, leaving the pane stretched from ChatFrame1's top-left
|
|
-- corner to the screen's bottom-right. Masked today only because
|
|
-- ChatFrame2 ships hidden, which is the worst kind of latent (decision
|
|
-- 1499). Every row here supplies a single complete anchor, so this is a
|
|
-- no-op for the other eight and the reference's plain SetPoint is
|
|
-- behaviour-identical on its own frames.
|
|
frame:ClearAllPoints()
|
|
frame:SetPoint(row.point or "BOTTOM", row.anchorTo or "UIParent",
|
|
row.rpoint or "BOTTOM", xOffset, yOffset)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Custom positioning the row table cannot express (ref l.1705-1738). Both rules
|
|
-- belong to the pet/stance ROW — the one row two different bars share — and without
|
|
-- them the pet bar lands on top of the bottom-left multibar's buttons
|
|
-- (director-caught: the bar drew across a row of spells, border and all).
|
|
--
|
|
-- (a) The shelf art IS the pet bar's border, and it only makes sense when the bar
|
|
-- sits directly on the main bar. Raised a row it would draw across whatever is
|
|
-- underneath, so the ref hides both strips (l.1707-1708) — the same move it makes
|
|
-- for the stance bar's own end-caps, which is why benilla's stance bar has none
|
|
-- (0270 anchors it statically at the raised position).
|
|
-- (b) The pet bar is the only managed entry whose row is a VAR: the loop above writes
|
|
-- the NUMBER PETACTIONBAR_YPOS (97, +43 with the bottom-left bar up) and anchors
|
|
-- nothing, so the ref re-seats the frame with it here (l.1735-1736).
|
|
--
|
|
-- Departure: the ref computes PETACTIONBAR_XPOS in ShowPetActionBar() and reads it
|
|
-- here, which only works because its bar slides in over several frames. Ours appears
|
|
-- at once, so the value is computed where it is used. Same rule
|
|
-- (PetActionBarFrame.lua:170-174): the two bars share this row, so a pet bar shown
|
|
-- while the stance bar is up starts 20px past the last stance button. That case is
|
|
-- rare but real — a possessed or charmed unit gives a pet bar to a class that also
|
|
-- has forms.
|
|
local bottomLeftUp = MultiBarBottomLeft and MultiBarBottomLeft:IsShown()
|
|
if ( SlidingActionBarTexture0 and SlidingActionBarTexture1 ) then
|
|
if ( bottomLeftUp ) then
|
|
SlidingActionBarTexture0:Hide()
|
|
SlidingActionBarTexture1:Hide()
|
|
else
|
|
SlidingActionBarTexture0:Show()
|
|
SlidingActionBarTexture1:Show()
|
|
end
|
|
end
|
|
if ( PetActionBarFrame ) then
|
|
PETACTIONBAR_XPOS = 36
|
|
local forms = GetNumShapeshiftForms and GetNumShapeshiftForms() or 0
|
|
if ( forms > 0 and ShapeshiftBarFrame and ShapeshiftBarFrame:IsShown() ) then
|
|
local last = getglobal("ShapeshiftButton"..forms)
|
|
local right = last and last:GetRight()
|
|
if ( right ) then
|
|
PETACTIONBAR_XPOS = right + 20
|
|
end
|
|
end
|
|
-- The ref guards this on IsShown() because a hidden bar of its is mid-slide and
|
|
-- owns its own anchor; ours has no slide, so re-seating a hidden frame is a
|
|
-- no-op and the guard would only add an ordering trap (OnShow fires the pass).
|
|
PetActionBarFrame:SetPoint("TOPLEFT", "MainMenuBar", "BOTTOMLEFT", PETACTIONBAR_XPOS, PETACTIONBAR_YPOS)
|
|
end
|
|
|
|
-- The under-minimap right stack (ref l.1740-1770): anchorY walks down capture bars →
|
|
-- quest timer → durability → quest watch, each shown frame pushing the next one down
|
|
-- by its own height. benilla ships the quest-timer, durability and quest-watch arms
|
|
-- (below); the capture bars above them don't exist yet, so anchorY starts at the ref's
|
|
-- initial 0. The reference never leaves DurabilityFrame on its static XML anchor
|
|
-- (whose +40 hangs the side glyphs off-screen — director-caught): every pass seats it
|
|
-- at -CONTAINER_OFFSET_X, minus 20 more when a showSeparate glyph
|
|
-- (shield/off-weapon/ranged) extends the art to the right — then drops the quest
|
|
-- tracker to the running anchorY so the two never collide at the same corner.
|
|
local anchorY = 0
|
|
-- ref l.1753-1757 verbatim (decision 1150). Seated whether shown or not — the ref
|
|
-- SetPoints unconditionally and only the height accrual is gated — so the frame is
|
|
-- already in place the frame it appears. Nil-guarded like every row here: the
|
|
-- durability-only test harness ships no quest-timer frame.
|
|
if ( QuestTimerFrame ) then
|
|
QuestTimerFrame:SetPoint("TOPRIGHT", "MinimapCluster", "BOTTOMRIGHT", -CONTAINER_OFFSET_X, anchorY)
|
|
if ( QuestTimerFrame:IsShown() ) then
|
|
anchorY = anchorY - QuestTimerFrame:GetHeight()
|
|
end
|
|
end
|
|
if ( DurabilityFrame ) then
|
|
local durabilityOffset = 0
|
|
if ( DurabilityShield:IsShown() or DurabilityOffWeapon:IsShown() or DurabilityRanged:IsShown() ) then
|
|
durabilityOffset = 20
|
|
end
|
|
DurabilityFrame:SetPoint("TOPRIGHT", "MinimapCluster", "BOTTOMRIGHT", -CONTAINER_OFFSET_X-durabilityOffset, anchorY)
|
|
if ( DurabilityFrame:IsShown() ) then
|
|
anchorY = anchorY - DurabilityFrame:GetHeight()
|
|
end
|
|
end
|
|
-- QuestWatchFrame is last in the ref's walk (l.1770), seated at the running anchorY so
|
|
-- it drops below the durability guy when he shows instead of overlapping him (both
|
|
-- anchor this same MinimapCluster corner — director-caught). benilla's tracker is
|
|
-- QuestWatchFrame; nil-guarded because the durability-only test harness (and
|
|
-- any config without a quest log) ships no such frame.
|
|
if ( QuestWatchFrame ) then
|
|
QuestWatchFrame:SetPoint("TOPRIGHT", "MinimapCluster", "BOTTOMRIGHT", -CONTAINER_OFFSET_X, anchorY)
|
|
end
|
|
|
|
-- Last: everything that seats itself from a var row this pass just rewrote. After the
|
|
-- frames, so a listener may measure one (the bag stack reads GetScreenHeight and its
|
|
-- own windows' heights, not another managed frame — but the ordering costs nothing and
|
|
-- removes a class of question).
|
|
for _, fn in ipairs(UIPARENT_MANAGED_POSITION_LISTENERS) do
|
|
fn()
|
|
end
|
|
-- …and the reference's own consumer, at the reference's own position
|
|
-- (UIParent.lua:1774). The open-bag stack re-anchors from here because the bags are
|
|
-- the reference's `ContainerFrame1..12` now (decision 1751) and this is the line that
|
|
-- used to be generalized into the listener table above — 1499's regression, closed the
|
|
-- way the real client closes it. Guarded because a machine with no client data does
|
|
-- not get `ContainerFrame.lua` at all, and then there is no bag stack to anchor.
|
|
if updateContainerFrameAnchors then
|
|
updateContainerFrameAnchors()
|
|
end
|
|
end
|
|
|
|
-- The published binding-label API — benilla's implementation of the behaviour ref
|
|
-- UIParent.lua:1819-1878 specifies (decision 1008, which closed 0997's "abbreviates
|
|
-- through KEY_ABBR" residue with the REAL mechanism: there never was a KEY_ABBR set, the
|
|
-- whole abbreviation is this one function). Addons call it by name, so the contract is:
|
|
--
|
|
-- * a nil name reads as the empty string;
|
|
-- * "CTRL-SHIFT-2" splits at its LAST dash — up to and including that dash is the
|
|
-- modifier run ("CTRL-SHIFT-"), what follows is the key ("2"). A name with no dash is
|
|
-- all key;
|
|
-- * the key renders through the `prefix..key` global (the KEY_* family), preferring a
|
|
-- `_MAC` twin on a Mac client, and falls back to the raw key name;
|
|
-- * returnAbbr — what the action bar's HotKey corners pass — shrinks ONE modifier to
|
|
-- its initial, so SHIFT-2 reads "s-2" (the director's ask), and collapses TWO OR MORE
|
|
-- to "·". The full form (tooltips, the Keybindings page) keeps the whole prefix.
|
|
--
|
|
-- Two knowing departures from the reference, both about its code rather than its
|
|
-- behaviour: its `gsub(tempName, "CAPSLOCK", "Caps")` line is dead — gsub returns a new
|
|
-- string and that result is discarded, so nothing can observe it — and has no counterpart
|
|
-- here; and its GetLocale()/IsMacClient() calls ride behind existence guards because this
|
|
-- VM defines neither yet, with the getglobal fallback covering the gap.
|
|
|
|
-- What a single modifier shrinks to on a button corner. German calls Control "STRG" (the
|
|
-- deDE swap below runs first), which is the one that needs two letters to stay legible.
|
|
-- The order is fixed so the pass is deterministic, but it does not matter: no abbreviation
|
|
-- occurs inside another modifier's name, so no substitution can feed the next.
|
|
local MODIFIER_ABBREVIATIONS = {
|
|
{ "CTRL", "c" },
|
|
{ "SHIFT", "s" },
|
|
{ "ALT", "a" },
|
|
{ "STRG", "st" },
|
|
}
|
|
|
|
function GetBindingText(name, prefix, returnAbbr)
|
|
if not name then
|
|
return ""
|
|
end
|
|
|
|
local modifiers, key = "", name
|
|
local modifierCount = 0
|
|
-- The greedy `.*` walks to the LAST dash, which is the one that divides the modifier
|
|
-- run from the key; gsub's second return then counts the dashes, one per modifier —
|
|
-- plus one more when the key ITSELF is a dash ("CTRL--"), which is why that binding
|
|
-- abbreviates as if it had two. The reference's dash-walking loop reaches both of the
|
|
-- same answers, and both are pinned by keybindings_tests.
|
|
local _, lastDash = strfind(name, "^.*%-")
|
|
if lastDash then
|
|
modifiers = strsub(name, 1, lastDash)
|
|
key = strsub(name, lastDash + 1)
|
|
_, modifierCount = gsub(modifiers, "%-", "-")
|
|
if GetLocale and GetLocale() == "deDE" then
|
|
modifiers = gsub(modifiers, "CTRL", "STRG")
|
|
end
|
|
end
|
|
|
|
if returnAbbr then
|
|
if modifierCount > 1 then
|
|
-- Two modifiers never fit a corner: the whole label becomes the dot.
|
|
return "·"
|
|
end
|
|
for _, abbreviation in ipairs(MODIFIER_ABBREVIATIONS) do
|
|
modifiers = gsub(modifiers, abbreviation[1], abbreviation[2])
|
|
end
|
|
end
|
|
|
|
prefix = prefix or ""
|
|
local label
|
|
if IsMacClient and IsMacClient() then
|
|
label = getglobal(prefix .. key .. "_MAC")
|
|
end
|
|
label = label or getglobal(prefix .. key) or key
|
|
return modifiers .. label
|
|
end
|
|
|
|
-- ref UIParent.lua:1051-1057, transcribed 1:1 (decision 1062). The reference's own
|
|
-- list joiner, and it is exactly as plain as it looks: a bare ", " between every item,
|
|
-- with NO "and" before the last one and NO localization string of any kind — 1.12 has
|
|
-- no LIST_DELIMITER/AND global for it to reach for, and it does not build one. (The
|
|
-- richer `BuildColoredListString` right below it in the reference — the per-item
|
|
-- met/unmet red — already lives in TradeSkillFrame.xml as a guarded global, 0437 phase
|
|
-- 3; folding that one up here beside this is a follow-up, not this slice's churn.)
|
|
--
|
|
-- Callers: the pet paper doll's diet tooltip and PetStable.lua's, both as
|
|
-- `format(PET_DIET_TEMPLATE, BuildListString(GetPetFoodTypes()))`.
|
|
--
|
|
-- ZERO arguments returns NIL, not "" — the ref reads `arg[1]` off an empty vararg and
|
|
-- returns it unchanged. That is load-bearing: `format("%s", nil)` errors, so a caller
|
|
-- that can pass nothing (a warlock minion's empty diet) must be gated by its own UI, and
|
|
-- it is — the diet icon is hunter-only. Quietly returning "" here would hide that.
|
|
--
|
|
-- Lua-5.0 `arg`/`arg.n` written as `select("#", ...)`/`select(i, ...)`, the 5.1-native
|
|
-- spelling — TradeSkillFrame.xml's own substitution.
|
|
function BuildListString(...)
|
|
local n = select("#", ...);
|
|
if ( n == 0 ) then
|
|
return nil;
|
|
end
|
|
local list = select(1, ...);
|
|
for i = 2, n do
|
|
local item = select(i, ...);
|
|
list = list..", "..item;
|
|
end
|
|
return list;
|
|
end
|
|
|
|
-- BenillaChatEdit_InsertLink(link) → true if it went in, else nil (decision 1059).
|
|
--
|
|
-- Every shift-click site in this UI does the same two things before inserting: check the
|
|
-- chat edit box is up, and — ours alone — check the link is not nil. That nil case is
|
|
-- **benilla's, not the reference's**: our item templates resolve asynchronously
|
|
-- (`CMSG_ITEM_QUERY_SINGLE`, answered a frame or many later), so a `Get…Link` getter
|
|
-- legitimately answers nil for a slot whose name and quality have not landed, where the real
|
|
-- client's synchronous item cache always has one. `EditBox:Insert` takes a string (the
|
|
-- byte-modelled `0x77bee0`), so an unguarded nil is a script error — which is exactly the
|
|
-- bug this function exists to make impossible to reintroduce at the next site. A dozen call
|
|
-- sites were growing a dozen copies of the same two-line guard, and three of them shipped
|
|
-- without it.
|
|
--
|
|
-- The RETURN is what lets a caller fork on "did chat take it?" — a bag slot's shift-click
|
|
-- posts the link when chat is open and opens the stack splitter when it is not (ref
|
|
-- ContainerFrame.lua:567-577). Blizzard themselves eventually factored this exact helper out
|
|
-- for this exact reason: 2.0's `ChatEdit_InsertLink(text)`, returning true/false, called from
|
|
-- every one of these sites. 1.12 has no such function, so ours carries a benilla name rather
|
|
-- than pretending to be reference API.
|
|
function BenillaChatEdit_InsertLink(link)
|
|
if not link then
|
|
return nil
|
|
end
|
|
if not ChatFrameEditBox or not ChatFrameEditBox:IsVisible() then
|
|
return nil
|
|
end
|
|
ChatFrameEditBox:Insert(link)
|
|
return true
|
|
end
|
|
|
|
-- ── LowerFrameLevel / RaiseFrameLevel (ref UIParent.lua l.1890-1896), verbatim ────────
|
|
--
|
|
-- Two lines each, and the reason to have them is that they are FRAMEXML, not engine: an
|
|
-- addon calling `RaiseFrameLevel(f)` is calling a function the client's own UI defines, and
|
|
-- without it that is `attempt to call global` at load (BetterCharacterStats dies there).
|
|
-- Our own ActionBar.xml already describes this operation in prose at three sites while
|
|
-- doing it by hand; those can move onto the real name whenever that file is next opened.
|
|
function LowerFrameLevel(frame)
|
|
frame:SetFrameLevel(frame:GetFrameLevel() - 1)
|
|
end
|
|
|
|
function RaiseFrameLevel(frame)
|
|
frame:SetFrameLevel(frame:GetFrameLevel() + 1)
|
|
end
|
|
|
|
-- ── The 1024x768 scale ratios (ref UIParent.lua l.1794-1802) ────────────────────────────
|
|
--
|
|
-- Two FrameXML helpers addons call by name (the raid pullouts and a dozen third-party
|
|
-- drag handlers), so they exist for the reason `MouseIsOver` and `RaiseFrameLevel` do:
|
|
-- missing, the caller dies on `attempt to call global`. The reference's constants are the
|
|
-- 1024x768 reference resolution, and its own callers multiply a `GetCursorPosition()`
|
|
-- reading by them to convert into pixel space.
|
|
--
|
|
-- **benilla's own drag code does NOT use them**, and that is deliberate rather than an
|
|
-- oversight: our UI space is 1:1 logical px and `GetCursorPosition` already reports in
|
|
-- the same space `SetPoint` offsets are read in (the same identity `MouseIsOver`'s
|
|
-- comment below records for `GetEffectiveScale`). Multiplying here would place a dragged
|
|
-- frame at 1.5x the cursor on a 1600-wide window. Kept for the addons, unused by us.
|
|
function GetScreenHeightScale()
|
|
return GetScreenHeight() / 768
|
|
end
|
|
|
|
function GetScreenWidthScale()
|
|
return GetScreenWidth() / 1024
|
|
end
|
|
|
|
-- ── MouseIsOver: is the cursor inside this frame, with optional per-edge padding ─────────
|
|
--
|
|
-- The idiom every hover-driven addon writes, and **9 corpus addons call it** by name.
|
|
-- Missing, each is `attempt to call global 'MouseIsOver'`. So the CONTRACT is 1.12's, quirks
|
|
-- and all (ref UIParent.lua l.1388-1417) — three of which look like bugs and are not:
|
|
--
|
|
-- · each offset is ADDED to its own edge, in that axis's positive direction. A positive
|
|
-- `topOffset` raises the ceiling (growing the box upward) while a positive
|
|
-- `bottomOffset` raises the floor (shrinking it from below). The reference's own caller
|
|
-- proves the intent: `MouseIsOver(chatFrame, 45, -10, -5, 5)` is 45 up into the tab
|
|
-- strip, 10 in from the bottom, 5 out each side.
|
|
-- · the padding is ALL OR NOTHING on `topOffset`: pass none and the other three are
|
|
-- dropped with it, so `MouseIsOver(f, nil, 5)` silently ignores the 5. Pass a topOffset
|
|
-- and omit any of the rest and it is an arithmetic error, not a zero — 1.12 raises
|
|
-- there too, and an addon written against it may lean on either half.
|
|
-- · the bounds are strict, so a cursor exactly on an edge is outside.
|
|
--
|
|
-- A frame that has never been laid out answers nil for its rect; nil is the right answer to
|
|
-- hand back for it, and the reference's own comment on that guard ("a symptom not the real
|
|
-- issue") is about where the unresolved frame came from, not about the guard.
|
|
--
|
|
-- ONE DIVERGENCE, and it is a no-op: the reference divides the cursor by
|
|
-- `frame:GetEffectiveScale()` because 1.12 hands Lua screen pixels. benilla's UI space is
|
|
-- 1:1 logical px and `GetEffectiveScale` is the constant 1 (`script/pointer.rs`,
|
|
-- `object.rs` l.271), so the division is written out rather than left in to divide by one
|
|
-- forever. If a real UI scale ever lands, this is one of the sites that needs it back.
|
|
function MouseIsOver(frame, topOffset, bottomOffset, leftOffset, rightOffset)
|
|
local x, y = GetCursorPosition()
|
|
local left, right = frame:GetLeft(), frame:GetRight()
|
|
local top, bottom = frame:GetTop(), frame:GetBottom()
|
|
if not left then
|
|
return
|
|
end
|
|
if not topOffset then
|
|
topOffset, bottomOffset, leftOffset, rightOffset = 0, 0, 0, 0
|
|
end
|
|
-- All four edges are padded before any of them is tested, and deliberately so: folding
|
|
-- the additions into the comparison would let a short circuit swallow the arithmetic
|
|
-- error that a half-supplied offset set is supposed to raise.
|
|
left, right = left + leftOffset, right + rightOffset
|
|
top, bottom = top + topOffset, bottom + bottomOffset
|
|
if x > left and x < right and y > bottom and y < top then
|
|
return 1
|
|
end
|
|
return nil
|
|
end
|
|
]]></Script>
|
|
</Ui>
|