Files
benilla-octo/crates/benilla-app/assets/ui/UIParent.xml
T
SamandClaude Fable 5 d99ad8bc7d the dressing room opens, and the pet gets its paper doll
- ctrl-click an item to try it on: the dressing room dresses your double, and shift-click posts the item link to chat
- the pet tab lands in the character window: paper doll stats, family and diet, the right-click menu, and dropping food on the pet feeds it
- water-only spells refuse on dry land: aquatic form gates to swimming, client-side like the real one
- the macro icon picker's white squares get their art back, and option rows explain themselves on hover

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 16:02:21 +07:00

407 lines
24 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 OnLoad/OnEvent (UIParent_OnLoad's
global event routing) is not ours to ship - no consumer yet.
Known seam, stated: the real client PARENTS every FrameXML frame under UIParent (scale/
visibility cascade); benilla's top-level frames still attach to the screen root. Same
rect, so anchors agree - re-parenting is a separate arc if a cascade consumer appears.
-->
<Frame name="UIParent" setAllPoints="true" frameStrata="MEDIUM"/>
<!--
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 is BenillaShapeshiftBarFrame (0270) and our pet bar is
BenillaPetActionBarFrame (0982), both substituted in the pet-bar visibility check —
either one showing raises the stack by the ref's "pet" delta, which is the ref's own
rule (the two bars share that row). Rows whose frames don't exist here (GroupLootFrame1,
TutorialFrameParent, FramerateLabel, ChatFrame2, MultiBarBottomLeft - ours is
BenillaMultiBarBottomLeft, statically anchored per 0270, like ShapeshiftBarFrame)
no-op through the ref's own `if ( frame )` guard and self-activate if the frames
ever land under those names.
- MultiBarLeft/MultiBarRight (the vertical bars),
ReputationWatchBar, MainMenuExpBar, and MainMenuBarMaxLevelBar don't exist yet:
nil-guarded (the ref reads them bare only because it always creates them).
- 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: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
UIPARENT_MANAGED_FRAME_POSITIONS = {}
-- Frames
UIPARENT_MANAGED_FRAME_POSITIONS["MultiBarBottomLeft"] = {baseY = 17, reputation = 9, maxLevel = -5, anchorTo = "ActionButton1", point = "BOTTOMLEFT", rpoint = "TOPLEFT"}
UIPARENT_MANAGED_FRAME_POSITIONS["GroupLootFrame1"] = {baseY = 60, bottomEither = 42, pet = 42, reputation = 9}
UIPARENT_MANAGED_FRAME_POSITIONS["TutorialFrameParent"] = {baseY = 55, bottomEither = 47, pet = 42, reputation = 9}
UIPARENT_MANAGED_FRAME_POSITIONS["FramerateLabel"] = {baseY = 64, bottomEither = 42, pet = 42, reputation = 9}
UIPARENT_MANAGED_FRAME_POSITIONS["CastingBarFrame"] = {baseY = 60, bottomEither = 40, pet = 40, reputation = 9}
UIPARENT_MANAGED_FRAME_POSITIONS["ChatFrame1"] = {baseY = 85, bottomLeft = 17, pet = 17, reputation = 9, maxLevel = -5, anchorTo = "UIParent", point = "BOTTOMLEFT", rpoint = "BOTTOMLEFT", xOffset = 32}
UIPARENT_MANAGED_FRAME_POSITIONS["ChatFrame2"] = {baseY = 85, bottomRight = 17, rightLeft = -88, rightRight = -43, reputation = 9, maxLevel = -5, anchorTo = "UIParent", point = "BOTTOMRIGHT", rpoint = "BOTTOMRIGHT", xOffset = -32}
UIPARENT_MANAGED_FRAME_POSITIONS["ShapeshiftBarFrame"] = {baseY = 0, bottomLeft = 45, reputation = 9, maxLevel = -5, anchorTo = "MainMenuBar", point = "BOTTOMLEFT", rpoint = "TOPLEFT", xOffset = 30}
-- Vars
UIPARENT_MANAGED_FRAME_POSITIONS["CONTAINER_OFFSET_X"] = {baseX = 0, rightLeft = 90, rightRight = 45, isVar = "xAxis"}
UIPARENT_MANAGED_FRAME_POSITIONS["CONTAINER_OFFSET_Y"] = {baseY = 70, bottomEither = 27, bottomRight = 0, reputation = 9, isVar = "yAxis", pet = 23}
UIPARENT_MANAGED_FRAME_POSITIONS["BATTLEFIELD_TAB_OFFSET_Y"] = {baseY = 210, bottomRight = 40, reputation = 9, isVar = "yAxis"}
UIPARENT_MANAGED_FRAME_POSITIONS["PETACTIONBAR_YPOS"] = {baseY = 97, bottomLeft = 43, reputation = 9, maxLevel = -5, isVar = "yAxis"}
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 = BenillaMultiBarBottomLeft and BenillaMultiBarBottomLeft:IsShown()
local bottomRightShown = BenillaMultiBarBottomRight and BenillaMultiBarBottomRight: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 ( ( BenillaPetActionBarFrame and BenillaPetActionBarFrame:IsShown() ) or ( BenillaShapeshiftBarFrame and BenillaShapeshiftBarFrame: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
-- Iterate through frames and set anchors according to the flags set
local frame, xOffset, yOffset, anchorTo, point, rpoint
for index, value in pairs(UIPARENT_MANAGED_FRAME_POSITIONS) do
frame = getglobal(index)
if ( frame ) then
-- Always start with base as the base offset or default to zero if no "none" specified
xOffset = 0
if ( value["baseX"] ) then
xOffset = value["baseX"]
elseif ( value["xOffset"] ) then
xOffset = value["xOffset"]
end
yOffset = 0
if ( value["baseY"] ) then
yOffset = value["baseY"]
end
-- Iterate through frames that affect y offsets
local hasBottomLeft, hasPetBar, hasBottomRight
for flag, flagValue in pairs(yOffsetFrames) do
if ( value[flagValue] ) then
if ( flagValue == "bottomLeft" ) then
hasBottomLeft = 1
elseif ( flagValue == "pet" ) then
hasPetBar = 1
elseif ( flagValue == "bottomRight" ) then
hasBottomRight = 1
end
yOffset = yOffset + value[flagValue]
end
end
if ( hasBottomLeft and hasPetBar ) then
yOffset = yOffset + 23
end
-- Iterate through frames that affect x offsets
for flag, flagValue in pairs(xOffsetFrames) do
if ( value[flagValue] ) then
xOffset = xOffset + value[flagValue]
end
end
-- Set up anchoring info
anchorTo = value["anchorTo"]
point = value["point"]
rpoint = value["rpoint"]
if ( not anchorTo ) then
anchorTo = "UIParent"
end
if ( not point ) then
point = "BOTTOM"
end
if ( not rpoint ) then
rpoint = "BOTTOM"
end
-- Anchor frame
if ( value["isVar"] ) then
if ( value["isVar"] == "xAxis" ) then
_G[index] = xOffset
else
_G[index] = yOffset
end
else
frame:SetPoint(point, anchorTo, rpoint, 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 = BenillaMultiBarBottomLeft and BenillaMultiBarBottomLeft:IsShown()
if ( BenillaSlidingActionBarTexture0 and BenillaSlidingActionBarTexture1 ) then
if ( bottomLeftUp ) then
BenillaSlidingActionBarTexture0:Hide()
BenillaSlidingActionBarTexture1:Hide()
else
BenillaSlidingActionBarTexture0:Show()
BenillaSlidingActionBarTexture1:Show()
end
end
if ( BenillaPetActionBarFrame ) then
PETACTIONBAR_XPOS = 36
local forms = GetNumShapeshiftForms and GetNumShapeshiftForms() or 0
if ( forms > 0 and BenillaShapeshiftBarFrame and BenillaShapeshiftBarFrame:IsShown() ) then
local last = getglobal("BenillaShapeshiftButton"..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).
BenillaPetActionBarFrame:SetPoint("TOPLEFT", "BenillaActionBar", "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 durability + quest-watch arms (below); the two
-- feeders above them (capture bars, quest timer) 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
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
-- BenillaQuestWatchFrame; nil-guarded because the durability-only test harness (and
-- any config without a quest log) ships no such frame.
if ( BenillaQuestWatchFrame ) then
BenillaQuestWatchFrame:SetPoint("TOPRIGHT", "MinimapCluster", "BOTTOMRIGHT", -CONTAINER_OFFSET_X, anchorY)
end
end
-- ref UIParent.lua:1819-1878, transcribed 1:1 (decision 1008 — closes 0997's
-- "abbreviates through KEY_ABBR" residue with the REAL mechanism: it never was a
-- KEY_ABBR set; the whole abbreviation is this Lua). The action bar's HotKey corners
-- call it with returnAbbr=1: ONE modifier abbreviates (CTRL→c, SHIFT→s, ALT→a — so
-- SHIFT-2 reads "s-2", the director's ask), TWO OR MORE collapse the whole label to
-- "·" — the ref's own multi-modifier dot. The full form (tooltips, the Keybindings
-- page) keeps the canonical prefixes and reads the base key's KEY_* global.
-- Transcription notes: the ref's dead `gsub(tempName, "CAPSLOCK", "Caps")` line
-- (result discarded — gsub doesn't mutate) is dropped; its GetLocale() deDE
-- CTRL→STRG swap and IsMacClient() KEY_*_MAC lookup ride behind existence guards
-- (this VM defines neither yet, and the getglobal fallback covers the gap).
function GetBindingText(name, prefix, returnAbbr)
if ( not name ) then
return "";
end
local tempName = name;
local i = strfind(name, "-");
local dashIndex = nil;
local count = 0;
while ( i ) do
if ( not dashIndex ) then
dashIndex = i;
else
dashIndex = dashIndex + i;
end
count = count + 1;
tempName = strsub(tempName, i + 1);
i = strfind(tempName, "-");
end
local modKeys = '';
if ( not dashIndex ) then
dashIndex = 0;
else
modKeys = strsub(name, 1, dashIndex);
if ( GetLocale and GetLocale() == "deDE" ) then
modKeys = gsub(modKeys, "CTRL", "STRG");
end
end
if ( returnAbbr ) then
if ( count > 1 ) then
return "·";
else
modKeys = gsub(modKeys, "CTRL", "c");
modKeys = gsub(modKeys, "SHIFT", "s");
modKeys = gsub(modKeys, "ALT", "a");
modKeys = gsub(modKeys, "STRG", "st");
end
end
if ( not prefix ) then
prefix = "";
end
local localizedName = nil;
if ( IsMacClient and IsMacClient() ) then
localizedName = getglobal(prefix..tempName.."_MAC");
end
if ( not localizedName ) then
localizedName = getglobal(prefix..tempName);
end
if ( not localizedName ) then
localizedName = tempName;
end
return modKeys..localizedName;
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` has no equivalent (LUA_COMPAT_VARARG isn't shipped) —
-- `select("#", ...)`/`select(i, ...)`, 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
]]></Script>
</Ui>