- the help window is the reference's own `HelpFrame.xml`, and ours was less than half its size, so this is mostly arrival: the category list, the per-category GM and harassment pages, the stuck page, the bug report, the PvP policy line; four things we had built go with the swap — one-click filing from Home, the Home unstick button, the downed-queue refusal message, and ticket abandonment, a capability loss rather than a look change, since `AbandonTicket` exists in neither the binding table nor the 1.12 FrameXML corpus and any return of it goes on top of the stock window; `GMSURVEY_DISPLAY` arrives with no producer, a real 1.12 event (`SignalEvent` `0x5e797b`) whose whole UI is a LoadOnDemand addon not on our chain; and the stock file needs two orderings ours did not, the micro-button row before its GM status ask and the buff frame before its repoll timer (1889, superseding 1687)
- the cast bar, the action-button regions and the font registry are the reference's own files too: our `CastingBar.xml` was `CastingBarFrame.xml` under another name, with the literal "Failed" where the reference reads the `FAILED` GlobalString (1886); `ActionButtonTemplate.xml` seats above our `ActionBar.xml` because the later-registered template wins, and it declares zero scripts, so the regions migrate while the handlers stay ours until `ActionButton.lua`'s verbs do (1887); `Fonts.xml` is manifest entry 0, all 51 fonts match by name, and its one real difference, `ITEM_QUALITY_COLORS`, moves to UIParent where the reference declares it (1888); the harnesses were the real cost — those reading our files off disk could not name a chain file and now read through the manifest's own rule — and the event-producer gate, having cried wolf at the cast bar's `SPELLCAST_INTERRUPTED` fired through a variable, now matches the fired tuple's shape
- of the 189 events benilla fires, three are not in the client — `BAG_UPDATE_DELAYED` (a Classic Era name), `LOOT_UPDATE` and `UPDATE_LOOT_ROLL` (invented here, no listener anywhere) — found against the reference binary's own string table, and a gate now fails any fire of a name the client cannot dispatch unless it wears a `BENILLA_` prefix (1883); two payloads the reference gives that we did not: `UNIT_HAPPINESS` carries its unit token, without which no `UNIT_*` consumer reads it, and `CHARACTER_POINTS_CHANGED` carries the point deltas `ChatFrame.lua` compares unguarded, so nil raised (1884); and `PLAYER_LEVEL_UP` carries all nine — level, health and mana gain, talent points (not on the wire: the client derives `(newLevel >= 10) ? 1 : 0` at `0x5e407c`) and the five stat gains — matched to the ding by level, a demotion's absent gains sent as zeros since a nil raises where a zero reads as no line
- the chain gap report could not run — one of its six probes had no player seated where a stock addon formats `UnitName("player")` at load, the other five each carrying an identical copy-pasted seed — and the repaired instrument prices each window's cost: 13 stock files load clean with nothing missing (the action bars, the cast bar, the fonts, the help, friends, game-menu and sound-options windows among them) and the near ones name their missing verb, with "loads clean" still not "swapped" (1885); and `UpdateNameplates`, recorded as the missing engine verb blocking the UIParent keystone, is FrameXML — defined in `UIOptionsFrame.lua`, absent from the binding table, its whole body four nameplate verbs that are already ours — so it is twelve lines transcribed into our options frame, the reference's own Show-then-Hide no-op included (1890 corrects 1878)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1114 lines
68 KiB
XML
1114 lines
68 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.
|
|
-- ── ITEM_QUALITY_COLORS (ref UIParent.lua l.65-72) ───────────────────────────────
|
|
-- Its reference home is THIS file. It lived in our own `Fonts.xml` until 1888 put
|
|
-- that file on the chain — the reference's `Fonts.xml` does not declare it, so the
|
|
-- table had to come back here or vanish. Every reader is below UIParent.xml in the
|
|
-- manifest (GameTooltip 169, UnitPopup 203, AuctionFrame 928; UnitPopup's is a
|
|
-- FILE-SCOPE read inside a table literal, which is the one that makes the order
|
|
-- load-bearing rather than incidental).
|
|
-- The item-quality palette — **built from the engine verb, exactly as the reference
|
|
-- builds it** (ref UIParent.lua l.65-72, verbatim shape; decision 1199):
|
|
--
|
|
-- ITEM_QUALITY_COLORS = { }
|
|
-- for i = -1, 6 do … = GetItemQualityColor(i) end
|
|
--
|
|
-- It used to be seven hand-typed rows here. They were CLOSE and not exact — 0.62 where
|
|
-- the client's byte is 0x9d/255 = 0.6157, 0.64/0.21/0.93 where Epic is
|
|
-- 0xa3/0x35/0xee — and they carried no `.hex`, which the reference's table has and
|
|
-- which addons splice straight into a chat link. `GetItemQualityColor` is now a real
|
|
-- engine binding over wow-5875-re RF-0055's byte-verified table, so the rounding and
|
|
-- the second source both go.
|
|
--
|
|
-- The loop starts at -1 like the reference's, and the -1 row is not a curiosity: it is the
|
|
-- colour a loot row wears while its item template is uncached, which is the value
|
|
-- `GetLootSlotInfo` answers there. `GetItemQualityColor`'s clamp is UNSIGNED, so -1 lands
|
|
-- on Common/white with 7-and-up (decision 1805, correcting 1199's "reads before the
|
|
-- array").
|
|
ITEM_QUALITY_COLORS = { }
|
|
for i = -1, 6 do
|
|
ITEM_QUALITY_COLORS[i] = { }
|
|
ITEM_QUALITY_COLORS[i].r,
|
|
ITEM_QUALITY_COLORS[i].g,
|
|
ITEM_QUALITY_COLORS[i].b,
|
|
ITEM_QUALITY_COLORS[i].hex = GetItemQualityColor(i)
|
|
end
|
|
|
|
-- ── UIParentLoadAddOn (ref UIParent.lua l.638-647, and now our slot for it) ──────
|
|
-- It lived in our own BasicControls.xml until 1881 migrated that file to the chain;
|
|
-- the reference never put it there, so the move is back to where it belongs.
|
|
-- The on-demand loader every "open the auction house / tradeskill / talent panel" path
|
|
-- goes through, and the one 5 corpus addons name — FuBar_MCPFu, Fubar_RareTrackerFu, MCP,
|
|
-- MikScrollingBattleText, SpecialTalent: five separate addons, not one library replicated
|
|
-- (1207/1240).
|
|
--
|
|
-- Every piece was already here and only the join was missing: `LoadAddOn` answers the
|
|
-- reference's own `(1, nil)` / `(nil, reason)` pair, `message` and `TEXT` are directly
|
|
-- above, and the `ADDON_*` reason words come from the real `GlobalStrings.lua` we RUN off
|
|
-- the patch chain rather than copy (1234).
|
|
--
|
|
-- **`FailedAddOnLoad` is the reference's own latch, and it is not decoration.** Without it
|
|
-- an addon that retries the load from a handler raises a modal on every attempt; with it
|
|
-- the failure shows once per addon name per session. It is exactly the detail this would
|
|
-- have lacked had it been reconstructed from the name instead of read.
|
|
--
|
|
-- Every reason our `LoadAddOn` can answer resolves to a string: MISSING, DISABLED,
|
|
-- NOT_DEMAND_LOADED, DEP_MISSING and DEP_DISABLED all have an `ADDON_..reason` word
|
|
-- (GlobalStrings l.16-31), the same reason set wow-re's `ui.md` l.2396 carves off
|
|
-- `0x51e780`. So the `getglobal` below cannot hand `format` a nil.
|
|
FailedAddOnLoad = {};
|
|
|
|
function UIParentLoadAddOn(name)
|
|
local loaded, reason = LoadAddOn(name)
|
|
if not loaded then
|
|
if not FailedAddOnLoad[name] then
|
|
message(format(TEXT(ADDON_LOAD_FAILED), name, TEXT(getglobal("ADDON_" .. reason))))
|
|
FailedAddOnLoad[name] = true
|
|
end
|
|
end
|
|
return loaded
|
|
end
|
|
|
|
-- 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 l.194-196, verbatim — the fifth squatter, and the smallest. Stock
|
|
-- `HelpFrame.xml` calls it when a GM survey is waiting (its l.1487), and its whole body is
|
|
-- `UIParentLoadAddOn("Blizzard_GMSurveyUI")` — a verb 1881 already moved here and an addon
|
|
-- that really is on the player's chain (patch-2.MPQ). So this is the reference's own
|
|
-- function, not a stand-in: 1889 needed it because the stock help window is on the chain
|
|
-- now and ours is gone.
|
|
function GMSurveyFrame_LoadUI()
|
|
UIParentLoadAddOn("Blizzard_GMSurveyUI")
|
|
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
|
|
|
|
-- ref UIParent.lua's own `RaidFrame_LoadUI` (l.182-184), verbatim — the fourth squatter,
|
|
-- after `InspectUnit` (1832), `ToggleTalentFrame` (1833) and `ShowMacroFrame` (1848).
|
|
--
|
|
-- **Unlike `MacroFrame_LoadUI`, this one cannot be left out.** That one was omitted because
|
|
-- nothing on the chain calls it; the reference's own `RaidFrame.lua` calls THIS on
|
|
-- `RAID_ROSTER_UPDATE` and again at `PLAYER_LOGIN` with a raid up (l.34, l.39), so its
|
|
-- absence is a raise every time the roster moves. It is a FrameXML function, not an engine
|
|
-- verb, and this file is our stand-in for `UIParent.lua` — the same relationship the three
|
|
-- above have. `UIParentLoadAddOn` is just above, and it no-ops on an addon that is
|
|
-- already loaded, which ours is: benilla.toc takes `Blizzard_RaidUI` eagerly (1874).
|
|
function RaidFrame_LoadUI()
|
|
UIParentLoadAddOn("Blizzard_RaidUI");
|
|
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>
|