the addon arc: the AddOns folder opens
- benilla loads real 1.12 addons from Interface/AddOns — TOC, XML and Lua through one loader, with per-addon saved variables, Bindings.xml and the lifecycle events addons expect - both AddOns screens are in: the character-select list with per-addon enable state, and the ESC menu's in-game list - the API surface is measured, not guessed — a corpus harness runs hundreds of real 1.12 addons and ranks exactly which globals, templates and verbs they demand - the Lua dialect is the client's own, read off the binary — 5.0 semantics with the 1.12 additions, and UI source files are bytes, not UTF-8 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,13 +8,13 @@
|
||||
ACTIONBAR_SLOT_CHANGED repaint, HasAction/GetActionTexture in, UseAction out.
|
||||
|
||||
Structure mirrors the real hierarchy so the draw order falls out of the frame tree:
|
||||
BenillaActionBar (1024x53, BOTTOM of screen)
|
||||
├─ BenillaExpBar (StatusBar, TOP) — drawn first ⇒ lowest
|
||||
│ ├─ BenillaExhaustionLevelFillBar (BORDER) — the pale rested wash up to the tick
|
||||
│ └─ BenillaExpBarOverlayFrame (DIALOG) — the hover "curr / max" numerals
|
||||
├─ BenillaActionBarArtFrame (fills the bar) — dwarf strips + end caps over the XP bar
|
||||
MainMenuBar (1024x53, BOTTOM of screen)
|
||||
├─ MainMenuExpBar (StatusBar, TOP) — drawn first ⇒ lowest
|
||||
│ ├─ ExhaustionLevelFillBar (BORDER) — the pale rested wash up to the tick
|
||||
│ └─ MainMenuBarOverlayFrame (DIALOG) — the hover "curr / max" numerals
|
||||
├─ MainMenuBarArtFrame (fills the bar) — dwarf strips + end caps over the XP bar
|
||||
│ └─ 12 buttons + page up/down — child frames ⇒ over the art
|
||||
└─ BenillaExhaustionTick (DIALOG) — the rested-boundary marker on the strip
|
||||
└─ ExhaustionTick (DIALOG) — the rested-boundary marker on the strip
|
||||
(The real client gets the same order via RaiseFrameLevel(MainMenuBarArtFrame); our loader's
|
||||
later-sibling-is-higher + child-over-parent rules reproduce it without the explicit raise.)
|
||||
|
||||
@@ -47,12 +47,12 @@
|
||||
- Exhaustion tick + rest fill, XP text (hover/cvar-gated), MaxLevelBar: OUT. The bar's
|
||||
right-hand cluster is otherwise complete — the backpack toggle + four bag slots live in
|
||||
BagFrame.xml and the eight micro buttons in MicroMenu.xml (both anchored into
|
||||
BenillaActionBarArtFrame and seated over the art by BenillaActionBarArt_SeatAbove below),
|
||||
MainMenuBarArtFrame and seated over the art by BenillaActionBarArt_SeatAbove below),
|
||||
and the performance meter is at the foot of this file. The KEYRING landed with decision
|
||||
0765: its button and container live in BagFrame.xml (the ref's own split —
|
||||
MainMenuBarBagButtons.xml / ContainerFrame.lua), and the bar's half of it — swapping the
|
||||
two right-hand strips to the keyring art and sliding the performance meter over to make
|
||||
room — is BenillaMainMenuBar_UpdateKeyRing below, exactly where the ref keeps it
|
||||
room — is MainMenuBar_UpdateKeyRing below, exactly where the ref keeps it
|
||||
(MainMenuBar.lua l.174-183).
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
@@ -126,7 +126,7 @@
|
||||
-- explicit level is the reliable lever.) Lives here, with the frame it's about, because two
|
||||
-- files now need it — it began as BagFrame.xml's private BenillaBagBar_SeatAboveActionBar.
|
||||
function BenillaActionBarArt_SeatAbove(frame)
|
||||
local art = getglobal("BenillaActionBarArtFrame")
|
||||
local art = getglobal("MainMenuBarArtFrame")
|
||||
if art then frame:SetFrameLevel(art:GetFrameLevel() + 1) end
|
||||
end
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
-- screen size and anchors, so only the file + TexCoords change (UI-MainMenuBar-KeyRing is
|
||||
-- 256x128 and holds exactly the two bands the ref names: 0.3359375 * 128 = 43px each).
|
||||
--
|
||||
-- Called by BenillaKeyRingButton's own OnEvent (BagFrame.xml). Everything is nil-guarded:
|
||||
-- Called by KeyRingButton's own OnEvent (BagFrame.xml). Everything is nil-guarded:
|
||||
-- this file loads without BagFrame.xml in several harnesses, and BagFrame.xml loads without
|
||||
-- this one in ~25 bag/bank/escape/merchant tests — either way the missing half no-ops.
|
||||
--
|
||||
@@ -150,17 +150,17 @@
|
||||
-- unless you destroy your last key, which then un-shows the keyring rather than leaving an
|
||||
-- empty one behind. The 60s pulse is out, consistently with decision 0304's "no button
|
||||
-- pulse" for the micro buttons.
|
||||
function BenillaMainMenuBar_UpdateKeyRing()
|
||||
function MainMenuBar_UpdateKeyRing()
|
||||
local show = HasKey and HasKey()
|
||||
-- The two right-hand strips (BenillaActionBarTexture2 at x=+128, Texture3 at x=+384),
|
||||
-- The two right-hand strips (MainMenuBarTexture2 at x=+128, Texture3 at x=+384),
|
||||
-- keyring plate or the ordinary dwarf plate. The dwarf coords are this file's own
|
||||
-- authored ones (see the strips below) — restated here because this is the only code
|
||||
-- that ever changes them back.
|
||||
local strips = {
|
||||
{ name = "BenillaActionBarTexture2",
|
||||
{ name = "MainMenuBarTexture2",
|
||||
keyring = 0.6640625, keyringBottom = 1.0,
|
||||
dwarf = 0.33203125, dwarfBottom = 0.5 },
|
||||
{ name = "BenillaActionBarTexture3",
|
||||
{ name = "MainMenuBarTexture3",
|
||||
keyring = 0.1640625, keyringBottom = 0.5,
|
||||
dwarf = 0.08203125, dwarfBottom = 0.25 },
|
||||
}
|
||||
@@ -178,12 +178,12 @@
|
||||
end
|
||||
-- The performance meter's recess moves with the plate: -227 without the keyring,
|
||||
-- -235 with it (ref l.180). Its own comment at the foot of this file has the geometry.
|
||||
local perf = getglobal("BenillaPerformanceBarFrame")
|
||||
local bar = getglobal("BenillaActionBar")
|
||||
local perf = getglobal("MainMenuBarPerformanceBarFrame")
|
||||
local bar = getglobal("MainMenuBar")
|
||||
if perf and bar then
|
||||
perf:SetPoint("BOTTOMRIGHT", bar, "BOTTOMRIGHT", (show and -235) or -227, -10)
|
||||
end
|
||||
local button = getglobal("BenillaKeyRingButton")
|
||||
local button = getglobal("KeyRingButton")
|
||||
if button then
|
||||
if show then button:Show() else button:Hide() end
|
||||
end
|
||||
@@ -307,20 +307,25 @@
|
||||
-- mouse-click fork above, a keybind never places (decision 0216 §7: the ref itself never
|
||||
-- routes ActionButtonUp through OnClick, so this no longer goes through Click() either —
|
||||
-- the two entry points share the id formula, not the click path). The ref's
|
||||
-- BonusActionBar fork and the SELFACTIONBUTTON onSelf argument are not modeled (no bonus
|
||||
-- bar frame, no self-cast modifier binding).
|
||||
function BenillaActionButtonDown(index)
|
||||
local button = getglobal("BenillaActionButton" .. index)
|
||||
-- BonusActionBar fork is not modeled (no bonus bar frame).
|
||||
--
|
||||
-- SIGNATURES ARE THE 1.12 ONES (decision 1199): `ActionButtonDown(id)` /
|
||||
-- `ActionButtonUp(id, onSelf)`, the shapes an addon calls. These two never took a frame —
|
||||
-- they take the bar INDEX in both clients — so nothing here changes but the names and the
|
||||
-- restored `onSelf`, which rides through to UseAction's third argument exactly as the ref's
|
||||
-- l.56 does (our binding accepts and ignores it — `script/action.rs`).
|
||||
function ActionButtonDown(id)
|
||||
local button = getglobal("ActionButton" .. id)
|
||||
if ( button:GetButtonState() == "NORMAL" ) then
|
||||
button:SetButtonState("PUSHED")
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaActionButtonUp(index)
|
||||
local button = getglobal("BenillaActionButton" .. index)
|
||||
function ActionButtonUp(id, onSelf)
|
||||
local button = getglobal("ActionButton" .. id)
|
||||
if ( button:GetButtonState() == "PUSHED" ) then
|
||||
button:SetButtonState("NORMAL")
|
||||
UseAction(BenillaActionButton_Action(button))
|
||||
UseAction(BenillaActionButton_Action(button), 0, onSelf)
|
||||
-- Ref ActionButtonUp:55-59: the checked ring reflects the use immediately.
|
||||
BenillaActionButton_UpdateState(button)
|
||||
end
|
||||
@@ -388,7 +393,8 @@
|
||||
-- yet): the 0.4 s flash toggle, and the 0.2 s range recheck — a bound slot's label
|
||||
-- tints red/grey, an unbound one wears the RANGE_INDICATOR dot (both legs the ref's,
|
||||
-- unified in 1008 — the SCOPE note).
|
||||
function BenillaActionButton_OnUpdate(button, elapsed)
|
||||
function ActionButton_OnUpdate(elapsed)
|
||||
local button = this
|
||||
if ( button.flashing == 1 ) then
|
||||
button.flashtime = button.flashtime - elapsed
|
||||
if ( button.flashtime <= 0 ) then
|
||||
@@ -610,7 +616,8 @@
|
||||
-- The XP bar (ref-MainMenuBar.lua MainMenuExpBar_Update): value = current XP within the
|
||||
-- level, max = XP-to-level. Both from the player-global UnitXP feed (PLAYER_XP /
|
||||
-- PLAYER_NEXT_LEVEL_XP). currXP >= 0 so min(0,currXP) is just 0.
|
||||
function BenillaExpBar_Update(bar)
|
||||
function MainMenuExpBar_Update()
|
||||
local bar = this
|
||||
local currXP = UnitXP("player")
|
||||
local nextXP = UnitXPMax("player")
|
||||
bar:SetMinMaxValues(0, nextXP)
|
||||
@@ -627,12 +634,12 @@
|
||||
TextStatusBar_Initialize()
|
||||
bar.textLockable = 1
|
||||
end
|
||||
BenillaExpBar_Update(bar)
|
||||
MainMenuExpBar_Update()
|
||||
end
|
||||
|
||||
-- ref-MainMenuBar.xml l.121-126: repaint, then hand arg1/arg2 to the CVAR_UPDATE watcher.
|
||||
function BenillaExpBar_OnEvent(bar)
|
||||
BenillaExpBar_Update(bar)
|
||||
MainMenuExpBar_Update()
|
||||
if TextStatusBar_OnEvent then TextStatusBar_OnEvent(arg1, arg2) end
|
||||
end
|
||||
|
||||
@@ -651,7 +658,7 @@
|
||||
-- The ref OnEnter's full body (ref-MainMenuBar.xml l.136-141), now that its machinery
|
||||
-- exists (decision 1082): the on-bar numerals (TextStatusBar.xml), the 1 s armed timer
|
||||
-- that appends the rest-state line to the held-open tooltip, and canAddRestStateLine —
|
||||
-- the handshake BenillaExhaustionToolTipText's newbie-tips branch reads. The trailing 1
|
||||
-- the handshake ExhaustionToolTipText's newbie-tips branch reads. The trailing 1
|
||||
-- is the ref's noNormalText: with detailed tips OFF this hover shows NOTHING rather than
|
||||
-- a bare "XP Bar". Transcribed, though 1.12's default (0661) never takes it.
|
||||
function BenillaExpBar_OnEnter(bar)
|
||||
@@ -659,7 +666,7 @@
|
||||
TextStatusBar_UpdateTextString(bar)
|
||||
ShowTextStatusBarText(bar)
|
||||
end
|
||||
BenillaExhaustionTick.timer = 1
|
||||
ExhaustionTick.timer = 1
|
||||
if not GameTooltip_AddNewbieTip then return end
|
||||
GameTooltip_AddNewbieTip(bar, XPBAR_LABEL, 1.0, 1.0, 1.0, NEWBIE_TOOLTIP_XPBAR, 1)
|
||||
GameTooltip.canAddRestStateLine = 1
|
||||
@@ -668,7 +675,7 @@
|
||||
function BenillaExpBar_OnLeave(bar)
|
||||
if HideTextStatusBarText then HideTextStatusBarText(bar) end
|
||||
if GameTooltip then GameTooltip:Hide() end
|
||||
BenillaExhaustionTick.timer = nil
|
||||
ExhaustionTick.timer = nil
|
||||
end
|
||||
|
||||
-- ── The max-level rail (ref-ReputationFrame.lua, the max-level slice) ─────────────────
|
||||
@@ -692,12 +699,12 @@
|
||||
newLevel = UnitLevel("player")
|
||||
end
|
||||
if newLevel == MAX_PLAYER_LEVEL then
|
||||
BenillaExpBar:Hide()
|
||||
BenillaMaxLevelBar:Show()
|
||||
BenillaExhaustionTick:Hide()
|
||||
MainMenuExpBar:Hide()
|
||||
MainMenuBarMaxLevelBar:Show()
|
||||
ExhaustionTick:Hide()
|
||||
else
|
||||
BenillaExpBar:Show()
|
||||
BenillaMaxLevelBar:Hide()
|
||||
MainMenuExpBar:Show()
|
||||
MainMenuBarMaxLevelBar:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -719,7 +726,8 @@
|
||||
-- the rep-bar-replaced-the-xp-bar hide, l.69-71) reference the faction watch bar, not
|
||||
-- built — its frame can't be shown, so the cut is an unreachable-code cut, not a
|
||||
-- behavior cut. The MainMenuBarMaxLevelBar guard is live since 1094 (the rail below).
|
||||
function BenillaExhaustionTick_OnLoad(tick)
|
||||
function ExhaustionTick_OnLoad()
|
||||
local tick = this
|
||||
tick:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
tick:RegisterEvent("PLAYER_XP_UPDATE")
|
||||
tick:RegisterEvent("UPDATE_EXHAUSTION")
|
||||
@@ -727,7 +735,8 @@
|
||||
tick:RegisterEvent("PLAYER_UPDATE_RESTING")
|
||||
end
|
||||
|
||||
function BenillaExhaustionTick_Update(tick)
|
||||
function ExhaustionTick_Update()
|
||||
local tick = this
|
||||
if event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_XP_UPDATE"
|
||||
or event == "UPDATE_EXHAUSTION" or event == "PLAYER_LEVEL_UP" then
|
||||
local playerCurrXP = UnitXP("player")
|
||||
@@ -738,24 +747,24 @@
|
||||
-- at the bar's RIGHT end. The live server never produces them (GetRestState's
|
||||
-- binding note) — kept because the ref keeps it.
|
||||
if exhaustionStateID >= 3 then
|
||||
tick:SetPoint("CENTER", "BenillaExpBar", "RIGHT", 0, 0)
|
||||
tick:SetPoint("CENTER", "MainMenuExpBar", "RIGHT", 0, 0)
|
||||
end
|
||||
if not exhaustionThreshold then
|
||||
tick:Hide()
|
||||
BenillaExhaustionLevelFillBar:Hide()
|
||||
ExhaustionLevelFillBar:Hide()
|
||||
else
|
||||
local exhaustionTickSet = max(((playerCurrXP + exhaustionThreshold)
|
||||
/ playerMaxXP) * BenillaExpBar:GetWidth(), 0)
|
||||
/ playerMaxXP) * MainMenuExpBar:GetWidth(), 0)
|
||||
tick:ClearAllPoints()
|
||||
if exhaustionTickSet > BenillaExpBar:GetWidth()
|
||||
or BenillaMaxLevelBar:IsShown() then
|
||||
if exhaustionTickSet > MainMenuExpBar:GetWidth()
|
||||
or MainMenuBarMaxLevelBar:IsShown() then
|
||||
tick:Hide()
|
||||
BenillaExhaustionLevelFillBar:Hide()
|
||||
ExhaustionLevelFillBar:Hide()
|
||||
else
|
||||
tick:Show()
|
||||
tick:SetPoint("CENTER", "BenillaExpBar", "LEFT", exhaustionTickSet, 0)
|
||||
BenillaExhaustionLevelFillBar:Show()
|
||||
BenillaExhaustionLevelFillBar:SetPoint("TOPRIGHT", "BenillaExpBar",
|
||||
tick:SetPoint("CENTER", "MainMenuExpBar", "LEFT", exhaustionTickSet, 0)
|
||||
ExhaustionLevelFillBar:Show()
|
||||
ExhaustionLevelFillBar:SetPoint("TOPRIGHT", "MainMenuExpBar",
|
||||
"TOPLEFT", exhaustionTickSet, 0)
|
||||
end
|
||||
end
|
||||
@@ -765,13 +774,13 @@
|
||||
if event == "PLAYER_ENTERING_WORLD" or event == "UPDATE_EXHAUSTION" then
|
||||
local exhaustionStateID = GetRestState()
|
||||
if exhaustionStateID == 1 then
|
||||
BenillaExpBar:SetStatusBarColor(0.0, 0.39, 0.88, 1.0)
|
||||
BenillaExhaustionLevelFillBar:SetVertexColor(0.0, 0.39, 0.88, 0.15)
|
||||
BenillaExhaustionTickHighlight:SetVertexColor(0.0, 0.39, 0.88)
|
||||
MainMenuExpBar:SetStatusBarColor(0.0, 0.39, 0.88, 1.0)
|
||||
ExhaustionLevelFillBar:SetVertexColor(0.0, 0.39, 0.88, 0.15)
|
||||
ExhaustionTickHighlight:SetVertexColor(0.0, 0.39, 0.88)
|
||||
elseif exhaustionStateID == 2 then
|
||||
BenillaExpBar:SetStatusBarColor(0.58, 0.0, 0.55, 1.0)
|
||||
BenillaExhaustionLevelFillBar:SetVertexColor(0.58, 0.0, 0.55, 0.15)
|
||||
BenillaExhaustionTickHighlight:SetVertexColor(0.58, 0.0, 0.55)
|
||||
MainMenuExpBar:SetStatusBarColor(0.58, 0.0, 0.55, 1.0)
|
||||
ExhaustionLevelFillBar:SetVertexColor(0.58, 0.0, 0.55, 0.15)
|
||||
ExhaustionTickHighlight:SetVertexColor(0.58, 0.0, 0.55)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -783,14 +792,14 @@
|
||||
-- gated on canAddRestStateLine. EXHAUST_TOOLTIP4 (time to fully rested) needs
|
||||
-- GetTimeToWellRested, which is nil on this wire — the line simply never renders,
|
||||
-- faithfully (the binding's note).
|
||||
function BenillaExhaustionToolTipText()
|
||||
function ExhaustionToolTipText()
|
||||
if SHOW_NEWBIE_TIPS ~= "1" then
|
||||
local x, y = BenillaExhaustionTick:GetCenter()
|
||||
if BenillaExhaustionTick:IsVisible() then
|
||||
local x, y = ExhaustionTick:GetCenter()
|
||||
if ExhaustionTick:IsVisible() then
|
||||
if x >= (GetScreenWidth() / 2) then
|
||||
GameTooltip:SetOwner(BenillaExhaustionTick, "ANCHOR_LEFT")
|
||||
GameTooltip:SetOwner(ExhaustionTick, "ANCHOR_LEFT")
|
||||
else
|
||||
GameTooltip:SetOwner(BenillaExhaustionTick, "ANCHOR_RIGHT")
|
||||
GameTooltip:SetOwner(ExhaustionTick, "ANCHOR_RIGHT")
|
||||
end
|
||||
else
|
||||
GameTooltip_SetDefaultAnchor(GameTooltip, UIParent)
|
||||
@@ -829,13 +838,13 @@
|
||||
|
||||
-- Driven from the XP BAR's OnUpdate (the ref wires it there, not on the tick — the timer
|
||||
-- only ever counts while the mouse is on the bar, which is what arms it).
|
||||
function BenillaExhaustionTick_OnUpdate(elapsed)
|
||||
if BenillaExhaustionTick.timer then
|
||||
if BenillaExhaustionTick.timer < 0 then
|
||||
BenillaExhaustionToolTipText()
|
||||
BenillaExhaustionTick.timer = nil
|
||||
function ExhaustionTick_OnUpdate(elapsed)
|
||||
if ExhaustionTick.timer then
|
||||
if ExhaustionTick.timer < 0 then
|
||||
ExhaustionToolTipText()
|
||||
ExhaustionTick.timer = nil
|
||||
else
|
||||
BenillaExhaustionTick.timer = BenillaExhaustionTick.timer - elapsed
|
||||
ExhaustionTick.timer = ExhaustionTick.timer - elapsed
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -845,8 +854,8 @@
|
||||
-- materialized; the bar's lockShow starts at the not-hovered refcount.
|
||||
function BenillaExpBarOverlay_OnLoad()
|
||||
if SetTextStatusBarText then -- engine tests load file subsets
|
||||
BenillaExpBar.lockShow = 0
|
||||
SetTextStatusBarText(BenillaExpBar, BenillaExpBarText)
|
||||
MainMenuExpBar.lockShow = 0
|
||||
SetTextStatusBarText(MainMenuExpBar, MainMenuBarExpText)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -897,11 +906,11 @@
|
||||
frame.updateInterval = PERFORMANCEBAR_UPDATE_INTERVAL
|
||||
local _, _, latency = GetNetStats()
|
||||
if latency > PERFORMANCEBAR_MEDIUM_LATENCY then
|
||||
BenillaPerformanceBar:SetVertexColor(1, 0, 0)
|
||||
MainMenuBarPerformanceBar:SetVertexColor(1, 0, 0)
|
||||
elseif latency > PERFORMANCEBAR_LOW_LATENCY then
|
||||
BenillaPerformanceBar:SetVertexColor(1, 1, 0)
|
||||
MainMenuBarPerformanceBar:SetVertexColor(1, 1, 0)
|
||||
else
|
||||
BenillaPerformanceBar:SetVertexColor(0, 1, 0)
|
||||
MainMenuBarPerformanceBar:SetVertexColor(0, 1, 0)
|
||||
end
|
||||
-- Held-open tooltips re-read the number on the same 10 s beat (the ref's own refresh).
|
||||
if frame.hover then
|
||||
@@ -910,12 +919,12 @@
|
||||
end
|
||||
|
||||
function BenillaPerformanceBar_OnEnter(button)
|
||||
BenillaPerformanceBarFrame.hover = 1
|
||||
MainMenuBarPerformanceBarFrame.hover = 1
|
||||
BenillaPerformanceBar_ShowTooltip(button)
|
||||
end
|
||||
|
||||
function BenillaPerformanceBar_OnLeave()
|
||||
BenillaPerformanceBarFrame.hover = nil
|
||||
MainMenuBarPerformanceBarFrame.hover = nil
|
||||
if GameTooltip then GameTooltip:Hide() end
|
||||
end
|
||||
]]></Script>
|
||||
@@ -982,14 +991,14 @@
|
||||
<OnClick>BenillaActionButton_OnClick(self)</OnClick>
|
||||
<OnDragStart>BenillaActionButton_OnDragStart(self)</OnDragStart>
|
||||
<OnReceiveDrag>BenillaActionButton_OnReceiveDrag(self)</OnReceiveDrag>
|
||||
<OnUpdate>BenillaActionButton_OnUpdate(self, arg1)</OnUpdate>
|
||||
<OnUpdate>ActionButton_OnUpdate(arg1)</OnUpdate>
|
||||
<OnEnter>BenillaActionButton_OnEnter(self)</OnEnter>
|
||||
<OnLeave>if GameTooltip then GameTooltip:Hide() end</OnLeave>
|
||||
</Scripts>
|
||||
</CheckButton>
|
||||
|
||||
<!-- The bar: 1024x53 anchored BOTTOM of the screen (ref-MainMenuBar.xml l.4-10). -->
|
||||
<Frame name="BenillaActionBar">
|
||||
<Frame name="MainMenuBar">
|
||||
<Size><AbsDimension x="1024" y="53"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
@@ -1005,7 +1014,7 @@
|
||||
-->
|
||||
<!-- enableMouse: the ref's MainMenuExpBar takes the mouse for its own hover (l.136-147).
|
||||
The strip is the bar's top 13 px, clear of every button on it. -->
|
||||
<StatusBar name="BenillaExpBar" enableMouse="true">
|
||||
<StatusBar name="MainMenuExpBar" enableMouse="true">
|
||||
<Size><AbsDimension x="1024" y="13"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativePoint="TOP"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
@@ -1019,33 +1028,33 @@
|
||||
<Layer level="BORDER">
|
||||
<!-- The rested fill (ref-MainMenuBar.xml l.20-33 ExhaustionLevelFillBar):
|
||||
the pale wash from the bar's LEFT edge to the exhaustion tick. 0-wide
|
||||
until BenillaExhaustionTick_Update stretches its TOPRIGHT to the tick
|
||||
until ExhaustionTick_Update stretches its TOPRIGHT to the tick
|
||||
and tints it (SetVertexColor a=0.15 over this authored white). -->
|
||||
<Texture name="BenillaExhaustionLevelFillBar">
|
||||
<Texture name="ExhaustionLevelFillBar">
|
||||
<Size><AbsDimension x="0" y="13"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="BenillaExpBar"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
<Anchor point="TOPLEFT" relativeTo="MainMenuExpBar"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="1.0" g="1.0" b="1.0" a="1.0"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="BenillaXPBarTexture0" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Texture name="MainMenuXPBarTexture0" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Size><AbsDimension x="256" y="10"/></Size>
|
||||
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="-384" y="3"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0.79296875" bottom="0.83203125"/>
|
||||
</Texture>
|
||||
<Texture name="BenillaXPBarTexture1" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Texture name="MainMenuXPBarTexture1" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Size><AbsDimension x="256" y="10"/></Size>
|
||||
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="-128" y="3"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0.54296875" bottom="0.58203125"/>
|
||||
</Texture>
|
||||
<Texture name="BenillaXPBarTexture2" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Texture name="MainMenuXPBarTexture2" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Size><AbsDimension x="256" y="10"/></Size>
|
||||
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="128" y="3"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0.29296875" bottom="0.33203125"/>
|
||||
</Texture>
|
||||
<Texture name="BenillaXPBarTexture3" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Texture name="MainMenuXPBarTexture3" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Size><AbsDimension x="256" y="10"/></Size>
|
||||
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="384" y="3"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0.04296875" bottom="0.08203125"/>
|
||||
@@ -1057,16 +1066,16 @@
|
||||
a DIALOG-strata child so the "1234 / 5678" text draws over the dwarf-notch
|
||||
art and the buttons; its own OnLoad wires the FontString to the bar
|
||||
(BenillaExpBarOverlay_OnLoad's note). -->
|
||||
<Frame name="BenillaExpBarOverlayFrame" frameStrata="DIALOG">
|
||||
<Frame name="MainMenuBarOverlayFrame" frameStrata="DIALOG">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="BenillaActionBar" relativePoint="TOPLEFT"/>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="BenillaActionBar" relativePoint="BOTTOMRIGHT"/>
|
||||
<Anchor point="TOPLEFT" relativeTo="MainMenuBar" relativePoint="TOPLEFT"/>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="MainMenuBar" relativePoint="BOTTOMRIGHT"/>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="BenillaExpBarText" inherits="TextStatusBarText">
|
||||
<FontString name="MainMenuBarExpText" inherits="TextStatusBarText">
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativeTo="BenillaExpBar"><Offset><AbsDimension x="0" y="1"/></Offset></Anchor>
|
||||
<Anchor point="CENTER" relativeTo="MainMenuExpBar"><Offset><AbsDimension x="0" y="1"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
@@ -1083,7 +1092,7 @@
|
||||
<OnEvent>BenillaExpBar_OnEvent(self)</OnEvent>
|
||||
<OnEnter>BenillaExpBar_OnEnter(self)</OnEnter>
|
||||
<OnLeave>BenillaExpBar_OnLeave(self)</OnLeave>
|
||||
<OnUpdate>BenillaExhaustionTick_OnUpdate(arg1)</OnUpdate>
|
||||
<OnUpdate>ExhaustionTick_OnUpdate(arg1)</OnUpdate>
|
||||
<OnValueChanged>
|
||||
if this:IsShown() and TextStatusBar_OnValueChanged then
|
||||
TextStatusBar_OnValueChanged()
|
||||
@@ -1095,23 +1104,23 @@
|
||||
<!--
|
||||
The exhaustion tick (ref-MainMenuBar.xml l.415-442): a 32×32 button riding the XP
|
||||
bar at the rested boundary, DIALOG strata so it draws over the bar art. Normal =
|
||||
the brass marker, Highlight = the ADD glow BenillaExhaustionTick_Update tints to
|
||||
the brass marker, Highlight = the ADD glow ExhaustionTick_Update tints to
|
||||
the rest state. Starts at the bar's CENTER exactly like the ref (its own first
|
||||
Update re-seats or hides it before anything is seen).
|
||||
-->
|
||||
<Button name="BenillaExhaustionTick" frameStrata="DIALOG">
|
||||
<Button name="ExhaustionTick" frameStrata="DIALOG">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativeTo="BenillaExpBar"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
<Anchor point="CENTER" relativeTo="MainMenuExpBar"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaExhaustionTick_OnLoad(self)</OnLoad>
|
||||
<OnEvent>BenillaExhaustionTick_Update(self)</OnEvent>
|
||||
<OnEnter>BenillaExhaustionToolTipText()</OnEnter>
|
||||
<OnLoad>ExhaustionTick_OnLoad()</OnLoad>
|
||||
<OnEvent>ExhaustionTick_Update()</OnEvent>
|
||||
<OnEnter>ExhaustionToolTipText()</OnEnter>
|
||||
<OnLeave>if GameTooltip then GameTooltip:Hide() end</OnLeave>
|
||||
</Scripts>
|
||||
<NormalTexture name="BenillaExhaustionTickNormal" file="Interface\MainMenuBar\UI-ExhaustionTickNormal"/>
|
||||
<HighlightTexture name="BenillaExhaustionTickHighlight" file="Interface\MainMenuBar\UI-ExhaustionTickHighlight" alphaMode="ADD"/>
|
||||
<NormalTexture name="ExhaustionTickNormal" file="Interface\MainMenuBar\UI-ExhaustionTickNormal"/>
|
||||
<HighlightTexture name="ExhaustionTickHighlight" file="Interface\MainMenuBar\UI-ExhaustionTickHighlight" alphaMode="ADD"/>
|
||||
</Button>
|
||||
|
||||
<!--
|
||||
@@ -1123,38 +1132,38 @@
|
||||
OnHide call UIParent_ManageFramePositions, which benilla doesn't build
|
||||
(DurabilityFrame.xml's note) — a named cut.
|
||||
-->
|
||||
<Frame name="BenillaMaxLevelBar" enableMouse="true" hidden="true">
|
||||
<Frame name="MainMenuBarMaxLevelBar" enableMouse="true" hidden="true">
|
||||
<Size><AbsDimension x="1024" y="7"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativePoint="TOP"><Offset><AbsDimension x="0" y="-11"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="BenillaMaxLevelBar0" file="Interface\MainMenuBar\UI-MainMenuBar-MaxLevel">
|
||||
<Texture name="MainMenuMaxLevelBar0" file="Interface\MainMenuBar\UI-MainMenuBar-MaxLevel">
|
||||
<Size><AbsDimension x="256" y="7"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM" relativePoint="TOP"><Offset><AbsDimension x="-384" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0" bottom="0.21875"/>
|
||||
</Texture>
|
||||
<Texture name="BenillaMaxLevelBar1" file="Interface\MainMenuBar\UI-MainMenuBar-MaxLevel">
|
||||
<Texture name="MainMenuMaxLevelBar1" file="Interface\MainMenuBar\UI-MainMenuBar-MaxLevel">
|
||||
<Size><AbsDimension x="256" y="7"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="BenillaMaxLevelBar0" relativePoint="RIGHT"/>
|
||||
<Anchor point="LEFT" relativeTo="MainMenuMaxLevelBar0" relativePoint="RIGHT"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0.25" bottom="0.46875"/>
|
||||
</Texture>
|
||||
<Texture name="BenillaMaxLevelBar2" file="Interface\MainMenuBar\UI-MainMenuBar-MaxLevel">
|
||||
<Texture name="MainMenuMaxLevelBar2" file="Interface\MainMenuBar\UI-MainMenuBar-MaxLevel">
|
||||
<Size><AbsDimension x="256" y="7"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="BenillaMaxLevelBar1" relativePoint="RIGHT"/>
|
||||
<Anchor point="LEFT" relativeTo="MainMenuMaxLevelBar1" relativePoint="RIGHT"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0.5" bottom="0.71875"/>
|
||||
</Texture>
|
||||
<Texture name="BenillaMaxLevelBar3" file="Interface\MainMenuBar\UI-MainMenuBar-MaxLevel">
|
||||
<Texture name="MainMenuMaxLevelBar3" file="Interface\MainMenuBar\UI-MainMenuBar-MaxLevel">
|
||||
<Size><AbsDimension x="256" y="7"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="BenillaMaxLevelBar2" relativePoint="RIGHT"/>
|
||||
<Anchor point="LEFT" relativeTo="MainMenuMaxLevelBar2" relativePoint="RIGHT"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0.75" bottom="0.96875"/>
|
||||
</Texture>
|
||||
@@ -1171,31 +1180,31 @@
|
||||
strips (ARTWORK) + the 2 end caps and page number (OVERLAY); the buttons are its
|
||||
children so they draw over the art.
|
||||
-->
|
||||
<Frame name="BenillaActionBarArtFrame">
|
||||
<Frame name="MainMenuBarArtFrame">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="BenillaActionBar" relativePoint="TOPLEFT"/>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="BenillaActionBar" relativePoint="BOTTOMRIGHT"/>
|
||||
<Anchor point="TOPLEFT" relativeTo="MainMenuBar" relativePoint="TOPLEFT"/>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="MainMenuBar" relativePoint="BOTTOMRIGHT"/>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<!-- 4 dwarf strips: 256x43, BOTTOM x=-384/-128/128/384 y=0, TexCoord bands
|
||||
(ref-MainMenuBar.xml l.232-283). Adjacent atlas rows ⇒ the joins are seamless. -->
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="BenillaActionBarTexture0" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Texture name="MainMenuBarTexture0" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Size><AbsDimension x="256" y="43"/></Size>
|
||||
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="-384" y="0"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0.83203125" bottom="1.0"/>
|
||||
</Texture>
|
||||
<Texture name="BenillaActionBarTexture1" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Texture name="MainMenuBarTexture1" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Size><AbsDimension x="256" y="43"/></Size>
|
||||
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="-128" y="0"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0.58203125" bottom="0.75"/>
|
||||
</Texture>
|
||||
<Texture name="BenillaActionBarTexture2" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Texture name="MainMenuBarTexture2" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Size><AbsDimension x="256" y="43"/></Size>
|
||||
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="128" y="0"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0.33203125" bottom="0.5"/>
|
||||
</Texture>
|
||||
<Texture name="BenillaActionBarTexture3" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Texture name="MainMenuBarTexture3" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Size><AbsDimension x="256" y="43"/></Size>
|
||||
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="384" y="0"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0.08203125" bottom="0.25"/>
|
||||
@@ -1207,16 +1216,16 @@
|
||||
fix landed in 87c97d7). Page number: GameFontNormalSmall CENTER (30,-5),
|
||||
static "1" (l.311-319). -->
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="BenillaActionBarLeftEndCap" file="Interface\MainMenuBar\UI-MainMenuBar-EndCap-Dwarf">
|
||||
<Texture name="MainMenuBarLeftEndCap" file="Interface\MainMenuBar\UI-MainMenuBar-EndCap-Dwarf">
|
||||
<Size><AbsDimension x="128" y="128"/></Size>
|
||||
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="-544" y="0"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
<Texture name="BenillaActionBarRightEndCap" file="Interface\MainMenuBar\UI-MainMenuBar-EndCap-Dwarf">
|
||||
<Texture name="MainMenuBarRightEndCap" file="Interface\MainMenuBar\UI-MainMenuBar-EndCap-Dwarf">
|
||||
<Size><AbsDimension x="128" y="128"/></Size>
|
||||
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="544" y="0"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="1.0" right="0.0" top="0.0" bottom="1.0"/>
|
||||
</Texture>
|
||||
<FontString name="BenillaActionBarPageNumber" inherits="GameFontNormalSmall" text="1">
|
||||
<FontString name="MainMenuBarPageNumber" inherits="GameFontNormalSmall" text="1">
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="30" y="-5"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
@@ -1227,71 +1236,71 @@
|
||||
<!-- The 12 buttons: Button1 at the art frame BOTTOMLEFT (8,4); 2..12 chain LEFT of
|
||||
the previous's RIGHT +6,0 (ref-ActionBarFrame.xml l.51-158). Replaces our old
|
||||
fixed 38px stride with the real 42px chain (36 wide + 6 gap). -->
|
||||
<CheckButton name="BenillaActionButton1" inherits="BenillaActionButtonTemplate">
|
||||
<CheckButton name="ActionButton1" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="8" y="4"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 1)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaActionButton2" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaActionButton1" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ActionButton2" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ActionButton1" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 2)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaActionButton3" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaActionButton2" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ActionButton3" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ActionButton2" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 3)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaActionButton4" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaActionButton3" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ActionButton4" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ActionButton3" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 4)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaActionButton5" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaActionButton4" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ActionButton5" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ActionButton4" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 5)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaActionButton6" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaActionButton5" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ActionButton6" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ActionButton5" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 6)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaActionButton7" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaActionButton6" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ActionButton7" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ActionButton6" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 7)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaActionButton8" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaActionButton7" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ActionButton8" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ActionButton7" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 8)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaActionButton9" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaActionButton8" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ActionButton9" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ActionButton8" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 9)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaActionButton10" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaActionButton9" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ActionButton10" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ActionButton9" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 10)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaActionButton11" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaActionButton10" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ActionButton11" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ActionButton10" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 11)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaActionButton12" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaActionButton11" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ActionButton12" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ActionButton11" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 12)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
|
||||
<!-- Page up/down: 32x32 at CENTER of the art frame's TOPLEFT +(522,-22)/(522,-42)
|
||||
(ref-ActionBarFrame.xml l.159-217). ART ONLY — we have no bar paging, so the
|
||||
clicks are no-ops (no OnClick handler). -->
|
||||
<Button name="BenillaActionBarUpButton">
|
||||
<Button name="ActionBarUpButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativeTo="BenillaActionBarArtFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="522" y="-22"/></Offset></Anchor>
|
||||
<Anchor point="CENTER" relativeTo="MainMenuBarArtFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="522" y="-22"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<NormalTexture file="Interface\MainMenuBar\UI-MainMenu-ScrollUpButton-Up"/>
|
||||
<PushedTexture file="Interface\MainMenuBar\UI-MainMenu-ScrollUpButton-Down"/>
|
||||
<HighlightTexture file="Interface\MainMenuBar\UI-MainMenu-ScrollUpButton-Highlight" alphaMode="ADD"/>
|
||||
</Button>
|
||||
<Button name="BenillaActionBarDownButton">
|
||||
<Button name="ActionBarDownButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativeTo="BenillaActionBarArtFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="522" y="-42"/></Offset></Anchor>
|
||||
<Anchor point="CENTER" relativeTo="MainMenuBarArtFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="522" y="-42"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<NormalTexture file="Interface\MainMenuBar\UI-MainMenu-ScrollDownButton-Up"/>
|
||||
<PushedTexture file="Interface\MainMenuBar\UI-MainMenu-ScrollDownButton-Down"/>
|
||||
@@ -1317,24 +1326,24 @@
|
||||
tree, and its explicit strata survives the parent's LOW subtree-force (attributes
|
||||
apply before nested <Frames> are built).
|
||||
|
||||
The -227 authored here is the NO-keyring position; BenillaMainMenuBar_UpdateKeyRing
|
||||
The -227 authored here is the NO-keyring position; MainMenuBar_UpdateKeyRing
|
||||
(top of this file) re-points it to -235 while the keyring is shown, the ref's own
|
||||
shift (MainMenuBar.lua l.180). Both offsets live in that one function. -->
|
||||
<Frame name="BenillaPerformanceBarFrame" frameStrata="LOW">
|
||||
<Frame name="MainMenuBarPerformanceBarFrame" frameStrata="LOW">
|
||||
<Size><AbsDimension x="16" y="64"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="BenillaActionBar" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-227" y="-10"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="MainMenuBar" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-227" y="-10"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="BenillaPerformanceBar" file="Interface\MainMenuBar\UI-MainMenuBar-PerformanceBar">
|
||||
<Texture name="MainMenuBarPerformanceBar" file="Interface\MainMenuBar\UI-MainMenuBar-PerformanceBar">
|
||||
<Size><AbsDimension x="20" y="66"/></Size>
|
||||
<Anchors><Anchor point="TOPRIGHT"/></Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Button name="BenillaPerformanceBarButton" setAllPoints="true" frameStrata="HIGH">
|
||||
<Button name="MainMenuBarPerformanceBarFrameButton" setAllPoints="true" frameStrata="HIGH">
|
||||
<Scripts>
|
||||
<OnEnter>BenillaPerformanceBar_OnEnter(self)</OnEnter>
|
||||
<OnLeave>BenillaPerformanceBar_OnLeave()</OnLeave>
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
<Ui>
|
||||
<!--
|
||||
The in-game AddOns list (decision 1197) — the ESC-menu half of the AddOns UI, and the
|
||||
button `GameMenuFrame.xml` has been carrying as `GameMenuButton_Pending(self, ADDONS)`
|
||||
since 0997 ("goes live by deleting one Pending call and writing its OnClick").
|
||||
|
||||
SCOPE — this frame has no reference. 1.12 ships an AddOns list only on the GLUE side
|
||||
(`GlueXML/AddonList.xml/.lua`, opened by CharacterSelectAddonsButton); in-world there is
|
||||
no such window and the ESC menu has no such entry. This is a deliberate benilla addition
|
||||
the director asked for, so nothing here is transcribed and nothing pretends to be. What IS
|
||||
borrowed from the reference is the BEHAVIOUR of its glue twin, because it is the same job:
|
||||
|
||||
- the row shape and count: ADDON_BUTTON_HEIGHT = 16, MAX_ADDONS_DISPLAYED = 19
|
||||
- AddonList_Update's three-colour title rule — gold loadable, red enabled-but-broken,
|
||||
grey disabled (SetTextColor 1/0.78/0, 1/0.1/0.1, 0.5/0.5/0.5)
|
||||
- the status column carrying `reason` (our GetAddOnInfo returns the reference's own
|
||||
token set — decision 1191 §6)
|
||||
- Okay saves, Cancel discards (SaveAddOns / ResetAddOns)
|
||||
|
||||
It is written entirely against the ELEVEN AddOn API verbs 1191 landed — GetNumAddOns,
|
||||
GetAddOnInfo, GetAddOnDependencies, EnableAddOn, DisableAddOn, IsAddOnLoaded — with no
|
||||
host support of its own. That is the point of it as much as the window is: if this panel
|
||||
works, the addon API an addon author would use works, and it is exercised by anyone who
|
||||
opens the ESC menu rather than by a test nobody runs.
|
||||
|
||||
WHEN CHANGES TAKE EFFECT — next login, not now. Enabling an addon mid-session would mean
|
||||
running its files into a VM that has already fired ADDON_LOADED/VARIABLES_LOADED/
|
||||
PLAYER_LOGIN, which is not a state the reference's own loader has a path for; disabling
|
||||
one cannot unload it at all. The panel says so on its face rather than leaving a player to
|
||||
discover it. The enable state is written at the session's end by the shutdown tail
|
||||
(decision 1191 §7, the reference's own last step at 0x490bd0).
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
-- Strings. The `X = X or "..."` idiom is MicroMenu.xml's: a real GlobalStrings table
|
||||
-- loaded later wins, and these are the fallbacks for a run without one.
|
||||
ADDONS = ADDONS or "AddOns"
|
||||
ADDON_LIST_ENABLE_ALL = ADDON_LIST_ENABLE_ALL or "Enable All"
|
||||
ADDON_LIST_DISABLE_ALL = ADDON_LIST_DISABLE_ALL or "Disable All"
|
||||
ADDON_LIST_APPLY_NOTE = ADDON_LIST_APPLY_NOTE or "Changes take effect at your next login."
|
||||
OKAY = OKAY or "Okay"
|
||||
CANCEL = CANCEL or "Cancel"
|
||||
|
||||
ADDON_BUTTON_HEIGHT = 16
|
||||
MAX_ADDONS_DISPLAYED = 19
|
||||
|
||||
-- The staged edit. `AddonList.original` is the enable state as the panel found it, so
|
||||
-- Cancel can put it back — the reference's `ResetAddOns` over the client's own records,
|
||||
-- which we do not have an engine verb for (SaveAddOns/ResetAddOns are GLUE-namespace
|
||||
-- globals and deliberately absent in-world, decision 1191 §6).
|
||||
AddonList = { offset = 0, original = {} }
|
||||
|
||||
function AddonList_Toggle()
|
||||
if AddonListFrame:IsVisible() then
|
||||
AddonList_OnCancel()
|
||||
else
|
||||
AddonList_Show()
|
||||
end
|
||||
end
|
||||
|
||||
function AddonList_Show()
|
||||
AddonList.offset = 0
|
||||
AddonList.original = {}
|
||||
for i = 1, GetNumAddOns() do
|
||||
local name, _, _, _, loadable, reason = GetAddOnInfo(i)
|
||||
-- "enabled" is everything except the player's own DISABLED — a dependency problem
|
||||
-- is not a checkbox state, it is a status line.
|
||||
AddonList.original[name] = not (reason == "DISABLED")
|
||||
end
|
||||
AddonListFrame:Show()
|
||||
AddonList_Update()
|
||||
end
|
||||
|
||||
-- One row's worth of facts, so the row painter below reads as a list of assignments
|
||||
-- rather than as a pile of GetAddOnInfo returns.
|
||||
local function rowFacts(index)
|
||||
local name, title, notes, url, loadable, reason = GetAddOnInfo(index)
|
||||
local enabled = not (reason == "DISABLED")
|
||||
return name, (title or name), loadable, reason, enabled, notes, url
|
||||
end
|
||||
|
||||
function AddonList_Update()
|
||||
local total = GetNumAddOns()
|
||||
-- Clamp first: a Disable All that shortens nothing still must not leave the offset
|
||||
-- past the end after the list is re-read.
|
||||
local maxOffset = total - MAX_ADDONS_DISPLAYED
|
||||
if maxOffset < 0 then maxOffset = 0 end
|
||||
if AddonList.offset > maxOffset then AddonList.offset = maxOffset end
|
||||
if AddonList.offset < 0 then AddonList.offset = 0 end
|
||||
|
||||
for i = 1, MAX_ADDONS_DISPLAYED do
|
||||
local entry = getglobal("AddonListEntry" .. i)
|
||||
local index = AddonList.offset + i
|
||||
if index > total then
|
||||
entry:Hide()
|
||||
else
|
||||
local name, title, loadable, reason, enabled = rowFacts(index)
|
||||
entry:SetID(index)
|
||||
|
||||
local check = getglobal("AddonListEntry" .. i .. "Check")
|
||||
check:SetChecked(enabled)
|
||||
|
||||
-- AddonList_Update's own colour rule, its own three values.
|
||||
local label = getglobal("AddonListEntry" .. i .. "Title")
|
||||
label:SetText(title)
|
||||
if loadable then
|
||||
label:SetTextColor(1.0, 0.78, 0.0)
|
||||
elseif enabled and reason ~= "DEP_DISABLED" then
|
||||
label:SetTextColor(1.0, 0.1, 0.1)
|
||||
else
|
||||
label:SetTextColor(0.5, 0.5, 0.5)
|
||||
end
|
||||
|
||||
-- The status column: the reference splices its token into a GlobalStrings
|
||||
-- lookup (`getglobal("ADDON_"..reason)`), which is why the token spelling is
|
||||
-- load-bearing (1191 §6). A token with no string falls back to the token, so
|
||||
-- a player sees something rather than a blank.
|
||||
local status = getglobal("AddonListEntry" .. i .. "Status")
|
||||
if reason then
|
||||
status:SetText(getglobal("ADDON_" .. reason) or reason)
|
||||
else
|
||||
status:SetText("")
|
||||
end
|
||||
entry:Show()
|
||||
end
|
||||
end
|
||||
|
||||
-- The scroll pair exists only while the list overflows — the reference's own
|
||||
-- GlueScrollFrame_Update condition.
|
||||
if total > MAX_ADDONS_DISPLAYED then
|
||||
AddonListScrollUp:Show()
|
||||
AddonListScrollDown:Show()
|
||||
else
|
||||
AddonListScrollUp:Hide()
|
||||
AddonListScrollDown:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
function AddonList_Scroll(delta)
|
||||
AddonList.offset = AddonList.offset + delta
|
||||
AddonList_Update()
|
||||
end
|
||||
|
||||
function AddonList_ToggleEntry(entryIndex)
|
||||
local name, _, _, _, _, reason = GetAddOnInfo(entryIndex)
|
||||
if reason == "DISABLED" then
|
||||
EnableAddOn(name)
|
||||
else
|
||||
DisableAddOn(name)
|
||||
end
|
||||
AddonList_Update()
|
||||
end
|
||||
|
||||
function AddonList_SetAll(on)
|
||||
if on then EnableAllAddOns() else DisableAllAddOns() end
|
||||
AddonList_Update()
|
||||
end
|
||||
|
||||
function AddonList_OnOk()
|
||||
PlaySound("igMainMenuOption")
|
||||
AddonListFrame:Hide()
|
||||
end
|
||||
|
||||
-- Cancel restores what the panel found, addon by addon — `ResetAddOns` written out,
|
||||
-- because that verb is glue-only and this window is not the glue.
|
||||
function AddonList_OnCancel()
|
||||
PlaySound("igMainMenuQuit")
|
||||
for i = 1, GetNumAddOns() do
|
||||
local name = GetAddOnInfo(i)
|
||||
local was = AddonList.original[name]
|
||||
if was == true then
|
||||
EnableAddOn(name)
|
||||
elseif was == false then
|
||||
DisableAddOn(name)
|
||||
end
|
||||
end
|
||||
AddonListFrame:Hide()
|
||||
end
|
||||
|
||||
-- The hover tooltip: title, notes, dependencies — AddonTooltip_Update's three lines in
|
||||
-- its order, on the shared GameTooltip rather than a private frame.
|
||||
function AddonList_RowEnter(row)
|
||||
local name, title, loadable, reason, enabled, notes = rowFacts(row:GetID())
|
||||
GameTooltip:SetOwner(row, "ANCHOR_RIGHT")
|
||||
GameTooltip:AddLine(title)
|
||||
if notes and notes ~= "" then
|
||||
GameTooltip:AddLine(notes, 1, 1, 1)
|
||||
end
|
||||
local deps = ""
|
||||
local list = { GetAddOnDependencies(row:GetID()) }
|
||||
for i = 1, getn(list) do
|
||||
if i == 1 then
|
||||
deps = (ADDON_DEPENDENCIES or "Dependencies: ") .. list[i]
|
||||
else
|
||||
deps = deps .. ", " .. list[i]
|
||||
end
|
||||
end
|
||||
if deps ~= "" then
|
||||
GameTooltip:AddLine(deps, 1, 0.82, 0)
|
||||
end
|
||||
GameTooltip:Show()
|
||||
end
|
||||
|
||||
function AddonList_RowLeave()
|
||||
GameTooltip:Hide()
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
<!-- The panel button face. This engine ships no shared UIPanelButtonTemplate — every panel
|
||||
defines its own (the MacroFrame/TrainerFrame precedent), and a local one also keeps this
|
||||
window independent of any other file's template names. -->
|
||||
<Button name="AddonListPanelButtonTemplate" virtual="true">
|
||||
<Size><AbsDimension x="90" y="22"/></Size>
|
||||
<NormalFont inherits="GameFontNormal"/>
|
||||
<HighlightFont inherits="GameFontHighlight"/>
|
||||
<DisabledFont inherits="GameFontDisable"/>
|
||||
<NormalTexture file="Interface\Buttons\UI-Panel-Button-Up"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></NormalTexture>
|
||||
<PushedTexture file="Interface\Buttons\UI-Panel-Button-Down"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></PushedTexture>
|
||||
<DisabledTexture file="Interface\Buttons\UI-Panel-Button-Disabled"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></DisabledTexture>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></HighlightTexture>
|
||||
</Button>
|
||||
|
||||
<!-- The two scroll arrows, the client's own `UI-ScrollBar-ScrollUpButton-*` art. -->
|
||||
<Button name="AddonListScrollUpTemplate" virtual="true">
|
||||
<Size><AbsDimension x="16" y="16"/></Size>
|
||||
<NormalTexture file="Interface\Buttons\UI-ScrollBar-ScrollUpButton-Up"><TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/></NormalTexture>
|
||||
<PushedTexture file="Interface\Buttons\UI-ScrollBar-ScrollUpButton-Down"><TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/></PushedTexture>
|
||||
<HighlightTexture file="Interface\Buttons\UI-ScrollBar-ScrollUpButton-Highlight" alphaMode="ADD"><TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/></HighlightTexture>
|
||||
</Button>
|
||||
<Button name="AddonListScrollDownTemplate" virtual="true">
|
||||
<Size><AbsDimension x="16" y="16"/></Size>
|
||||
<NormalTexture file="Interface\Buttons\UI-ScrollBar-ScrollDownButton-Up"><TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/></NormalTexture>
|
||||
<PushedTexture file="Interface\Buttons\UI-ScrollBar-ScrollDownButton-Down"><TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/></PushedTexture>
|
||||
<HighlightTexture file="Interface\Buttons\UI-ScrollBar-ScrollDownButton-Highlight" alphaMode="ADD"><TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/></HighlightTexture>
|
||||
</Button>
|
||||
|
||||
<!-- One row: a checkbox, a title, a status column. 16 tall, the reference's own
|
||||
ADDON_BUTTON_HEIGHT. -->
|
||||
<Button name="AddonListEntryTemplate" virtual="true">
|
||||
<Size><AbsDimension x="440" y="16"/></Size>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString name="$parentTitle" inherits="GameFontNormalSmall" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT"><Offset><AbsDimension x="26" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="$parentStatus" inherits="GameFontDisableSmall" justifyH="RIGHT">
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<CheckButton name="$parentCheck">
|
||||
<Size><AbsDimension x="20" y="20"/></Size>
|
||||
<Anchors><Anchor point="LEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-CheckBox-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-CheckBox-Down"/>
|
||||
<CheckedTexture file="Interface\Buttons\UI-CheckBox-Check"/>
|
||||
<Scripts>
|
||||
<OnClick>AddonList_ToggleEntry(this:GetParent():GetID())</OnClick>
|
||||
</Scripts>
|
||||
</CheckButton>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnEnter>AddonList_RowEnter(this)</OnEnter>
|
||||
<OnLeave>AddonList_RowLeave()</OnLeave>
|
||||
<OnClick>AddonList_ToggleEntry(this:GetID())</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<Frame name="AddonListFrame" frameStrata="DIALOG" enableMouse="true" hidden="true">
|
||||
<Size><AbsDimension x="480" y="420"/></Size>
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background"
|
||||
edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
|
||||
<BackgroundInsets><AbsInset left="11" right="12" top="12" bottom="11"/></BackgroundInsets>
|
||||
<TileSize><AbsValue val="32"/></TileSize>
|
||||
<EdgeSize><AbsValue val="32"/></EdgeSize>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="$parentTitle" inherits="GameFontNormalLarge">
|
||||
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<FontString name="$parentNote" inherits="GameFontDisableSmall">
|
||||
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="60"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Button name="AddonListEntry1" inherits="AddonListEntryTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="20" y="-46"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="AddonListEntry2" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry1" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry3" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry2" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry4" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry3" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry5" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry4" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry6" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry5" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry7" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry6" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry8" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry7" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry9" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry8" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry10" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry9" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry11" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry10" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry12" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry11" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry13" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry12" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry14" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry13" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry15" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry14" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry16" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry15" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry17" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry16" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry18" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry17" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="AddonListEntry19" inherits="AddonListEntryTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="AddonListEntry18" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
|
||||
<Button name="AddonListScrollUp" inherits="AddonListScrollUpTemplate" hidden="true">
|
||||
<Anchors><Anchor point="TOPRIGHT"><Offset><AbsDimension x="-2" y="-42"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnClick>AddonList_Scroll(-1)</OnClick></Scripts>
|
||||
</Button>
|
||||
<Button name="AddonListScrollDown" inherits="AddonListScrollDownTemplate" hidden="true">
|
||||
<Anchors><Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-2" y="86"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnClick>AddonList_Scroll(1)</OnClick></Scripts>
|
||||
</Button>
|
||||
|
||||
<Button name="AddonListEnableAll" inherits="AddonListPanelButtonTemplate">
|
||||
<Size><AbsDimension x="104" y="22"/></Size>
|
||||
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="20" y="20"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>this:SetText(ADDON_LIST_ENABLE_ALL)</OnLoad>
|
||||
<OnClick>AddonList_SetAll(1)</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="AddonListDisableAll" inherits="AddonListPanelButtonTemplate">
|
||||
<Size><AbsDimension x="104" y="22"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="AddonListEnableAll" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>this:SetText(ADDON_LIST_DISABLE_ALL)</OnLoad>
|
||||
<OnClick>AddonList_SetAll(nil)</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="AddonListOkay" inherits="AddonListPanelButtonTemplate">
|
||||
<Size><AbsDimension x="90" y="22"/></Size>
|
||||
<Anchors><Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-20" y="20"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>this:SetText(OKAY)</OnLoad>
|
||||
<OnClick>AddonList_OnOk()</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="AddonListCancel" inherits="AddonListPanelButtonTemplate">
|
||||
<Size><AbsDimension x="90" y="22"/></Size>
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="AddonListOkay" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>this:SetText(CANCEL)</OnLoad>
|
||||
<OnClick>AddonList_OnCancel()</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
AddonListFrameTitle:SetText(ADDONS)
|
||||
AddonListFrameNote:SetText(ADDON_LIST_APPLY_NOTE)
|
||||
</OnLoad>
|
||||
<OnShow>AddonList_Update()</OnShow>
|
||||
<OnMouseWheel>AddonList_Scroll(-arg1)</OnMouseWheel>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
@@ -4,7 +4,7 @@
|
||||
(dressing arc, decision 0084; whole-space generalization, decision 0216 slice 2): the
|
||||
geometry and art below are QUOTED from the real `ContainerFrame.xml`/`ContainerFrame.lua`
|
||||
(scratchpad `ref-framexml/ContainerFrame.xml`, `ContainerFrame.lua`; line citations inline),
|
||||
turned onto our own C_Container feed/intents. FIVE windows now — the backpack (bag id 0, 16
|
||||
turned onto our own container feed/intents. FIVE windows now — the backpack (bag id 0, 16
|
||||
slots, its own hand-authored block below, unchanged in shape) plus the four equipped bags
|
||||
(ids 1..4, `BenillaBagWindowTemplate`, up to 20 slots each) — the ContainerFrameTemplate
|
||||
generalization this file's header always said was coming.
|
||||
@@ -46,7 +46,7 @@
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
-- The five windows' shared slot count cap: the backpack is always exactly 16
|
||||
-- (C_Container.GetContainerNumSlots(0) never varies — PACK_SLOTS is a wire constant); the
|
||||
-- (GetContainerNumSlots(0) never varies — PACK_SLOTS is a wire constant); the
|
||||
-- equipped-bag windows show up to this many (5 rows × 4 columns — the reference template's
|
||||
-- true max is 36, but 20 is vanilla's largest actual equippable bag, and building out to 36
|
||||
-- slot buttons per window for content that can never exist would be pure waste. Hides the
|
||||
@@ -56,7 +56,7 @@
|
||||
-- The keyring's container id (ref MainMenuBarBagButtons.lua:1) — decision 0765. It is an
|
||||
-- ordinary container in every respect the seam cares about: the app feeds it like any other
|
||||
-- bag (its slots are the player descriptor's own 81.., no container object), and every
|
||||
-- C_Container verb, the cursor, the pending-op lock and the wire drains take it unchanged.
|
||||
-- container verb, the cursor, the pending-op lock and the wire drains take it unchanged.
|
||||
-- What makes it special is only what the reference makes special: the window's art, its
|
||||
-- open/close sound, and the fact that its capacity is not a wire field.
|
||||
KEYRING_CONTAINER = -2
|
||||
@@ -67,7 +67,7 @@
|
||||
-- and feeds it as the container's own size, so this reads it back rather than keeping a
|
||||
-- second copy of the ladder that could drift from the one the feed uses.
|
||||
function GetKeyRingSize()
|
||||
return C_Container.GetContainerNumSlots(KEYRING_CONTAINER)
|
||||
return GetContainerNumSlots(KEYRING_CONTAINER)
|
||||
end
|
||||
|
||||
-- BenillaBagSlot_OnLoad(button, physIndex): physIndex is the button's CHAIN position (1 =
|
||||
@@ -99,7 +99,10 @@
|
||||
if button.slot == nil then return end -- a hidden button (physIndex past the bag's size)
|
||||
local bagId = button:GetParent():GetID()
|
||||
if mouseButton == "LeftButton" then
|
||||
local info = C_Container.GetContainerItemInfo(bagId, button.slot)
|
||||
-- The reference's own five returns (ContainerFrame.lua:241) — decision 1199
|
||||
-- put this binding back on its 1.12 shape.
|
||||
local texture, itemCount, locked, quality, readable =
|
||||
GetContainerItemInfo(bagId, button.slot)
|
||||
-- The modifier fork, the reference's own order (ContainerFrame.lua:563-579;
|
||||
-- BankFrame.lua:141-152 for a bank slot, which is this same handler at container
|
||||
-- -1). `ignoreModifiers` (the drag-trio calls, both true) suppresses it whole —
|
||||
@@ -107,7 +110,7 @@
|
||||
--
|
||||
-- CTRL: preview the item on your own character in the dressing room (decision 1060).
|
||||
if IsControlKeyDown() and not ignoreModifiers then
|
||||
DressUpItemLink(C_Container.GetContainerItemLink(bagId, button.slot))
|
||||
DressUpItemLink(GetContainerItemLink(bagId, button.slot))
|
||||
return
|
||||
end
|
||||
-- SHIFT: post the item's link into the chat edit box if it's open, ELSE split the
|
||||
@@ -119,10 +122,10 @@
|
||||
-- arm never splits, even in the one benilla state where nothing can be posted
|
||||
-- (an unresolved template — `BenillaChatEdit_InsertLink`'s own note).
|
||||
if ChatFrameEditBox and ChatFrameEditBox:IsShown() then
|
||||
BenillaChatEdit_InsertLink(C_Container.GetContainerItemLink(bagId, button.slot))
|
||||
BenillaChatEdit_InsertLink(GetContainerItemLink(bagId, button.slot))
|
||||
return
|
||||
end
|
||||
if info and not info.isLocked and (info.stackCount or 1) >= 2 then
|
||||
if texture ~= nil and not locked and (itemCount or 1) >= 2 then
|
||||
button.SplitStack = function(btn, n)
|
||||
SplitContainerItem(bagId, btn.slot, n)
|
||||
BenillaBagWindow_Repaint(btn:GetParent())
|
||||
@@ -130,22 +133,23 @@
|
||||
-- Ref ContainerFrameItemButton_OnLoad/_OnClick (ContainerFrame.lua:558-560,
|
||||
-- 573-576): the owner's own SplitStack closure carries the bag/slot so
|
||||
-- StackSplitFrameOkay_Click's generic `owner.SplitStack(owner, split)` call
|
||||
-- needs no window-specific knowledge. SplitContainerItem is a TOP-LEVEL
|
||||
-- global (cursor.rs — not under C_Container despite the container-verb
|
||||
-- naming; GetContainerItemInfo/PickupContainerItem ARE namespaced).
|
||||
OpenStackSplitFrame(info.stackCount, button, "BOTTOMRIGHT", "TOPRIGHT")
|
||||
-- needs no window-specific knowledge. SplitContainerItem lives in cursor.rs
|
||||
-- rather than container.rs — it is the one container-named verb whose
|
||||
-- payload is the cursor's, not a bag's — but like every container verb it is
|
||||
-- a plain top-level global (decision 1198).
|
||||
OpenStackSplitFrame(itemCount, button, "BOTTOMRIGHT", "TOPRIGHT")
|
||||
end
|
||||
return
|
||||
end
|
||||
-- A plain click hides any open split frame (ref ContainerFrame.lua:581) before the
|
||||
-- ordinary pickup/place proceeds.
|
||||
local split = getglobal("BenillaStackSplitFrame")
|
||||
local split = getglobal("StackSplitFrame")
|
||||
if split then split:Hide() end
|
||||
-- PickupContainerItem is the single drag entry point (real client): an empty cursor
|
||||
-- picks this slot up; a held cursor places/swaps into it (same slot = cancel). Hide the
|
||||
-- hover tooltip (the item just left the slot for the cursor) and repaint so the picked
|
||||
-- slot dims / un-dims immediately — the swap itself settles when the server answers.
|
||||
C_Container.PickupContainerItem(bagId, button.slot)
|
||||
PickupContainerItem(bagId, button.slot)
|
||||
GameTooltip:Hide()
|
||||
BenillaBagWindow_Repaint(button:GetParent())
|
||||
elseif mouseButton == "RightButton" then
|
||||
@@ -154,7 +158,7 @@
|
||||
ClearCursor()
|
||||
BenillaBagWindow_Repaint(button:GetParent())
|
||||
else
|
||||
C_Container.UseContainerItem(bagId, button.slot)
|
||||
UseContainerItem(bagId, button.slot)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -169,11 +173,12 @@
|
||||
function BenillaBagSlot_OnEnter(button)
|
||||
if button.slot == nil then return end
|
||||
local bagId = button:GetParent():GetID()
|
||||
local info = C_Container.GetContainerItemInfo(bagId, button.slot)
|
||||
local texture, itemCount, locked, quality, readable =
|
||||
GetContainerItemInfo(bagId, button.slot)
|
||||
-- An emptied slot drops its tooltip (ref ContainerFrame_Update's texture-nil branch,
|
||||
-- ContainerFrame.lua:281-283) — without this the OnUpdate re-enter below would leave
|
||||
-- a stale tooltip up after the item is sold or moved out from under the hover.
|
||||
if not info then
|
||||
if not texture then
|
||||
if GameTooltip:IsOwned(button) then GameTooltip:Hide() end
|
||||
return
|
||||
end
|
||||
@@ -195,10 +200,10 @@
|
||||
-- loop). The ref's `IsControlKeyDown() and hasItem` magnifier arm (l.638) is the
|
||||
-- dress-up preview's own cursor tell — it landed with the dressing room (decision
|
||||
-- 1060); `info` is non-nil above, which is the ref's `hasItem`.
|
||||
if BenillaMerchantFrame and BenillaMerchantFrame:IsShown()
|
||||
and BenillaMerchantFrame.selectedTab == 1 and not InRepairMode() then
|
||||
if MerchantFrame and MerchantFrame:IsShown()
|
||||
and MerchantFrame.selectedTab == 1 and not InRepairMode() then
|
||||
ShowContainerSellCursor(bagId, button.slot)
|
||||
elseif info.isReadable or IsControlKeyDown() then
|
||||
elseif readable or IsControlKeyDown() then
|
||||
ShowInspectCursor()
|
||||
else
|
||||
ResetCursor()
|
||||
@@ -224,10 +229,10 @@
|
||||
end
|
||||
|
||||
-- The item's use-cooldown sweep (ref ContainerFrame_UpdateCooldown,
|
||||
-- ContainerFrame.lua:295-299, verbatim shape over our C_Container dialect).
|
||||
-- ContainerFrame.lua:295-299, verbatim shape over our container dialect).
|
||||
function BenillaBagSlot_UpdateCooldown(button, bagId)
|
||||
local cooldown = getglobal(button:GetName() .. "Cooldown")
|
||||
local start, duration, enable = C_Container.GetContainerItemCooldown(bagId, button.slot)
|
||||
local start, duration, enable = GetContainerItemCooldown(bagId, button.slot)
|
||||
CooldownFrame_SetTimer(cooldown, start, duration, enable)
|
||||
end
|
||||
|
||||
@@ -237,7 +242,7 @@
|
||||
-- (l.248-253: occupied → UpdateCooldown, empty → Cooldown:Hide()).
|
||||
function BenillaBagSlot_Update(button)
|
||||
local bagId = button:GetParent():GetID()
|
||||
local size = C_Container.GetContainerNumSlots(bagId)
|
||||
local size = GetContainerNumSlots(bagId)
|
||||
if button.physIndex > size then
|
||||
button.slot = nil
|
||||
button:Hide()
|
||||
@@ -245,19 +250,19 @@
|
||||
end
|
||||
button.slot = size - button.physIndex + 1
|
||||
button:Show()
|
||||
local info = C_Container.GetContainerItemInfo(bagId, button.slot)
|
||||
if info then
|
||||
local texture, itemCount, locked = GetContainerItemInfo(bagId, button.slot)
|
||||
if texture ~= nil or itemCount ~= nil then
|
||||
BenillaBagSlot_UpdateCooldown(button, bagId)
|
||||
button.icon:SetTexture(info.iconFileID or BENILLA_FALLBACK_ICON)
|
||||
button.icon:SetTexture(texture or BENILLA_FALLBACK_ICON)
|
||||
-- A locked slot (its item is up on the cursor, or a send is still pending
|
||||
-- server-side, decision 0216 §4/0218 §3) dims, the real client's drag feedback.
|
||||
if info.isLocked then
|
||||
if locked then
|
||||
button.icon:SetVertexColor(0.4, 0.4, 0.4)
|
||||
else
|
||||
button.icon:SetVertexColor(1, 1, 1)
|
||||
end
|
||||
if (info.stackCount or 1) > 1 then
|
||||
button.count:SetText(info.stackCount)
|
||||
if (itemCount or 1) > 1 then
|
||||
button.count:SetText(itemCount)
|
||||
else
|
||||
button.count:SetText("")
|
||||
end
|
||||
@@ -269,7 +274,7 @@
|
||||
end
|
||||
|
||||
-- The generic per-window repaint (decision 0216 slice 2): every slot up to this window's
|
||||
-- cap, the title (C_Container.GetBagName — dynamic since a live bag swap changes it), and the money
|
||||
-- cap, the title (GetBagName — dynamic since a live bag swap changes it), and the money
|
||||
-- purse (backpack only, ContainerFrame.lua l.314). BenillaBagFrame_Update (below) is the
|
||||
-- backpack's zero-arg convenience alias, kept for existing callers (UiPanels.xml's
|
||||
-- OpenBackpack/CloseBackpack, and the tests that patch it directly).
|
||||
@@ -292,7 +297,7 @@
|
||||
if bagId == KEYRING_CONTAINER then
|
||||
title:SetText(KEYRING)
|
||||
else
|
||||
title:SetText(C_Container.GetBagName(bagId) or "")
|
||||
title:SetText(GetBagName(bagId) or "")
|
||||
end
|
||||
end
|
||||
if bagId == 0 then
|
||||
@@ -305,7 +310,7 @@
|
||||
if portrait then
|
||||
portrait:SetPortraitToTexture("Interface\\ContainerFrame\\KeyRing-Bag-Icon")
|
||||
end
|
||||
local size = C_Container.GetContainerNumSlots(bagId)
|
||||
local size = GetContainerNumSlots(bagId)
|
||||
if size > 0 then BenillaBagWindow_FitBackground(frame, size) end
|
||||
else
|
||||
-- The window's portrait is the bag's OWN item icon (the real client's
|
||||
@@ -333,7 +338,7 @@
|
||||
end
|
||||
end
|
||||
-- Equipped bags snug-fit their stitched background to the live row count.
|
||||
local size = C_Container.GetContainerNumSlots(bagId)
|
||||
local size = GetContainerNumSlots(bagId)
|
||||
if size > 0 then BenillaBagWindow_FitBackground(frame, size) end
|
||||
end
|
||||
end
|
||||
@@ -505,7 +510,7 @@
|
||||
function BenillaOpenAllBags()
|
||||
for bagId = 0, 4 do
|
||||
-- The backpack always exists; an equipped-bag slot only when a bag is in it.
|
||||
if bagId == 0 or C_Container.GetContainerNumSlots(bagId) > 0 then
|
||||
if bagId == 0 or GetContainerNumSlots(bagId) > 0 then
|
||||
local frame = getglobal(BENILLA_BAG_WINDOWS[bagId + 1])
|
||||
if frame and not frame:IsShown() then
|
||||
BenillaBagWindow_Repaint(frame) -- bag 0 routes through its observable alias
|
||||
@@ -542,7 +547,7 @@
|
||||
-- MainMenuBarBackpackButton, bags 1..4 → CharacterBag(N-1)Slot).
|
||||
function BenillaBagWindow_BarButton(frame)
|
||||
local name = frame:GetName()
|
||||
if name == "BenillaKeyRingFrame" then return getglobal("BenillaKeyRingButton") end
|
||||
if name == "BenillaKeyRingFrame" then return getglobal("KeyRingButton") end
|
||||
for bagId = 0, 4 do
|
||||
if name == BENILLA_BAG_WINDOWS[bagId + 1] then
|
||||
if bagId == 0 then return getglobal("BenillaBagToggle") end
|
||||
@@ -653,7 +658,7 @@
|
||||
-- DEFERRAL: no drag-to-equip a bag onto the bar yet (that's the paper-doll drag arc, slice
|
||||
-- 3's scope, not this one).
|
||||
-- The bag bar (and the backpack toggle) are RELOCATED onto the action bar — anchored into
|
||||
-- BenillaActionBarArtFrame's bottom-right — which means they need seating one frame level
|
||||
-- MainMenuBarArtFrame's bottom-right — which means they need seating one frame level
|
||||
-- above the bar art or it paints over their icons. That lever lives with the frame it's
|
||||
-- about: BenillaActionBarArt_SeatAbove (ActionBar.xml, loaded before this file), which
|
||||
-- carries the full explanation; the micro-button row is its second caller. Called
|
||||
@@ -805,8 +810,14 @@
|
||||
-- nothing when your keyring is full. We print the string that exists.
|
||||
function PutKeyInKeyRing()
|
||||
for i = 1, GetKeyRingSize() do
|
||||
if not C_Container.GetContainerItemInfo(KEYRING_CONTAINER, i) then
|
||||
C_Container.PickupContainerItem(KEYRING_CONTAINER, i)
|
||||
-- Occupancy is `texture or itemCount`, not `texture` alone. The reference tests
|
||||
-- the texture (ContainerFrame_Update) because ITS client always has the icon by
|
||||
-- the time a slot is populated; ours can hold an item whose icon has not resolved
|
||||
-- yet, and reading that as an empty slot would file the key on top of it
|
||||
-- (decision 1199 — the transient our five-value shape exposed).
|
||||
local texture, itemCount = GetContainerItemInfo(KEYRING_CONTAINER, i)
|
||||
if texture == nil and itemCount == nil then
|
||||
PickupContainerItem(KEYRING_CONTAINER, i)
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -816,7 +827,7 @@
|
||||
end
|
||||
|
||||
-- ref KeyRingButton's OnLoad/OnClick/OnReceiveDrag (MainMenuBarBagButtons.xml l.142-199).
|
||||
-- The button is authored HIDDEN and only ever revealed by BenillaMainMenuBar_UpdateKeyRing
|
||||
-- The button is authored HIDDEN and only ever revealed by MainMenuBar_UpdateKeyRing
|
||||
-- (ActionBar.xml), which is where the whole show/hide law and its divergence note live.
|
||||
function BenillaKeyRingButton_OnLoad(button)
|
||||
button:SetID(KEYRING_CONTAINER)
|
||||
@@ -832,7 +843,7 @@
|
||||
end
|
||||
|
||||
function BenillaKeyRingButton_OnEvent()
|
||||
if BenillaMainMenuBar_UpdateKeyRing then BenillaMainMenuBar_UpdateKeyRing() end
|
||||
if MainMenuBar_UpdateKeyRing then MainMenuBar_UpdateKeyRing() end
|
||||
end
|
||||
|
||||
function BenillaKeyRingButton_OnClick(button)
|
||||
@@ -1141,7 +1152,7 @@
|
||||
</Texture>
|
||||
<!-- Title: the bag name, GameFontHighlight, 112×12 at the frame TOPLEFT (47,-10)
|
||||
(ref-ContainerFrame.xml l.120-131). BenillaBagWindow_Update keeps it current via
|
||||
C_Container.GetBagName(0), which always answers "Backpack" — the authored text is just the
|
||||
GetBagName(0), which always answers "Backpack" — the authored text is just the
|
||||
pre-first-Update placeholder. -->
|
||||
<FontString name="BenillaBagFrameName" inherits="GameFontHighlight" text="Backpack">
|
||||
<Size><AbsDimension x="112" y="12"/></Size>
|
||||
@@ -1555,13 +1566,13 @@
|
||||
(the real client's MainMenuBarBackpackButton, ref-MainMenuBarBagButtons.xml l.54-64): a 37x37
|
||||
button parented/anchored to the bar art frame at BOTTOMRIGHT (-6,2). Its behavior (toggle the
|
||||
backpack window) is unchanged; only its seat moved from the free-floating screen corner onto
|
||||
the bar. (BenillaActionBarArtFrame is defined in ActionBar.xml, which the loader loads before
|
||||
the bar. (MainMenuBarArtFrame is defined in ActionBar.xml, which the loader loads before
|
||||
this file; the anchor resolves by name.)
|
||||
-->
|
||||
<CheckButton name="BenillaBagToggle">
|
||||
<Size><AbsDimension x="37" y="37"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="BenillaActionBarArtFrame" relativePoint="BOTTOMRIGHT">
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="MainMenuBarArtFrame" relativePoint="BOTTOMRIGHT">
|
||||
<Offset><AbsDimension x="-6" y="2"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
@@ -1844,7 +1855,7 @@
|
||||
<!--
|
||||
The keyring button (decision 0765) — ref KeyRingButton, MainMenuBarBagButtons.xml l.142-199.
|
||||
Sits at the FAR left of the bag row, past BenillaBagBarSlot4 (the ref's CharacterBag3Slot),
|
||||
and is authored HIDDEN: nothing reveals it but BenillaMainMenuBar_UpdateKeyRing, and only
|
||||
and is authored HIDDEN: nothing reveals it but MainMenuBar_UpdateKeyRing, and only
|
||||
while HasKey() — the point being that a character with no key has no keyring at all, and the
|
||||
bar's plate has no socket for one.
|
||||
|
||||
@@ -1857,7 +1868,7 @@
|
||||
an $parentIcon layer, so Disable_BagButtons' SetDesaturation(b.icon) has nothing to grey —
|
||||
and the ref leaves it out of its own Disable_BagButtons for the same reason.
|
||||
-->
|
||||
<CheckButton name="BenillaKeyRingButton" hidden="true">
|
||||
<CheckButton name="KeyRingButton" hidden="true">
|
||||
<Size><AbsDimension x="18" y="39"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativeTo="BenillaBagBarSlot4" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
benilla's bank window (decision 0604 phase 4): the vault is already streamed (the 24 bank
|
||||
slots + 6 bank bags ride the descriptor at login, exactly like the backpack — see the
|
||||
decision's "pinned mechanism"), so this file is purely the window over the existing
|
||||
C_Container/cursor plumbing. Geometry/art below are QUOTED from the real `BankFrame.xml`
|
||||
container/cursor plumbing. Geometry/art below are QUOTED from the real `BankFrame.xml`
|
||||
(`WoW/_extracted_framexml/BankFrame.xml`/`.lua`, read this session — line-cited inline),
|
||||
turned onto OUR house idioms per decision 0068/0084: no `ItemButtonTemplate`/
|
||||
`SmallMoneyFrameTemplate`/`UIPanelButtonTemplate` ship, so every one of those is either a
|
||||
@@ -59,7 +59,8 @@
|
||||
BENILLA_BANK_ITEM_SLOTS = 24 -- NUM_BANKGENERIC_SLOTS (ref BankFrame.lua:3)
|
||||
BENILLA_BANK_BAG_SLOTS = 6 -- NUM_BANKBAGSLOTS (ref BankFrame.lua:4)
|
||||
|
||||
function BenillaBankFrame_OnLoad(frame)
|
||||
function BankFrame_OnLoad()
|
||||
local frame = this
|
||||
SetPortraitTexture(getglobal(frame:GetName() .. "Portrait"), "npc")
|
||||
frame:RegisterEvent("BANKFRAME_OPENED")
|
||||
frame:RegisterEvent("BANKFRAME_CLOSED")
|
||||
@@ -68,7 +69,7 @@
|
||||
frame:RegisterEvent("PLAYER_MONEY")
|
||||
-- Container -1 is a container like any other: the same lock/cooldown maintenance
|
||||
-- events the bag windows register (BagFrame.xml's BenillaBagFrame_OnLoad), handled
|
||||
-- below by BenillaBankFrame_OnEvent directly (not delegated — the shared window-level
|
||||
-- below by BankFrame_OnEvent directly (not delegated — the shared window-level
|
||||
-- repaint helper caps at BENILLA_BAG_WINDOW_MAX_SLOTS=20, short of the bank's 24).
|
||||
frame:RegisterEvent("ITEM_LOCK_CHANGED")
|
||||
frame:RegisterEvent("BAG_UPDATE_COOLDOWN")
|
||||
@@ -81,14 +82,14 @@
|
||||
if name == nil or name == "" then
|
||||
name = "Banker"
|
||||
end
|
||||
getglobal("BenillaBankFrameTitleText"):SetText(name)
|
||||
getglobal("BankFrameTitleText"):SetText(name)
|
||||
end
|
||||
|
||||
-- Repaint every one of the 24 generic slots (PLAYERBANKSLOTS_CHANGED names ONE slot; the
|
||||
-- spec allows repainting all 24 — cheap, and spares tracking a slot->button map).
|
||||
function BenillaBankFrame_RepaintItems()
|
||||
for i = 1, BENILLA_BANK_ITEM_SLOTS do
|
||||
local button = getglobal("BenillaBankItem" .. i)
|
||||
local button = getglobal("BankItem" .. i)
|
||||
if button then BenillaBagSlot_Update(button) end
|
||||
end
|
||||
end
|
||||
@@ -100,7 +101,7 @@
|
||||
function BenillaBankFrame_UpdateBagRow()
|
||||
local numSlots, full = GetNumBankSlots()
|
||||
for i = 1, BENILLA_BANK_BAG_SLOTS do
|
||||
local button = getglobal("BenillaBankBagButton" .. i)
|
||||
local button = getglobal("BankBagButton" .. i)
|
||||
if button then
|
||||
local texture = BenillaGetBankBagTexture(i)
|
||||
button.icon:SetTexture(texture or "Interface\\PaperDoll\\UI-PaperDoll-Slot-Bag")
|
||||
@@ -115,15 +116,15 @@
|
||||
end
|
||||
|
||||
local cost = GetBankSlotCost(numSlots)
|
||||
BenillaBankFrame.nextSlotCost = cost
|
||||
BenillaMoney_Set("BenillaBankFrameCostMoney", cost, true)
|
||||
BankFrame.nextSlotCost = cost
|
||||
BenillaMoney_Set("BankFrameCostMoney", cost, true)
|
||||
if GetMoney() >= cost then
|
||||
BenillaMoney_SetColor("BenillaBankFrameCostMoney", 1.0, 1.0, 1.0)
|
||||
BenillaMoney_SetColor("BankFrameCostMoney", 1.0, 1.0, 1.0)
|
||||
else
|
||||
BenillaMoney_SetColor("BenillaBankFrameCostMoney", 1.0, 0.1, 0.1)
|
||||
BenillaMoney_SetColor("BankFrameCostMoney", 1.0, 0.1, 0.1)
|
||||
end
|
||||
|
||||
local purchaseFrame = getglobal("BenillaBankFramePurchaseInfo")
|
||||
local purchaseFrame = getglobal("BankFramePurchaseInfo")
|
||||
if full then
|
||||
purchaseFrame:Hide()
|
||||
else
|
||||
@@ -131,7 +132,8 @@
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaBankFrame_OnEvent(frame)
|
||||
function BankFrame_OnEvent(event)
|
||||
local frame = this
|
||||
if event == "BANKFRAME_OPENED" then
|
||||
BenillaBankFrame_SetTitle(arg1)
|
||||
BenillaBankFrame_RepaintItems()
|
||||
@@ -144,7 +146,7 @@
|
||||
elseif event == "PLAYERBANKBAGSLOTS_CHANGED" then
|
||||
BenillaBankFrame_UpdateBagRow()
|
||||
elseif event == "PLAYER_MONEY" then
|
||||
BenillaMoney_Set("BenillaBankFrameMoney", GetMoney())
|
||||
BenillaMoney_Set("BankFrameMoney", GetMoney())
|
||||
BenillaBankFrame_UpdateBagRow() -- the cost coins' afford color can flip too
|
||||
elseif event == "ITEM_LOCK_CHANGED" then
|
||||
if arg1 == -1 then
|
||||
@@ -159,15 +161,15 @@
|
||||
|
||||
function BenillaBankFrame_CloseButton_OnClick()
|
||||
CloseBankFrame()
|
||||
HideUIPanel(getglobal("BenillaBankFrame"))
|
||||
HideUIPanel(getglobal("BankFrame"))
|
||||
end
|
||||
|
||||
-- CloseBankBagFrames (ref BankFrame.lua:119-123), over our popout naming.
|
||||
function BenillaCloseBankBagFrames()
|
||||
function CloseBankBagFrames()
|
||||
for j = 1, BENILLA_BANK_BAG_SLOTS do
|
||||
local frame = getglobal("BenillaBankBagFrame" .. j)
|
||||
if frame and frame:IsShown() then frame:Hide() end
|
||||
local button = getglobal("BenillaBankBagButton" .. j)
|
||||
local button = getglobal("BankBagButton" .. j)
|
||||
if button then button:SetChecked(nil) end
|
||||
end
|
||||
end
|
||||
@@ -204,7 +206,7 @@
|
||||
-- Only a slot actually holding a bag opens its popout — the reference's ToggleBag on
|
||||
-- a bagless (or unpurchased) slot is a no-op beyond the click sound; the container
|
||||
-- feed reports 0 slots exactly when no bag object sits in the slot.
|
||||
if C_Container.GetContainerNumSlots(4 + id) == 0 then
|
||||
if GetContainerNumSlots(4 + id) == 0 then
|
||||
button:SetChecked(nil)
|
||||
return
|
||||
end
|
||||
@@ -272,7 +274,7 @@
|
||||
</Scripts>
|
||||
</CheckButton>
|
||||
|
||||
<Frame name="BenillaBankFrame" id="-1" hidden="true">
|
||||
<Frame name="BankFrame" id="-1" hidden="true">
|
||||
<!-- The real 384×512 window (ref l.77-90). No <Anchors> — ShowUIPanel/SetLeftFrame SetPoints
|
||||
this to the left slot on open, the same NPC-session convention as Merchant/Trainer/Mail.
|
||||
id="-1" = BANK_CONTAINER: the 24 item buttons below read it via GetParent():GetID(), the
|
||||
@@ -282,7 +284,7 @@
|
||||
<Layer level="BACKGROUND">
|
||||
<!-- NPC portrait 60×60 TOPLEFT (7,-6) (ref l.93-104), bound in OnLoad via
|
||||
SetPortraitTexture(region, "npc") — the Merchant/Trainer/Gossip convention. -->
|
||||
<Texture name="BenillaBankFramePortrait">
|
||||
<Texture name="BankFramePortrait">
|
||||
<Size><AbsDimension x="60" y="60"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="7" y="-6"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
@@ -296,7 +298,7 @@
|
||||
</Texture>
|
||||
<!-- Title: the banker's name (ref BankFrameTitleText l.115-123, GameFontHighlight,
|
||||
CENTER (6,233)); "Banker" until BANKFRAME_OPENED's arg1 lands. -->
|
||||
<FontString name="BenillaBankFrameTitleText" inherits="GameFontHighlight" justifyH="CENTER" text="Banker">
|
||||
<FontString name="BankFrameTitleText" inherits="GameFontHighlight" justifyH="CENTER" text="Banker">
|
||||
<Anchors><Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="6" y="233"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<!-- Section labels (ref l.124-141: ITEMSLOTTEXT/BAGSLOTTEXT, GameFontNormal, CENTER
|
||||
@@ -312,7 +314,7 @@
|
||||
<Frames>
|
||||
<!-- Close X: the house minimize-button convention (Merchant/Trainer/Mail), TOPRIGHT
|
||||
(-30,-8) — see the header's deviation note on the reference's own offset. -->
|
||||
<Button name="BenillaBankFrameCloseButton">
|
||||
<Button name="BankFrameCloseButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors><Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-30" y="-8"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Up"/>
|
||||
@@ -325,100 +327,100 @@
|
||||
TOPLEFT(40,-73), 12px column gap, -7px row gap — QUOTED verbatim. Each is a
|
||||
BenillaBagSlotTemplate button (BagFrame.xml) read against container -1; physIndex
|
||||
is `25 - i` so button i resolves to slot i (see the header's deviation note). -->
|
||||
<Button name="BenillaBankItem1" inherits="BenillaBagSlotTemplate">
|
||||
<Button name="BankItem1" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="40" y="-73"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 24)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem2" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem1" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem2" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem1" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 23)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem3" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem2" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem3" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem2" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 22)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem4" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem3" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem4" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem3" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 21)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem5" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem4" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem5" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem4" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 20)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem6" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem5" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem6" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem5" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 19)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem7" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem7" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 18)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem8" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem7" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem8" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem7" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 17)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem9" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem8" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem9" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem8" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 16)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem10" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem9" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem10" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem9" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 15)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem11" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem10" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem11" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem10" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 14)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem12" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem11" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem12" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem11" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 13)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem13" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem13" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 12)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem14" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem13" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem14" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem13" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 11)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem15" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem14" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem15" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem14" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 10)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem16" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem15" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem16" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem15" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 9)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem17" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem16" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem17" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem16" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 8)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem18" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem17" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem18" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem17" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 7)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem19" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem13" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem19" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem13" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 6)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem20" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem19" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem20" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem19" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 5)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem21" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem20" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem21" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem20" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 4)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem22" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem21" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem22" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem21" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 3)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem23" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem22" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem23" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem22" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 2)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaBankItem24" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem23" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="BankItem24" inherits="BenillaBagSlotTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem23" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 1)</OnLoad></Scripts>
|
||||
</Button>
|
||||
|
||||
@@ -426,28 +428,28 @@
|
||||
column gap. Button id = the bag-button number (1..6, what BankButtonIDToInvSlotID
|
||||
and BenillaGetBankBagTexture expect) — NOT the container id (5..10), which the
|
||||
popout windows below carry instead via their own `id=`. -->
|
||||
<CheckButton name="BenillaBankBagButton1" inherits="BenillaBankBagButtonTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankItem19" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-32"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="BankBagButton1" inherits="BenillaBankBagButtonTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankItem19" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-32"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBankBagButton_OnLoad(self, 1)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaBankBagButton2" inherits="BenillaBankBagButtonTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankBagButton1" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="BankBagButton2" inherits="BenillaBankBagButtonTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankBagButton1" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBankBagButton_OnLoad(self, 2)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaBankBagButton3" inherits="BenillaBankBagButtonTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankBagButton2" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="BankBagButton3" inherits="BenillaBankBagButtonTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankBagButton2" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBankBagButton_OnLoad(self, 3)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaBankBagButton4" inherits="BenillaBankBagButtonTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankBagButton3" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="BankBagButton4" inherits="BenillaBankBagButtonTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankBagButton3" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBankBagButton_OnLoad(self, 4)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaBankBagButton5" inherits="BenillaBankBagButtonTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankBagButton4" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="BankBagButton5" inherits="BenillaBankBagButtonTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankBagButton4" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBankBagButton_OnLoad(self, 5)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaBankBagButton6" inherits="BenillaBankBagButtonTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBankBagButton5" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="BankBagButton6" inherits="BenillaBankBagButtonTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BankBagButton5" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaBankBagButton_OnLoad(self, 6)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
|
||||
@@ -455,46 +457,46 @@
|
||||
window (-15,-105). House coin rig (BenillaMoney_Set) in place of the reference's
|
||||
SmallMoneyFrameTemplate; the Purchase button is a local UI-Panel-Button redraw (no
|
||||
UIPanelButtonTemplate ships — the house convention every NPC window uses). -->
|
||||
<Frame name="BenillaBankFramePurchaseInfo">
|
||||
<Frame name="BankFramePurchaseInfo">
|
||||
<Size><AbsDimension x="300" y="72"/></Size>
|
||||
<Anchors><Anchor point="CENTER" relativeTo="BenillaBankFrame" relativePoint="CENTER"><Offset><AbsDimension x="-15" y="-105"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="CENTER" relativeTo="BankFrame" relativePoint="CENTER"><Offset><AbsDimension x="-15" y="-105"/></Offset></Anchor></Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<FontString inherits="GameFontHighlight" text="Do you wish to purchase space for an additional bag?" justifyH="LEFT">
|
||||
<Size><AbsDimension x="280" y="0"/></Size>
|
||||
<Anchors><Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="10" y="20"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<FontString name="BenillaBankFrameCostLabel" inherits="GameFontNormal" text="Cost:" justifyH="LEFT">
|
||||
<FontString name="BankFrameCostLabel" inherits="GameFontNormal" text="Cost:" justifyH="LEFT">
|
||||
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="30" y="-10"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Frame name="BenillaBankFrameCostMoneyCoin1">
|
||||
<Frame name="BankFrameCostMoneyCoin1">
|
||||
<Size><AbsDimension x="13" y="13"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaBankFrameCostLabel" relativePoint="RIGHT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BankFrameCostLabel" relativePoint="RIGHT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor></Anchors>
|
||||
<Layers><Layer level="OVERLAY">
|
||||
<Texture name="$parentIcon" file="Interface\MoneyFrame\UI-MoneyIcons"><Size><AbsDimension x="13" y="13"/></Size><Anchors><Anchor point="RIGHT" relativePoint="RIGHT"/></Anchors><TexCoords left="0" right="0.25" top="0" bottom="1"/></Texture>
|
||||
<FontString name="$parentNum" inherits="NumberFontNormal" justifyH="RIGHT"><Size><AbsDimension x="20" y="13"/></Size><Anchors><Anchor point="RIGHT" relativePoint="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor></Anchors></FontString>
|
||||
</Layer></Layers>
|
||||
</Frame>
|
||||
<Frame name="BenillaBankFrameCostMoneyCoin2">
|
||||
<Frame name="BankFrameCostMoneyCoin2">
|
||||
<Size><AbsDimension x="13" y="13"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaBankFrameCostMoneyCoin1" relativePoint="RIGHT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BankFrameCostMoneyCoin1" relativePoint="RIGHT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor></Anchors>
|
||||
<Layers><Layer level="OVERLAY">
|
||||
<Texture name="$parentIcon" file="Interface\MoneyFrame\UI-MoneyIcons"><Size><AbsDimension x="13" y="13"/></Size><Anchors><Anchor point="RIGHT" relativePoint="RIGHT"/></Anchors><TexCoords left="0.25" right="0.5" top="0" bottom="1"/></Texture>
|
||||
<FontString name="$parentNum" inherits="NumberFontNormal" justifyH="RIGHT"><Size><AbsDimension x="20" y="13"/></Size><Anchors><Anchor point="RIGHT" relativePoint="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor></Anchors></FontString>
|
||||
</Layer></Layers>
|
||||
</Frame>
|
||||
<Frame name="BenillaBankFrameCostMoneyCoin3">
|
||||
<Frame name="BankFrameCostMoneyCoin3">
|
||||
<Size><AbsDimension x="13" y="13"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaBankFrameCostMoneyCoin2" relativePoint="RIGHT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BankFrameCostMoneyCoin2" relativePoint="RIGHT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor></Anchors>
|
||||
<Layers><Layer level="OVERLAY">
|
||||
<Texture name="$parentIcon" file="Interface\MoneyFrame\UI-MoneyIcons"><Size><AbsDimension x="13" y="13"/></Size><Anchors><Anchor point="RIGHT" relativePoint="RIGHT"/></Anchors><TexCoords left="0.5" right="0.75" top="0" bottom="1"/></Texture>
|
||||
<FontString name="$parentNum" inherits="NumberFontNormal" justifyH="RIGHT"><Size><AbsDimension x="20" y="13"/></Size><Anchors><Anchor point="RIGHT" relativePoint="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor></Anchors></FontString>
|
||||
</Layer></Layers>
|
||||
</Frame>
|
||||
<Button name="BenillaBankFramePurchaseButton" text="Purchase">
|
||||
<Button name="BankFramePurchaseButton" text="Purchase">
|
||||
<Size><AbsDimension x="124" y="21"/></Size>
|
||||
<Anchors><Anchor point="RIGHT" relativePoint="RIGHT"><Offset><AbsDimension x="-20" y="-10"/></Offset></Anchor></Anchors>
|
||||
<NormalFont inherits="GameFontNormal"/>
|
||||
@@ -512,7 +514,7 @@
|
||||
<!-- The player's own money (ref BankFrameMoneyFrame, l.501-509: BOTTOMRIGHT(-38,102)).
|
||||
Same right-to-left coin chain as MerchantFrame's purse — copper-first, right-flush
|
||||
off Coin1 (BenillaMoney_Set's own module doc, MerchantFrame.xml). -->
|
||||
<Frame name="BenillaBankFrameMoneyCoin1">
|
||||
<Frame name="BankFrameMoneyCoin1">
|
||||
<Size><AbsDimension x="13" y="13"/></Size>
|
||||
<Anchors><Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-51" y="102"/></Offset></Anchor></Anchors>
|
||||
<Layers><Layer level="OVERLAY">
|
||||
@@ -520,17 +522,17 @@
|
||||
<FontString name="$parentNum" inherits="NumberFontNormal" justifyH="RIGHT"><Size><AbsDimension x="20" y="13"/></Size><Anchors><Anchor point="RIGHT" relativePoint="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor></Anchors></FontString>
|
||||
</Layer></Layers>
|
||||
</Frame>
|
||||
<Frame name="BenillaBankFrameMoneyCoin2">
|
||||
<Frame name="BankFrameMoneyCoin2">
|
||||
<Size><AbsDimension x="13" y="13"/></Size>
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BenillaBankFrameMoneyCoin1" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BankFrameMoneyCoin1" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor></Anchors>
|
||||
<Layers><Layer level="OVERLAY">
|
||||
<Texture name="$parentIcon" file="Interface\MoneyFrame\UI-MoneyIcons"><Size><AbsDimension x="13" y="13"/></Size><Anchors><Anchor point="RIGHT" relativePoint="RIGHT"/></Anchors><TexCoords left="0.25" right="0.5" top="0" bottom="1"/></Texture>
|
||||
<FontString name="$parentNum" inherits="NumberFontNormal" justifyH="RIGHT"><Size><AbsDimension x="20" y="13"/></Size><Anchors><Anchor point="RIGHT" relativePoint="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor></Anchors></FontString>
|
||||
</Layer></Layers>
|
||||
</Frame>
|
||||
<Frame name="BenillaBankFrameMoneyCoin3">
|
||||
<Frame name="BankFrameMoneyCoin3">
|
||||
<Size><AbsDimension x="13" y="13"/></Size>
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BenillaBankFrameMoneyCoin2" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BankFrameMoneyCoin2" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor></Anchors>
|
||||
<Layers><Layer level="OVERLAY">
|
||||
<Texture name="$parentIcon" file="Interface\MoneyFrame\UI-MoneyIcons"><Size><AbsDimension x="13" y="13"/></Size><Anchors><Anchor point="RIGHT" relativePoint="RIGHT"/></Anchors><TexCoords left="0" right="0.25" top="0" bottom="1"/></Texture>
|
||||
<FontString name="$parentNum" inherits="NumberFontNormal" justifyH="RIGHT"><Size><AbsDimension x="20" y="13"/></Size><Anchors><Anchor point="RIGHT" relativePoint="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor></Anchors></FontString>
|
||||
@@ -538,14 +540,14 @@
|
||||
</Frame>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaBankFrame_OnLoad(self)</OnLoad>
|
||||
<OnEvent>BenillaBankFrame_OnEvent(self)</OnEvent>
|
||||
<OnLoad>BankFrame_OnLoad()</OnLoad>
|
||||
<OnEvent>BankFrame_OnEvent(event)</OnEvent>
|
||||
<!-- OnShow/OnHide (ref l.518-526): OpenBackpack/CloseBackpack per the 0561 contract
|
||||
(MerchantFrame.xml's own placement is the model); the reference OnHide order
|
||||
(CloseBankBagFrames(); CloseBankFrame(); PlaySound(...)) kept exactly, with
|
||||
CloseBackpack folded in at the Merchant convention's spot. -->
|
||||
<OnShow>OpenBackpack(); PlaySound("igMainMenuOpen")</OnShow>
|
||||
<OnHide>BenillaCloseBankBagFrames(); CloseBankFrame(); CloseBackpack(); PlaySound("igMainMenuClose")</OnHide>
|
||||
<OnHide>CloseBankBagFrames(); CloseBankFrame(); CloseBackpack(); PlaySound("igMainMenuClose")</OnHide>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
<Ui>
|
||||
<!--
|
||||
Interface\FrameXML\BasicControls.xml, transcribed — the reference's second file, right
|
||||
after Fonts.xml, and one of the smallest in FrameXML. It carries four things an addon can
|
||||
reach and we had none of:
|
||||
|
||||
TEXT(text) the identity function 1.12 wraps every displayed string in. **26 corpus
|
||||
addons call it**, because half the shipped FrameXML does and they copied
|
||||
the idiom. Missing, it is `attempt to call global 'TEXT'`.
|
||||
message(text) the client's "show the player this" dialog. Its own popup, not a chat
|
||||
line. Present in 64 corpus files — but read the line before ranking it
|
||||
(1207): 55 of those are copies of `Dewdrop-2.0.lua`, and the genuine
|
||||
callers are about ten addons.
|
||||
_ERRORMESSAGE what `message` is implemented in terms of, and the reference's default
|
||||
error handler. Two corpus addons replace it outright (ImprovedErrorFrame
|
||||
is one), which they cannot do to a name that does not exist.
|
||||
ScriptErrors the dialog both of the above put text into, plus DialogBoxFrame and the
|
||||
DialogButton*Texture trio it is built from — three of which the corpus
|
||||
also inherits from directly (`inherits_demand`, 2 addons each).
|
||||
|
||||
The three fonts this needs — GameFontRedLarge, DialogButtonNormalText,
|
||||
DialogButtonHighlightText — are already in Fonts.xml, so this file is self-contained
|
||||
against the one that loads before it, exactly as it is in the reference's own toc order.
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
-- ── _ERRORMESSAGE / message (ref BasicControls.xml l.5-15) ─────────────────────────────
|
||||
--
|
||||
-- VERBATIM, including `debuginfo()`. That line was guarded here when this file landed,
|
||||
-- because `debuginfo` was an engine global we lacked and whose behaviour was uncarved —
|
||||
-- and declaring a no-op under a real 1.12 name is exactly the "capability absent without a
|
||||
-- failure" class 1203 named. wow-re has since carved it (2026-08-11): `0x7027f0` is
|
||||
-- `33 c0 c3` — `xor eax,eax; ret`, three bytes, no call, no memory write. It genuinely
|
||||
-- does nothing and returns nothing, and five of its `debug*` siblings are byte-identical.
|
||||
-- So the no-op in `stdlib.rs` is the REFERENCE's no-op, the guard was measuring our own
|
||||
-- ignorance rather than a real branch, and it is gone.
|
||||
function _ERRORMESSAGE(msg)
|
||||
debuginfo()
|
||||
if not ScriptErrors:IsVisible() then
|
||||
ScriptErrors_Message:SetText(msg)
|
||||
ScriptErrors:Show()
|
||||
end
|
||||
return msg
|
||||
end
|
||||
|
||||
function message(text)
|
||||
_ERRORMESSAGE(text)
|
||||
end
|
||||
|
||||
-- ── AND ONE LINE OF THE REFERENCE DELIBERATELY NOT TRANSCRIBED ─────────────────────────
|
||||
-- The reference ends this block with `seterrorhandler(_ERRORMESSAGE);`. We do not.
|
||||
--
|
||||
-- benilla's default handler is `__benilla_script_error` (decision 1195), which pushes into
|
||||
-- `UiScript::errors()` — the channel the corpus harness's whole blocker table is built on,
|
||||
-- and the only place a Lua fault is visible to any instrument we own. Installing
|
||||
-- `_ERRORMESSAGE` would route every runtime error into a modal dialog instead and blind
|
||||
-- `blockers`, `demand` and every A/B that depends on them, to buy a dialog nobody has asked
|
||||
-- for yet.
|
||||
--
|
||||
-- The addon-facing behaviour is unchanged either way: `geterrorhandler()` still answers a
|
||||
-- callable (the 1195 pair), `geterrorhandler()(msg)` still reports, and an addon that
|
||||
-- installs its OWN handler with `seterrorhandler` still wins outright. What differs is only
|
||||
-- which handler is installed when nobody has chosen — and that is ours to choose while the
|
||||
-- error channel is load-bearing for the arc's instruments. Revisit when a player-facing
|
||||
-- Lua-error surface exists; the one line is the whole change.
|
||||
|
||||
-- ── TEXT (ref l.22-26, verbatim) ──────────────────────────────────────────────────────
|
||||
-- "This function is used to translate text (actually it's already translated as-is)" is
|
||||
-- the reference's own comment. It really is the identity function: 1.12 shipped one
|
||||
-- localisation per client and the hook was never used.
|
||||
function TEXT(text)
|
||||
return text
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
<!-- The dialog button faces (ref l.28-37). Same three-piece art as UIPanelButtonTemplate's,
|
||||
with their own names because the reference's dialogs inherit these directly. -->
|
||||
<Texture name="DialogButtonNormalTexture" file="Interface\Buttons\UI-Panel-Button-Up" virtual="true">
|
||||
<TexCoords left="0" right="0.625" top="0" bottom="0.6875"/>
|
||||
</Texture>
|
||||
<Texture name="DialogButtonPushedTexture" file="Interface\Buttons\UI-Panel-Button-Down" virtual="true">
|
||||
<TexCoords left="0" right="0.625" top="0" bottom="0.6875"/>
|
||||
</Texture>
|
||||
<Texture name="DialogButtonHighlightTexture" file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD" virtual="true">
|
||||
<TexCoords left="0" right="0.625" top="0" bottom="0.6875"/>
|
||||
</Texture>
|
||||
|
||||
<!-- DialogBoxFrame (ref l.38-77): a centered tooltip-bordered box with one OKAY button that
|
||||
hides its parent. Virtual — ScriptErrors below is the only thing in shipped FrameXML that
|
||||
inherits it, but two corpus addons inherit it too. -->
|
||||
<Frame name="DialogBoxFrame" virtual="true" toplevel="true" frameStrata="DIALOG" hidden="true">
|
||||
<Anchors>
|
||||
<Anchor point="CENTER"/>
|
||||
</Anchors>
|
||||
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
|
||||
<EdgeSize><AbsValue val="32"/></EdgeSize>
|
||||
<TileSize><AbsValue val="32"/></TileSize>
|
||||
<BackgroundInsets><AbsInset left="11" right="12" top="12" bottom="11"/></BackgroundInsets>
|
||||
</Backdrop>
|
||||
<Frames>
|
||||
<Button name="$parentButton">
|
||||
<Size><AbsDimension x="128" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="16"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
this:GetParent():Hide()
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
<NormalTexture inherits="DialogButtonNormalTexture"/>
|
||||
<PushedTexture inherits="DialogButtonPushedTexture"/>
|
||||
<HighlightTexture inherits="DialogButtonHighlightTexture"/>
|
||||
<ButtonText text="OKAY"/>
|
||||
<NormalFont inherits="DialogButtonNormalText"/>
|
||||
<HighlightFont inherits="DialogButtonHighlightText"/>
|
||||
</Button>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- "This is the general scripting error dialog" (ref l.79-100), verbatim. -->
|
||||
<Frame name="ScriptErrors" inherits="DialogBoxFrame">
|
||||
<Size><AbsDimension x="384" y="128"/></Size>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="ScriptErrors_Message" inherits="GameFontRedLarge" justifyH="CENTER" justifyV="TOP" nonspacewrap="true">
|
||||
<Size><AbsDimension x="360" y="0"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP"><Offset><AbsDimension x="0" y="-16"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Frame>
|
||||
</Ui>
|
||||
@@ -1,56 +1,110 @@
|
||||
<Ui>
|
||||
<!--
|
||||
The player buff/debuff bar (decisions 0255/0257 — the player aura arc). Geometry, textures,
|
||||
colours, and every timing constant are QUOTED from the real 1.12 BuffFrame (extracted
|
||||
Interface\FrameXML\BuffFrame.xml/.lua), turned onto benilla's own aura feed. Same shape as
|
||||
UnitFrames.xml: the reference's data path was two 1.12-only calls (GetPlayerBuff +
|
||||
GetPlayerBuffTexture/…Applications/…DispelType/…TimeLeft) that we did NOT implement; our
|
||||
engine exposes the one Era aura function instead (decision 0068), so the handlers here read
|
||||
UnitBuff/UnitDebuff and the countdown rides GetTime(), exactly as the cast bar does.
|
||||
The player buff/debuff bar and the temporary-weapon-enchant row (decisions 0255/0257 — the
|
||||
player aura arc). Geometry, textures, colours, every timing constant AND now the data path
|
||||
are the real 1.12 BuffFrame's (extracted Interface\FrameXML\BuffFrame.xml/.lua).
|
||||
|
||||
SCOPE (decision 0255): the LOCAL PLAYER only. The reference's temporary-weapon-enchant frame
|
||||
(TemporaryEnchantFrame / TempEnchant1-2) is deliberately out — there is no weapon-enchant wire
|
||||
yet — so the bar is anchored to the reference's *no-enchant* steady state: 16 buff buttons
|
||||
(two rows of 8) above 8 debuff buttons, right-aligned under the minimap at UIParent TOPRIGHT
|
||||
(-175,-13), the point the reference's BuffFrame snaps to when no temp enchant is present
|
||||
(BuffFrame_Enchant_OnUpdate's no-enchant branch).
|
||||
THE INDEX SPACE — the thing to understand before touching a handler. There are TWO numbers
|
||||
here and they are not the same number:
|
||||
|
||||
DURATIONS ARE A SETTING NOW (decision 1139). 0255 shipped the durations-shown geometry
|
||||
* a button's `id` is a 0-BASED ORDINAL WITHIN ITS OWN FILTER. BuffButton0..15 are
|
||||
id="0".."15" under HELPFUL; BuffButton16..23 are id="0".."7" under HARMFUL. It is a
|
||||
loop counter and nothing else.
|
||||
* `GetPlayerBuff(id, filter)` turns that ordinal into a CACHE POSITION — the aura's
|
||||
physical index in the player's display cache — plus `untilCancelled`. **Every** sibling
|
||||
verb (GetPlayerBuffTexture/…DispelType/…Applications/…TimeLeft, CancelPlayerBuff,
|
||||
GameTooltip:SetPlayerBuff) consumes that position, never the button's id.
|
||||
|
||||
So a button is a two-step: enumerate to a position on the event, then read the position. The
|
||||
position is ABSOLUTE — the number a "HARMFUL" walk hands back is the number an unfiltered
|
||||
walk would — which is why a debuff button's handle can never resolve against the buff list,
|
||||
structurally, with no filter argument threaded through the read path. A miss is `-1`, never
|
||||
nil, and the accessors all tolerate `-1` directly (byte-verified; the family's signature
|
||||
table and the traps are `benilla_ui::script::aura`'s module header).
|
||||
|
||||
This file used to read the Era `UnitBuff`/`UnitDebuff` pair because the 1.12 verbs did not
|
||||
exist. They landed in b2ede294, so the adaptation is gone: the handlers below are the
|
||||
reference's, on the reference's numbers.
|
||||
|
||||
SCOPE. The LOCAL PLAYER's auras, plus the reference's two temporary-weapon-enchant slots.
|
||||
0255 left TemporaryEnchantFrame out ("there is no weapon-enchant wire yet"); there is —
|
||||
`ITEM_FIELD_ENCHANTMENT`'s TEMP slot for the charges and `SMSG_ITEM_ENCHANT_TIME_UPDATE`
|
||||
for the countdown, both already feeding the item tooltip (decisions 0915/0920) — so
|
||||
`GetWeaponEnchantInfo` reads them and the row is here. The bar's resting place is
|
||||
unchanged: right-aligned under the minimap at UIParent TOPRIGHT (-175,-13), which is where
|
||||
BuffFrame_Enchant_OnUpdate's no-enchant branch parks it. With an enchant equipped the TOP
|
||||
buff row alone slides left to clear the enchant icons (BuffFrame re-anchors to
|
||||
TemporaryEnchantFrame's TOPLEFT); rows 2 and 3 hang off TempEnchant1/TemporaryEnchantFrame
|
||||
and do not move. That is the reference's behaviour, not a choice made here.
|
||||
|
||||
DURATIONS ARE A SETTING (decision 1139). 0255 shipped the durations-shown geometry
|
||||
unconditionally — the director's call, and there was no panel to hang a switch on — and said
|
||||
in as many words that "if a CVar system later arrives, the geometry is already the branch it
|
||||
would select". It arrived, as the saved-variable store rather than a CVar (1134): the store
|
||||
below is SHOW_BUFF_DURATIONS, the Interface page's Buff Durations row drives it, and BOTH of
|
||||
the reference's geometries are live again — rows at y = 0/−45/−90 with timers, 0/−35/−70
|
||||
without (BenillaBuffButtons_UpdatePositions). Our default is "1", the reference's "0": the
|
||||
without (BuffButtons_UpdatePositions). Our default is "1", the reference's "0": the
|
||||
director's look is what the bar comes up wearing, and what Defaults walks it back to.
|
||||
|
||||
ORDER IS STATE. The list UnitBuff/UnitDebuff enumerate is the app's insertion-ordered display
|
||||
cache (crate::ui_aura, decision 0257): a new aura appends at the end, a dropped one repacks —
|
||||
NOT ascending slot. Button N simply shows the Nth buff/debuff of that list; the bar's identity
|
||||
is the app's, not this file's.
|
||||
ORDER IS STATE. The player's list the app pushes (crate::ui_aura, decision 0257) IS the
|
||||
reference's insertion-ordered display cache: a new aura appends at the end, a dropped one
|
||||
repacks — NOT ascending slot. That is precisely why a "cache position" needs no structure of
|
||||
its own on our side; position N of that list is position N of the reference's cache.
|
||||
|
||||
DURATION / PERMANENCE. expirationTime (UnitBuff's 6th return) is 0 for a permanent aura — the
|
||||
1.12 wire sends no duration packet for one (byte-verified, decision 0257), which is exactly the
|
||||
reference's "until cancelled". So `expirationTime > 0` is our untilCancelled test: a timed aura
|
||||
counts down and flashes under 31s; a permanent one shows no timer and never flashes.
|
||||
DURATION / PERMANENCE is `untilCancelled`, GetPlayerBuff's second return — NOT
|
||||
"expirationTime is 0", which is what this file tested before the verbs landed. They are
|
||||
different questions and the difference is visible: untilCancelled is derived from the
|
||||
SPELL (SpellDuration.dbc's infinite row, plus the area-aura override —
|
||||
`benilla::ui_aura::until_cancelled`, byte-verified at 0x4e452e-0x4e45c5), so it is right on
|
||||
the frame an aura appears, BEFORE any duration packet has landed. An aura that really is
|
||||
timed but whose duration has not arrived yet therefore behaves as the reference does — it
|
||||
counts from "0 s" and pulses for the frame or two before the stamp joins — instead of
|
||||
silently showing no timer at all, which is what an expirationTime test cannot tell apart
|
||||
from a permanent aura.
|
||||
|
||||
THE COUNTDOWN IS A POLL, NOT A CACHE. The aura EVENT is the discrete-change edge (icon, stacks,
|
||||
dispel class); the remaining time is re-read from UnitBuff/UnitDebuff in OnUpdate, every frame,
|
||||
by every visible button. That is the reference's own division of labour — BuffButton_Update
|
||||
caches the buff INDEX on PLAYER_AURAS_CHANGED, BuffButton_OnUpdate calls GetPlayerBuffTimeLeft
|
||||
with it each frame (ref BuffFrame.lua l.128-130) — and caching the expiry on the event instead
|
||||
is what made refreshed buffs freeze at "0 s" and freshly cast ones show no timer (0846).
|
||||
THE COUNTDOWN IS A POLL, NOT A CACHE. The aura EVENT is the discrete-change edge (icon,
|
||||
stacks, dispel class); the remaining time is re-read every frame by every visible button.
|
||||
That is the reference's own division of labour and it is now literally its code —
|
||||
BuffButton_Update caches the buff POSITION on PLAYER_AURAS_CHANGED, BuffButton_OnUpdate
|
||||
calls GetPlayerBuffTimeLeft with it each frame (ref BuffFrame.lua l.128-130) — and caching
|
||||
the expiry on the event instead is what made refreshed buffs freeze at "0 s" and freshly
|
||||
cast ones show no timer (decision 0846).
|
||||
|
||||
RIGHT-CLICK CANCEL. RegisterForClicks("RightButtonUp") + CancelUnitBuff (the Era name for the
|
||||
reference's CancelPlayerBuff) — resolved within the button's own filter so a debuff button's id
|
||||
never resolves against the buff list. The server cancels by spell id and refuses anything the
|
||||
wire's cancelable bit didn't allow, so a debuff right-click is a silent no-op, as in 1.12.
|
||||
RIGHT-CLICK CANCEL. RegisterForClicks("RightButtonUp") + CancelPlayerBuff(this.buffIndex).
|
||||
The server cancels by spell id and refuses anything the wire's cancelable bit didn't allow,
|
||||
so a debuff right-click is a silent no-op, as in 1.12. `CancelUnitBuff` (the Era name) is
|
||||
the same verb over the same cache behind the same gate and the same CMSG_CANCEL_AURA queue;
|
||||
this file simply no longer needs the Era spelling.
|
||||
|
||||
DEVIATIONS from the reference .lua (noted, not bugs):
|
||||
DEVIATIONS from the reference .lua/.xml (noted, not bugs):
|
||||
- The count FontString is BLANKED ("") rather than hidden — an empty string draws nothing,
|
||||
same look. A site that predates region Show/Hide; equivalent either way. (The DURATION
|
||||
strings used the same shortcut until 1139 and no longer do: a hidden setting has to hide
|
||||
a region, not write an empty string into a visible one.)
|
||||
- The dispel-class lookup sits INSIDE the `if (debuffSlot)` guard. The reference runs
|
||||
`GetPlayerBuffDispelType(GetPlayerBuff(this:GetID(), "HARMFUL"))` for all 24 buttons and
|
||||
then throws the answer away for the 16 helpful ones, which have no $parentBorder to tint.
|
||||
Provably invisible (the value's only consumer is that SetVertexColor), and it drops 16
|
||||
cache walks per aura event.
|
||||
- The colour is `DebuffTypeColor[debuffType] or DebuffTypeColor["none"]` where the
|
||||
reference branches on `if (debuffType)`. Same answer for the five known classes; ours also
|
||||
survives a dispel name the table has no row for, where the reference indexes nil.
|
||||
- An icon path we cannot resolve falls back to BENILLA_FALLBACK_ICON rather than
|
||||
SetTexture(nil) — carried forward from 0255, and the same fallback the action bar uses.
|
||||
The reference has no such art; ours would otherwise draw an invisible button.
|
||||
- BuffButtons_UpdatePositions is CALLED ONCE AT LOAD (from TemporaryEnchantFrame's OnLoad,
|
||||
the first moment every frame it names exists). The reference never calls it at load
|
||||
because its default is "0" and its static XML anchors already ARE the "0" geometry; ours
|
||||
defaults to "1", so the load-time call is what makes the default honest.
|
||||
- TempEnchant1/2 ship `hidden="true"`. The reference leaves them shown and lets the first
|
||||
BuffFrame_Enchant_OnUpdate hide them — one frame of two empty enchant borders at the top
|
||||
right, which is a flicker worth not having.
|
||||
- BuffButtonTempEnchant is STANDALONE rather than inheriting BuffButtonTemplate and blanking
|
||||
its handlers with empty elements — our loader keeps the inherited handler where the
|
||||
reference clears it. Measured, not assumed; see the template's own note.
|
||||
- `hasEnchant = 1` (ref BuffFrame.lua l.185/208) is dropped: a leaked local that becomes a
|
||||
global, is never cleared, and is read by nothing.
|
||||
- GameTooltip is nil-guarded. Engine tests load file subsets without GameTooltip.xml.
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
-- The timer switch (ref uvar, UIOptionsFrame.lua l.21/104 — its `default = "0"`). This
|
||||
@@ -70,7 +124,8 @@
|
||||
BUFF_DURATION_WARNING_TIME = 60
|
||||
|
||||
-- The dispel-class border tints (BuffFrame.lua l.9-14); "none" is the undispellable red the
|
||||
-- debuff overlay wears by default. UnitDebuff's debuffType return keys this table.
|
||||
-- debuff overlay wears by default. GetPlayerBuffDispelType's return keys this table — it
|
||||
-- hands back the SpellDispelType.dbc name, or nil, never "" and never "none".
|
||||
DebuffTypeColor = {
|
||||
["none"] = { r = 0.80, g = 0.00, b = 0.00 },
|
||||
["Magic"] = { r = 0.20, g = 0.60, b = 1.00 },
|
||||
@@ -102,36 +157,27 @@
|
||||
-- The frame-wide flash pulse (BuffFrame_OnLoad / _OnUpdate, BuffFrame.lua l.17-56). One
|
||||
-- BUFF_ALPHA_VALUE is computed per frame and every low-on-time button reads it, so they blink
|
||||
-- in unison. The bar also seats each duration string under its button here.
|
||||
function BenillaBuffFrame_OnLoad(frame)
|
||||
function BuffFrame_OnLoad()
|
||||
BuffFrameUpdateTime = 0
|
||||
BuffFrameFlashTime = 0
|
||||
BuffFrameFlashState = 1
|
||||
BUFF_ALPHA_VALUE = 1
|
||||
for i = 0, 23 do
|
||||
getglobal("BuffButton" .. i .. "Duration"):SetPoint("TOP", "BuffButton" .. i, "BOTTOM", 0, 0)
|
||||
end
|
||||
BenillaBuffButtons_UpdatePositions()
|
||||
end
|
||||
|
||||
-- BuffButtons_UpdatePositions (BuffFrame.lua l.152-160): the ROW PITCH depends on whether a
|
||||
-- timer sits under each icon. Shown, the three rows land at y = 0/−45/−90 (a 30px icon + a
|
||||
-- 15px gutter); hidden, at 0/−35/−70 — the icons close up to the same 5px the columns use.
|
||||
-- The reference reaches those two geometries by re-anchoring through its TempEnchant chain,
|
||||
-- which we do not have; same two numbers, our own anchors, and SetPoint on a point already
|
||||
-- held overwrites it (layout.rs — the client's own SetPoint law).
|
||||
--
|
||||
-- This is the Buff Durations row's `applyFunc`, and the ONLY thing the setting needs applied:
|
||||
-- the timer text itself is re-decided every frame by the poll below, so a click changes what
|
||||
-- the next frame draws without anyone telling it. Geometry is the part that is stated once.
|
||||
function BenillaBuffButtons_UpdatePositions()
|
||||
local gutter = -5
|
||||
if SHOW_BUFF_DURATIONS == "1" then
|
||||
gutter = -15
|
||||
function BuffFrame_OnUpdate(elapsed)
|
||||
-- The tooltip re-poll's throttle (ref l.29-33): oscillates around zero with a
|
||||
-- TOOLTIP_UPDATE_TIME period, and BuffButton_OnUpdate refreshes a hovered plate only on
|
||||
-- the frames it is non-positive. ActionBar.xml declares TOOLTIP_UPDATE_TIME and loads
|
||||
-- first (benilla.toc), so it is always a number by the time this runs.
|
||||
if BuffFrameUpdateTime > 0 then
|
||||
BuffFrameUpdateTime = BuffFrameUpdateTime - elapsed
|
||||
else
|
||||
BuffFrameUpdateTime = BuffFrameUpdateTime + TOOLTIP_UPDATE_TIME
|
||||
end
|
||||
BuffButton8:SetPoint("TOP", "BuffButton0", "BOTTOM", 0, gutter)
|
||||
BuffButton16:SetPoint("TOPRIGHT", "BuffButton8", "BOTTOMRIGHT", 0, gutter)
|
||||
end
|
||||
|
||||
function BenillaBuffFrame_OnUpdate(elapsed)
|
||||
BuffFrameFlashTime = BuffFrameFlashTime - elapsed
|
||||
if BuffFrameFlashTime < 0 then
|
||||
local overtime = -BuffFrameFlashTime
|
||||
@@ -155,148 +201,250 @@
|
||||
BUFF_ALPHA_VALUE = (BUFF_ALPHA_VALUE * (1 - BUFF_MIN_ALPHA)) + BUFF_MIN_ALPHA
|
||||
end
|
||||
|
||||
-- A button caches its filter (set in the template OnLoad), its per-filter id (the XML `id`,
|
||||
-- 1-based to match our UnitBuff/UnitDebuff index), takes right-clicks, and listens for the
|
||||
-- aura change. The first paint hides it until the feed reports an aura.
|
||||
function BenillaBuffButton_OnLoad(button)
|
||||
button:RegisterForClicks("RightButtonUp")
|
||||
button:RegisterEvent("UNIT_AURA")
|
||||
BenillaBuffButton_Update(button)
|
||||
end
|
||||
|
||||
function BenillaBuffButton_OnEvent(button)
|
||||
if arg1 == "player" then
|
||||
BenillaBuffButton_Update(button)
|
||||
end
|
||||
end
|
||||
|
||||
-- The button's aura: the Nth of its own filter, in the app's display order. The ONE lookup
|
||||
-- both handlers use — the ref's BuffButton_Update and BuffButton_OnUpdate likewise resolve
|
||||
-- the same buffIndex, one on the event and one per frame.
|
||||
function BenillaBuffButton_Aura(button)
|
||||
local id = button:GetID()
|
||||
if button.buffFilter == "HARMFUL" then
|
||||
return UnitDebuff("player", id)
|
||||
end
|
||||
return UnitBuff("player", id)
|
||||
end
|
||||
|
||||
-- Pull the button's aura and repaint the discrete parts: icon, stack count, debuff-border
|
||||
-- tint. Those are what the aura-change event exists for. The countdown is NOT cached here —
|
||||
-- see BenillaBuffButton_OnUpdate. No aura ⇒ hide the button (which hides its icon/border
|
||||
-- with it) and blank its duration string.
|
||||
-- BuffFrame_UpdateDuration (BuffFrame.lua l.247-259), gate and all: the timer draws only when
|
||||
-- the setting is on AND there is a live one. `timeLeft` nil covers both of the other cases —
|
||||
-- no aura in this slot, and a permanent aura (no duration on the wire). Hidden, not blanked:
|
||||
-- see the header's deviation note.
|
||||
function BenillaBuffButton_SetDuration(button, timeLeft)
|
||||
local durationText = getglobal(button:GetName() .. "Duration")
|
||||
if SHOW_BUFF_DURATIONS ~= "1" or not timeLeft then
|
||||
durationText:Hide()
|
||||
return
|
||||
end
|
||||
durationText:SetText(SecondsToTimeAbbrev(timeLeft))
|
||||
if timeLeft < BUFF_DURATION_WARNING_TIME then
|
||||
durationText:SetVertexColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
|
||||
-- BuffButtons_UpdatePositions (BuffFrame.lua l.152-160): the ROW PITCH depends on whether a
|
||||
-- timer sits under each icon. Shown, the three rows land at y = 0/−45/−90 (a 30px icon + a
|
||||
-- 15px gutter); hidden, at 0/−35/−70 — the icons close up to the same 5px the columns use.
|
||||
-- The anchors are the reference's own, through the TempEnchant chain: row 2 hangs off
|
||||
-- TempEnchant1 and row 3 off TemporaryEnchantFrame, which is what keeps them still while the
|
||||
-- top row slides left to clear an equipped enchant. SetPoint on a point already held
|
||||
-- overwrites it (layout.rs — the client's own SetPoint law).
|
||||
--
|
||||
-- This is the Buff Durations row's `applyFunc`, and the ONLY thing the setting needs applied:
|
||||
-- the timer text itself is re-decided every frame by the poll below, so a click changes what
|
||||
-- the next frame draws without anyone telling it. Geometry is the part that is stated once.
|
||||
function BuffButtons_UpdatePositions()
|
||||
if SHOW_BUFF_DURATIONS == "1" then
|
||||
BuffButton8:SetPoint("TOP", "TempEnchant1", "BOTTOM", 0, -15)
|
||||
BuffButton16:SetPoint("TOPRIGHT", "TemporaryEnchantFrame", "TOPRIGHT", 0, -90)
|
||||
else
|
||||
durationText:SetVertexColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)
|
||||
BuffButton8:SetPoint("TOP", "TempEnchant1", "BOTTOM", 0, -5)
|
||||
BuffButton16:SetPoint("TOPRIGHT", "TemporaryEnchantFrame", "TOPRIGHT", 0, -70)
|
||||
end
|
||||
durationText:Show()
|
||||
end
|
||||
|
||||
function BenillaBuffButton_Update(button)
|
||||
local name, icon, count, debuffType = BenillaBuffButton_Aura(button)
|
||||
-- The button's aura, resolved on the discrete-change edge and CACHED AS A POSITION
|
||||
-- (ref BuffButton_Update, l.58-107). `this:GetID()` is the 0-based ordinal within
|
||||
-- this.buffFilter; buffIndex is the cache position every read below uses.
|
||||
function BuffButton_Update()
|
||||
local buffIndex, untilCancelled = GetPlayerBuff(this:GetID(), this.buffFilter)
|
||||
this.buffIndex = buffIndex
|
||||
this.untilCancelled = untilCancelled
|
||||
local buffDuration = getglobal(this:GetName() .. "Duration")
|
||||
|
||||
if not name then
|
||||
button:Hide()
|
||||
BenillaBuffButton_SetDuration(button, nil)
|
||||
-- -1, not nil: the miss sentinel the whole family agrees on.
|
||||
if buffIndex < 0 then
|
||||
this:Hide()
|
||||
buffDuration:Hide()
|
||||
return
|
||||
end
|
||||
button:SetAlpha(1.0)
|
||||
button:Show()
|
||||
this:SetAlpha(1.0)
|
||||
this:Show()
|
||||
if SHOW_BUFF_DURATIONS == "1" then
|
||||
buffDuration:Show()
|
||||
else
|
||||
buffDuration:Hide()
|
||||
end
|
||||
|
||||
getglobal(button:GetName() .. "Icon"):SetTexture(icon or BENILLA_FALLBACK_ICON)
|
||||
getglobal(this:GetName() .. "Icon"):SetTexture(GetPlayerBuffTexture(buffIndex) or BENILLA_FALLBACK_ICON)
|
||||
|
||||
-- Debuff border, tinted by dispel class (harmful buttons only — helpful ones have no
|
||||
-- $parentBorder, so getglobal is nil and we skip it).
|
||||
local border = getglobal(button:GetName() .. "Border")
|
||||
if border then
|
||||
-- $parentBorder, so getglobal is nil and the whole lookup is skipped; see the header).
|
||||
local debuffSlot = getglobal(this:GetName() .. "Border")
|
||||
if debuffSlot then
|
||||
local debuffType = GetPlayerBuffDispelType(GetPlayerBuff(this:GetID(), "HARMFUL"))
|
||||
local color = DebuffTypeColor[debuffType] or DebuffTypeColor["none"]
|
||||
border:SetVertexColor(color.r, color.g, color.b)
|
||||
debuffSlot:SetVertexColor(color.r, color.g, color.b)
|
||||
end
|
||||
|
||||
-- Stack count, shown only above 1 (BuffButton_Update l.95-102).
|
||||
local countText = getglobal(button:GetName() .. "Count")
|
||||
if count and count > 1 then
|
||||
countText:SetText(count)
|
||||
-- Stack count, shown only above 1 (ref l.94-102). GetPlayerBuffApplications is a NUMBER
|
||||
-- on every path — 1 for an absent position — so `> 1` needs no nil guard.
|
||||
local buffCount = getglobal(this:GetName() .. "Count")
|
||||
local count = GetPlayerBuffApplications(buffIndex)
|
||||
if count > 1 then
|
||||
buffCount:SetText(count)
|
||||
else
|
||||
countText:SetText("")
|
||||
buffCount:SetText("")
|
||||
end
|
||||
|
||||
if GameTooltip and GameTooltip:IsOwned(this) then
|
||||
GameTooltip:SetPlayerBuff(buffIndex)
|
||||
end
|
||||
end
|
||||
|
||||
-- Per-frame: RE-READ the expiration (never a cached one), count it down against GetTime(),
|
||||
-- flash in the last 31s, and write the abbreviated timer (highlight-white under 60s).
|
||||
-- expirationTime == 0 is a permanent aura (no duration on the wire) — our "until
|
||||
-- cancelled": no timer, no flash.
|
||||
-- A button takes right-clicks and listens for the aura rebuild. The first paint hides it
|
||||
-- until the feed reports an aura. PLAYER_AURAS_CHANGED is the reference's event and the app
|
||||
-- fires it beside the Era-shaped UNIT_AURA on the same rebuild (`ui_aura`).
|
||||
function BuffButton_OnLoad()
|
||||
-- Valid tokens for "buffFilter": HELPFUL, HARMFUL, CANCELABLE, NOT_CANCELABLE. Note the
|
||||
-- mask starts at ZERO once a filter string is given, so a bare "CANCELABLE" would match
|
||||
-- nothing — the sign must always be named.
|
||||
BuffButton_Update()
|
||||
this:RegisterForClicks("RightButtonUp")
|
||||
this:RegisterEvent("PLAYER_AURAS_CHANGED")
|
||||
end
|
||||
|
||||
function BuffButton_OnEvent(event)
|
||||
if event == "PLAYER_AURAS_CHANGED" then
|
||||
BuffButton_Update()
|
||||
end
|
||||
end
|
||||
|
||||
-- Per-frame: RE-READ the remaining time from the cached POSITION (never a cached duration),
|
||||
-- flash in the last 31s, write the abbreviated timer, and tick a hovered tooltip.
|
||||
--
|
||||
-- The re-read every frame is the reference's own shape, and it is load-bearing: ref
|
||||
-- BuffFrame.lua l.128-130 calls GetPlayerBuffTimeLeft(this.buffIndex) inside
|
||||
-- BuffButton_OnUpdate, caching only the INDEX on PLAYER_AURAS_CHANGED. A duration that
|
||||
-- changes without any discrete change — a recast, a DoT reapplied, cast pushback, the
|
||||
-- The re-read every frame is the reference's own shape, and it is load-bearing: a duration
|
||||
-- that changes without any discrete change — a recast, a DoT reapplied, cast pushback, the
|
||||
-- server's replay of every duration at world entry — fires no aura event, so a button that
|
||||
-- cached its expiry on the event would keep counting the OLD one down, sit at "0 s" while
|
||||
-- the buff is still live, and never show a timer for an aura whose duration landed after
|
||||
-- its icon did. Both were live defects; the poll is what makes them unrepresentable
|
||||
-- (decision 0846).
|
||||
function BenillaBuffButton_OnUpdate(button)
|
||||
local name, _, _, _, _, expirationTime = BenillaBuffButton_Aura(button)
|
||||
if not name or not expirationTime or expirationTime <= 0 then
|
||||
button:SetAlpha(1.0)
|
||||
BenillaBuffButton_SetDuration(button, nil)
|
||||
--
|
||||
-- The untilCancelled early-out (ref l.124-127) is what keeps a permanent aura from flashing
|
||||
-- a "0 s" countdown while waiting for a duration packet that is never coming: it is answered
|
||||
-- from the SPELL, so it is right before any packet arrives. This frame runs only on visible
|
||||
-- frames (`UiScript::tick`), exactly as the client's does, so a hidden button's stale
|
||||
-- buffIndex is never read.
|
||||
function BuffButton_OnUpdate()
|
||||
local buffDuration = getglobal(this:GetName() .. "Duration")
|
||||
if this.untilCancelled == 1 then
|
||||
buffDuration:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
local timeLeft = expirationTime - GetTime()
|
||||
if timeLeft < 0 then
|
||||
timeLeft = 0
|
||||
end
|
||||
-- The flash is NOT gated on the setting: the reference pulses a button in its last 31s
|
||||
-- from BuffButton_OnUpdate, whether or not a timer is drawn under it (l.128-138), and
|
||||
-- with the timers off it is the only thing left saying "about to drop".
|
||||
local buffIndex = this.buffIndex
|
||||
local timeLeft = GetPlayerBuffTimeLeft(buffIndex)
|
||||
-- The flash is NOT gated on the timer setting: the reference pulses a button in its last
|
||||
-- 31s whether or not a number is drawn under it, and with the timers off it is the only
|
||||
-- thing left saying "about to drop".
|
||||
if timeLeft < BUFF_WARNING_TIME then
|
||||
button:SetAlpha(BUFF_ALPHA_VALUE)
|
||||
this:SetAlpha(BUFF_ALPHA_VALUE)
|
||||
else
|
||||
button:SetAlpha(1.0)
|
||||
this:SetAlpha(1.0)
|
||||
end
|
||||
|
||||
BuffFrame_UpdateDuration(this, timeLeft)
|
||||
|
||||
if BuffFrameUpdateTime > 0 then
|
||||
return
|
||||
end
|
||||
if GameTooltip and GameTooltip:IsOwned(this) then
|
||||
GameTooltip:SetPlayerBuff(buffIndex)
|
||||
end
|
||||
BenillaBuffButton_SetDuration(button, timeLeft)
|
||||
end
|
||||
|
||||
-- Right-click cancels (BuffButton_OnClick → CancelPlayerBuff). Resolve within the button's own
|
||||
-- filter so a debuff button's id can't reach into the buff list; the app sends CMSG_CANCEL_AURA
|
||||
-- for a cancelable buff and drops the rest.
|
||||
-- Hover: the aura tooltip through the engine channel (decision 0274 P2 — the verified
|
||||
-- aura variant: white AuraDescription + the SetPlayerBuff-only remaining line). The
|
||||
-- OnUpdate re-poll keeps the remaining line ticking while hovered. Anchor: the ref's
|
||||
-- BuffButton hangs the plate BELOW-left (ref BuffFrame.xml l.37 ANCHOR_BOTTOMLEFT —
|
||||
-- the buff row sits at the screen's top-right, so the tooltip drops under it).
|
||||
function BenillaBuffButton_OnEnter(button)
|
||||
if not GameTooltip then return end -- engine tests load file subsets
|
||||
GameTooltip:SetOwner(button, "ANCHOR_BOTTOMLEFT")
|
||||
GameTooltip:SetPlayerBuff(button:GetID(), button.buffFilter)
|
||||
-- Right-click cancels by CACHE POSITION (ref l.148-150). The app sends CMSG_CANCEL_AURA for
|
||||
-- a cancelable aura and drops the rest, so a debuff right-click is a silent no-op.
|
||||
function BuffButton_OnClick()
|
||||
CancelPlayerBuff(this.buffIndex)
|
||||
end
|
||||
|
||||
function BenillaBuffButton_OnLeave(button)
|
||||
GameTooltip:Hide()
|
||||
-- The temporary-weapon-enchant row (ref l.162-233). GetWeaponEnchantInfo is the whole data
|
||||
-- path: two flags, two remaining-MILLISECOND counts, two charge counts. Off hand takes
|
||||
-- TempEnchant1 and main hand the next free slot, so with both enchanted the main hand sits
|
||||
-- to the LEFT of the off hand — the reference's own ordering.
|
||||
function BuffFrame_Enchant_OnUpdate(elapsed)
|
||||
local hasMainHandEnchant, mainHandExpiration, mainHandCharges, hasOffHandEnchant, offHandExpiration, offHandCharges = GetWeaponEnchantInfo()
|
||||
|
||||
-- No enchants, kick out early — and park BuffFrame back on its resting point.
|
||||
if not hasMainHandEnchant and not hasOffHandEnchant then
|
||||
TempEnchant1:Hide()
|
||||
TempEnchant1Duration:Hide()
|
||||
TempEnchant2:Hide()
|
||||
TempEnchant2Duration:Hide()
|
||||
BuffFrame:SetPoint("TOPRIGHT", "TemporaryEnchantFrame", "TOPRIGHT", 0, 0)
|
||||
return
|
||||
end
|
||||
|
||||
local enchantButton
|
||||
local textureName
|
||||
local enchantIndex = 0
|
||||
if hasOffHandEnchant then
|
||||
enchantIndex = enchantIndex + 1
|
||||
textureName = GetInventoryItemTexture("player", 17)
|
||||
TempEnchant1:SetID(17)
|
||||
TempEnchant1Icon:SetTexture(textureName or BENILLA_FALLBACK_ICON)
|
||||
TempEnchant1:Show()
|
||||
|
||||
if offHandExpiration then
|
||||
offHandExpiration = offHandExpiration / 1000
|
||||
end
|
||||
BuffFrame_UpdateDuration(TempEnchant1, offHandExpiration)
|
||||
|
||||
if offHandExpiration and offHandExpiration < BUFF_WARNING_TIME then
|
||||
TempEnchant1:SetAlpha(BUFF_ALPHA_VALUE)
|
||||
else
|
||||
TempEnchant1:SetAlpha(1.0)
|
||||
end
|
||||
end
|
||||
if hasMainHandEnchant then
|
||||
enchantIndex = enchantIndex + 1
|
||||
enchantButton = getglobal("TempEnchant" .. enchantIndex)
|
||||
textureName = GetInventoryItemTexture("player", 16)
|
||||
enchantButton:SetID(16)
|
||||
getglobal(enchantButton:GetName() .. "Icon"):SetTexture(textureName or BENILLA_FALLBACK_ICON)
|
||||
enchantButton:Show()
|
||||
|
||||
if mainHandExpiration then
|
||||
mainHandExpiration = mainHandExpiration / 1000
|
||||
end
|
||||
BuffFrame_UpdateDuration(enchantButton, mainHandExpiration)
|
||||
|
||||
if mainHandExpiration and mainHandExpiration < BUFF_WARNING_TIME then
|
||||
enchantButton:SetAlpha(BUFF_ALPHA_VALUE)
|
||||
else
|
||||
enchantButton:SetAlpha(1.0)
|
||||
end
|
||||
end
|
||||
-- Hide unused enchants
|
||||
for i = enchantIndex + 1, 2 do
|
||||
getglobal("TempEnchant" .. i):Hide()
|
||||
getglobal("TempEnchant" .. i .. "Duration"):Hide()
|
||||
end
|
||||
|
||||
-- Position buff frame: the row is `enchantIndex` icons wide, and the bar starts one
|
||||
-- 5px gutter to its left.
|
||||
TemporaryEnchantFrame:SetWidth(enchantIndex * 32)
|
||||
BuffFrame:SetPoint("TOPRIGHT", "TemporaryEnchantFrame", "TOPLEFT", -5, 0)
|
||||
end
|
||||
|
||||
function BenillaBuffButton_OnClick(button)
|
||||
CancelUnitBuff("player", button:GetID(), button.buffFilter)
|
||||
function BuffFrame_EnchantButton_OnUpdate()
|
||||
if GameTooltip and GameTooltip:IsOwned(this) then
|
||||
BuffFrame_EnchantButton_OnEnter()
|
||||
end
|
||||
end
|
||||
|
||||
-- The enchant hover is the ITEM's tooltip, not an aura's — the plate hangs below-left like
|
||||
-- the buff buttons' (ref l.242-245).
|
||||
function BuffFrame_EnchantButton_OnEnter()
|
||||
if not GameTooltip then return end
|
||||
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMLEFT")
|
||||
GameTooltip:SetInventoryItem("player", this:GetID())
|
||||
end
|
||||
|
||||
-- BuffFrame_UpdateDuration (ref l.247-260), gate and all: the timer draws only when the
|
||||
-- setting is on AND there is a live one. `timeLeft` nil is the enchant row's "no countdown
|
||||
-- known" case. Hidden, not blanked — see the header's deviation note.
|
||||
function BuffFrame_UpdateDuration(buffButton, timeLeft)
|
||||
local duration = getglobal(buffButton:GetName() .. "Duration")
|
||||
if SHOW_BUFF_DURATIONS == "1" and timeLeft then
|
||||
duration:SetText(SecondsToTimeAbbrev(timeLeft))
|
||||
if timeLeft < BUFF_DURATION_WARNING_TIME then
|
||||
duration:SetVertexColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
|
||||
else
|
||||
duration:SetVertexColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)
|
||||
end
|
||||
duration:Show()
|
||||
else
|
||||
duration:Hide()
|
||||
end
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
<!-- 30x30 icon + a bottom-right stack count. BuffButtonTemplate, BuffFrame.xml l.4-44 (minus the
|
||||
tooltip OnEnter/OnLeave — a later arc). -->
|
||||
<Button name="BenillaBuffButtonTemplate" virtual="true">
|
||||
<!-- 30x30 icon + a bottom-right stack count (ref BuffButtonTemplate, BuffFrame.xml l.4-44).
|
||||
The base template's own filter is the reference's HELPFUL|HARMFUL; the two concrete
|
||||
variants below pin one sign each. -->
|
||||
<Button name="BuffButtonTemplate" virtual="true">
|
||||
<Size><AbsDimension x="30" y="30"/></Size>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
@@ -309,27 +457,38 @@
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnEvent>BenillaBuffButton_OnEvent(this)</OnEvent>
|
||||
<OnUpdate>BenillaBuffButton_OnUpdate(this)</OnUpdate>
|
||||
<OnClick>BenillaBuffButton_OnClick(this)</OnClick>
|
||||
<OnEnter>BenillaBuffButton_OnEnter(this)</OnEnter>
|
||||
<OnLeave>BenillaBuffButton_OnLeave(this)</OnLeave>
|
||||
<OnLoad>
|
||||
this.buffFilter = "HELPFUL|HARMFUL"
|
||||
BuffButton_OnLoad()
|
||||
</OnLoad>
|
||||
<OnEvent>BuffButton_OnEvent(event)</OnEvent>
|
||||
<OnUpdate>BuffButton_OnUpdate()</OnUpdate>
|
||||
<OnClick>BuffButton_OnClick()</OnClick>
|
||||
<OnEnter>
|
||||
if GameTooltip then
|
||||
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMLEFT")
|
||||
GameTooltip:SetPlayerBuff(this.buffIndex)
|
||||
end
|
||||
</OnEnter>
|
||||
<OnLeave>
|
||||
if GameTooltip then GameTooltip:Hide() end
|
||||
</OnLeave>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- Helpful variant: filter HELPFUL, no border (BuffButtonHelpful, l.50-57). -->
|
||||
<Button name="BenillaBuffButtonHelpful" inherits="BenillaBuffButtonTemplate" virtual="true">
|
||||
<!-- Helpful variant: filter HELPFUL, no border (ref l.50-57). -->
|
||||
<Button name="BuffButtonHelpful" inherits="BuffButtonTemplate" virtual="true">
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
this.buffFilter = "HELPFUL"
|
||||
BenillaBuffButton_OnLoad(this)
|
||||
BuffButton_OnLoad()
|
||||
</OnLoad>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- Harmful variant: filter HARMFUL + the dispel-tinted border overlay (BuffButtonHarmful,
|
||||
l.58-82). 33x32 UI-Debuff-Overlays, the exact reference TexCoords, tint set at runtime. -->
|
||||
<Button name="BenillaBuffButtonHarmful" inherits="BenillaBuffButtonTemplate" virtual="true">
|
||||
<!-- Harmful variant: filter HARMFUL + the dispel-tinted border overlay (ref l.58-82).
|
||||
33x32 UI-Debuff-Overlays, the exact reference TexCoords, tint set at runtime. -->
|
||||
<Button name="BuffButtonHarmful" inherits="BuffButtonTemplate" virtual="true">
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="$parentBorder" file="Interface\Buttons\UI-Debuff-Overlays">
|
||||
@@ -344,23 +503,69 @@
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
this.buffFilter = "HARMFUL"
|
||||
BenillaBuffButton_OnLoad(this)
|
||||
BuffButton_OnLoad()
|
||||
</OnLoad>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- Temp-enchant slot: the same 30x30 icon under a 32x32 UI-TempEnchant-Border (ref l.83-117).
|
||||
It carries no filter and no buffIndex — its data is GetWeaponEnchantInfo and its hover is
|
||||
the ITEM's tooltip.
|
||||
|
||||
**STANDALONE, where the reference inherits BuffButtonTemplate and then blanks the four aura
|
||||
handlers with empty `<OnLoad></OnLoad>` / `<OnEvent>` / `<OnClick>` elements.** Our loader
|
||||
does not implement that idiom: `Loader::compile_handler` returns None for an empty body, so
|
||||
`apply_scripts` skips the SetScript entirely and the INHERITED handler survives. Written
|
||||
the reference's way and MEASURED, TempEnchant1 ran `BuffButton_OnLoad`, registered
|
||||
PLAYER_AURAS_CHANGED, and painted the player's first buff in the corner — and a right-click
|
||||
on it would have cancelled that aura. Writing the three regions out removes the dependence
|
||||
on a loader semantic we have not byte-verified; the engine gap is real and worth closing on
|
||||
its own evidence, not silently here. -->
|
||||
<Button name="BuffButtonTempEnchant" virtual="true">
|
||||
<Size><AbsDimension x="30" y="30"/></Size>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentIcon"/>
|
||||
<FontString name="$parentCount" inherits="NumberFontNormal">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-2" y="2"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="$parentBorder" file="Interface\Buttons\UI-TempEnchant-Border">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnUpdate>BuffFrame_EnchantButton_OnUpdate()</OnUpdate>
|
||||
<OnEnter>BuffFrame_EnchantButton_OnEnter()</OnEnter>
|
||||
<OnLeave>
|
||||
if GameTooltip then GameTooltip:Hide() end
|
||||
</OnLeave>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- The duration text under a button: the ref routes these through a virtual FontString
|
||||
template (BuffButtonDurationTemplate, l.45-49 — GameFontNormalSmall + a bare TOP anchor),
|
||||
but region TEMPLATES are not in the v1 loader's object model (inherits= on a FontString
|
||||
resolves Font objects only), and the anchor is re-seated by BenillaBuffFrame_OnLoad
|
||||
anyway — so each instance inherits the Font object directly. Same resolved font, no
|
||||
indirection; the loader gap is the named v1 limit, warned once if reintroduced. -->
|
||||
template (BuffButtonDurationTemplate, l.45-49 — GameFontNormalSmall + a bare TOP anchor).
|
||||
Region templates DO exist in the loader now (Loader::expand_region), but chaining to a FONT
|
||||
OBJECT through one does not: `framexml::merge` lets the instance's own `inherits=` win, so
|
||||
the spliced element still reads `inherits="BuffButtonDurationTemplate"` and
|
||||
`apply_fontstring_font` looks that up in the FONT registry — a separate namespace — and
|
||||
misses. Each instance therefore names GameFontNormalSmall directly; the anchor is re-seated
|
||||
by BuffFrame_OnLoad / TemporaryEnchantFrame's OnLoad anyway. Same resolved font, no
|
||||
indirection. -->
|
||||
<!--
|
||||
The bar. Right-aligned under the minimap (UIParent TOPRIGHT -175,-13). 16 helpful buttons in
|
||||
two rows of 8 (ids 1-16), then 8 harmful (ids 1-8), rows 45px apart: button0 top-right;
|
||||
buttons 1-7 step left (-5 gap); button8 opens row 2 under button0 (-15 gutter for the timer);
|
||||
button16 opens the debuff row under button8. The 24 duration strings live in the frame's
|
||||
ARTWORK layer (over the icons' BACKGROUND), named to pair with their button.
|
||||
The bar. Right-aligned under the minimap (UIParent TOPRIGHT -175,-13 — the reference's XML
|
||||
says -205 and lets the first Enchant_OnUpdate snap it here; ours starts where it rests, so
|
||||
the bar is never one frame out of place). 16 helpful buttons in two rows of 8 (ids 0-15),
|
||||
then 8 harmful (ids 0-7). The 24 duration strings live in the frame's ARTWORK layer (over the
|
||||
icons' BACKGROUND), named to pair with their button. BuffButton8's and BuffButton16's static
|
||||
anchors here are the no-timer geometry; BuffButtons_UpdatePositions re-seats both at load.
|
||||
-->
|
||||
<Frame name="BuffFrame" parent="UIParent" frameStrata="LOW" toplevel="true">
|
||||
<Size><AbsDimension x="50" y="50"/></Size>
|
||||
@@ -398,85 +603,127 @@
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<!-- Row 0: helpful buttons 0-7, right to left. -->
|
||||
<Button name="BuffButton0" inherits="BenillaBuffButtonHelpful" id="1">
|
||||
<!-- Row 0: helpful ordinals 0-7, right to left. -->
|
||||
<Button name="BuffButton0" inherits="BuffButtonHelpful" id="0">
|
||||
<Anchors><Anchor point="TOPRIGHT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton1" inherits="BenillaBuffButtonHelpful" id="2">
|
||||
<Button name="BuffButton1" inherits="BuffButtonHelpful" id="1">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton0" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton2" inherits="BenillaBuffButtonHelpful" id="3">
|
||||
<Button name="BuffButton2" inherits="BuffButtonHelpful" id="2">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton1" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton3" inherits="BenillaBuffButtonHelpful" id="4">
|
||||
<Button name="BuffButton3" inherits="BuffButtonHelpful" id="3">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton2" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton4" inherits="BenillaBuffButtonHelpful" id="5">
|
||||
<Button name="BuffButton4" inherits="BuffButtonHelpful" id="4">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton3" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton5" inherits="BenillaBuffButtonHelpful" id="6">
|
||||
<Button name="BuffButton5" inherits="BuffButtonHelpful" id="5">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton4" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton6" inherits="BenillaBuffButtonHelpful" id="7">
|
||||
<Button name="BuffButton6" inherits="BuffButtonHelpful" id="6">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton5" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton7" inherits="BenillaBuffButtonHelpful" id="8">
|
||||
<Button name="BuffButton7" inherits="BuffButtonHelpful" id="7">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton6" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<!-- Row 1: helpful buttons 8-15. Button8 opens the row under button0, with the 15px timer gutter. -->
|
||||
<Button name="BuffButton8" inherits="BenillaBuffButtonHelpful" id="9">
|
||||
<Anchors><Anchor point="TOP" relativeTo="BuffButton0" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-15"/></Offset></Anchor></Anchors>
|
||||
<!-- Row 1: helpful ordinals 8-15. Re-anchored to TempEnchant1 by BuffButtons_UpdatePositions. -->
|
||||
<Button name="BuffButton8" inherits="BuffButtonHelpful" id="8">
|
||||
<Anchors><Anchor point="TOP" relativeTo="BuffButton0" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton9" inherits="BenillaBuffButtonHelpful" id="10">
|
||||
<Button name="BuffButton9" inherits="BuffButtonHelpful" id="9">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton8" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton10" inherits="BenillaBuffButtonHelpful" id="11">
|
||||
<Button name="BuffButton10" inherits="BuffButtonHelpful" id="10">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton9" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton11" inherits="BenillaBuffButtonHelpful" id="12">
|
||||
<Button name="BuffButton11" inherits="BuffButtonHelpful" id="11">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton10" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton12" inherits="BenillaBuffButtonHelpful" id="13">
|
||||
<Button name="BuffButton12" inherits="BuffButtonHelpful" id="12">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton11" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton13" inherits="BenillaBuffButtonHelpful" id="14">
|
||||
<Button name="BuffButton13" inherits="BuffButtonHelpful" id="13">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton12" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton14" inherits="BenillaBuffButtonHelpful" id="15">
|
||||
<Button name="BuffButton14" inherits="BuffButtonHelpful" id="14">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton13" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton15" inherits="BenillaBuffButtonHelpful" id="16">
|
||||
<Button name="BuffButton15" inherits="BuffButtonHelpful" id="15">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton14" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<!-- Row 2: harmful buttons 16-23. Button16 opens the debuff row under button8, right-aligned. -->
|
||||
<Button name="BuffButton16" inherits="BenillaBuffButtonHarmful" id="1">
|
||||
<Anchors><Anchor point="TOPRIGHT" relativeTo="BuffButton8" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-15"/></Offset></Anchor></Anchors>
|
||||
<!-- Row 2: harmful ordinals 0-7. Re-anchored to TemporaryEnchantFrame by
|
||||
BuffButtons_UpdatePositions. (The reference's static anchor here names
|
||||
relativePoint="BOTTOM" against a TOPRIGHT point — a typo it never notices because
|
||||
UpdatePositions overwrites it; BOTTOMRIGHT is what it meant.) -->
|
||||
<Button name="BuffButton16" inherits="BuffButtonHarmful" id="0">
|
||||
<Anchors><Anchor point="TOPRIGHT" relativeTo="BuffButton8" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton17" inherits="BenillaBuffButtonHarmful" id="2">
|
||||
<Button name="BuffButton17" inherits="BuffButtonHarmful" id="1">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton16" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton18" inherits="BenillaBuffButtonHarmful" id="3">
|
||||
<Button name="BuffButton18" inherits="BuffButtonHarmful" id="2">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton17" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton19" inherits="BenillaBuffButtonHarmful" id="4">
|
||||
<Button name="BuffButton19" inherits="BuffButtonHarmful" id="3">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton18" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton20" inherits="BenillaBuffButtonHarmful" id="5">
|
||||
<Button name="BuffButton20" inherits="BuffButtonHarmful" id="4">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton19" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton21" inherits="BenillaBuffButtonHarmful" id="6">
|
||||
<Button name="BuffButton21" inherits="BuffButtonHarmful" id="5">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton20" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton22" inherits="BenillaBuffButtonHarmful" id="7">
|
||||
<Button name="BuffButton22" inherits="BuffButtonHarmful" id="6">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton21" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BuffButton23" inherits="BenillaBuffButtonHarmful" id="8">
|
||||
<Button name="BuffButton23" inherits="BuffButtonHarmful" id="7">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BuffButton22" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaBuffFrame_OnLoad(this)</OnLoad>
|
||||
<OnUpdate>BenillaBuffFrame_OnUpdate(arg1)</OnUpdate>
|
||||
<OnLoad>BuffFrame_OnLoad()</OnLoad>
|
||||
<OnUpdate>BuffFrame_OnUpdate(arg1)</OnUpdate>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
<!-- The temporary-weapon-enchant row (ref BuffFrame.xml l.385-431). It owns the top-right
|
||||
corner; BuffFrame hangs off it, which is why the bar's own anchor above is this frame's
|
||||
point. Two slots, right to left, so with both weapons enchanted the main hand sits left of
|
||||
the off hand.
|
||||
|
||||
Declared AFTER BuffFrame so that by the time this OnLoad runs every name
|
||||
BuffButtons_UpdatePositions touches exists — the reference gets away with never calling it
|
||||
at load because its default is the geometry its static anchors already hold; ours is not. -->
|
||||
<Frame name="TemporaryEnchantFrame" parent="UIParent" frameStrata="LOW" toplevel="true">
|
||||
<Size><AbsDimension x="36" y="36"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" relativeTo="UIParent" relativePoint="TOPRIGHT">
|
||||
<Offset><AbsDimension x="-175" y="-13"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<FontString name="TempEnchant1Duration" inherits="GameFontNormalSmall"/>
|
||||
<FontString name="TempEnchant2Duration" inherits="GameFontNormalSmall"/>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Button name="TempEnchant1" inherits="BuffButtonTempEnchant" hidden="true">
|
||||
<Anchors><Anchor point="TOPRIGHT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="TempEnchant2" inherits="BuffButtonTempEnchant" hidden="true">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="TempEnchant1" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
TempEnchant1Duration:SetPoint("TOP", "TempEnchant1", "BOTTOM", 0, 0)
|
||||
TempEnchant2Duration:SetPoint("TOP", "TempEnchant2", "BOTTOM", 0, 0)
|
||||
BuffButtons_UpdatePositions()
|
||||
</OnLoad>
|
||||
<OnUpdate>BuffFrame_Enchant_OnUpdate(arg1)</OnUpdate>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,173 @@
|
||||
"Say:" with static insets.
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
-- ── SlashCmdList — the addon slash-command registry (decision 1195) ────────────────────
|
||||
-- Declared HERE, not in Rust, because the 1.12 client declares it in ChatFrame.lua and the
|
||||
-- captured `_G` attributes it `table framexml`. An addon's registration is three lines at
|
||||
-- FILE SCOPE:
|
||||
-- SLASH_MYADDON1 = "/myaddon"
|
||||
-- SlashCmdList["MYADDON"] = function(msg) ... end
|
||||
-- so a missing table is not a missing feature, it is `attempt to index global
|
||||
-- 'SlashCmdList'` on the addon's very first chunk — 26 of a 218-addon corpus died there.
|
||||
-- The dispatch walk is `benilla_ui::script::slash`, transcribing ChatEdit_ParseText's.
|
||||
--
|
||||
-- It starts EMPTY: benilla's own commands are dispatched host-side (decision 0881's table)
|
||||
-- where the reference registers them here. That divergence is recorded in `slash.rs`.
|
||||
SlashCmdList = {}
|
||||
|
||||
-- ── ChatTypeInfo — the per-chat-type table addons read colors out of ──────────────────
|
||||
-- Declared HERE because the 1.12 client declares it at the top of ChatFrame.lua, and it
|
||||
-- is an ADDON-FACING table before it is ours: `local info = ChatTypeInfo["SAY"]` then
|
||||
-- `frame:AddMessage(text, info.r, info.g, info.b, info.id)` is the standard way an addon
|
||||
-- prints a colored line.
|
||||
--
|
||||
-- Measured, and the raw count is misleading enough to be worth writing down: 234 corpus
|
||||
-- sites, but **192 of them are one line** — AceEvent-2.0's
|
||||
-- `ChatTypeInfo and ChatTypeInfo.WHISPER and ChatTypeInfo.WHISPER.r`, a GUARDED readiness
|
||||
-- probe that copy-pastes into 64 addons and cannot raise. The reads that actually index
|
||||
-- the table are 42 sites in 6 addons (ChatLog, oRA2/Participant's RaidWarn, Glory-2.0's
|
||||
-- two FuBar hosts, FuBar_HeyFu) — those are `attempt to index global 'ChatTypeInfo'`
|
||||
-- without it, and they are the reason this exists.
|
||||
--
|
||||
-- Three columns, three sources:
|
||||
-- sticky the reference's own literals (ChatFrame.lua l.10-114) — the five sticky types
|
||||
-- are SAY/PARTY/RAID/GUILD/BATTLEGROUND, which `ui_chat::edit` already quotes.
|
||||
-- id GetChatTypeIndex's 1-based index into the engine's 94-entry color registry
|
||||
-- (wow-re `chat-color-table.md`, static table 0x804710) — the ref sets it in
|
||||
-- ChatFrame_OnLoad, and AddMessage's 5th argument is what consumes it.
|
||||
-- r,g,b that registry's byte defaults, /255 — the engine hands Lua 0..1 floats
|
||||
-- (it re-normalizes with the f32 1/255 at 0x8026c8).
|
||||
--
|
||||
-- ONE DIVERGENCE, and it is a shortcut through a two-step: the reference's OnLoad writes
|
||||
-- 1.0/1.0/1.0 into every entry and waits for the engine's UPDATE_CHAT_COLOR storm to fill
|
||||
-- the real values in (ChatFrame.lua l.1349 is the only writer in the whole default UI).
|
||||
-- benilla has no chat-color settings surface and nothing that fires that event, so the
|
||||
-- values are seeded at their END STATE directly. `ui_chat::event::default_color` is the
|
||||
-- same wow-re table rendered host-side for our own feed, and
|
||||
-- `chat_type_info_matches_the_host_color_table` fails if the two ever drift.
|
||||
--
|
||||
-- REPLY and COMBAT_ERROR are the two keys FrameXML declares that the engine's registry
|
||||
-- does NOT contain, so GetChatTypeIndex answers 0 for both and no UPDATE_CHAT_COLOR is
|
||||
-- ever fired for either name. They do NOT share a fate, and the first version of this
|
||||
-- block got that wrong by generalising:
|
||||
-- COMBAT_ERROR nothing writes it, ever — id 0, white, on the real client too.
|
||||
-- REPLY the UPDATE_CHAT_COLOR handler MIRRORS WHISPER INTO IT by hand
|
||||
-- (ChatFrame.lua l.1357-1365: `if strupper(arg1) == "WHISPER" then
|
||||
-- info = ChatTypeInfo["REPLY"] ... end`). So its end state is WHISPER's
|
||||
-- FF80FF, which is what the /r header shows, and seeding it white was a
|
||||
-- bug the comment above it was busy justifying.
|
||||
-- CHANNEL1..CHANNEL10 are the boot-seeded extras (indices 95-104), each colored from the
|
||||
-- live CHANNEL entry — FFC0C0.
|
||||
ChatTypeInfo = {}
|
||||
local function chatType(name, sticky, id, r, g, b)
|
||||
ChatTypeInfo[name] = { sticky = sticky, id = id, r = r / 255, g = g / 255, b = b / 255 }
|
||||
end
|
||||
chatType("SAY", 1, 1, 255, 255, 255)
|
||||
chatType("PARTY", 1, 2, 170, 170, 255)
|
||||
chatType("RAID", 1, 3, 255, 127, 0)
|
||||
chatType("GUILD", 1, 4, 64, 255, 64)
|
||||
chatType("OFFICER", 0, 5, 64, 192, 64)
|
||||
chatType("YELL", 0, 6, 255, 64, 64)
|
||||
chatType("WHISPER", 0, 7, 255, 128, 255)
|
||||
chatType("WHISPER_INFORM", 0, 8, 255, 128, 255)
|
||||
chatType("REPLY", 0, 0, 255, 128, 255)
|
||||
chatType("EMOTE", 0, 9, 255, 128, 64)
|
||||
chatType("TEXT_EMOTE", 0, 10, 255, 128, 64)
|
||||
chatType("SYSTEM", 0, 11, 255, 255, 0)
|
||||
chatType("MONSTER_WHISPER", 0, 27, 179, 179, 179)
|
||||
chatType("MONSTER_SAY", 0, 12, 255, 255, 159)
|
||||
chatType("MONSTER_YELL", 0, 13, 255, 64, 64)
|
||||
chatType("MONSTER_EMOTE", 0, 14, 255, 128, 64)
|
||||
chatType("CHANNEL", 0, 15, 255, 192, 192)
|
||||
chatType("CHANNEL_JOIN", 0, 16, 192, 128, 128)
|
||||
chatType("CHANNEL_LEAVE", 0, 17, 192, 128, 128)
|
||||
chatType("CHANNEL_LIST", 0, 18, 192, 128, 128)
|
||||
chatType("CHANNEL_NOTICE", 0, 19, 192, 192, 192)
|
||||
chatType("CHANNEL_NOTICE_USER", 0, 20, 192, 192, 192)
|
||||
chatType("AFK", 0, 21, 255, 128, 255)
|
||||
chatType("DND", 0, 22, 255, 128, 255)
|
||||
chatType("IGNORED", 0, 23, 255, 0, 0)
|
||||
chatType("SKILL", 0, 24, 85, 85, 255)
|
||||
chatType("LOOT", 0, 25, 0, 170, 0)
|
||||
chatType("COMBAT_ERROR", 0, 0, 255, 255, 255)
|
||||
chatType("COMBAT_MISC_INFO", 0, 26, 128, 128, 255)
|
||||
chatType("CHANNEL1", 0, 95, 255, 192, 192)
|
||||
chatType("CHANNEL2", 0, 96, 255, 192, 192)
|
||||
chatType("CHANNEL3", 0, 97, 255, 192, 192)
|
||||
chatType("CHANNEL4", 0, 98, 255, 192, 192)
|
||||
chatType("CHANNEL5", 0, 99, 255, 192, 192)
|
||||
chatType("CHANNEL6", 0, 100, 255, 192, 192)
|
||||
chatType("CHANNEL7", 0, 101, 255, 192, 192)
|
||||
chatType("CHANNEL8", 0, 102, 255, 192, 192)
|
||||
chatType("CHANNEL9", 0, 103, 255, 192, 192)
|
||||
chatType("CHANNEL10", 0, 104, 255, 192, 192)
|
||||
chatType("COMBAT_SELF_HITS", 0, 28, 255, 255, 255)
|
||||
chatType("COMBAT_SELF_MISSES", 0, 29, 255, 255, 255)
|
||||
chatType("COMBAT_PET_HITS", 0, 30, 255, 255, 255)
|
||||
chatType("COMBAT_PET_MISSES", 0, 31, 255, 255, 255)
|
||||
chatType("COMBAT_PARTY_HITS", 0, 32, 255, 255, 255)
|
||||
chatType("COMBAT_PARTY_MISSES", 0, 33, 255, 255, 255)
|
||||
chatType("COMBAT_FRIENDLYPLAYER_HITS", 0, 34, 255, 255, 255)
|
||||
chatType("COMBAT_FRIENDLYPLAYER_MISSES", 0, 35, 255, 255, 255)
|
||||
chatType("COMBAT_HOSTILEPLAYER_HITS", 0, 36, 255, 255, 255)
|
||||
chatType("COMBAT_HOSTILEPLAYER_MISSES", 0, 37, 255, 255, 255)
|
||||
chatType("COMBAT_CREATURE_VS_SELF_HITS", 0, 38, 255, 47, 47)
|
||||
chatType("COMBAT_CREATURE_VS_SELF_MISSES", 0, 39, 255, 47, 47)
|
||||
chatType("COMBAT_CREATURE_VS_PARTY_HITS", 0, 40, 255, 255, 255)
|
||||
chatType("COMBAT_CREATURE_VS_PARTY_MISSES", 0, 41, 255, 255, 255)
|
||||
chatType("COMBAT_CREATURE_VS_CREATURE_HITS", 0, 42, 255, 255, 255)
|
||||
chatType("COMBAT_CREATURE_VS_CREATURE_MISSES", 0, 43, 255, 255, 255)
|
||||
chatType("COMBAT_FRIENDLY_DEATH", 0, 44, 255, 255, 255)
|
||||
chatType("COMBAT_HOSTILE_DEATH", 0, 45, 255, 255, 255)
|
||||
chatType("COMBAT_XP_GAIN", 0, 46, 111, 111, 255)
|
||||
chatType("COMBAT_HONOR_GAIN", 0, 82, 224, 202, 10)
|
||||
chatType("SPELL_SELF_DAMAGE", 0, 47, 255, 255, 0)
|
||||
chatType("SPELL_SELF_BUFF", 0, 48, 255, 255, 0)
|
||||
chatType("SPELL_PET_DAMAGE", 0, 49, 255, 255, 255)
|
||||
chatType("SPELL_PET_BUFF", 0, 50, 255, 255, 255)
|
||||
chatType("SPELL_PARTY_DAMAGE", 0, 51, 255, 255, 255)
|
||||
chatType("SPELL_PARTY_BUFF", 0, 52, 255, 255, 255)
|
||||
chatType("SPELL_FRIENDLYPLAYER_DAMAGE", 0, 53, 255, 255, 255)
|
||||
chatType("SPELL_FRIENDLYPLAYER_BUFF", 0, 54, 255, 255, 255)
|
||||
chatType("SPELL_HOSTILEPLAYER_DAMAGE", 0, 55, 255, 255, 255)
|
||||
chatType("SPELL_HOSTILEPLAYER_BUFF", 0, 56, 255, 255, 255)
|
||||
chatType("SPELL_CREATURE_VS_SELF_DAMAGE", 0, 57, 202, 76, 217)
|
||||
chatType("SPELL_CREATURE_VS_SELF_BUFF", 0, 58, 255, 255, 255)
|
||||
chatType("SPELL_CREATURE_VS_PARTY_DAMAGE", 0, 59, 255, 255, 255)
|
||||
chatType("SPELL_CREATURE_VS_PARTY_BUFF", 0, 60, 255, 255, 255)
|
||||
chatType("SPELL_CREATURE_VS_CREATURE_DAMAGE", 0, 61, 255, 255, 255)
|
||||
chatType("SPELL_CREATURE_VS_CREATURE_BUFF", 0, 62, 255, 255, 255)
|
||||
chatType("SPELL_TRADESKILLS", 0, 63, 255, 255, 255)
|
||||
chatType("SPELL_DAMAGESHIELDS_ON_SELF", 0, 64, 255, 255, 255)
|
||||
chatType("SPELL_DAMAGESHIELDS_ON_OTHERS", 0, 65, 255, 255, 255)
|
||||
chatType("SPELL_AURA_GONE_SELF", 0, 66, 255, 255, 255)
|
||||
chatType("SPELL_AURA_GONE_PARTY", 0, 67, 255, 255, 255)
|
||||
chatType("SPELL_AURA_GONE_OTHER", 0, 68, 255, 255, 255)
|
||||
chatType("SPELL_ITEM_ENCHANTMENTS", 0, 69, 255, 255, 255)
|
||||
chatType("SPELL_BREAK_AURA", 0, 70, 255, 255, 255)
|
||||
chatType("SPELL_PERIODIC_SELF_DAMAGE", 0, 71, 255, 255, 255)
|
||||
chatType("SPELL_PERIODIC_SELF_BUFFS", 0, 72, 255, 255, 255)
|
||||
chatType("SPELL_PERIODIC_PARTY_DAMAGE", 0, 73, 255, 255, 255)
|
||||
chatType("SPELL_PERIODIC_PARTY_BUFFS", 0, 74, 255, 255, 255)
|
||||
chatType("SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE", 0, 75, 255, 255, 255)
|
||||
chatType("SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS", 0, 76, 255, 255, 255)
|
||||
chatType("SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE", 0, 77, 255, 255, 255)
|
||||
chatType("SPELL_PERIODIC_HOSTILEPLAYER_BUFFS", 0, 78, 255, 255, 255)
|
||||
chatType("SPELL_PERIODIC_CREATURE_DAMAGE", 0, 79, 255, 255, 255)
|
||||
chatType("SPELL_PERIODIC_CREATURE_BUFFS", 0, 80, 255, 255, 255)
|
||||
chatType("SPELL_FAILED_LOCALPLAYER", 0, 81, 255, 255, 255)
|
||||
chatType("BG_SYSTEM_NEUTRAL", 0, 83, 255, 120, 10)
|
||||
chatType("BG_SYSTEM_ALLIANCE", 0, 84, 0, 174, 239)
|
||||
chatType("BG_SYSTEM_HORDE", 0, 85, 255, 0, 0)
|
||||
chatType("COMBAT_FACTION_CHANGE", 0, 86, 128, 128, 255)
|
||||
chatType("MONEY", 0, 87, 255, 255, 0)
|
||||
chatType("RAID_LEADER", 0, 88, 255, 219, 183)
|
||||
chatType("RAID_WARNING", 0, 89, 255, 219, 183)
|
||||
chatType("RAID_BOSS_EMOTE", 0, 91, 255, 219, 183)
|
||||
chatType("FILTERED", 0, 92, 255, 0, 0)
|
||||
chatType("BATTLEGROUND", 1, 93, 255, 127, 0)
|
||||
chatType("BATTLEGROUND_LEADER", 0, 94, 255, 219, 183)
|
||||
|
||||
-- ── BenillaFCF: the FloatingChatFrame behaviors, transcribed (ref FloatingChatFrame.lua)
|
||||
-- Quoted constants: CHAT_TAB_SHOW_DELAY=0.2, CHAT_FRAME_FADE_TIME=0.15,
|
||||
-- DEFAULT_CHATFRAME_ALPHA=0.25, CHAT_BUTTON_FLASH_TIME=0.5,
|
||||
@@ -111,7 +278,7 @@
|
||||
-- fade over 0.15s, conceal on leave; a flashing tab forces the tabs visible; the
|
||||
-- selected window's textures ride the same fade toward alpha 0.25 (the chat-cache
|
||||
-- default window alpha is 0 — the box only exists on hover).
|
||||
function BenillaFCF_OnUpdate(elapsed)
|
||||
function FCF_OnUpdate(elapsed)
|
||||
local s = BenillaFCF
|
||||
local x, y = GetCursorPosition()
|
||||
if overDock(x, y) then
|
||||
@@ -171,7 +338,8 @@
|
||||
|
||||
-- ChatFrame_OnUpdate transcription: the bottom button's blink while scrolled up
|
||||
-- (CHAT_BUTTON_FLASH_TIME = 0.5s toggle; hidden the moment the view is AtBottom).
|
||||
function BenillaChatFrame_OnUpdate(frame, elapsed)
|
||||
function ChatFrame_OnUpdate(elapsed)
|
||||
local frame = this
|
||||
local flash = getglobal(frame:GetName() .. "BottomButtonFlash")
|
||||
if not flash then return end
|
||||
if frame:AtBottom() then
|
||||
@@ -235,7 +403,8 @@
|
||||
BENILLA_UIMENU_NUMBUTTONS = 32
|
||||
BENILLA_UIMENU_TIMEOUT = 2.0
|
||||
|
||||
function BenillaUIMenu_Initialize(menu)
|
||||
function UIMenu_Initialize()
|
||||
local menu = this
|
||||
menu.numButtons = 0
|
||||
menu.subMenu = ""
|
||||
local name = menu:GetName()
|
||||
@@ -251,7 +420,8 @@
|
||||
end
|
||||
|
||||
-- Rows past the button pool are dropped (the ref message()s; nothing we author overflows).
|
||||
function BenillaUIMenu_AddButton(menu, text, shortcut, func, nested)
|
||||
function UIMenu_AddButton(text, shortcut, func, nested)
|
||||
local menu = this
|
||||
local id = menu.numButtons + 1
|
||||
if id > BENILLA_UIMENU_NUMBUTTONS then return end
|
||||
menu.numButtons = id
|
||||
@@ -268,12 +438,12 @@
|
||||
menu:SetHeight(id * BENILLA_UIMENU_BUTTON_HEIGHT + BENILLA_UIMENU_BORDER_HEIGHT * 2)
|
||||
end
|
||||
|
||||
function BenillaUIMenu_OnShow()
|
||||
function UIMenu_OnShow()
|
||||
this.timeleft = BENILLA_UIMENU_TIMEOUT
|
||||
this.counting = 1
|
||||
end
|
||||
|
||||
function BenillaUIMenu_OnUpdate(elapsed)
|
||||
function UIMenu_OnUpdate(elapsed)
|
||||
if this.counting == 1 then
|
||||
local timeleft = (this.timeleft or BENILLA_UIMENU_TIMEOUT) - elapsed
|
||||
if timeleft <= 0 then
|
||||
@@ -284,29 +454,29 @@
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaUIMenu_StartCounting(menu)
|
||||
function UIMenu_StartCounting(menu)
|
||||
menu.counting = 1
|
||||
if menu.parentMenu then BenillaUIMenu_StartCounting(getglobal(menu.parentMenu)) end
|
||||
if menu.parentMenu then UIMenu_StartCounting(getglobal(menu.parentMenu)) end
|
||||
end
|
||||
|
||||
function BenillaUIMenu_StopCounting(menu)
|
||||
function UIMenu_StopCounting(menu)
|
||||
menu.counting = 0
|
||||
menu.timeleft = BENILLA_UIMENU_TIMEOUT
|
||||
if menu.parentMenu then BenillaUIMenu_StopCounting(getglobal(menu.parentMenu)) end
|
||||
if menu.parentMenu then UIMenu_StopCounting(getglobal(menu.parentMenu)) end
|
||||
end
|
||||
|
||||
function BenillaUIMenuButton_OnLoad()
|
||||
function UIMenuButton_OnLoad()
|
||||
this:SetPoint("TOP", this:GetParent():GetName(), "TOP", 0,
|
||||
-((this:GetID() - 1) * BENILLA_UIMENU_BUTTON_HEIGHT) - BENILLA_UIMENU_BORDER_HEIGHT)
|
||||
end
|
||||
|
||||
function BenillaUIMenuButton_OnClick()
|
||||
function UIMenuButton_OnClick()
|
||||
if this.func then this.func() end
|
||||
this:GetParent():Hide()
|
||||
PlaySound("UChatScrollButton")
|
||||
end
|
||||
|
||||
function BenillaUIMenuButton_OnEnter()
|
||||
function UIMenuButton_OnEnter()
|
||||
local nested = this.nested
|
||||
if nested then
|
||||
local menu = getglobal(nested)
|
||||
@@ -317,15 +487,15 @@
|
||||
menu:SetPoint("BOTTOMLEFT", this:GetName(), "BOTTOMRIGHT", 10, -12)
|
||||
menu:Show()
|
||||
end
|
||||
BenillaUIMenu_StopCounting(menu)
|
||||
UIMenu_StopCounting(menu)
|
||||
else
|
||||
BenillaUIMenu_StopCounting(this:GetParent())
|
||||
UIMenu_StopCounting(this:GetParent())
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaUIMenuButton_OnLeave()
|
||||
BenillaUIMenu_StartCounting(this:GetParent())
|
||||
if this.nested then BenillaUIMenu_StartCounting(getglobal(this.nested)) end
|
||||
function UIMenuButton_OnLeave()
|
||||
UIMenu_StartCounting(this:GetParent())
|
||||
if this.nested then UIMenu_StartCounting(getglobal(this.nested)) end
|
||||
end
|
||||
|
||||
-- ── The chat menu rows (ref ChatMenu_* in ChatFrame.lua, adapted): every type row
|
||||
@@ -342,16 +512,16 @@
|
||||
eb:SetText(prefix .. (eb:GetText() or ""))
|
||||
end
|
||||
|
||||
function BenillaChatMenu_OnLoad()
|
||||
BenillaUIMenu_Initialize(this)
|
||||
BenillaUIMenu_AddButton(this, "Say", "/s", function() BenillaChatMenu_Type("/s ") end)
|
||||
BenillaUIMenu_AddButton(this, "Party Chat", "/p", function() BenillaChatMenu_Type("/p ") end)
|
||||
BenillaUIMenu_AddButton(this, "Guild Chat", "/g", function() BenillaChatMenu_Type("/g ") end)
|
||||
BenillaUIMenu_AddButton(this, "Yell", "/y", function() BenillaChatMenu_Type("/y ") end)
|
||||
BenillaUIMenu_AddButton(this, "Whisper", "/w", function() BenillaChatMenu_Type("/w ") end)
|
||||
BenillaUIMenu_AddButton(this, "Emote", "/e", function() BenillaChatMenu_Type("/e ") end, "EmoteMenu")
|
||||
BenillaUIMenu_AddButton(this, "Reply", "/r", function() BenillaChatMenu_Type("/r ") end)
|
||||
BenillaUIMenu_AddButton(this, "Voice Emote", nil, nil, "VoiceMacroMenu")
|
||||
function ChatMenu_OnLoad()
|
||||
UIMenu_Initialize()
|
||||
UIMenu_AddButton("Say", "/s", function() BenillaChatMenu_Type("/s ") end)
|
||||
UIMenu_AddButton("Party Chat", "/p", function() BenillaChatMenu_Type("/p ") end)
|
||||
UIMenu_AddButton("Guild Chat", "/g", function() BenillaChatMenu_Type("/g ") end)
|
||||
UIMenu_AddButton("Yell", "/y", function() BenillaChatMenu_Type("/y ") end)
|
||||
UIMenu_AddButton("Whisper", "/w", function() BenillaChatMenu_Type("/w ") end)
|
||||
UIMenu_AddButton("Emote", "/e", function() BenillaChatMenu_Type("/e ") end, "EmoteMenu")
|
||||
UIMenu_AddButton("Reply", "/r", function() BenillaChatMenu_Type("/r ") end)
|
||||
UIMenu_AddButton("Voice Emote", nil, nil, "VoiceMacroMenu")
|
||||
end
|
||||
|
||||
-- An emote row submits its /command through the REAL chat pipeline (SubmitChatInput →
|
||||
@@ -379,11 +549,11 @@
|
||||
end
|
||||
|
||||
function BenillaOnEmoteMenuLoad(list)
|
||||
BenillaUIMenu_Initialize(this)
|
||||
UIMenu_Initialize()
|
||||
this.parentMenu = "ChatMenu"
|
||||
for _, cmd in ipairs(list) do
|
||||
local c = cmd
|
||||
BenillaUIMenu_AddButton(this, cmd, nil, function() BenillaEmoteMenu_Click(c) end)
|
||||
UIMenu_AddButton(cmd, nil, function() BenillaEmoteMenu_Click(c) end)
|
||||
end
|
||||
end
|
||||
]]></Script>
|
||||
@@ -560,8 +730,8 @@
|
||||
if event == "EXECUTE_CHAT_LINE" then SubmitChatInput(arg1) end
|
||||
</OnEvent>
|
||||
<OnUpdate>
|
||||
BenillaFCF_OnUpdate(arg1)
|
||||
BenillaChatFrame_OnUpdate(this, arg1)
|
||||
FCF_OnUpdate(arg1)
|
||||
ChatFrame_OnUpdate(arg1)
|
||||
</OnUpdate>
|
||||
<OnMouseWheel>
|
||||
-- WoW wheel convention: +1 per notch up, -1 down. Up = older messages.
|
||||
@@ -694,16 +864,16 @@
|
||||
<Size><AbsDimension x="128" y="16"/></Size>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
BenillaUIMenuButton_OnLoad()
|
||||
UIMenuButton_OnLoad()
|
||||
</OnLoad>
|
||||
<OnClick>
|
||||
BenillaUIMenuButton_OnClick()
|
||||
UIMenuButton_OnClick()
|
||||
</OnClick>
|
||||
<OnEnter>
|
||||
BenillaUIMenuButton_OnEnter()
|
||||
UIMenuButton_OnEnter()
|
||||
</OnEnter>
|
||||
<OnLeave>
|
||||
BenillaUIMenuButton_OnLeave()
|
||||
UIMenuButton_OnLeave()
|
||||
</OnLeave>
|
||||
</Scripts>
|
||||
<Layers>
|
||||
@@ -762,13 +932,13 @@
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnShow>
|
||||
BenillaUIMenu_OnShow()
|
||||
UIMenu_OnShow()
|
||||
PlaySound("UChatScrollButton")
|
||||
this:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b)
|
||||
this:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
|
||||
</OnShow>
|
||||
<OnUpdate>
|
||||
BenillaUIMenu_OnUpdate(arg1)
|
||||
UIMenu_OnUpdate(arg1)
|
||||
</OnUpdate>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
@@ -819,10 +989,10 @@
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
BenillaChatMenu_OnLoad()
|
||||
ChatMenu_OnLoad()
|
||||
</OnLoad>
|
||||
<OnShow>
|
||||
BenillaUIMenu_OnShow()
|
||||
UIMenu_OnShow()
|
||||
EmoteMenu:Hide()
|
||||
VoiceMacroMenu:Hide()
|
||||
this:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b)
|
||||
@@ -903,6 +1073,14 @@
|
||||
chat-cache window COLOR, which ships 0 0 0; the ChatFrameBackground art itself is
|
||||
white, so untinted it reads as a white wash over the world). -->
|
||||
<Script><![CDATA[
|
||||
-- `DEFAULT_CHAT_FRAME = ChatFrame1` — the reference's own line (ChatFrame.lua), and the
|
||||
-- name half of FrameXML actually prints through. It sits here rather than in the header
|
||||
-- script because a <Script> block runs where it stands: at the top of this file
|
||||
-- ChatFrame1 does not exist yet. Measured demand: 3 of the 218 vanilla-corpus addons
|
||||
-- die on `attempt to index global 'DEFAULT_CHAT_FRAME'` without it, and many more reach
|
||||
-- it on a later path.
|
||||
DEFAULT_CHAT_FRAME = ChatFrame1
|
||||
|
||||
BenillaFCF_SelectDock(1)
|
||||
for i = 1, 2 do
|
||||
for _, t in ipairs(BenillaFCF_Textures(i)) do
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
<Ui>
|
||||
<!--
|
||||
Interface\FrameXML\ColorPickerFrame.xml, transcribed — the shared colour dialog. Line
|
||||
numbers below are that file's (336 lines, XML only; it ships no `.lua`, its whole script is
|
||||
inline and the one function that drives it, `UIDropDownMenuButton_OpenColorPicker`, lives in
|
||||
UIDropDownMenu.lua).
|
||||
|
||||
WHY IT IS HERE, in the arc's own terms: **86 of the 218 corpus addons reach
|
||||
`ColorPickerFrame`, 80 reach `OpacitySliderFrame`** — and 1207's lesson applies before those
|
||||
numbers do, so read the lines. 64 of the files are copies of `Dewdrop-2.0.lua`, which does
|
||||
not roll its own picker: it drives THIS one, with the exact block below (Dewdrop-2.0.lua
|
||||
l.425-465, verbatim from a corpus copy):
|
||||
|
||||
ColorPickerFrame.func = function() … ColorPickerFrame:GetColorRGB() …
|
||||
OpacitySliderFrame:GetValue() … end
|
||||
ColorPickerFrame.hasOpacity = this.hasOpacity
|
||||
ColorPickerFrame.opacityFunc = ColorPickerFrame.func
|
||||
ColorPickerFrame.opacity = 1 - this.opacity
|
||||
ColorPickerFrame:SetColorRGB(this.r, this.g, this.b)
|
||||
ColorPickerFrame.cancelFunc = function() … end
|
||||
ShowUIPanel(ColorPickerFrame)
|
||||
|
||||
Dewdrop is *the* Ace2 menu library, so that block is how EVERY colour option in EVERY Ace2
|
||||
addon's config menu opens. On top of it sit ~14 addons that name the frame directly
|
||||
(TipBuddy, CT_UnitFrames, CT_RaidAssist, CooldownCount, PowerAuras, Bongos_Options,
|
||||
MikScrollingBattleText, EQL3, RangeColor, ColorPickerPlus, QuestHistory, Radar,
|
||||
IgniteStatus, Bagnon_Core), and `AceConsole-2.0.lua` l.1402-1406, which does
|
||||
`ColorPickerOkayButton:GetScript("OnClick")` and chains it — so the two BUTTON names are API,
|
||||
not decoration.
|
||||
|
||||
The widget itself (`<ColorSelect>`, `SetColorRGB`/`GetColorRGB`/`OnColorSelect`) is
|
||||
engine-side, in `benilla-ui/src/script/colorselect.rs` — read its module doc before touching
|
||||
anything colour-valued here. Two things live there that are not obvious and are byte-verified
|
||||
(wow-re `system/ui/scratch/colorselect-color-law.md`, a §5 cross-check dispatched from this
|
||||
repo for exactly this window): the widget's state is **HSV floats**, not RGB, and the
|
||||
set→read round trip is deliberately **not** the identity — 9.75 % of colours come back one
|
||||
step low on a channel, and the FrameXML `GetColorRGB … SetColorRGB` idiom ratchets. It also
|
||||
says why the widget could not be a few methods hung on this one frame (TipBuddy declares two
|
||||
`<ColorSelect>` frames of its own).
|
||||
|
||||
── THE ONE THING THIS WINDOW CANNOT DO, AND IT IS THE BIG ONE ──────────────────────────────
|
||||
**There is no colour wheel, so there is no way to CHOOSE a colour in it yet.** The reference
|
||||
ends its `<ColorSelect>` with four sub-elements this file does not carry (ref l.186-221):
|
||||
|
||||
<ColorWheelTexture name="ColorPickerWheel"/> 128x128, TOPLEFT (16, -32)
|
||||
<ColorWheelThumbTexture/> 10x10, Interface\Buttons\UI-ColorPicker-Buttons,
|
||||
TexCoords 0 / 0.15625 / 0 / 0.625
|
||||
<ColorValueTexture/> 32x128, LEFT of ColorPickerWheel's RIGHT at (24, 0)
|
||||
<ColorValueThumbTexture/> 48x14, same sheet, TexCoords 0.25 / 1.0 / 0 / 0.875
|
||||
|
||||
Look at what the first and third do NOT have: a `file=`. The hue wheel and the value strip
|
||||
are **generated by the client**, not loaded — there is no BLP anywhere in the MPQ chain that
|
||||
is the colour wheel, only the two little thumb markers that ride on top of it. Rendering
|
||||
them is an app-renderer job (an HSV wheel + a value ramp drawn for a `FrameKind::ColorSelect`
|
||||
rect), not a FrameXML one, so the elements are omitted rather than shipped inert — their
|
||||
geometry is recorded above, and dropping them back in is the day that renderer exists.
|
||||
(RF-28 has the loader hooks: `<ColorWheelTexture>` `0x78b580`, `<ColorWheelThumbTexture>`
|
||||
`0x78b850`, `<ColorValueTexture>` `0x78b8a0`, `<ColorValueThumbTexture>` `0x78ba90` — and the
|
||||
INTERACTION half is already carved too, so that day is cheaper than it looks: wow-re's
|
||||
`colorselect-color-law.md` §5 has the press/drag handler verbatim — a click jumps the colour
|
||||
immediately, `H = (atan2(ny, nx) + π)·180/π`, `S = min(radius, 1)`, `V = clamp((y − bottom) /
|
||||
(top − bottom), 0, 1)`, all raw f32 straight into the widget's HSV, no quantizer.)
|
||||
|
||||
Everything ELSE works, and it is most of what the corpus actually exercises: an addon sets a
|
||||
colour and the swatch shows it, the opacity slider is live and drives `opacityFunc` on every
|
||||
drag, Okay commits through `func` + `opacityFunc`, Cancel restores through `cancelFunc`, and
|
||||
both buttons are real named buttons whose `OnClick` an addon can fetch and chain. The
|
||||
reference's own geometry is kept exactly — including the 128x128 gap on the left where the
|
||||
wheel belongs, and the 365-wide / 305-wide `OnShow` swap around it — because half a window at
|
||||
the right size is honest and a re-designed window is not.
|
||||
|
||||
OTHER DEVIATIONS, each forced:
|
||||
· **`<OnKeyDown>` (ref l.177-184) is not transcribed** — this engine gives plain frames no
|
||||
raw key events (SCRIPT_KINDS has no `OnKeyDown`), the same wall AddonList.xml hit
|
||||
(decision 1197). Its ESCAPE arm is `HideUIPanel(this) + cancelFunc(previousValues)`, i.e.
|
||||
exactly what the Cancel button does, so the arm is carried in UiPanels.xml's ESC chain as
|
||||
`ColorPickerCancelButton:Click()` — the reference's own idiom for this (its game-menu
|
||||
chain cancel-clicks `OptionsFrameCancel` the same way). ESC therefore CANCELS, it does not
|
||||
merely hide, which is the half people get wrong.
|
||||
· **`OpacityFrame` (ref l.223-335) is not transcribed.** The second frame in the reference's
|
||||
file: a standalone 80x180 opacity popup with its own slider and a fullscreen
|
||||
click-off-to-close button, used by FloatingChatFrame's chat-window alpha. **Zero corpus
|
||||
callers** and no benilla consumer, so it waits for its first customer (decision 1195). Its
|
||||
globals are `OpacityFrame`, `OpacityFrameSlider`, `OpacityFrameCloseButton`, and
|
||||
`OpacityFrame.opacityFunc`/`.saveOpacityFunc`.
|
||||
· **The unguarded `ColorPickerFrame.func()` in Okay's OnClick (ref l.89) is KEPT unguarded.**
|
||||
It is one of the few places quirk-for-quirk beats tidying: an addon that opened this window
|
||||
without setting `func` errors in the real client too, and a guard here would swallow that
|
||||
into a window that silently does nothing.
|
||||
· `<ColorSelect>` carries no `<Layers>` `hidden` art beyond the reference's three regions;
|
||||
`ColorSwatch` and `ColorPickerFrameHeader` keep their unprefixed reference names, which is
|
||||
required — `ColorPickerFrame.xml`'s own `<OnColorSelect>` addresses `ColorSwatch` by name,
|
||||
and so would any addon that re-skins the window.
|
||||
-->
|
||||
|
||||
<Script><![CDATA[
|
||||
-- ref GlobalStrings.lua l.770 / l.132 / l.1458 (enUS), in the `X = X or "…"` FALLBACK form
|
||||
-- this file set uses throughout: the app runs the real localized GlobalStrings.lua off the
|
||||
-- patch chain BEFORE the shipped XML, so these only bind on a surface without it (a headless
|
||||
-- test). `text="COLOR_PICKER"` / `text="OKAY"` / `text="CANCEL"` below are global-string
|
||||
-- KEYS, not literals — the loader resolves each through the Lua global (rf28 `0x703bf0`).
|
||||
COLOR_PICKER = COLOR_PICKER or "Color Picker"
|
||||
OKAY = OKAY or "Okay"
|
||||
CANCEL = CANCEL or "Cancel"
|
||||
]]></Script>
|
||||
|
||||
<!-- ref l.3-9. 365 wide is the WITH-opacity width; OnShow narrows it to 305 when an addon did
|
||||
not ask for the slider. `movable="true"` is the reference's (this engine only honours it
|
||||
once something calls StartMoving, which nothing here does — same as the reference, whose
|
||||
frame has no drag handler either: the attribute is there for addons). -->
|
||||
<ColorSelect name="ColorPickerFrame" toplevel="true" parent="UIParent" movable="true" enableMouse="true" enableKeyboard="true" hidden="true">
|
||||
<Size><AbsDimension x="365" y="200"/></Size>
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
|
||||
<BackgroundInsets><AbsInset left="11" right="12" top="12" bottom="11"/></BackgroundInsets>
|
||||
<TileSize><AbsValue val="32"/></TileSize>
|
||||
<EdgeSize><AbsValue val="32"/></EdgeSize>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<!-- ref l.23-35. The live preview: a 32x32 solid quad repainted from OnColorSelect.
|
||||
Unprefixed name, the reference's own — see the header. -->
|
||||
<Texture name="ColorSwatch">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="225" y="-32"/></Offset></Anchor></Anchors>
|
||||
<Color r="1.0" g="1.0" b="1.0" a="1.0"/>
|
||||
</Texture>
|
||||
<!-- ref l.36-47. The same header plate the game menu and every dialog wears. -->
|
||||
<Texture name="ColorPickerFrameHeader" file="Interface\DialogFrame\UI-DialogBox-Header">
|
||||
<Size><AbsDimension x="256" y="64"/></Size>
|
||||
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="0" y="12"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
<!-- ref l.48-56. Unnamed in the reference too. -->
|
||||
<FontString inherits="GameFontNormal" text="COLOR_PICKER">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeTo="ColorPickerFrameHeader"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<!-- ref l.60-77. Cancel is declared FIRST because Okay anchors to it. Its OnClick is the
|
||||
reference's, verbatim: hide, then hand the addon back what it had. -->
|
||||
<Button name="ColorPickerCancelButton" inherits="GameMenuButtonTemplate" text="CANCEL">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-10" y="10"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
HideUIPanel(this:GetParent());
|
||||
if ( ColorPickerFrame.cancelFunc ) then
|
||||
ColorPickerFrame.cancelFunc(ColorPickerFrame.previousValues);
|
||||
end
|
||||
PlaySound("igMainMenuOptionCheckBoxOn");
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<!-- ref l.78-96. `func()` unguarded, `opacityFunc()` guarded — the reference's own
|
||||
asymmetry, kept (header). AceConsole-2.0 fetches THIS handler with
|
||||
`ColorPickerOkayButton:GetScript("OnClick")` and calls it from its own replacement,
|
||||
so the name and the script slot are both load-bearing. -->
|
||||
<Button name="ColorPickerOkayButton" inherits="GameMenuButtonTemplate" text="OKAY">
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativeTo="ColorPickerCancelButton" relativePoint="LEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
HideUIPanel(this:GetParent());
|
||||
ColorPickerFrame.func();
|
||||
if ( ColorPickerFrame.opacityFunc ) then
|
||||
ColorPickerFrame.opacityFunc();
|
||||
end
|
||||
PlaySound("igMainMenuOptionCheckBoxOn");
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<!-- ref l.97-158. A real Slider, so it drags: 0..1 in 0.01 steps, VERTICAL, seated to
|
||||
the RIGHT of the swatch. Its `$parentText` label is EMPTY in the reference (only
|
||||
OpacityFrame's copy carries text="OPACITY") — transcribed as written, not
|
||||
"improved". Note the sense: 1 is FULLY OPAQUE, and every caller inverts
|
||||
(`ColorPickerFrame.opacity = 1 - this.opacity`, and Dewdrop's func reads back
|
||||
`1 - OpacitySliderFrame:GetValue()`). -->
|
||||
<Slider name="OpacitySliderFrame" orientation="VERTICAL" minValue="0" maxValue="1" valueStep="0.01" defaultValue="1">
|
||||
<Size><AbsDimension x="16" y="128"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="ColorSwatch" relativePoint="TOPRIGHT"><Offset><AbsDimension x="32" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Backdrop bgFile="Interface\Buttons\UI-SliderBar-Background" edgeFile="Interface\Buttons\UI-SliderBar-Border" tile="true">
|
||||
<EdgeSize><AbsValue val="8"/></EdgeSize>
|
||||
<TileSize><AbsValue val="8"/></TileSize>
|
||||
<BackgroundInsets><AbsInset left="3" right="3" top="6" bottom="6"/></BackgroundInsets>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="$parentText" inherits="GameFontNormalSmall">
|
||||
<Anchors><Anchor point="BOTTOM" relativePoint="TOP"/></Anchors>
|
||||
</FontString>
|
||||
<FontString inherits="GameFontHighlightSmall" text="0%">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="2" y="3"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString inherits="GameFontHighlightSmall" text="100%">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-2" y="3"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnValueChanged>
|
||||
if ( ColorPickerFrame.opacityFunc ) then
|
||||
ColorPickerFrame.opacityFunc();
|
||||
end
|
||||
</OnValueChanged>
|
||||
</Scripts>
|
||||
<ThumbTexture file="Interface\Buttons\UI-SliderBar-Button-Vertical">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
</ThumbTexture>
|
||||
</Slider>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<!-- ref l.161-170. The opacity slider is opt-in per caller, and the window narrows by
|
||||
the slider's 60 units when nobody asked for it. -->
|
||||
<OnShow>
|
||||
if ( this.hasOpacity ) then
|
||||
OpacitySliderFrame:Show();
|
||||
OpacitySliderFrame:SetValue(this.opacity);
|
||||
this:SetWidth(365);
|
||||
else
|
||||
OpacitySliderFrame:Hide();
|
||||
this:SetWidth(305);
|
||||
end
|
||||
</OnShow>
|
||||
<!-- ref l.171-176. The widget's own slot, fired by SetColorRGB with the colour the
|
||||
widget now holds (colorselect.rs). This is the ONLY thing that paints the swatch —
|
||||
no caller touches it — and it is also where an addon's live-preview `func` runs. -->
|
||||
<OnColorSelect>
|
||||
ColorSwatch:SetTexture(arg1, arg2, arg3);
|
||||
if ( this.func ) then
|
||||
this.func();
|
||||
end
|
||||
</OnColorSelect>
|
||||
</Scripts>
|
||||
</ColorSelect>
|
||||
</Ui>
|
||||
@@ -197,10 +197,10 @@
|
||||
end
|
||||
elseif event == "UNIT_POWER_UPDATE" then
|
||||
-- Ref: UNIT_MANA + UnitMana/UnitManaMax (header deviation — the Era surface).
|
||||
local mana = UnitPower("player")
|
||||
local mana = UnitMana("player")
|
||||
local powerType = UnitPowerType("player")
|
||||
if arg1 == "player" then
|
||||
if mana / UnitPowerMax("player") <= COMBAT_TEXT_LOW_MANA_THRESHOLD and powerType == 0 then
|
||||
if mana / UnitManaMax("player") <= COMBAT_TEXT_LOW_MANA_THRESHOLD and powerType == 0 then
|
||||
if not CombatText.lowMana then
|
||||
messageType = "MANA_LOW"
|
||||
CombatText.lowMana = 1
|
||||
|
||||
@@ -58,46 +58,46 @@
|
||||
</Layers>
|
||||
</Frame>
|
||||
|
||||
<Frame name="BenillaComboFrame" frameStrata="MEDIUM" toplevel="true" parent="UIParent" hidden="true" alpha="0">
|
||||
<Frame name="ComboFrame" frameStrata="MEDIUM" toplevel="true" parent="UIParent" hidden="true" alpha="0">
|
||||
<Size><AbsDimension x="256" y="32"/></Size>
|
||||
<!-- ref l.56-62: TOPRIGHT of the target frame, (-44,-9). -->
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" relativeTo="BenillaTargetFrame">
|
||||
<Anchor point="TOPRIGHT" relativeTo="TargetFrame">
|
||||
<Offset><AbsDimension x="-44" y="-9"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Frames>
|
||||
<Frame name="BenillaComboPoint1" inherits="BenillaComboPointTemplate">
|
||||
<Frame name="ComboPoint1" inherits="BenillaComboPointTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaComboPoint2" inherits="BenillaComboPointTemplate">
|
||||
<Frame name="ComboPoint2" inherits="BenillaComboPointTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeTo="BenillaComboPoint1" relativePoint="BOTTOM">
|
||||
<Anchor point="TOP" relativeTo="ComboPoint1" relativePoint="BOTTOM">
|
||||
<Offset><AbsDimension x="7" y="4"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaComboPoint3" inherits="BenillaComboPointTemplate">
|
||||
<Frame name="ComboPoint3" inherits="BenillaComboPointTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeTo="BenillaComboPoint2" relativePoint="BOTTOM">
|
||||
<Anchor point="TOP" relativeTo="ComboPoint2" relativePoint="BOTTOM">
|
||||
<Offset><AbsDimension x="5" y="2"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaComboPoint4" inherits="BenillaComboPointTemplate">
|
||||
<Frame name="ComboPoint4" inherits="BenillaComboPointTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeTo="BenillaComboPoint3" relativePoint="BOTTOM">
|
||||
<Anchor point="TOP" relativeTo="ComboPoint3" relativePoint="BOTTOM">
|
||||
<Offset><AbsDimension x="2" y="1"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Frame>
|
||||
<!-- The fifth is the odd one out: the reference sizes it 15×18 (ref l.101-104). -->
|
||||
<Frame name="BenillaComboPoint5" inherits="BenillaComboPointTemplate">
|
||||
<Frame name="ComboPoint5" inherits="BenillaComboPointTemplate">
|
||||
<Size><AbsDimension x="15" y="18"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeTo="BenillaComboPoint4" relativePoint="BOTTOM">
|
||||
<Anchor point="TOP" relativeTo="ComboPoint4" relativePoint="BOTTOM">
|
||||
<Offset><AbsDimension x="0" y="1"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
@@ -107,11 +107,11 @@
|
||||
<OnLoad>
|
||||
this:RegisterEvent("PLAYER_TARGET_CHANGED");
|
||||
this:RegisterEvent("PLAYER_COMBO_POINTS");
|
||||
BenillaComboPoint1Highlight:SetAlpha(0);
|
||||
BenillaComboPoint1Shine:SetAlpha(0);
|
||||
ComboPoint1Highlight:SetAlpha(0);
|
||||
ComboPoint1Shine:SetAlpha(0);
|
||||
</OnLoad>
|
||||
<OnEvent>
|
||||
BenillaComboPointsFrame_OnEvent();
|
||||
ComboPointsFrame_OnEvent();
|
||||
</OnEvent>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
@@ -130,22 +130,22 @@
|
||||
-- ref ComboFrame.lua `ComboPointsFrame_OnEvent`. Only points ABOVE the last count animate
|
||||
-- (`i > COMBO_FRAME_LAST_NUM_POINTS`) — a repaint at the same count must not re-flare every
|
||||
-- dot, which is why the last count is remembered across events rather than recomputed.
|
||||
function BenillaComboPointsFrame_OnEvent()
|
||||
function ComboPointsFrame_OnEvent()
|
||||
local comboPoints = GetComboPoints();
|
||||
if comboPoints > 0 then
|
||||
if not BenillaComboFrame:IsVisible() then
|
||||
BenillaComboFrame:Show();
|
||||
UIFrameFadeIn(BenillaComboFrame, COMBOFRAME_FADE_IN);
|
||||
if not ComboFrame:IsVisible() then
|
||||
ComboFrame:Show();
|
||||
UIFrameFadeIn(ComboFrame, COMBOFRAME_FADE_IN);
|
||||
end
|
||||
for i = 1, MAX_COMBO_POINTS do
|
||||
local comboPointHighlight = getglobal("BenillaComboPoint"..i.."Highlight");
|
||||
local comboPointShine = getglobal("BenillaComboPoint"..i.."Shine");
|
||||
local comboPointHighlight = getglobal("ComboPoint"..i.."Highlight");
|
||||
local comboPointShine = getglobal("ComboPoint"..i.."Shine");
|
||||
if i <= comboPoints then
|
||||
if i > COMBO_FRAME_LAST_NUM_POINTS then
|
||||
local fadeInfo = {};
|
||||
fadeInfo.mode = "IN";
|
||||
fadeInfo.timeToFade = COMBOFRAME_HIGHLIGHT_FADE_IN;
|
||||
fadeInfo.finishedFunc = BenillaComboPointShineFadeIn;
|
||||
fadeInfo.finishedFunc = ComboPointShineFadeIn;
|
||||
fadeInfo.finishedArg1 = comboPointShine;
|
||||
UIFrameFade(comboPointHighlight, fadeInfo);
|
||||
end
|
||||
@@ -155,25 +155,25 @@
|
||||
end
|
||||
end
|
||||
else
|
||||
BenillaComboPoint1Highlight:SetAlpha(0);
|
||||
BenillaComboPoint1Shine:SetAlpha(0);
|
||||
BenillaComboFrame:Hide();
|
||||
ComboPoint1Highlight:SetAlpha(0);
|
||||
ComboPoint1Shine:SetAlpha(0);
|
||||
ComboFrame:Hide();
|
||||
end
|
||||
COMBO_FRAME_LAST_NUM_POINTS = comboPoints;
|
||||
end
|
||||
|
||||
function BenillaComboPointShineFadeIn(frame)
|
||||
function ComboPointShineFadeIn(frame)
|
||||
local fadeInfo = {};
|
||||
fadeInfo.mode = "IN";
|
||||
fadeInfo.timeToFade = COMBOFRAME_SHINE_FADE_IN;
|
||||
fadeInfo.finishedFunc = BenillaComboPointShineFadeOut;
|
||||
fadeInfo.finishedFunc = ComboPointShineFadeOut;
|
||||
fadeInfo.finishedArg1 = frame:GetName();
|
||||
UIFrameFade(frame, fadeInfo);
|
||||
end
|
||||
|
||||
-- The name round-trip is the reference's own workaround ("hack since a frame can't have a
|
||||
-- reference to itself in it") — kept, so the chain reads as its source does.
|
||||
function BenillaComboPointShineFadeOut(frameName)
|
||||
function ComboPointShineFadeOut(frameName)
|
||||
UIFrameFadeOut(getglobal(frameName), COMBOFRAME_SHINE_FADE_OUT);
|
||||
end
|
||||
]]>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
session), the UIPanel slot system (carries NO hardcoded <Anchors> — ShowUIPanel/SetLeftFrame
|
||||
SetPoints it), OnShow/OnHide PlaySound pair.
|
||||
- TradeSkillFrame.xml — the FLAT layout constraint (below), the faux-scroll list kit
|
||||
(BenillaFauxScrollFrameTemplate, ScrollTemplates.xml), the locally-reconstructed
|
||||
(FauxScrollFrameTemplate, ScrollTemplates.xml), the locally-reconstructed
|
||||
UIPanelButtonTemplate/close-button/item-button idioms (no such templates ship in this house
|
||||
dialect — TalentFrame.xml's own ENGINE API note, restated per-window, StackSplit.xml's own
|
||||
"own local virtual template, not a cross-file inherits" convention), the persistent
|
||||
@@ -316,8 +316,8 @@
|
||||
end
|
||||
|
||||
local scrollFrame = getglobal("BenillaCraftListScrollFrame")
|
||||
BenillaFauxScrollFrame_Update(scrollFrame, numCrafts, CRAFTS_DISPLAYED, CRAFT_SKILL_HEIGHT)
|
||||
local craftOffset = BenillaFauxScrollFrame_GetOffset(scrollFrame)
|
||||
FauxScrollFrame_Update(scrollFrame, numCrafts, CRAFTS_DISPLAYED, CRAFT_SKILL_HEIGHT)
|
||||
local craftOffset = FauxScrollFrame_GetOffset(scrollFrame)
|
||||
|
||||
-- The skill-rank StatusBar (ref l.107-123): Show/Hide are real here (real widgets, not
|
||||
-- flattened Layer regions).
|
||||
@@ -437,7 +437,7 @@
|
||||
ExpandCraftSkillLine(0)
|
||||
else
|
||||
self.collapsed = true
|
||||
BenillaFauxScrollFrame_SetOffset(getglobal("BenillaCraftListScrollFrame"), 0)
|
||||
FauxScrollFrame_SetOffset(getglobal("BenillaCraftListScrollFrame"), 0)
|
||||
CollapseCraftSkillLine(0)
|
||||
end
|
||||
end
|
||||
@@ -513,7 +513,7 @@
|
||||
else
|
||||
BenillaCraftFrame_SetSelection(GetCraftSelectionIndex())
|
||||
end
|
||||
BenillaFauxScrollFrame_SetOffset(getglobal("BenillaCraftListScrollFrame"), 0)
|
||||
FauxScrollFrame_SetOffset(getglobal("BenillaCraftListScrollFrame"), 0)
|
||||
SetPortraitTexture(getglobal("BenillaCraftFramePortrait"), "player")
|
||||
BenillaCraftFrame_Update()
|
||||
ShowUIPanel(frame)
|
||||
@@ -529,7 +529,7 @@
|
||||
BenillaCraftFrame_SetSelection(selection)
|
||||
elseif GetNumCrafts() > 0 then
|
||||
BenillaCraftFrame_SetSelection(1)
|
||||
BenillaFauxScrollFrame_SetOffset(getglobal("BenillaCraftListScrollFrame"), 0)
|
||||
FauxScrollFrame_SetOffset(getglobal("BenillaCraftListScrollFrame"), 0)
|
||||
end
|
||||
BenillaCraftFrame_Update()
|
||||
elseif event == "CRAFT_CLOSE" then
|
||||
@@ -839,11 +839,11 @@
|
||||
<Button name="BenillaCraft7" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft6" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="BenillaCraft8" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft7" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
|
||||
<!-- The faux-scroll frame (BenillaFauxScrollFrameTemplate, ScrollTemplates.xml): invisible
|
||||
<!-- The faux-scroll frame (FauxScrollFrameTemplate, ScrollTemplates.xml): invisible
|
||||
container over the 8 rows; only its right-hand Slider scroll bar renders. Sized to the
|
||||
8-row span (CRAFTS_DISPLAYED × CRAFT_SKILL_HEIGHT), TradeSkillFrame.xml's own
|
||||
same-TOPLEFT-as-row1 convention. updateFunc set in BenillaCraftFrame_OnLoad. -->
|
||||
<Frame name="BenillaCraftListScrollFrame" inherits="BenillaFauxScrollFrameTemplate">
|
||||
<Frame name="BenillaCraftListScrollFrame" inherits="FauxScrollFrameTemplate">
|
||||
<Size><AbsDimension x="293" y="128"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="22" y="-96"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
|
||||
@@ -229,7 +229,7 @@
|
||||
end
|
||||
end
|
||||
-- The ref hides the gossip window under the confirm (UIParent.lua l.412).
|
||||
local gossip = getglobal("BenillaGossipFrame")
|
||||
local gossip = getglobal("GossipFrame")
|
||||
if gossip and gossip:IsVisible() then
|
||||
HideUIPanel(gossip)
|
||||
end
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
-- tries the clicked item on. Both halves ride ONE ordered intent queue app-side, so the
|
||||
-- reset can never overtake the try-on (dressup.rs's module doc).
|
||||
function DressUpItem(item)
|
||||
if not BenillaDressUpFrame:IsVisible() then
|
||||
ShowUIPanel(BenillaDressUpFrame)
|
||||
if not DressUpFrame:IsVisible() then
|
||||
ShowUIPanel(DressUpFrame)
|
||||
BenillaDressUpModel_Dress() -- ref: DressUpModel:SetUnit("player")
|
||||
end
|
||||
BenillaDressUpModel_TryOn(item) -- ref: DressUpModel:TryOn(item)
|
||||
@@ -86,10 +86,10 @@
|
||||
-- character window's level line documents).
|
||||
function SetDressUpBackground()
|
||||
local texture = DressUpTexturePath()
|
||||
BenillaDressUpBackgroundTopLeft:SetTexture(texture .. 1)
|
||||
BenillaDressUpBackgroundTopRight:SetTexture(texture .. 2)
|
||||
BenillaDressUpBackgroundBotLeft:SetTexture(texture .. 3)
|
||||
BenillaDressUpBackgroundBotRight:SetTexture(texture .. 4)
|
||||
DressUpBackgroundTopLeft:SetTexture(texture .. 1)
|
||||
DressUpBackgroundTopRight:SetTexture(texture .. 2)
|
||||
DressUpBackgroundBotLeft:SetTexture(texture .. 3)
|
||||
DressUpBackgroundBotRight:SetTexture(texture .. 4)
|
||||
end
|
||||
|
||||
-- The pane's facing — the paper doll's pair of handlers, over this room's own booth yaw
|
||||
@@ -147,7 +147,7 @@
|
||||
<HighlightTexture file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></HighlightTexture>
|
||||
</Button>
|
||||
|
||||
<Frame name="BenillaDressUpFrame" toplevel="true" hidden="true">
|
||||
<Frame name="DressUpFrame" toplevel="true" hidden="true">
|
||||
<!-- The ref's 384×512 window. No <Anchors>: ShowUIPanel seats it in the left slot, this
|
||||
house's convention for every panel window (MerchantFrame.xml/CraftFrame.xml). -->
|
||||
<Size><AbsDimension x="384" y="512"/></Size>
|
||||
@@ -156,7 +156,7 @@
|
||||
<!-- The "player" portrait, 60×60 at TOPLEFT (7,-6) (ref l.20-33) — BACKGROUND so the
|
||||
window art's ring frames it; bound in OnShow, like every other portrait here. -->
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="BenillaDressUpFramePortrait">
|
||||
<Texture name="DressUpFramePortrait">
|
||||
<Size><AbsDimension x="60" y="60"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="7" y="-6"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
@@ -180,61 +180,61 @@
|
||||
<Size><AbsDimension x="128" y="256"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="256" y="-256"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
<FontString name="BenillaDressUpFrameTitleText" inherits="GameFontHighlight" text="DRESSUP_FRAME">
|
||||
<FontString name="DressUpFrameTitleText" inherits="GameFontHighlight" text="DRESSUP_FRAME">
|
||||
<Size><AbsDimension x="300" y="14"/></Size>
|
||||
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<FontString name="BenillaDressUpFrameDescriptionText" inherits="GameFontNormalSmall" text="DRESSUP_FRAME_INSTRUCTIONS">
|
||||
<FontString name="DressUpFrameDescriptionText" inherits="GameFontNormalSmall" text="DRESSUP_FRAME_INSTRUCTIONS">
|
||||
<Size><AbsDimension x="260" y="36"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativeTo="BenillaDressUpFrameTitleText" relativePoint="BOTTOM"><Offset><AbsDimension x="10" y="-22"/></Offset></Anchor>
|
||||
<Anchor point="CENTER" relativeTo="DressUpFrameTitleText" relativePoint="BOTTOM"><Offset><AbsDimension x="10" y="-22"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
<!-- The race backdrop behind the model, four quadrants (ref l.73-118); the files are
|
||||
chosen per race by SetDressUpBackground above. -->
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="BenillaDressUpBackgroundTopLeft">
|
||||
<Texture name="DressUpBackgroundTopLeft">
|
||||
<Size><AbsDimension x="256" y="255"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="22" y="-76"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
<Texture name="BenillaDressUpBackgroundTopRight">
|
||||
<Texture name="DressUpBackgroundTopRight">
|
||||
<Size><AbsDimension x="62" y="255"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaDressUpBackgroundTopLeft" relativePoint="TOPRIGHT"/></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="DressUpBackgroundTopLeft" relativePoint="TOPRIGHT"/></Anchors>
|
||||
</Texture>
|
||||
<Texture name="BenillaDressUpBackgroundBotLeft">
|
||||
<Texture name="DressUpBackgroundBotLeft">
|
||||
<Size><AbsDimension x="256" y="128"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaDressUpBackgroundTopLeft" relativePoint="BOTTOMLEFT"/></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="DressUpBackgroundTopLeft" relativePoint="BOTTOMLEFT"/></Anchors>
|
||||
</Texture>
|
||||
<Texture name="BenillaDressUpBackgroundBotRight">
|
||||
<Texture name="DressUpBackgroundBotRight">
|
||||
<Size><AbsDimension x="62" y="128"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaDressUpBackgroundTopLeft" relativePoint="BOTTOMRIGHT"/></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="DressUpBackgroundTopLeft" relativePoint="BOTTOMRIGHT"/></Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<!-- The corner close X: the ref's UIPanelCloseButton seat (CENTER of TOPRIGHT, -46,-25 —
|
||||
ref l.120-127) on the minimize-button art every close button in this house uses. -->
|
||||
<Button name="BenillaDressUpFrameCloseButton">
|
||||
<Button name="DressUpFrameCloseButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors><Anchor point="CENTER" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-46" y="-25"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Down"/>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Highlight" alphaMode="ADD"/>
|
||||
<Scripts>
|
||||
<OnClick>HideUIPanel(BenillaDressUpFrame)</OnClick>
|
||||
<OnClick>HideUIPanel(DressUpFrame)</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<!-- Close (ref l.128-146) and Reset (l.147-165, chained off Close's LEFT). Reset is the
|
||||
ref's `DressUpModel:Dress()` + its gsTitleOptionOK click. -->
|
||||
<Button name="BenillaDressUpFrameCancelButton" inherits="BenillaDressUpPanelButtonTemplate" text="CLOSE">
|
||||
<Button name="DressUpFrameCancelButton" inherits="BenillaDressUpPanelButtonTemplate" text="CLOSE">
|
||||
<Anchors><Anchor point="CENTER" relativePoint="TOPLEFT"><Offset><AbsDimension x="305" y="-422"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnClick>HideUIPanel(BenillaDressUpFrame)</OnClick>
|
||||
<OnClick>HideUIPanel(DressUpFrame)</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaDressUpFrameResetButton" inherits="BenillaDressUpPanelButtonTemplate" text="RESET">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BenillaDressUpFrameCancelButton" relativePoint="LEFT"/></Anchors>
|
||||
<Button name="DressUpFrameResetButton" inherits="BenillaDressUpPanelButtonTemplate" text="RESET">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="DressUpFrameCancelButton" relativePoint="LEFT"/></Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
BenillaDressUpModel_Dress()
|
||||
@@ -245,7 +245,7 @@
|
||||
|
||||
<!-- The model pane (ref the <DressUpModel> widget, l.166-207): 316×351 anchored BOTTOM
|
||||
(-11,+85). A plain Frame sampling the "dressup" booth bake (header). -->
|
||||
<Frame name="BenillaDressUpModelFrame">
|
||||
<Frame name="DressUpModelFrame">
|
||||
<Size><AbsDimension x="316" y="351"/></Size>
|
||||
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="-11" y="85"/></Offset></Anchor></Anchors>
|
||||
<Layers>
|
||||
@@ -267,26 +267,26 @@
|
||||
then right chained off its TOPRIGHT. Both edges registered, so one tap fires
|
||||
OnClick twice and holding hands the spin to the pane's OnUpdate — the ref's
|
||||
own tap-and-hold (decision 0638 §3). -->
|
||||
<Button name="BenillaDressUpModelFrameRotateLeftButton">
|
||||
<Button name="DressUpModelFrameRotateLeftButton">
|
||||
<Size><AbsDimension x="35" y="35"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaDressUpFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="21" y="-75"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="DressUpFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="21" y="-75"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-RotationLeft-Button-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-RotationLeft-Button-Down"/>
|
||||
<HighlightTexture file="Interface\Buttons\ButtonHilight-Round" alphaMode="ADD"/>
|
||||
<Scripts>
|
||||
<OnLoad>self:RegisterForClicks("LeftButtonDown", "LeftButtonUp")</OnLoad>
|
||||
<OnClick>BenillaDressUpModel_RotateLeft(getglobal("BenillaDressUpModelFrame"))</OnClick>
|
||||
<OnClick>BenillaDressUpModel_RotateLeft(getglobal("DressUpModelFrame"))</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaDressUpModelFrameRotateRightButton">
|
||||
<Button name="DressUpModelFrameRotateRightButton">
|
||||
<Size><AbsDimension x="35" y="35"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaDressUpModelFrameRotateLeftButton" relativePoint="TOPRIGHT"/></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="DressUpModelFrameRotateLeftButton" relativePoint="TOPRIGHT"/></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-RotationRight-Button-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-RotationRight-Button-Down"/>
|
||||
<HighlightTexture file="Interface\Buttons\ButtonHilight-Round" alphaMode="ADD"/>
|
||||
<Scripts>
|
||||
<OnLoad>self:RegisterForClicks("LeftButtonDown", "LeftButtonUp")</OnLoad>
|
||||
<OnClick>BenillaDressUpModel_RotateRight(getglobal("BenillaDressUpModelFrame"))</OnClick>
|
||||
<OnClick>BenillaDressUpModel_RotateRight(getglobal("DressUpModelFrame"))</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
@@ -303,7 +303,7 @@
|
||||
<OnLoad>SetDressUpBackground()</OnLoad>
|
||||
<OnShow>
|
||||
SetDressUpBackground()
|
||||
SetPortraitTexture(BenillaDressUpFramePortrait, "player")
|
||||
SetPortraitTexture(DressUpFramePortrait, "player")
|
||||
PlaySound("igCharacterInfoOpen")
|
||||
</OnShow>
|
||||
<!-- Closing empties the booth (dressup.rs's `Close` intent): the ref's widget keeps its
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
BENILLA_ERRORS_FADE = 1.0 -- INTERIM (comment above)
|
||||
|
||||
function BenillaErrorsFrame_AddMessage(text, r, g, b)
|
||||
local frame = getglobal("BenillaErrorsFrame")
|
||||
local frame = getglobal("UIErrorsFrame")
|
||||
-- insertMode="TOP": newest first, older lines shift down and off the list.
|
||||
table.insert(frame.messages, 1, { text = text, r = r, g = g, b = b, age = 0 })
|
||||
while table.getn(frame.messages) > BENILLA_ERRORS_LINES do
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
function BenillaErrorsFrame_Paint(frame)
|
||||
for i = 1, BENILLA_ERRORS_LINES do
|
||||
local line = getglobal("BenillaErrorsFrameLine" .. i)
|
||||
local line = getglobal("UIErrorsFrameLine" .. i)
|
||||
local msg = frame.messages[i]
|
||||
if msg then
|
||||
line:SetText(msg.text)
|
||||
@@ -49,19 +49,24 @@
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaErrorsFrame_OnLoad(frame)
|
||||
function UIErrorsFrame_OnLoad()
|
||||
local frame = this
|
||||
frame.messages = {}
|
||||
frame:RegisterEvent("UI_INFO_MESSAGE")
|
||||
frame:RegisterEvent("UI_ERROR_MESSAGE")
|
||||
frame:Hide()
|
||||
end
|
||||
|
||||
function BenillaErrorsFrame_OnEvent(frame)
|
||||
if not arg1 then return end
|
||||
-- ref UIErrorsFrame_OnEvent(event, message) (UIErrorsFrame.lua:8-15) — the message is the
|
||||
-- DECLARED second parameter, not the `arg1` global: the XML passes `arg1` into it exactly
|
||||
-- as the reference's own does, and an addon calling the 1.12 arity is then read correctly.
|
||||
function UIErrorsFrame_OnEvent(event, message)
|
||||
local frame = this
|
||||
if not message then return end
|
||||
if event == "UI_INFO_MESSAGE" then
|
||||
BenillaErrorsFrame_AddMessage(arg1, 1.0, 1.0, 0.0) -- ref UIErrorsFrame.lua:12
|
||||
BenillaErrorsFrame_AddMessage(message, 1.0, 1.0, 0.0) -- ref UIErrorsFrame.lua:12
|
||||
elseif event == "UI_ERROR_MESSAGE" then
|
||||
BenillaErrorsFrame_AddMessage(arg1, 1.0, 0.1, 0.1) -- ref UIErrorsFrame.lua:14
|
||||
BenillaErrorsFrame_AddMessage(message, 1.0, 0.1, 0.1) -- ref UIErrorsFrame.lua:14
|
||||
end
|
||||
end
|
||||
|
||||
@@ -86,7 +91,7 @@
|
||||
own content is flat FontString REGIONS at level 0 while those panels carry dozens of nested
|
||||
child frames at level 1+, so left in the default MEDIUM its toasts draw UNDER whichever
|
||||
panel is open — invisible in exactly the state you most want to read them. Decision 0597. -->
|
||||
<Frame name="BenillaErrorsFrame" frameStrata="HIGH">
|
||||
<Frame name="UIErrorsFrame" frameStrata="HIGH">
|
||||
<Size><AbsDimension x="512" y="60"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativePoint="TOP">
|
||||
@@ -95,23 +100,23 @@
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString name="BenillaErrorsFrameLine1" inherits="GameFontNormalLarge" justifyH="CENTER">
|
||||
<FontString name="UIErrorsFrameLine1" inherits="GameFontNormalLarge" justifyH="CENTER">
|
||||
<Size><AbsDimension x="512" y="0"/></Size>
|
||||
<Anchors><Anchor point="TOP" relativePoint="TOP"/></Anchors>
|
||||
</FontString>
|
||||
<FontString name="BenillaErrorsFrameLine2" inherits="GameFontNormalLarge" justifyH="CENTER">
|
||||
<FontString name="UIErrorsFrameLine2" inherits="GameFontNormalLarge" justifyH="CENTER">
|
||||
<Size><AbsDimension x="512" y="0"/></Size>
|
||||
<Anchors><Anchor point="TOP" relativeTo="BenillaErrorsFrameLine1" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="TOP" relativeTo="UIErrorsFrameLine1" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<FontString name="BenillaErrorsFrameLine3" inherits="GameFontNormalLarge" justifyH="CENTER">
|
||||
<FontString name="UIErrorsFrameLine3" inherits="GameFontNormalLarge" justifyH="CENTER">
|
||||
<Size><AbsDimension x="512" y="0"/></Size>
|
||||
<Anchors><Anchor point="TOP" relativeTo="BenillaErrorsFrameLine2" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="TOP" relativeTo="UIErrorsFrameLine2" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaErrorsFrame_OnLoad(self)</OnLoad>
|
||||
<OnEvent>BenillaErrorsFrame_OnEvent(self)</OnEvent>
|
||||
<OnLoad>UIErrorsFrame_OnLoad()</OnLoad>
|
||||
<OnEvent>UIErrorsFrame_OnEvent(event, arg1)</OnEvent>
|
||||
<OnUpdate>BenillaErrorsFrame_OnUpdate(self, arg1)</OnUpdate>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
@@ -28,6 +28,23 @@
|
||||
the two color tables tooltip AddLine calls read. Defined here (the ref's own home, loaded
|
||||
first) so every window shares one verified set. -->
|
||||
<Script><![CDATA[
|
||||
-- ── The four font-PATH globals (ref Fonts.xml l.4-7) ──────────────────────────────────
|
||||
-- This file quoted the reference's colour block (l.8-19) and skipped the four lines above
|
||||
-- it. `STANDARD_TEXT_FONT` is the one that matters: **68 corpus addons name it**, and per
|
||||
-- 1207's rule that number is read rather than quoted — 64 of them are copies of
|
||||
-- `Dewdrop-2.0.lua` and only 4 are addon code. But Dewdrop is *the* Ace2 menu library, and
|
||||
-- it calls `button.text:SetFont(STANDARD_TEXT_FONT, height)` **unguarded** at two of its
|
||||
-- three sites (l.207 and l.496; only l.2294 writes `STANDARD_TEXT_FONT or "Fonts\\..."`),
|
||||
-- so a nil global is a failed SetFont on every menu button the Ace2 ecosystem draws.
|
||||
--
|
||||
-- The other three come with it because they are the same four-line block and cost nothing:
|
||||
-- UNIT_NAME_FONT (0 corpus addons), DAMAGE_TEXT_FONT (1), NAMEPLATE_FONT (1). All four are
|
||||
-- the same face in 1.12 — the reference assigns FRIZQT__ to every one — and that identity
|
||||
-- is the reference's, not a simplification of ours.
|
||||
STANDARD_TEXT_FONT = "Fonts\\FRIZQT__.TTF" -- ref Fonts.xml l.4
|
||||
UNIT_NAME_FONT = "Fonts\\FRIZQT__.TTF" -- l.5
|
||||
DAMAGE_TEXT_FONT = "Fonts\\FRIZQT__.TTF" -- l.6
|
||||
NAMEPLATE_FONT = "Fonts\\FRIZQT__.TTF" -- l.7
|
||||
NORMAL_FONT_COLOR_CODE = "|cffffd200" -- ref Fonts.xml l.8
|
||||
HIGHLIGHT_FONT_COLOR_CODE = "|cffffffff" -- l.9
|
||||
RED_FONT_COLOR_CODE = "|cffff2020" -- l.10
|
||||
@@ -43,19 +60,59 @@
|
||||
-- one color this file was missing: a passive spell's name text (SpellBookFrame.lua:385).
|
||||
PASSIVE_SPELL_FONT_COLOR = { r = 0.77, g = 0.64, b = 0 } -- l.20
|
||||
|
||||
-- The 1.12 item-quality palette (ref Constants.lua's ITEM_QUALITY_COLORS, the real
|
||||
-- global name) — promoted here on its second customer (the unit popup's loot-threshold
|
||||
-- rows; LootFrame.xml aliases its old private copy to this). Indexed 0..6; values match
|
||||
-- GetItemQualityColor and ui_items.rs's quality_color.
|
||||
ITEM_QUALITY_COLORS = {
|
||||
[0] = { r = 0.62, g = 0.62, b = 0.62 }, -- Poor (gray)
|
||||
[1] = { r = 1.00, g = 1.00, b = 1.00 }, -- Common (white)
|
||||
[2] = { r = 0.12, g = 1.00, b = 0.00 }, -- Uncommon (green)
|
||||
[3] = { r = 0.00, g = 0.44, b = 0.87 }, -- Rare (blue)
|
||||
[4] = { r = 0.64, g = 0.21, b = 0.93 }, -- Epic (purple)
|
||||
[5] = { r = 1.00, g = 0.50, b = 0.00 }, -- Legendary (orange)
|
||||
[6] = { r = 0.90, g = 0.80, b = 0.50 }, -- Artifact (light gold)
|
||||
-- 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: `UIParent.lua` reads one index before the
|
||||
-- array there, and our binding answers Poor for it rather than reproducing that.
|
||||
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
|
||||
|
||||
-- ── RAID_CLASS_COLORS (ref Fonts.xml l.43-53), transcribed verbatim ───────────────────
|
||||
--
|
||||
-- The per-class colour every raid/party addon paints names with. **22 corpus addons** reach
|
||||
-- it — nine of them through `Babble-Class-2.0`, but seven more by hand (KLHThreatMeter,
|
||||
-- CT_RaidAssist, CT_ExpenseHistory, _LazyPig, FuBar_oRA2CooldownFu, and BigWigs in two
|
||||
-- plugins) — and absent it is `bad argument #1 to 'pairs' (table expected, got nil)` at
|
||||
-- file scope. That was the whole of the harness's remaining `table expected` row: three
|
||||
-- addons, one missing table, found by asking the blocker table which addons were behind it.
|
||||
--
|
||||
-- TWO THINGS THAT LOOK WRONG AND ARE THE REFERENCE'S: `SHAMAN` carries the same pink as
|
||||
-- `PALADIN` (shamans were Horde-only and paladins Alliance-only in 1.12, so the two never
|
||||
-- shared a raid frame and Blizzard never gave shamans their own), and there is no `ROGUE`
|
||||
-- yellow anywhere else in the client to reconcile it with. Transcribed as found; a later
|
||||
-- client's palette is not this one's.
|
||||
RAID_CLASS_COLORS = {
|
||||
["HUNTER"] = { r = 0.67, g = 0.83, b = 0.45 },
|
||||
["WARLOCK"] = { r = 0.58, g = 0.51, b = 0.79 },
|
||||
["PRIEST"] = { r = 1.0, g = 1.0, b = 1.0 },
|
||||
["PALADIN"] = { r = 0.96, g = 0.55, b = 0.73 },
|
||||
["MAGE"] = { r = 0.41, g = 0.8, b = 0.94 },
|
||||
["ROGUE"] = { r = 1.0, g = 0.96, b = 0.41 },
|
||||
["DRUID"] = { r = 1.0, g = 0.49, b = 0.04 },
|
||||
["SHAMAN"] = { r = 0.96, g = 0.55, b = 0.73 },
|
||||
["WARRIOR"] = { r = 0.78, g = 0.61, b = 0.43 },
|
||||
}
|
||||
-- The three OTHER tables the reference's Fonts.xml `<Script>` block carries —
|
||||
-- CHAT_FONT_HEIGHTS, MATERIAL_TEXT_COLOR_TABLE, MATERIAL_TITLETEXT_COLOR_TABLE — are
|
||||
-- deliberately not here: **zero corpus addons name any of them**, and nothing benilla ships
|
||||
-- reads them. They cost nine lines each the day something does.
|
||||
]]></Script>
|
||||
|
||||
<!-- MasterFont: the shadow-only root the GameFont* families chain through (ref l.56). -->
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -81,9 +81,14 @@
|
||||
- The Options label stays a literal "Options": 1.12 GlobalStrings has no era
|
||||
GAMEMENU_OPTIONS (checked; its OPTIONS_MENU is "Options Menu", a different string).
|
||||
- The ref frame is `toplevel="true" movable="true" parent="UIParent"`. Dropped: this engine has
|
||||
no cross-file `parent=` attachment (SkillFrame.xml's header comment), no frame-level raise,
|
||||
and `movable` is inert in the ref too (GameMenuFrame defines no drag scripts, so it never
|
||||
actually moves). `frameStrata="DIALOG"` is what puts it over the world UI, and that is kept.
|
||||
no cross-file `parent=` attachment (SkillFrame.xml's header comment), and `movable` is inert
|
||||
in the ref too (GameMenuFrame defines no drag scripts, so it never actually moves).
|
||||
`frameStrata="DIALOG"` is what puts it over the world UI, and that is kept.
|
||||
**The "no frame-level raise" half of that reason is gone**: the raise law is built
|
||||
(`script::object::toplevel`, wow-re `ui/scratch/toplevel-raise.md`) and the loader honours
|
||||
`toplevel="true"`. Re-adding it here is a one-word change and would make the Esc menu come
|
||||
to the front over any other DIALOG-strata frame it overlaps; left off pending the
|
||||
director's call on the look, not on the mechanism.
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
-- The reference GlobalStrings (patch-2.MPQ Interface\FrameXML\GlobalStrings.lua), quoted
|
||||
@@ -179,23 +184,14 @@
|
||||
<FontHeight><AbsValue val="13"/></FontHeight>
|
||||
</Font>
|
||||
|
||||
<!-- Era MainMenuFrameButtonTemplate (Classic/Frame/MainMenuFrameTemplates.xml l.11-16) =
|
||||
UIPanelButtonTemplate at 144x21 with a white/white/grey label trio — byte-for-byte the
|
||||
1.12 GameMenuButtonTemplate (UIPanelTemplates.xml l.403-410): same art, same trio, same
|
||||
size. UIPanelButtonTemplate itself is not transcribed as a shared virtual (no other
|
||||
benilla file inherits it — each open-codes the same four-state art, e.g. QuestFrame.xml
|
||||
l.753); its textures are inlined here with the ref TexCoords. -->
|
||||
<Button name="GameMenuButtonTemplate" virtual="true">
|
||||
<Size><AbsDimension x="144" y="21"/></Size>
|
||||
<ButtonText name="$parentText"/>
|
||||
<NormalTexture file="Interface\Buttons\UI-Panel-Button-Up"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></NormalTexture>
|
||||
<PushedTexture file="Interface\Buttons\UI-Panel-Button-Down"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></PushedTexture>
|
||||
<DisabledTexture file="Interface\Buttons\UI-Panel-Button-Disabled"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></DisabledTexture>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></HighlightTexture>
|
||||
<NormalFont inherits="GameFontHighlight"/>
|
||||
<HighlightFont inherits="GameFontHighlight"/>
|
||||
<DisabledFont inherits="GameFontDisable"/>
|
||||
</Button>
|
||||
<!-- `GameMenuButtonTemplate` MOVED OUT, to UIPanelTemplates.xml — the reference's own file for
|
||||
it (ref UIPanelTemplates.xml l.403-410), and now ColorPickerFrame.xml's Okay/Cancel need it
|
||||
too, from far above this file's LAST-in-the-toc seat. It lived here, with its four state
|
||||
textures inlined, only because `UIPanelButtonTemplate` had not been transcribed yet; it now
|
||||
has been, so the reference's actual body (`inherits="UIPanelButtonTemplate"`, 144x21, the
|
||||
white/white/grey label trio) replaces the private copy verbatim. Era's
|
||||
MainMenuFrameButtonTemplate (Classic/Frame/MainMenuFrameTemplates.xml l.11-16) is that same
|
||||
button — same art, same trio, same size — so the header's ART BYTES note is unchanged. -->
|
||||
|
||||
<Frame name="GameMenuFrame" frameStrata="DIALOG" enableMouse="true" hidden="true">
|
||||
<!-- The era layout engine's own numbers (header SCOPE note): 28+144+28 wide, 32-top /
|
||||
@@ -257,7 +253,18 @@
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="GameMenuButtonOptions" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-20"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts><OnLoad>GameMenuButton_Pending(self, ADDONS)</OnLoad></Scripts>
|
||||
<!-- LIVE since decision 1197 (AddonList.xml). The Macros entry's own body shape:
|
||||
hide the menu, show the window. This entry has no reference — 1.12's ESC menu
|
||||
has no AddOns row, its list is glue-side only — so it is a deliberate benilla
|
||||
addition, and AddonList.xml's header says so rather than implying otherwise. -->
|
||||
<Scripts>
|
||||
<OnLoad>self:SetText(ADDONS)</OnLoad>
|
||||
<OnClick>
|
||||
PlaySound("igMainMenuOption")
|
||||
HideUIPanel(getglobal("GameMenuFrame"))
|
||||
AddonList_Toggle()
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="GameMenuButtonEditMode" inherits="GameMenuButtonTemplate">
|
||||
<Anchors>
|
||||
|
||||
@@ -4,14 +4,23 @@
|
||||
SetOwner anchor law, auto-size + right-flush, IsOwned, FadeOut, and the OnTooltipCleared/
|
||||
OnTooltipAddMoney script protocol all live engine-side (benilla-ui script/tooltip.rs); the
|
||||
line pairs are engine-created named regions (GameTooltipTextLeftN/RightN — real globals,
|
||||
like the template pairs reference Lua addresses by name). This file declares the INSTANCE:
|
||||
the plate (backdrop art + colors QUOTED from ref-GameTooltip.xml/.lua, line cites inline),
|
||||
the money coin trio, and the Lua that is genuinely FrameXML's in the real client
|
||||
(GameTooltip_SetDefaultAnchor, GameTooltip_AddNewbieTip, SetTooltipMoney) — plus the P0 item-stat renderer, which
|
||||
like the template pairs reference Lua addresses by name). This file declares the two
|
||||
TEMPLATES and their instances: the plate (backdrop art + colors QUOTED from
|
||||
ref-GameTooltip.xml/.lua, line cites inline), the money coin trio, and the Lua that is
|
||||
genuinely FrameXML's in the real client (GameTooltip_SetDefaultAnchor,
|
||||
GameTooltip_AddNewbieTip, SetTooltipMoney) — plus the P0 item-stat renderer, which
|
||||
moves engine-side in 0274 P1 with the full template feed.
|
||||
|
||||
The frame's global name is the real one — GameTooltip — so transcribed reference code
|
||||
(MinimapCluster's zone hover, UnitFrame's TextLeft1 recolor) works verbatim.
|
||||
|
||||
The reference splits this across two files — Interface\FrameXML\GameTooltipTemplate.xml
|
||||
holds the virtual GameTooltipTemplate (l.3-627) and ShoppingTooltipTemplate (l.628-1217);
|
||||
GameTooltip.xml <Include>s it and declares only the instances. Both templates live here
|
||||
instead, ABOVE their instances, because that is the whole of their reach: the tooltip file
|
||||
already loads before every window that hovers, and keeping them in one file means the
|
||||
template is registered by the time the instances (and, later, ItemRef.xml's) expand it
|
||||
without a second manifest entry to keep in order. Same registry, same splice, one file.
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
-- Tooltip default colours (VERIFIED, ref-GameTooltip.lua l.3-4). The border is white, the
|
||||
@@ -114,7 +123,14 @@
|
||||
-- ref GameTooltip_OnHide (ref-GameTooltip.lua l.100-104, quoted): the plate colours
|
||||
-- reset for the next owner and the default-corner flag drops. The ref's OnHide also
|
||||
-- hides ShoppingTooltip1/2 - that pair lands with the compare-tooltip slice (0274 P4).
|
||||
--
|
||||
-- `self or this`: the corpus calls this the REFERENCE way, with no argument at all
|
||||
-- (TipBuddy.xml l.1412, atsw.xml l.3420, and TipBuddy.lua l.2520 which REPLACES the global
|
||||
-- and calls the saved original bare). The signature note above explains the corpus-wide
|
||||
-- shift to a leading parameter; nothing lets us edit an addon, and `this` is exactly what
|
||||
-- the reference reads here, so the two spellings mean the same thing.
|
||||
function GameTooltip_OnHide(self)
|
||||
self = self or this
|
||||
self:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b)
|
||||
self:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
|
||||
self.default = nil
|
||||
@@ -147,6 +163,7 @@
|
||||
-- ships in MerchantFrame.xml (the money kit's home) — guard for engine tests that load
|
||||
-- this file alone; in the app every file is loaded.
|
||||
function GameTooltip_ClearMoney(frame)
|
||||
frame = frame or this
|
||||
if BenillaMoney_Clear then
|
||||
BenillaMoney_Clear(frame:GetName() .. "Money")
|
||||
end
|
||||
@@ -214,23 +231,57 @@
|
||||
this:SetStatusBarColor(r, g, b)
|
||||
end
|
||||
|
||||
-- `self or this` for the same reason as GameTooltip_OnHide: an addon inheriting
|
||||
-- GameTooltipTemplate re-declares <OnLoad> to add its own line and opens it with the
|
||||
-- reference's bare `GameTooltip_OnLoad();` (TipBuddy.xml l.1402, atsw.xml l.3417) — a nil
|
||||
-- self there is a load ERROR, and the plate it was about to tint never gets its colours.
|
||||
-- The body is the ref's two lines and NOTHING else. It used to end with a `self:Hide()` we
|
||||
-- had added, which the reference (l.79-82, the whole function) does not have — inert for
|
||||
-- our four tooltips, since every one of them carries `hidden="true"` and the loader's
|
||||
-- attribute pass runs Hide() long before OnLoad fires, but NOT inert for an addon: two of
|
||||
-- the 27 declare `hidden="false"` on purpose (PowerAuras.xml l.20, SimpleActionSets.xml
|
||||
-- l.349) and never call Show(), so the extra line hid a tooltip the real client leaves up.
|
||||
-- A helper that quietly does more than the reference's is exactly the kind of thing that
|
||||
-- only shows up when someone else's code calls it.
|
||||
function GameTooltip_OnLoad(self)
|
||||
self = self or this
|
||||
-- The plate colours (ref GameTooltip_OnLoad, ref-GameTooltip.lua l.79-82).
|
||||
self:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b)
|
||||
self:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
|
||||
self:Hide()
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
<GameTooltip name="GameTooltip" frameStrata="TOOLTIP" hidden="true">
|
||||
<!-- A default size so the plate resolves the first frame it is shown; the engine's
|
||||
auto-size pass takes over once the lines are measured (one frame's latency). NO
|
||||
load-time anchor (the ref instance carries none): every show path seats the plate
|
||||
first - SetOwner's anchor law for owned hovers, OnTooltipSetDefaultAnchor for world
|
||||
hovers - and a leftover static anchor is exactly what parked the world tooltip at
|
||||
screen center when the default-anchor script wasn't wired. -->
|
||||
<Size><AbsDimension x="120" y="32"/></Size>
|
||||
<!-- The tooltip plate (ref-GameTooltip.xml l.33-43 / ref-GameTooltipTemplate.xml l.4-14):
|
||||
<!--
|
||||
GameTooltipTemplate (ref-GameTooltipTemplate.xml l.3-627) — the shared tooltip SHAPE, and
|
||||
the single most-wanted template in the addon corpus: 27 addons name it in an XML
|
||||
`inherits=` (TipBuddy, AtlasLoot, CT_BarMod, MikScrollingBattleText, TheoryCraft, …) and
|
||||
two more reach it through CreateFrame("GameTooltip", name, nil, "GameTooltipTemplate").
|
||||
Every one of those declarations is a <GameTooltip> element, so the template's job is to
|
||||
hand them the PLATE and the STATUS BAR; the line pairs they then read
|
||||
(`getglobal(tip:GetName().."TextLeft1")`) are engine-created per instance and can never
|
||||
carry the template's name.
|
||||
|
||||
What is here is what the reference's template carries and all our instances shared, moved
|
||||
verbatim from the GameTooltip instance below: the backdrop plate, the Thicken deviation
|
||||
(its rationale note travels WITH it — this is the shared plate, not one window's), the
|
||||
$parentStatusBar, and the OnLoad/OnHide pair (ref l.611-616).
|
||||
|
||||
What is deliberately NOT here, and stays on the GameTooltip instance:
|
||||
- The money coin trio and the OnTooltipAddMoney/OnTooltipCleared pair. The reference's
|
||||
template carries a $parentMoneyFrame (l.598-608, a SmallMoneyFrameTemplate), so its
|
||||
handler is safe on every instance; ours is a bespoke three-coin row that only
|
||||
GameTooltip declares, so a templated OnTooltipAddMoney would index a MoneyCoin1 that
|
||||
does not exist. Moving the row up would give ItemRefTooltip and every addon tooltip a
|
||||
coin line they render today without — a visible change, so it waits for the director.
|
||||
- OnTooltipSetDefaultAnchor (ref l.617-619). The engine fires it by name on `GameTooltip`
|
||||
alone (script/tooltip_unit.rs looks the frame up), so on any other instance it is dead
|
||||
weight.
|
||||
`clampedToScreen="true"` is the reference's own attribute and is a no-op for us: decision
|
||||
0352 made the flag default-ON for every GameTooltip-kind frame, because the reference plate
|
||||
observably clamps though its XML never sets it. Kept because it is what the reference says.
|
||||
-->
|
||||
<GameTooltip name="GameTooltipTemplate" frameStrata="TOOLTIP" clampedToScreen="true" hidden="true" virtual="true">
|
||||
<!-- The tooltip plate (ref-GameTooltipTemplate.xml l.4-14 / ref-GameTooltip.xml l.33-43):
|
||||
UI-Tooltip-Background tiled, UI-Tooltip-Border edge, EdgeSize 16, TileSize 16, insets
|
||||
5/5/5/5. Colours are set in OnLoad (SetBackdropColor/BorderColor) — no <Color> here, so
|
||||
the plate starts ctor-white and OnLoad tints it. -->
|
||||
@@ -255,6 +306,46 @@
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<!-- The mouseover health bar (ref-GameTooltipTemplate.xml l.575-597, quoted): 8px
|
||||
tall, hanging below the plate, UI-TargetingFrame-BarFill, colored by
|
||||
HealthBar_OnValueChanged. The ENGINE drives min/max/value from the live unit
|
||||
snapshot (the byte law's HEALTH watcher — SetUnit + every set_unit push), finding
|
||||
it by the `<name>StatusBar` global — so it has to be the TEMPLATE's, not one
|
||||
instance's: TipBuddy replaces the whole unit tooltip and both `parent=` and
|
||||
`relativeTo=` a `TipBuddyTooltipStatusBar` it never declares
|
||||
(TipBuddy.xml l.2320, l.2591) — a name only this element can publish. -->
|
||||
<StatusBar name="$parentStatusBar" hidden="true">
|
||||
<Size><AbsDimension x="0" y="8"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-2" y="-1"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnValueChanged>HealthBar_OnValueChanged(arg1)</OnValueChanged>
|
||||
</Scripts>
|
||||
<BarTexture name="$parentTexture" file="Interface\TargetingFrame\UI-TargetingFrame-BarFill"/>
|
||||
</StatusBar>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<!-- ref-GameTooltipTemplate.xml l.611-616. An instance that needs more re-declares the
|
||||
handler and calls the same helper first (the reference's own shape, and ours:
|
||||
GameTooltip adds its event registration, ItemRefTooltip its padding) — the loader
|
||||
walks every <Scripts> block in splice order, so the instance's SetScript lands
|
||||
last and wins. -->
|
||||
<OnLoad>GameTooltip_OnLoad(self)</OnLoad>
|
||||
<OnHide>GameTooltip_OnHide(this)</OnHide>
|
||||
</Scripts>
|
||||
</GameTooltip>
|
||||
|
||||
<GameTooltip name="GameTooltip" inherits="GameTooltipTemplate" frameStrata="TOOLTIP" hidden="true">
|
||||
<!-- A default size so the plate resolves the first frame it is shown; the engine's
|
||||
auto-size pass takes over once the lines are measured (one frame's latency). NO
|
||||
load-time anchor (the ref instance carries none): every show path seats the plate
|
||||
first - SetOwner's anchor law for owned hovers, OnTooltipSetDefaultAnchor for world
|
||||
hovers - and a leftover static anchor is exactly what parked the world tooltip at
|
||||
screen center when the default-anchor script wasn't wired. -->
|
||||
<Size><AbsDimension x="120" y="32"/></Size>
|
||||
<Frames>
|
||||
<!-- The money row's coin trio (the real $parentMoneyFrame — GameTooltipTemplate.xml,
|
||||
patch.MPQ — as our shared coin-slot shape; MerchantFrame.xml's BenillaMoney_Set
|
||||
@@ -315,21 +406,6 @@
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Frame>
|
||||
<!-- The mouseover health bar (ref-GameTooltipTemplate.xml l.575-597, quoted): 8px
|
||||
tall, hanging below the plate, UI-TargetingFrame-BarFill, colored by
|
||||
HealthBar_OnValueChanged. The ENGINE drives min/max/value from the live unit
|
||||
snapshot (the byte law's HEALTH watcher — SetUnit + every set_unit push). -->
|
||||
<StatusBar name="$parentStatusBar" hidden="true">
|
||||
<Size><AbsDimension x="0" y="8"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-2" y="-1"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnValueChanged>HealthBar_OnValueChanged(arg1)</OnValueChanged>
|
||||
</Scripts>
|
||||
<BarTexture name="$parentTexture" file="Interface\TargetingFrame\UI-TargetingFrame-BarFill"/>
|
||||
</StatusBar>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
@@ -363,47 +439,25 @@
|
||||
</Scripts>
|
||||
</GameTooltip>
|
||||
|
||||
<!-- The shopping-compare pair (ref GameTooltip.xml l.25-26 / ShoppingTooltipTemplate,
|
||||
GameTooltipTemplate.xml l.628+): two more GameTooltip widgets the engine seats on the
|
||||
paperdoll slot when SHOW_COMPARE_TOOLTIP fires (CharacterFrame.xml's listener). The
|
||||
declared TextLeft/Right1-4 pairs are the template's own small-font ladder, faces quoted
|
||||
verbatim (including its odd GameFontNormal cells) — the engine ADOPTS declared pairs and
|
||||
clones the last pair's faces for lines it grows past them (tooltip.rs ensure_lines).
|
||||
Same plate as the main tooltip, including the Thicken deviation (its rationale is on the
|
||||
GameTooltip instance above); no coin trio — the compare cut ends before money. -->
|
||||
<GameTooltip name="ShoppingTooltip1" frameStrata="TOOLTIP" hidden="true">
|
||||
<Size><AbsDimension x="120" y="32"/></Size>
|
||||
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
|
||||
<EdgeSize><AbsValue val="16"/></EdgeSize>
|
||||
<TileSize><AbsValue val="16"/></TileSize>
|
||||
<BackgroundInsets><AbsInset left="5" right="5" top="5" bottom="5"/></BackgroundInsets>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentThicken">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="5" y="-5"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-5" y="5"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="0.09" g="0.09" b="0.19" a="0.4"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="$parentTextLeft1" inherits="GameFontNormalSmall" hidden="true"/>
|
||||
<FontString name="$parentTextRight1" inherits="GameFontNormal" hidden="true"/>
|
||||
<FontString name="$parentTextLeft2" inherits="GameFontNormal" hidden="true"/>
|
||||
<FontString name="$parentTextRight2" inherits="GameFontHighlightSmall" hidden="true"/>
|
||||
<FontString name="$parentTextLeft3" inherits="GameFontHighlightSmall" hidden="true"/>
|
||||
<FontString name="$parentTextRight3" inherits="GameFontHighlightSmall" hidden="true"/>
|
||||
<FontString name="$parentTextLeft4" inherits="GameTooltipTextSmall" hidden="true"/>
|
||||
<FontString name="$parentTextRight4" inherits="GameFontHighlightSmall" hidden="true"/>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnLoad>GameTooltip_OnLoad(self)</OnLoad>
|
||||
</Scripts>
|
||||
</GameTooltip>
|
||||
<GameTooltip name="ShoppingTooltip2" frameStrata="TOOLTIP" hidden="true">
|
||||
<!--
|
||||
ShoppingTooltipTemplate (ref-GameTooltipTemplate.xml l.628-1217) — the shopping-compare
|
||||
pair's own template, a SEPARATE virtual from GameTooltipTemplate exactly as the reference
|
||||
has it (its instances, ref-GameTooltip.xml l.24-25, inherit this and not the main one).
|
||||
It repeats the plate rather than inheriting it because the reference's does, and because
|
||||
inheriting GameTooltipTemplate would hand the compare plates its OnHide, its status bar and
|
||||
(later) its money row — none of which the compare cut has.
|
||||
|
||||
The declared TextLeft/Right1-4 pairs are this template's own small-font ladder, faces
|
||||
quoted verbatim (including its odd GameFontNormal cells) — the engine ADOPTS declared pairs
|
||||
and clones the last pair's faces for lines it grows past them (tooltip.rs ensure_lines).
|
||||
Same plate as the main tooltip, including the Thicken deviation (its rationale is on
|
||||
GameTooltipTemplate above); no coin trio — the compare cut ends before money.
|
||||
|
||||
No addon in the corpus names this template (0 of 218, against GameTooltipTemplate's 27), so
|
||||
it earns its keep as OUR de-duplication, not as an addon surface: the two instances below
|
||||
were byte-identical apart from the name.
|
||||
-->
|
||||
<GameTooltip name="ShoppingTooltipTemplate" frameStrata="TOOLTIP" hidden="true" virtual="true">
|
||||
<Size><AbsDimension x="120" y="32"/></Size>
|
||||
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
|
||||
<EdgeSize><AbsValue val="16"/></EdgeSize>
|
||||
@@ -435,4 +489,10 @@
|
||||
<OnLoad>GameTooltip_OnLoad(self)</OnLoad>
|
||||
</Scripts>
|
||||
</GameTooltip>
|
||||
|
||||
<!-- The shopping-compare pair (ref GameTooltip.xml l.24-25, quoted down to the one-line shape):
|
||||
two more GameTooltip widgets the engine seats on the paperdoll slot when
|
||||
SHOW_COMPARE_TOOLTIP fires (CharacterFrame.xml's listener). -->
|
||||
<GameTooltip name="ShoppingTooltip1" inherits="ShoppingTooltipTemplate" frameStrata="TOOLTIP" hidden="true"/>
|
||||
<GameTooltip name="ShoppingTooltip2" inherits="ShoppingTooltipTemplate" frameStrata="TOOLTIP" hidden="true"/>
|
||||
</Ui>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
benilla's gossip dialog (decision 0081 phase 3), RE-SKINNED to the real 1.12 gossip/quest
|
||||
greeting window (dressing arc, decision 0084): the geometry and art below are QUOTED from the
|
||||
real `GossipFrame.xml` (scratchpad `ref-GossipFrame.xml`; line citations inline), turned onto
|
||||
our own feeds/intents. The name `BenillaGossipFrame`, its panel registration
|
||||
(UiPanels.xml's UIPanelWindows["BenillaGossipFrame"], area="left", pushable=0), the Era gossip
|
||||
our own feeds/intents. The name `GossipFrame`, its panel registration
|
||||
(UiPanels.xml's UIPanelWindows["GossipFrame"], area="left", pushable=0), the Era gossip
|
||||
feeds (GetGossipText/GetGossipOptions/IsGossipOptionCoded/SelectGossipOption/CloseGossip/
|
||||
GetNumGossipQuests/GetGossipQuestInfo/SelectGossipQuest), and the intent functions are
|
||||
UNCHANGED — only the window's dressing is real now.
|
||||
@@ -24,7 +24,7 @@
|
||||
over our OWN flatter seam (`benilla-ui` `script/gossip.rs`): the app already resolves the wire's
|
||||
available/active split and delivers ONE ordered `GetNumGossipQuests()`/`GetGossipQuestInfo(i)`
|
||||
list (quest rows first, in wire order), so `BenillaGossipFrame_Update` below runs a single loop
|
||||
over the shared `BenillaGossipRow1..8` pool — quest rows fill from i=1, option rows fill after
|
||||
over the shared `GossipRow1..8` pool — quest rows fill from i=1, option rows fill after
|
||||
them — instead of the ref's three separate counters; a row's `.kind` ("quest"/"option") plus its
|
||||
`.itemIndex` (the row's 1-based position within ITS OWN list) is all `BenillaGossipRow_OnClick`
|
||||
needs to call the right one of `SelectGossipQuest`/`SelectGossipOption` (ref's three intents
|
||||
@@ -62,7 +62,7 @@
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
-- The shared row pool's cap (decision 0088 §3): quest rows fill from slot 1, option rows fill
|
||||
-- after them, both drawing from the SAME BenillaGossipRow1..32 buttons (ref's single
|
||||
-- after them, both drawing from the SAME GossipRow1..32 buttons (ref's single
|
||||
-- GossipTitleButton pool, l.24-29/63-128). This IS the reference's number
|
||||
-- (`NUMGOSSIPBUTTONS = 32`, ref-GossipFrame.lua l.1); benilla ran a cap of 8 only because an
|
||||
-- overflowing menu had nowhere to go — with the scroll frame below, it does.
|
||||
@@ -72,8 +72,8 @@
|
||||
-- measure (BenillaScroll_ResizeChild, ScrollTemplates.xml). Built once at load, like the
|
||||
-- quest window's per-panel lists.
|
||||
BENILLA_GOSSIP_SCROLL_WIDGETS = (function()
|
||||
local t = { "BenillaGossipGreetingText" }
|
||||
for i = 1, BENILLA_GOSSIP_MAX do table.insert(t, "BenillaGossipRow" .. i) end
|
||||
local t = { "GossipGreetingText" }
|
||||
for i = 1, BENILLA_GOSSIP_MAX do table.insert(t, "GossipRow" .. i) end
|
||||
return t
|
||||
end)()
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
-- (the ref grows its child with the 15px GossipSpacerFrame under the last row instead —
|
||||
-- ref-GossipFrame.lua l.39-44; the shared kit's BENILLA_SCROLL_PAD is that breathing room).
|
||||
function BenillaGossipFrame_SizeScrollChild()
|
||||
BenillaScroll_ResizeChild("BenillaGossipGreetingScroll", BENILLA_GOSSIP_SCROLL_WIDGETS)
|
||||
BenillaScroll_ResizeChild("GossipGreetingScroll", BENILLA_GOSSIP_SCROLL_WIDGETS)
|
||||
end
|
||||
|
||||
-- The convergence driver (the tab-fit / quest-panel idiom): while the menu is up, re-fit
|
||||
@@ -135,7 +135,7 @@
|
||||
-- so the scroll range sees their settled heights in the same pass.
|
||||
function BenillaGossipFrame_OnUpdate()
|
||||
for i = 1, BENILLA_GOSSIP_MAX do
|
||||
local button = getglobal("BenillaGossipRow" .. i)
|
||||
local button = getglobal("GossipRow" .. i)
|
||||
if button:IsShown() then
|
||||
BenillaGossipRow_Resize(button)
|
||||
end
|
||||
@@ -156,13 +156,13 @@
|
||||
end
|
||||
|
||||
function BenillaGossipFrame_Update()
|
||||
getglobal("BenillaGossipGreetingText"):SetText(GetGossipText() or "")
|
||||
getglobal("GossipGreetingText"):SetText(GetGossipText() or "")
|
||||
local numQuests = GetNumGossipQuests()
|
||||
-- GetGossipOptions() returns a flat vararg of (label, type) pairs (the Era shape).
|
||||
local opts = { GetGossipOptions() }
|
||||
local numOptions = #opts / 2
|
||||
for i = 1, BENILLA_GOSSIP_MAX do
|
||||
local button = getglobal("BenillaGossipRow" .. i)
|
||||
local button = getglobal("GossipRow" .. i)
|
||||
if i <= numQuests then
|
||||
-- Quest rows fill first (ref l.27-28: available then active, already ordered by
|
||||
-- the app before it reaches GetGossipQuestInfo) — the row's OWN 1-based position
|
||||
@@ -201,11 +201,12 @@
|
||||
end
|
||||
-- The ref's GossipFrameUpdate tail (ref-GossipFrame.lua l.46-48): a repaint always lands
|
||||
-- back at the TOP of the menu, then re-ranges the bar off the new content.
|
||||
getglobal("BenillaGossipGreetingScroll"):SetVerticalScroll(0)
|
||||
getglobal("GossipGreetingScroll"):SetVerticalScroll(0)
|
||||
BenillaGossipFrame_SizeScrollChild()
|
||||
end
|
||||
|
||||
function BenillaGossipFrame_OnLoad(frame)
|
||||
function GossipFrame_OnLoad()
|
||||
local frame = this
|
||||
-- The NPC portrait: the "npc" unit token → the interaction NPC's live model bake
|
||||
-- (crates/benilla/src/portrait, InteractNpc). Bound once; the app re-resolves it every
|
||||
-- frame, so a revisit to a different gossip NPC shows that face.
|
||||
@@ -217,10 +218,11 @@
|
||||
-- The gossip NPC's name arrives as arg1 of GOSSIP_SHOW (the app resolves the NPC guid through
|
||||
-- the NameCache); nil/empty until it lands → a blank banner (mirrors the merchant title).
|
||||
function BenillaGossipFrame_SetTitle(name)
|
||||
getglobal("BenillaGossipNpcNameText"):SetText(name or "")
|
||||
getglobal("GossipNpcNameText"):SetText(name or "")
|
||||
end
|
||||
|
||||
function BenillaGossipFrame_OnEvent(frame)
|
||||
function GossipFrame_OnEvent()
|
||||
local frame = this
|
||||
if event == "GOSSIP_SHOW" then
|
||||
BenillaGossipFrame_SetTitle(arg1)
|
||||
BenillaGossipFrame_Update()
|
||||
@@ -234,7 +236,7 @@
|
||||
-- vanilla's client-side close sends no packet; the app clears the menu on this intent
|
||||
-- (which fires GOSSIP_CLOSED), and we hide immediately so there's no one-frame lag.
|
||||
CloseGossip()
|
||||
HideUIPanel(getglobal("BenillaGossipFrame"))
|
||||
HideUIPanel(getglobal("GossipFrame"))
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
@@ -274,7 +276,7 @@
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<Frame name="BenillaGossipFrame" hidden="true">
|
||||
<Frame name="GossipFrame" hidden="true">
|
||||
<!-- The real 384×512 window (ref l.128). No <Anchors> — ShowUIPanel/SetLeftFrame SetPoints this
|
||||
to the left slot on open. -->
|
||||
<Size><AbsDimension x="384" y="512"/></Size>
|
||||
@@ -322,7 +324,7 @@
|
||||
on the window at TOP -23. The app delivers the name as arg1 of GOSSIP_SHOW; blank
|
||||
until it lands. CENTER + the single-point (~zero-width) rect keeps the name centred
|
||||
regardless of length, exactly like the merchant's title. -->
|
||||
<FontString name="BenillaGossipNpcNameText" inherits="GameFontHighlight" justifyH="CENTER">
|
||||
<FontString name="GossipNpcNameText" inherits="GameFontHighlight" justifyH="CENTER">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativePoint="TOP"><Offset><AbsDimension x="0" y="-23"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
@@ -348,7 +350,7 @@
|
||||
re-anchored per update by GossipFrame.lua l.39-44); ours is the shared kit's
|
||||
BENILLA_SCROLL_PAD, the same breathing room by a plainer mechanism.
|
||||
-->
|
||||
<ScrollFrame name="BenillaGossipGreetingScroll">
|
||||
<ScrollFrame name="GossipGreetingScroll">
|
||||
<Size><AbsDimension x="300" y="334"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="23" y="-81"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
@@ -378,7 +380,7 @@
|
||||
<Layer level="ARTWORK">
|
||||
<!-- Greeting body: the real GossipGreetingText (ref l.241-252) — QuestFont,
|
||||
LEFT, 270 wide so the engine wraps it, at the child's +(10,-10). -->
|
||||
<FontString name="BenillaGossipGreetingText" inherits="QuestFont" justifyH="LEFT">
|
||||
<FontString name="GossipGreetingText" inherits="QuestFont" justifyH="LEFT">
|
||||
<Size><AbsDimension x="270" y="0"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="10" y="-10"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
@@ -392,38 +394,38 @@
|
||||
the rest off the previous row's BOTTOMLEFT (ref l.256-269) — one static
|
||||
chain, so rows re-flow off each other's measured heights with no runtime
|
||||
repositioning. -->
|
||||
<Button name="BenillaGossipRow1" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipGreetingText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-10" y="-20"/></Offset></Anchor></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow2" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow1" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow3" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow2" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow4" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow3" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow5" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow4" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow6" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow5" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow7" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow6" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow8" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow7" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow9" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow8" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow10" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow9" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow11" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow10" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow12" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow11" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow13" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow12" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow14" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow13" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow15" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow14" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow16" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow15" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow17" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow16" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow18" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow17" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow19" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow18" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow20" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow19" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow21" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow20" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow22" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow21" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow23" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow22" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow24" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow23" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow25" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow24" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow26" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow25" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow27" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow26" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow28" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow27" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow29" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow28" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow30" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow29" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow31" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow30" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="BenillaGossipRow32" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaGossipRow31" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow1" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipGreetingText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-10" y="-20"/></Offset></Anchor></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow2" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow1" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow3" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow2" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow4" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow3" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow5" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow4" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow6" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow5" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow7" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow6" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow8" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow7" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow9" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow8" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow10" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow9" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow11" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow10" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow12" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow11" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow13" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow12" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow14" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow13" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow15" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow14" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow16" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow15" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow17" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow16" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow18" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow17" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow19" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow18" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow20" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow19" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow21" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow20" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow22" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow21" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow23" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow22" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow24" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow23" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow25" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow24" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow26" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow25" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow27" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow26" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow28" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow27" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow29" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow28" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow30" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow29" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow31" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow30" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
<Button name="GossipRow32" inherits="BenillaGossipRowTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="GossipRow31" relativePoint="BOTTOMLEFT"/></Anchors><Scripts><OnLoad>BenillaGossipRow_OnLoad(self)</OnLoad></Scripts></Button>
|
||||
</Frames>
|
||||
</Frame>
|
||||
</Frames>
|
||||
@@ -431,7 +433,7 @@
|
||||
<!-- Close: the real GossipFrameCloseButton position (CENTER on the window's TOPRIGHT,
|
||||
-42,-31; ref l.179-187), on the minimize-button art our close already used (the real
|
||||
UIPanelCloseButton art rides the tooltip/dialog arc). -->
|
||||
<Button name="BenillaGossipCloseButton">
|
||||
<Button name="GossipCloseButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-42" y="-31"/></Offset></Anchor>
|
||||
@@ -451,7 +453,7 @@
|
||||
(GlobalStrings GOODBYE = "Goodbye") is an OVERLAY FontString centred on the button (the
|
||||
real ButtonText draws in the OVERLAY layer). Wired to the same close path as the close
|
||||
button (CloseGossip → HideUIPanel), mirroring the ref's OnClick HideUIPanel(GossipFrame). -->
|
||||
<Button name="BenillaGossipGoodbyeButton">
|
||||
<Button name="GossipGoodbyeButton">
|
||||
<Size><AbsDimension x="78" y="22"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-39" y="73"/></Offset></Anchor>
|
||||
@@ -487,8 +489,8 @@
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaGossipFrame_OnLoad(self)</OnLoad>
|
||||
<OnEvent>BenillaGossipFrame_OnEvent(self)</OnEvent>
|
||||
<OnLoad>GossipFrame_OnLoad()</OnLoad>
|
||||
<OnEvent>GossipFrame_OnEvent()</OnEvent>
|
||||
<!-- The row auto-height's settle pass (see BenillaGossipRow_Resize): our text measure
|
||||
is a frame late, so the fit re-runs here until every visible row's label measure
|
||||
lands. Fires only while the window is effectively visible. -->
|
||||
|
||||
@@ -62,12 +62,24 @@
|
||||
may be `nil` while the item template is still in flight). `GetLootRollItemLink` DOES exist now
|
||||
(decision 1059) but is the click's, not the hover's: the tooltip wants the id, not the markup.
|
||||
|
||||
`toplevel="true"` (ref l.243) IS OMITTED — `SetToplevel` isn't in the v1 object model yet
|
||||
(`loader/geometry.rs`'s `apply_attrs`: a warn-once "gap", same as `movable`/`resizable`); other
|
||||
benilla toplevel popups (BuffFrame.xml, CastingBar.xml) carry the attribute anyway and simply
|
||||
eat the warning, but it has ZERO engine effect either way today, so keeping this file's own
|
||||
load warning-free (this assignment's own bar) costs nothing real — trivially re-added the day
|
||||
`SetToplevel` lands.
|
||||
`toplevel="true"` (ref l.243) IS STILL OMITTED, but for a NEW reason — the old one ("`SetToplevel`
|
||||
isn't in the v1 object model yet ... trivially re-added the day `SetToplevel` lands") has expired.
|
||||
`SetToplevel` landed: the loader calls the real method and the raise law — occlusion-gated,
|
||||
confined to this frame's own stratum — is `script::object::toplevel` (wow-re
|
||||
`ui/scratch/toplevel-raise.md`).
|
||||
|
||||
What adding it back does, measured rather than guessed: `GroupLootFrame1`'s rect
|
||||
(390.5,60)-(633.5,144) overlaps `ExhaustionTick` — `ActionBar.xml` l.1111, `frameStrata="DIALOG"`
|
||||
transcribed verbatim from ref-MainMenuBar.xml l.415-442 — in a ~2.5 px band, so showing roll
|
||||
frame 1 raises its whole subtree to DIALOG level 3 while frame 2 (which overlaps nothing, the
|
||||
four stack with a 15 px gap) stays at level 0. That is exactly what the reference does, and the
|
||||
only pixels it moves are the sliver where the roll frame meets the tick. It does, however, break
|
||||
`group_loot_tests::start_loot_roll_claims_frames_in_order_and_paints_the_roll`, whose
|
||||
`quad_center("UI-GroupLoot-Dice-Up")` picks the FIRST dice quad in painter order as a stand-in
|
||||
for "frame 1's Need button" — an unstated assumption that declaration order is draw order among
|
||||
the four, which the raise legitimately ends. That is a fixture proxy, not the test's assertion
|
||||
(the BoP-confirm gate it proves is untouched), so re-adding the attribute is one word plus a
|
||||
two-line fix to that helper — a director's call on the look, not a blocked mechanism.
|
||||
|
||||
NAMED FONT OBJECTS ARE SUPPORTED on this branch (the loader's top-level `<Font>` handling,
|
||||
`loader/mod.rs`'s `do_font`, plus the full `Fonts.xml` transcription, both landed since
|
||||
@@ -75,9 +87,9 @@
|
||||
`inherits="GameFontNormalSmall"` below resolves for real; no stale caveat needed here.
|
||||
|
||||
IN-FLIGHT ITEMS: `GetLootRollItemInfo`'s `name`/`texture`/`quality` are `nil` while the item
|
||||
template query hasn't landed yet (`loot_roll.rs`). `BenillaGroupLootFrame_OnShow` falls back to
|
||||
template query hasn't landed yet (`loot_roll.rs`). `GroupLootFrame_OnShow` falls back to
|
||||
`BENILLA_FALLBACK_ICON` / an empty name / common (quality 1) — the same fallback shape
|
||||
`BenillaLootFrame_Update` uses for an in-flight loot row.
|
||||
`LootFrame_Update` uses for an in-flight loot row.
|
||||
|
||||
The Era API this drives (`benilla-ui/src/script/loot_roll.rs`, decision 0591):
|
||||
`GetLootRollItemInfo(rollID)` -> texture,name,count,quality,bindOnPickUp,itemID;
|
||||
@@ -125,7 +137,7 @@
|
||||
-- of the four frames for a freshly opened roll, seed its rollID/rollTime, and prime the
|
||||
-- Timer StatusBar's max before showing it (fields set BEFORE Show(), so OnShow below can
|
||||
-- already read frame.rollID).
|
||||
function BenillaGroupLootFrame_OpenNewFrame(rollId, rollTime)
|
||||
function GroupLootFrame_OpenNewFrame(rollId, rollTime)
|
||||
for i = 1, BENILLA_NUM_GROUP_LOOT_FRAMES do
|
||||
local frame = getglobal("GroupLootFrame" .. i)
|
||||
if frame and not frame:IsVisible() then
|
||||
@@ -143,7 +155,7 @@
|
||||
-- GroupLootFrame_OnShow's body (ref l.260-277): resolve the roll's display fields and paint
|
||||
-- the icon/name/backdrop. name/texture/quality may be nil while the item template query is
|
||||
-- still in flight (loot_roll.rs) — fall back to the fallback icon / blank name / common (1)
|
||||
-- quality rather than erroring, the same fallback shape BenillaLootFrame_Update uses for an
|
||||
-- quality rather than erroring, the same fallback shape LootFrame_Update uses for an
|
||||
-- in-flight loot row (LootFrame.xml).
|
||||
--
|
||||
-- SPLIT OUT OF OnShow, unlike the reference, because it must be re-runnable: the ref's
|
||||
@@ -182,7 +194,8 @@
|
||||
nameText:SetVertexColor(color.r, color.g, color.b)
|
||||
end
|
||||
|
||||
function BenillaGroupLootFrame_OnShow(frame)
|
||||
function GroupLootFrame_OnShow()
|
||||
local frame = this
|
||||
BenillaGroupLootFrame_Paint(frame)
|
||||
end
|
||||
|
||||
@@ -195,7 +208,8 @@
|
||||
frame:RegisterEvent("UPDATE_LOOT_ROLL")
|
||||
end
|
||||
|
||||
function BenillaGroupLootFrame_OnEvent(frame)
|
||||
function GroupLootFrame_OnEvent()
|
||||
local frame = this
|
||||
if arg1 ~= frame.rollID then
|
||||
return
|
||||
end
|
||||
@@ -320,7 +334,7 @@
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<!-- The gold BoP dragon flourish (ref l.287-298): hidden by default, shown only for a
|
||||
bind-on-pickup roll (BenillaGroupLootFrame_OnShow). -->
|
||||
bind-on-pickup roll (GroupLootFrame_OnShow). -->
|
||||
<Texture name="$parentDecoration" file="Interface\DialogFrame\UI-DialogBox-Gold-Dragon" hidden="true">
|
||||
<Size><AbsDimension x="120" y="120"/></Size>
|
||||
<Anchors>
|
||||
@@ -363,7 +377,7 @@
|
||||
</Button>
|
||||
<!-- Pass (ref $parentPassButton, l.364-384): the ref inherits UIPanelCloseButton, which
|
||||
this engine ships no template for — reconstructed on the same minimize-button art
|
||||
BenillaLootFrame's own close button uses (LootFrame.xml's identical "no
|
||||
LootFrame's own close button uses (LootFrame.xml's identical "no
|
||||
UIPanelCloseButton template ships" precedent), at the ref's own 32x32/position. -->
|
||||
<Button name="$parentPassButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
@@ -448,8 +462,8 @@
|
||||
</Backdrop>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaGroupLootFrame_OnLoad(self)</OnLoad>
|
||||
<OnShow>BenillaGroupLootFrame_OnShow(self)</OnShow>
|
||||
<OnEvent>BenillaGroupLootFrame_OnEvent(self)</OnEvent>
|
||||
<OnShow>GroupLootFrame_OnShow()</OnShow>
|
||||
<OnEvent>GroupLootFrame_OnEvent()</OnEvent>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
@@ -495,7 +509,7 @@
|
||||
</OnLoad>
|
||||
<OnEvent>
|
||||
if event == "START_LOOT_ROLL" then
|
||||
BenillaGroupLootFrame_OpenNewFrame(arg1, arg2)
|
||||
GroupLootFrame_OpenNewFrame(arg1, arg2)
|
||||
elseif event == "CONFIRM_LOOT_ROLL" then
|
||||
-- Ref UIParent.lua:517-524 verbatim, minus the central router: a Need/Greed on a
|
||||
-- BIND-ON-PICKUP roll sends nothing and asks first (decision 0594). arg1 = rollID,
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
|
||||
-- ref InspectFrame_OnHide (l.53-59): drop the token, play the close sound, and tell the
|
||||
-- engine to stop inspecting (which stops the app's per-frame slot resolve). No subframe
|
||||
-- hide-loop is needed (unlike BenillaCharacterFrame_OnHide's): the one page is a true
|
||||
-- hide-loop is needed (unlike CharacterFrame_OnHide's): the one page is a true
|
||||
-- engine child of this frame, so it hides on the parent's cascade.
|
||||
function BenillaInspectFrame_OnHide(frame)
|
||||
frame.unit = nil
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
<!--
|
||||
The chat-link tooltip (decision 0274 P4) - ref ItemRef.xml/ItemRef.lua, quoted: a
|
||||
standalone GameTooltip instance parked above the bottom center that SetItemRef fills via
|
||||
SetHyperlink when a chat item link is clicked, plus its corner close button. The plate is
|
||||
the shared tooltip plate (GameTooltip.xml has the art + Thicken rationale); the ref gets
|
||||
it from GameTooltipTemplate.
|
||||
SetHyperlink when a chat item link is clicked, plus its corner close button. The plate,
|
||||
the status bar and the OnHide come from GameTooltipTemplate — the same inheritance the ref
|
||||
uses (ref ItemRef.xml l.4) — declared in GameTooltip.xml, which the manifest loads first.
|
||||
|
||||
Departures, each named:
|
||||
- No coin row (ref: the template's $parentMoneyFrame): our money row is the coin trio the
|
||||
GameTooltip instance declares, and its OnTooltipAddMoney/OnTooltipCleared pair stays
|
||||
there with it, so a chat-link tooltip renders no price line. The reasoning is on
|
||||
GameTooltipTemplate.
|
||||
- No drag (ref: movable + RegisterForDrag + StartMoving/StopMovingOrSizing +
|
||||
ValidateFramePosition): benilla has no user-moved-frame affordance yet - the window
|
||||
sits on its declared anchor.
|
||||
@@ -87,27 +91,11 @@
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
<GameTooltip name="ItemRefTooltip" frameStrata="HIGH" hidden="true">
|
||||
<GameTooltip name="ItemRefTooltip" inherits="GameTooltipTemplate" frameStrata="HIGH" hidden="true">
|
||||
<Size><AbsDimension x="128" y="64"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="80"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
|
||||
<EdgeSize><AbsValue val="16"/></EdgeSize>
|
||||
<TileSize><AbsValue val="16"/></TileSize>
|
||||
<BackgroundInsets><AbsInset left="5" right="5" top="5" bottom="5"/></BackgroundInsets>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentThicken">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="5" y="-5"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-5" y="5"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="0.09" g="0.09" b="0.19" a="0.4"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<!-- ref ItemRefCloseButton (ItemRef.xml l.17-36, quoted): the corner minimize-style
|
||||
close; OnLoad's SetPadding(16) keeps it off the text. -->
|
||||
@@ -125,11 +113,15 @@
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<!-- The template's own OnLoad, re-declared to add the padding (ref ItemRef.xml
|
||||
l.38-42's shape, minus its drag arming — see the no-drag departure above); the
|
||||
loader's later SetScript wins, so GameTooltip_OnLoad has to be called here too.
|
||||
OnHide is the template's unchanged, so it is not re-declared — ref ItemRef.xml
|
||||
declares none either. -->
|
||||
<OnLoad>
|
||||
GameTooltip_OnLoad(self)
|
||||
self:SetPadding(16)
|
||||
</OnLoad>
|
||||
<OnHide>GameTooltip_OnHide(this)</OnHide>
|
||||
</Scripts>
|
||||
</GameTooltip>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<!--
|
||||
The item-text reader — letters, books, plaques (the mail arc's "permanent copy" lands
|
||||
here). Transcribed from the reference ItemTextFrame.xml (354 l) + ItemTextFrame.lua
|
||||
(104 l); geometry/art quoted verbatim, names Benilla-prefixed. Driven by the engine's
|
||||
(104 l); geometry/art quoted verbatim, names the reference's own. Driven by the engine's
|
||||
item-text seam (benilla-ui/src/script/item_text.rs) + crate::ui_item_text's feed/drain:
|
||||
the app fires the reference event flow ITEM_TEXT_BEGIN → ITEM_TEXT_READY → ITEM_TEXT_CLOSED.
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
["Bronze"] = {0.93, 0.82, 0}
|
||||
}
|
||||
-- ref UIParent.lua l.1880-1888.
|
||||
function BenillaGetMaterialTextColors(material)
|
||||
function GetMaterialTextColors(material)
|
||||
local textColor = BENILLA_MATERIAL_TEXT_COLOR_TABLE[material]
|
||||
local titleColor = BENILLA_MATERIAL_TITLETEXT_COLOR_TABLE[material]
|
||||
if not (textColor and titleColor) then
|
||||
@@ -56,7 +56,7 @@
|
||||
end
|
||||
|
||||
-- ref ItemTextFrame.lua l.1-8.
|
||||
function BenillaItemTextFrame_OnLoad()
|
||||
function ItemTextFrame_OnLoad()
|
||||
this:RegisterEvent("ITEM_TEXT_BEGIN")
|
||||
this:RegisterEvent("ITEM_TEXT_TRANSLATION")
|
||||
this:RegisterEvent("ITEM_TEXT_READY")
|
||||
@@ -64,26 +64,26 @@
|
||||
end
|
||||
|
||||
-- ref l.10-96.
|
||||
function BenillaItemTextFrame_OnEvent(event)
|
||||
function ItemTextFrame_OnEvent(event)
|
||||
if event == "ITEM_TEXT_BEGIN" then
|
||||
BenillaItemTextTitleText:SetText(ItemTextGetItem())
|
||||
BenillaItemTextScrollFrame:Hide()
|
||||
BenillaItemTextCurrentPage:Hide()
|
||||
BenillaItemTextStatusBar:Hide()
|
||||
BenillaItemTextPrevPageButton:Hide()
|
||||
BenillaItemTextNextPageButton:Hide()
|
||||
ItemTextTitleText:SetText(ItemTextGetItem())
|
||||
ItemTextScrollFrame:Hide()
|
||||
ItemTextCurrentPage:Hide()
|
||||
ItemTextStatusBar:Hide()
|
||||
ItemTextPrevPageButton:Hide()
|
||||
ItemTextNextPageButton:Hide()
|
||||
local material = ItemTextGetMaterial()
|
||||
if not material then material = "Parchment" end
|
||||
local textColor = BenillaGetMaterialTextColors(material)
|
||||
BenillaItemTextPageText:SetTextColor(textColor[1], textColor[2], textColor[3])
|
||||
local textColor = GetMaterialTextColors(material)
|
||||
ItemTextPageText:SetTextColor(textColor[1], textColor[2], textColor[3])
|
||||
return
|
||||
end
|
||||
if event == "ITEM_TEXT_TRANSLATION" then
|
||||
BenillaItemTextPrevPageButton:Hide()
|
||||
BenillaItemTextNextPageButton:Hide()
|
||||
ItemTextPrevPageButton:Hide()
|
||||
ItemTextNextPageButton:Hide()
|
||||
this.translationElapsed = 0
|
||||
BenillaItemTextStatusBar:SetMinMaxValues(0, arg1)
|
||||
BenillaItemTextStatusBar:Show()
|
||||
ItemTextStatusBar:SetMinMaxValues(0, arg1)
|
||||
ItemTextStatusBar:Show()
|
||||
ShowUIPanel(this)
|
||||
if not this:IsVisible() then CloseItemText() end
|
||||
return
|
||||
@@ -92,45 +92,45 @@
|
||||
local creator = ItemTextGetCreator()
|
||||
if creator then
|
||||
creator = "\n\n".."From,".."\n"..creator.."\n\n" -- ITEM_TEXT_FROM
|
||||
BenillaItemTextPageText:SetText("\n"..ItemTextGetText()..creator)
|
||||
ItemTextPageText:SetText("\n"..ItemTextGetText()..creator)
|
||||
else
|
||||
BenillaItemTextPageText:SetText("\n"..ItemTextGetText().."\n")
|
||||
ItemTextPageText:SetText("\n"..ItemTextGetText().."\n")
|
||||
end
|
||||
BenillaItemTextScrollFrame:Show()
|
||||
ItemTextScrollFrame:Show()
|
||||
local page = ItemTextGetPage()
|
||||
local next = ItemTextHasNextPage()
|
||||
local material = ItemTextGetMaterial()
|
||||
if not material then material = "Parchment" end
|
||||
if material == "Parchment" then
|
||||
BenillaItemTextMaterialTopLeft:Hide()
|
||||
BenillaItemTextMaterialTopRight:Hide()
|
||||
BenillaItemTextMaterialBotLeft:Hide()
|
||||
BenillaItemTextMaterialBotRight:Hide()
|
||||
ItemTextMaterialTopLeft:Hide()
|
||||
ItemTextMaterialTopRight:Hide()
|
||||
ItemTextMaterialBotLeft:Hide()
|
||||
ItemTextMaterialBotRight:Hide()
|
||||
else
|
||||
BenillaItemTextMaterialTopLeft:Show()
|
||||
BenillaItemTextMaterialTopRight:Show()
|
||||
BenillaItemTextMaterialBotLeft:Show()
|
||||
BenillaItemTextMaterialBotRight:Show()
|
||||
BenillaItemTextMaterialTopLeft:SetTexture("Interface\\ItemTextFrame\\ItemText-"..material.."-TopLeft")
|
||||
BenillaItemTextMaterialTopRight:SetTexture("Interface\\ItemTextFrame\\ItemText-"..material.."-TopRight")
|
||||
BenillaItemTextMaterialBotLeft:SetTexture("Interface\\ItemTextFrame\\ItemText-"..material.."-BotLeft")
|
||||
BenillaItemTextMaterialBotRight:SetTexture("Interface\\ItemTextFrame\\ItemText-"..material.."-BotRight")
|
||||
ItemTextMaterialTopLeft:Show()
|
||||
ItemTextMaterialTopRight:Show()
|
||||
ItemTextMaterialBotLeft:Show()
|
||||
ItemTextMaterialBotRight:Show()
|
||||
ItemTextMaterialTopLeft:SetTexture("Interface\\ItemTextFrame\\ItemText-"..material.."-TopLeft")
|
||||
ItemTextMaterialTopRight:SetTexture("Interface\\ItemTextFrame\\ItemText-"..material.."-TopRight")
|
||||
ItemTextMaterialBotLeft:SetTexture("Interface\\ItemTextFrame\\ItemText-"..material.."-BotLeft")
|
||||
ItemTextMaterialBotRight:SetTexture("Interface\\ItemTextFrame\\ItemText-"..material.."-BotRight")
|
||||
end
|
||||
if (page > 1) or next then
|
||||
BenillaItemTextCurrentPage:SetText(page)
|
||||
BenillaItemTextCurrentPage:Show()
|
||||
ItemTextCurrentPage:SetText(page)
|
||||
ItemTextCurrentPage:Show()
|
||||
if page > 1 then
|
||||
BenillaItemTextPrevPageButton:Show()
|
||||
ItemTextPrevPageButton:Show()
|
||||
else
|
||||
BenillaItemTextPrevPageButton:Hide()
|
||||
ItemTextPrevPageButton:Hide()
|
||||
end
|
||||
if next then
|
||||
BenillaItemTextNextPageButton:Show()
|
||||
ItemTextNextPageButton:Show()
|
||||
else
|
||||
BenillaItemTextNextPageButton:Hide()
|
||||
ItemTextNextPageButton:Hide()
|
||||
end
|
||||
end
|
||||
BenillaItemTextStatusBar:Hide()
|
||||
ItemTextStatusBar:Hide()
|
||||
ShowUIPanel(this)
|
||||
if not this:IsVisible() then CloseItemText() end
|
||||
return
|
||||
@@ -142,15 +142,15 @@
|
||||
end
|
||||
|
||||
-- ref l.98-104.
|
||||
function BenillaItemTextFrame_OnUpdate(elapsed)
|
||||
if BenillaItemTextStatusBar:IsVisible() then
|
||||
function ItemTextFrame_OnUpdate(elapsed)
|
||||
if ItemTextStatusBar:IsVisible() then
|
||||
elapsed = this.translationElapsed + elapsed
|
||||
BenillaItemTextStatusBar:SetValue(elapsed)
|
||||
ItemTextStatusBar:SetValue(elapsed)
|
||||
this.translationElapsed = elapsed
|
||||
end
|
||||
end
|
||||
]]></Script>
|
||||
<Frame name="BenillaItemTextFrame" toplevel="true" movable="true" enableMouse="true" hidden="true" parent="UIParent">
|
||||
<Frame name="ItemTextFrame" toplevel="true" movable="true" enableMouse="true" hidden="true" parent="UIParent">
|
||||
<Size><AbsDimension x="384" y="512"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-104"/></Offset></Anchor></Anchors>
|
||||
<HitRectInsets><AbsInset left="0" right="30" top="0" bottom="70"/></HitRectInsets>
|
||||
@@ -182,29 +182,29 @@
|
||||
<Layer level="ARTWORK">
|
||||
<!-- The non-parchment material corners (stone/marble/…): textures swapped in by
|
||||
ITEM_TEXT_READY; hidden entirely for parchment (ref l.56-70). -->
|
||||
<Texture name="BenillaItemTextMaterialTopLeft" hidden="true">
|
||||
<Texture name="ItemTextMaterialTopLeft" hidden="true">
|
||||
<Size><AbsDimension x="256" y="256"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="21" y="-75"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
<Texture name="BenillaItemTextMaterialTopRight" hidden="true">
|
||||
<Texture name="ItemTextMaterialTopRight" hidden="true">
|
||||
<Size><AbsDimension x="64" y="256"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaItemTextMaterialTopLeft" relativePoint="TOPRIGHT"/></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="ItemTextMaterialTopLeft" relativePoint="TOPRIGHT"/></Anchors>
|
||||
</Texture>
|
||||
<Texture name="BenillaItemTextMaterialBotLeft" hidden="true">
|
||||
<Texture name="ItemTextMaterialBotLeft" hidden="true">
|
||||
<Size><AbsDimension x="256" y="128"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaItemTextMaterialTopLeft" relativePoint="BOTTOMLEFT"/></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="ItemTextMaterialTopLeft" relativePoint="BOTTOMLEFT"/></Anchors>
|
||||
</Texture>
|
||||
<Texture name="BenillaItemTextMaterialBotRight" hidden="true">
|
||||
<Texture name="ItemTextMaterialBotRight" hidden="true">
|
||||
<Size><AbsDimension x="64" y="128"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaItemTextMaterialTopLeft" relativePoint="BOTTOMRIGHT"/></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="ItemTextMaterialTopLeft" relativePoint="BOTTOMRIGHT"/></Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString name="BenillaItemTextTitleText" inherits="GameFontNormal">
|
||||
<FontString name="ItemTextTitleText" inherits="GameFontNormal">
|
||||
<Size><AbsDimension x="224" y="14"/></Size>
|
||||
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="6" y="230"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<FontString name="BenillaItemTextCurrentPage" inherits="GameFontNormal">
|
||||
<FontString name="ItemTextCurrentPage" inherits="GameFontNormal">
|
||||
<Size><AbsDimension x="192" y="0"/></Size>
|
||||
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="10" y="-50"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
@@ -213,7 +213,7 @@
|
||||
<Frames>
|
||||
<!-- ref ItemTextScrollFrame l.145-219: the plain-Frame stand-in (see the header) with
|
||||
the ref's own flanking scrollbar art kept verbatim. -->
|
||||
<Frame name="BenillaItemTextScrollFrame" hidden="true">
|
||||
<Frame name="ItemTextScrollFrame" hidden="true">
|
||||
<Size><AbsDimension x="280" y="355"/></Size>
|
||||
<Anchors><Anchor point="TOPRIGHT"><Offset><AbsDimension x="-66" y="-76"/></Offset></Anchor></Anchors>
|
||||
<Layers>
|
||||
@@ -222,28 +222,28 @@
|
||||
SetPoint time — a target declared later falls back to the parent (and
|
||||
the black track lands on the parchment). -->
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="BenillaItemTextScrollFrameTop" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
|
||||
<Texture name="ItemTextScrollFrameTop" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
|
||||
<Size><AbsDimension x="28" y="256"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="0" y="5"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0" right="0.484375" top="0" bottom="1.0"/>
|
||||
</Texture>
|
||||
<Texture name="BenillaItemTextScrollFrameBottom" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
|
||||
<Texture name="ItemTextScrollFrameBottom" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
|
||||
<Size><AbsDimension x="28" y="107"/></Size>
|
||||
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0.515625" right="1.0" top="0" bottom="0.4140625"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="BenillaItemTextScrollFrameMiddle">
|
||||
<Texture name="ItemTextScrollFrameMiddle">
|
||||
<Size><AbsDimension x="25" y="365"/></Size>
|
||||
<Anchors><Anchor point="TOPRIGHT" relativeTo="BenillaItemTextScrollFrameTop"/></Anchors>
|
||||
<Anchors><Anchor point="TOPRIGHT" relativeTo="ItemTextScrollFrameTop"/></Anchors>
|
||||
<Color r="0" g="0" b="0" a="0.8"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<!-- ref ItemTextPageText (SimpleHTML 270×304 at 0,-15 in the scroll child)
|
||||
as a FontString at the same rect. -->
|
||||
<FontString name="BenillaItemTextPageText" inherits="ItemTextFontNormal" justifyH="LEFT" justifyV="TOP">
|
||||
<FontString name="ItemTextPageText" inherits="ItemTextFontNormal" justifyH="LEFT" justifyV="TOP">
|
||||
<Size><AbsDimension x="270" y="304"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-15"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
@@ -252,9 +252,9 @@
|
||||
</Frame>
|
||||
<!-- ref ItemTextStatusBar l.220-251 — the ITEM_TEXT_TRANSLATION progress bar; never
|
||||
fires on our letters, kept for fidelity. -->
|
||||
<StatusBar name="BenillaItemTextStatusBar" hidden="true">
|
||||
<StatusBar name="ItemTextStatusBar" hidden="true">
|
||||
<Size><AbsDimension x="250" y="21"/></Size>
|
||||
<Anchors><Anchor point="BOTTOM" relativeTo="BenillaItemTextFrame"><Offset><AbsDimension x="-5" y="128"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="BOTTOM" relativeTo="ItemTextFrame"><Offset><AbsDimension x="-5" y="128"/></Offset></Anchor></Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture file="Interface\PaperDollInfoFrame\UI-Character-Skills-BarBorder">
|
||||
@@ -270,7 +270,7 @@
|
||||
<BarTexture file="Interface\TargetingFrame\UI-TargetingFrame-BarFill"/>
|
||||
<BarColor r="0.25" g="0.75" b="0.25"/>
|
||||
</StatusBar>
|
||||
<Button name="BenillaItemTextPrevPageButton" hidden="true">
|
||||
<Button name="ItemTextPrevPageButton" hidden="true">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors><Anchor point="CENTER" relativePoint="TOPLEFT"><Offset><AbsDimension x="90" y="-56"/></Offset></Anchor></Anchors>
|
||||
<Layers>
|
||||
@@ -291,7 +291,7 @@
|
||||
<DisabledTexture file="Interface\Buttons\UI-SpellbookIcon-PrevPage-Disabled"/>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
|
||||
</Button>
|
||||
<Button name="BenillaItemTextNextPageButton" hidden="true">
|
||||
<Button name="ItemTextNextPageButton" hidden="true">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors><Anchor point="CENTER" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-55" y="-56"/></Offset></Anchor></Anchors>
|
||||
<Layers>
|
||||
@@ -312,11 +312,11 @@
|
||||
<DisabledTexture file="Interface\Buttons\UI-SpellbookIcon-NextPage-Disabled"/>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
|
||||
</Button>
|
||||
<Button name="BenillaItemTextCloseButton">
|
||||
<Button name="ItemTextCloseButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors><Anchor point="CENTER" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-45" y="-26"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnClick>HideUIPanel(BenillaItemTextFrame)</OnClick>
|
||||
<OnClick>HideUIPanel(ItemTextFrame)</OnClick>
|
||||
</Scripts>
|
||||
<NormalTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Down"/>
|
||||
@@ -324,9 +324,9 @@
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaItemTextFrame_OnLoad()</OnLoad>
|
||||
<OnEvent>BenillaItemTextFrame_OnEvent(event)</OnEvent>
|
||||
<OnUpdate>BenillaItemTextFrame_OnUpdate(arg1)</OnUpdate>
|
||||
<OnLoad>ItemTextFrame_OnLoad()</OnLoad>
|
||||
<OnEvent>ItemTextFrame_OnEvent(event)</OnEvent>
|
||||
<OnUpdate>ItemTextFrame_OnUpdate(arg1)</OnUpdate>
|
||||
<OnShow>PlaySound("igMainMenuOpen")</OnShow>
|
||||
<OnHide>
|
||||
CloseItemText()
|
||||
|
||||
@@ -302,7 +302,7 @@
|
||||
function KeyBindingsPage_Update()
|
||||
local page = KeyBindingsPage
|
||||
local scroll = getglobal("OptionsFrameContainerBodyKeybindingsScrollFrame")
|
||||
local offset = BenillaFauxScrollFrame_GetOffset(scroll)
|
||||
local offset = FauxScrollFrame_GetOffset(scroll)
|
||||
for i = 1, KEYBINDINGS_DISPLAYED do
|
||||
local name = KEYBINDINGS_ROW_BASE .. i
|
||||
local row = getglobal(name)
|
||||
@@ -317,7 +317,7 @@
|
||||
row:Hide()
|
||||
end
|
||||
end
|
||||
BenillaFauxScrollFrame_Update(scroll, table.getn(page.items),
|
||||
FauxScrollFrame_Update(scroll, table.getn(page.items),
|
||||
KEYBINDINGS_DISPLAYED, KEYBINDINGS_ROW_HEIGHT)
|
||||
KeyBindings_UpdateUnbindKey()
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<!--
|
||||
benilla's loot window (decision 0084), RE-SKINNED to the real 1.12 loot window: the geometry
|
||||
and art below are QUOTED from the real `LootFrame.xml` (scratchpad `LootFrame.xml`; line
|
||||
citations inline), turned onto our own feeds/intents. The name `BenillaLootFrame`, its panel
|
||||
citations inline), turned onto our own feeds/intents. The name `LootFrame`, its panel
|
||||
registration (UiPanels.xml area="left", pushable=7), the loot feeds, the 4-rows-per-page +
|
||||
pager logic, and the intent functions are UNCHANGED — only the window's dressing is real now.
|
||||
|
||||
@@ -163,8 +163,8 @@
|
||||
end
|
||||
|
||||
-- LootFrame_Update (LootFrame.lua l.67-119): fill the visible rows for the current page.
|
||||
function BenillaLootFrame_Update()
|
||||
local frame = getglobal("BenillaLootFrame")
|
||||
function LootFrame_Update()
|
||||
local frame = getglobal("LootFrame")
|
||||
local numItems = GetNumLootItems()
|
||||
-- Rows per page: 4, or 3 when there's a pager to make room for (l.70-73).
|
||||
local perPage = BENILLA_LOOT_NUMBUTTONS
|
||||
@@ -185,7 +185,7 @@
|
||||
end
|
||||
|
||||
for index = 1, BENILLA_LOOT_NUMBUTTONS do
|
||||
local button = getglobal("BenillaLootButton" .. index)
|
||||
local button = getglobal("LootButton" .. index)
|
||||
local slot = (perPage * (frame.page - 1)) + index
|
||||
if slot <= numItems and index <= perPage
|
||||
and (LootSlotIsItem(slot) or LootSlotIsCoin(slot)) then
|
||||
@@ -210,52 +210,54 @@
|
||||
|
||||
-- Pager visibility (LootFrame.lua l.105-118): Up when past page 1, Down when more remain.
|
||||
if frame.page > 1 then
|
||||
getglobal("BenillaLootUpButton"):Show()
|
||||
getglobal("LootUpButton"):Show()
|
||||
else
|
||||
getglobal("BenillaLootUpButton"):Hide()
|
||||
getglobal("LootUpButton"):Hide()
|
||||
end
|
||||
if frame.page < maxPage then
|
||||
getglobal("BenillaLootDownButton"):Show()
|
||||
getglobal("LootDownButton"):Show()
|
||||
else
|
||||
getglobal("BenillaLootDownButton"):Hide()
|
||||
getglobal("LootDownButton"):Hide()
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaLootFrame_PageDown()
|
||||
local frame = getglobal("BenillaLootFrame")
|
||||
function LootFrame_PageDown()
|
||||
local frame = getglobal("LootFrame")
|
||||
frame.page = frame.page + 1
|
||||
BenillaLootFrame_Update()
|
||||
LootFrame_Update()
|
||||
end
|
||||
|
||||
function BenillaLootFrame_PageUp()
|
||||
local frame = getglobal("BenillaLootFrame")
|
||||
function LootFrame_PageUp()
|
||||
local frame = getglobal("LootFrame")
|
||||
frame.page = frame.page - 1
|
||||
BenillaLootFrame_Update()
|
||||
LootFrame_Update()
|
||||
end
|
||||
|
||||
function BenillaLootFrame_OnLoad(frame)
|
||||
function LootFrame_OnLoad()
|
||||
local frame = this
|
||||
frame.page = 1
|
||||
frame:RegisterEvent("LOOT_OPENED")
|
||||
frame:RegisterEvent("LOOT_UPDATE")
|
||||
frame:RegisterEvent("LOOT_CLOSED")
|
||||
end
|
||||
|
||||
function BenillaLootFrame_OnEvent(frame)
|
||||
function LootFrame_OnEvent(event)
|
||||
local frame = this
|
||||
if event == "LOOT_OPENED" then
|
||||
-- A fresh loot: reset to page 1, paint, show (LootFrame.lua l.14-16).
|
||||
frame.page = 1
|
||||
BenillaLootFrame_Update()
|
||||
LootFrame_Update()
|
||||
ShowUIPanel(frame)
|
||||
elseif event == "LOOT_UPDATE" then
|
||||
-- A content change while open (name/quality landed, a row/coin removed): repaint,
|
||||
-- keeping the current page.
|
||||
BenillaLootFrame_Update()
|
||||
LootFrame_Update()
|
||||
elseif event == "LOOT_CLOSED" then
|
||||
HideUIPanel(frame)
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaLootFrame_OnShow()
|
||||
function LootFrame_OnShow()
|
||||
-- Open sound — the window-sound convention (decision 0090), wrapper form (this handler
|
||||
-- has real logic beyond the sound, so it can't be a bare inline PlaySound).
|
||||
-- (LootFrame.lua LootFrame_OnShow, l.131-141). ONLY the empty-roll fork and the fishing
|
||||
@@ -267,16 +269,16 @@
|
||||
-- so we stay silent for it — there is no kit to guess. (DR 0086: vmangos masks the
|
||||
-- lootable flag for empty rolls, so a live empty window is rare — but the branch must
|
||||
-- exist.)
|
||||
getglobal("BenillaLootPortraitOverlay"):SetTexture("Interface\\TargetingFrame\\TargetDead")
|
||||
getglobal("LootPortraitOverlay"):SetTexture("Interface\\TargetingFrame\\TargetDead")
|
||||
if GetNumLootItems() == 0 then
|
||||
PlaySound("LOOTWINDOWOPENEMPTY")
|
||||
elseif IsFishingLoot() then
|
||||
PlaySound("FISHING REEL IN")
|
||||
getglobal("BenillaLootPortraitOverlay"):SetTexture("Interface\\LootFrame\\FishingLoot-Icon")
|
||||
getglobal("LootPortraitOverlay"):SetTexture("Interface\\LootFrame\\FishingLoot-Icon")
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaLootFrame_OnHide()
|
||||
function LootFrame_OnHide()
|
||||
-- Closing the window releases the loot (LootFrame.lua l.143-145). The app only sends the
|
||||
-- release when a loot is actually open, so a hide after the state already cleared is a
|
||||
-- harmless no-op. No close sound here: LootFrame_OnHide plays none (the loot-close kit is
|
||||
@@ -286,7 +288,7 @@
|
||||
|
||||
function BenillaLootCloseButton_OnClick()
|
||||
-- Hide immediately (client-authoritative); OnHide's CloseLoot() sends the release.
|
||||
HideUIPanel(getglobal("BenillaLootFrame"))
|
||||
HideUIPanel(getglobal("LootFrame"))
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
@@ -353,7 +355,7 @@
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<Frame name="BenillaLootFrame" hidden="true">
|
||||
<Frame name="LootFrame" hidden="true">
|
||||
<!-- The real 256×256 window (ref l.59-60). No <Anchors> here — ShowUIPanel SetPoints this to
|
||||
its slot. -->
|
||||
<Size><AbsDimension x="256" y="256"/></Size>
|
||||
@@ -364,7 +366,7 @@
|
||||
OnShow resets it to the skull and swaps a fishing loot to FishingLoot-Icon
|
||||
(decision 1086), exactly as the ref's OnShow does (l.133-140). -->
|
||||
<Layer level="BORDER">
|
||||
<Texture name="BenillaLootPortraitOverlay" file="Interface\TargetingFrame\TargetDead">
|
||||
<Texture name="LootPortraitOverlay" file="Interface\TargetingFrame\TargetDead">
|
||||
<Size><AbsDimension x="58" y="58"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="10" y="-8"/></Offset></Anchor>
|
||||
@@ -376,7 +378,7 @@
|
||||
<Layer level="ARTWORK">
|
||||
<Texture file="Interface\LootFrame\UI-LootPanel"/>
|
||||
<!-- Title: the real ITEMS label (ref l.89-97) — GameFontNormal at CENTER +(-12,102). -->
|
||||
<FontString name="BenillaLootTitle" inherits="GameFontNormal" text="Items">
|
||||
<FontString name="LootTitle" inherits="GameFontNormal" text="Items">
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="-12" y="102"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
@@ -386,27 +388,27 @@
|
||||
<Frames>
|
||||
<!-- The real row stack (ref l.139-174): row 1 at the window TOPLEFT +(24,-80), each next
|
||||
row centred on the prior row's BOTTOM -4. -->
|
||||
<Button name="BenillaLootButton1" inherits="BenillaLootButtonTemplate">
|
||||
<Button name="LootButton1" inherits="BenillaLootButtonTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="24" y="-80"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaLootItem_OnLoad(self, 1)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaLootButton2" inherits="BenillaLootButtonTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="BenillaLootButton1" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
|
||||
<Button name="LootButton2" inherits="BenillaLootButtonTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="LootButton1" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaLootItem_OnLoad(self, 2)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaLootButton3" inherits="BenillaLootButtonTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="BenillaLootButton2" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
|
||||
<Button name="LootButton3" inherits="BenillaLootButtonTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="LootButton2" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaLootItem_OnLoad(self, 3)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaLootButton4" inherits="BenillaLootButtonTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="BenillaLootButton3" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
|
||||
<Button name="LootButton4" inherits="BenillaLootButtonTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="LootButton3" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaLootItem_OnLoad(self, 4)</OnLoad></Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- Pager (ref l.175-216): Up (page back) at the window BOTTOMLEFT +(25,16), Down (page
|
||||
forward) at the Up button's RIGHT +86 — both in the bottom-left of the panel. The
|
||||
ChatIcon scroll arrows are the real art (ref l.191-193, 212-214). -->
|
||||
<Button name="BenillaLootUpButton" hidden="true">
|
||||
<Button name="LootUpButton" hidden="true">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="25" y="16"/></Offset></Anchor>
|
||||
@@ -415,24 +417,24 @@
|
||||
<PushedTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollUp-Down"/>
|
||||
<DisabledTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollUp-Disabled"/>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
|
||||
<Scripts><OnClick>BenillaLootFrame_PageUp()</OnClick></Scripts>
|
||||
<Scripts><OnClick>LootFrame_PageUp()</OnClick></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaLootDownButton" hidden="true">
|
||||
<Button name="LootDownButton" hidden="true">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="BenillaLootUpButton" relativePoint="RIGHT"><Offset><AbsDimension x="86" y="0"/></Offset></Anchor>
|
||||
<Anchor point="LEFT" relativeTo="LootUpButton" relativePoint="RIGHT"><Offset><AbsDimension x="86" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<NormalTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollDown-Up"/>
|
||||
<PushedTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollDown-Down"/>
|
||||
<DisabledTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollDown-Disabled"/>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
|
||||
<Scripts><OnClick>BenillaLootFrame_PageDown()</OnClick></Scripts>
|
||||
<Scripts><OnClick>LootFrame_PageDown()</OnClick></Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- Close: the real LootCloseButton position (CENTER on the window's TOPRIGHT, -81,-26;
|
||||
ref l.119-129), on the minimize-button art it already used (the real LootCloseButton
|
||||
uses this very art, ref l.135-137). -->
|
||||
<Button name="BenillaLootCloseButton">
|
||||
<Button name="LootCloseButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-81" y="-26"/></Offset></Anchor>
|
||||
@@ -444,10 +446,10 @@
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaLootFrame_OnLoad(self)</OnLoad>
|
||||
<OnEvent>BenillaLootFrame_OnEvent(self)</OnEvent>
|
||||
<OnShow>BenillaLootFrame_OnShow()</OnShow>
|
||||
<OnHide>BenillaLootFrame_OnHide()</OnHide>
|
||||
<OnLoad>LootFrame_OnLoad()</OnLoad>
|
||||
<OnEvent>LootFrame_OnEvent(event)</OnEvent>
|
||||
<OnShow>LootFrame_OnShow()</OnShow>
|
||||
<OnHide>LootFrame_OnHide()</OnHide>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
|
||||
@@ -350,7 +350,7 @@
|
||||
local popup = getglobal("BenillaMacroPopupFrame")
|
||||
local scrollFrame = getglobal("BenillaMacroPopupScrollFrame")
|
||||
local numMacroIcons = GetNumMacroIcons()
|
||||
local macroPopupOffset = BenillaFauxScrollFrame_GetOffset(scrollFrame)
|
||||
local macroPopupOffset = FauxScrollFrame_GetOffset(scrollFrame)
|
||||
|
||||
if resetName then
|
||||
if popup.mode == "new" then
|
||||
@@ -379,7 +379,7 @@
|
||||
end
|
||||
end
|
||||
|
||||
BenillaFauxScrollFrame_Update(scrollFrame,
|
||||
FauxScrollFrame_Update(scrollFrame,
|
||||
math.ceil(numMacroIcons / NUM_ICONS_PER_ROW), NUM_ICON_ROWS, MACRO_ICON_ROW_HEIGHT)
|
||||
end
|
||||
|
||||
@@ -404,7 +404,7 @@
|
||||
local popup = getglobal("BenillaMacroPopupFrame")
|
||||
local scrollFrame = getglobal("BenillaMacroPopupScrollFrame")
|
||||
popup.selectedIcon = button:GetID()
|
||||
+ (BenillaFauxScrollFrame_GetOffset(scrollFrame) * NUM_ICONS_PER_ROW)
|
||||
+ (FauxScrollFrame_GetOffset(scrollFrame) * NUM_ICONS_PER_ROW)
|
||||
getglobal("BenillaMacroFrameSelectedMacroButtonIcon")
|
||||
:SetTexture(GetMacroIconInfo(popup.selectedIcon))
|
||||
BenillaMacroPopupOkayButton_Update()
|
||||
@@ -892,13 +892,13 @@
|
||||
|
||||
<!-- The icon grid's faux scroll (ref a ClassTrainerListScrollFrameTemplate, l.759-775).
|
||||
The frame itself is invisible; its ONE visible consequence is where the bar seats
|
||||
(BenillaFauxScrollFrameTemplate hangs it off the frame's right edge +6, inset 16
|
||||
(FauxScrollFrameTemplate hangs it off the frame's right edge +6, inset 16
|
||||
top and bottom). So its rect is the reference's VERBATIM — 296x195 at TOPRIGHT
|
||||
(-39,-67) — which puts the bar at popup x 264..280, on the plate's groove and
|
||||
bracketing the 4-row grid. 0983 sized it to the grid instead (252x152 at TOPLEFT
|
||||
24,-85) and the bar came out at x 282..298: hanging half off the plate's right
|
||||
edge, over the world. -->
|
||||
<Frame name="BenillaMacroPopupScrollFrame" inherits="BenillaFauxScrollFrameTemplate">
|
||||
<Frame name="BenillaMacroPopupScrollFrame" inherits="FauxScrollFrameTemplate">
|
||||
<Size><AbsDimension x="296" y="195"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-39" y="-67"/></Offset></Anchor>
|
||||
@@ -907,7 +907,7 @@
|
||||
<!-- The faux kit's contract (ScrollTemplates.xml): the owner sets updateFunc,
|
||||
the shared Slider handler calls it whenever the row offset moves. -->
|
||||
<OnLoad>
|
||||
BenillaFauxScrollFrame_OnLoad(self)
|
||||
ScrollFrame_OnLoad()
|
||||
self.updateFunc = BenillaMacroPopupFrame_Update
|
||||
</OnLoad>
|
||||
</Scripts>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,8 @@
|
||||
benilla's vendor window (decision 0081 phase 4), RE-SKINNED to the real 1.12 merchant window
|
||||
(dressing arc, decision 0084): the geometry and art below are QUOTED from the real
|
||||
`MerchantFrame.xml` (scratchpad `ref-MerchantFrame.xml`; line citations inline), turned onto our
|
||||
own feeds/intents. The name `BenillaMerchantFrame`, its panel registration
|
||||
(UiPanels.xml's UIPanelWindows["BenillaMerchantFrame"], area="left"), the Era vendor feeds
|
||||
own feeds/intents. The name `MerchantFrame`, its panel registration
|
||||
(UiPanels.xml's UIPanelWindows["MerchantFrame"], area="left"), the Era vendor feeds
|
||||
(GetMerchantNumItems/GetMerchantItemInfo/BuyMerchantItem/CloseMerchant, GetMoney), and the
|
||||
intent functions are UNCHANGED — only the window's dressing is real now.
|
||||
|
||||
@@ -199,14 +199,14 @@
|
||||
-- The merchant-list index a row button shows on the current page (ref index arithmetic,
|
||||
-- MerchantFrame.lua l.74).
|
||||
function BenillaMerchantFrame_ItemIndex(button)
|
||||
return ((BenillaMerchantFrame.page or 1) - 1) * BENILLA_MERCHANT_MAX + button.merchantIndex
|
||||
return ((MerchantFrame.page or 1) - 1) * BENILLA_MERCHANT_MAX + button.merchantIndex
|
||||
end
|
||||
|
||||
function BenillaMerchantItem_OnClick(button, mouseButton)
|
||||
-- Buyback tab: either click buys the slot back (ref MerchantItemButton_OnClick's else
|
||||
-- arm, l.358-361) — and, as there, with NO modifier branch of any kind: 1.12 ships no
|
||||
-- GetBuybackItemLink, so a buyback row carries no link at all (ui_merchant.rs).
|
||||
if BenillaMerchantFrame.selectedTab == 2 then
|
||||
if MerchantFrame.selectedTab == 2 then
|
||||
BuybackItem(button.merchantIndex)
|
||||
return
|
||||
end
|
||||
@@ -254,7 +254,7 @@
|
||||
-- offset back to the icon's TOPRIGHT (37 − 153 = −116). No buy-price line — the price is
|
||||
-- on the row, and the real vendor tooltip doesn't repeat it.
|
||||
function BenillaMerchantItem_OnEnter(button)
|
||||
if BenillaMerchantFrame.selectedTab == 2 then
|
||||
if MerchantFrame.selectedTab == 2 then
|
||||
if not GetBuybackItemInfo(button.merchantIndex) then return end
|
||||
GameTooltip:SetOwner(button, "ANCHOR_RIGHT", -116, 0)
|
||||
GameTooltip:SetBuybackItem(button.merchantIndex)
|
||||
@@ -272,14 +272,14 @@
|
||||
-- Buy tab: remember the row so the frame's OnUpdate arms the coin every frame — Buy
|
||||
-- vs the grayed UnableBuy by affordability, or the Inspect magnifier while Ctrl is held
|
||||
-- (ref MerchantFrame.itemHover + OnUpdate l.82/725-731).
|
||||
BenillaMerchantFrame.itemHover = index
|
||||
MerchantFrame.itemHover = index
|
||||
end
|
||||
|
||||
function BenillaMerchantItem_OnLeave(button)
|
||||
GameTooltip:Hide()
|
||||
-- Drop the coin/inspect override and stop the OnUpdate arming (ref OnLeave l.87-90).
|
||||
ResetCursor()
|
||||
BenillaMerchantFrame.itemHover = nil
|
||||
MerchantFrame.itemHover = nil
|
||||
end
|
||||
|
||||
-- One row's item fill — the shape both pages share (ref fills name/texture/count/money
|
||||
@@ -337,13 +337,13 @@
|
||||
-- The merchant page (ref MerchantFrame_UpdateMerchantInfo l.66-193): the paged 10 rows,
|
||||
-- the repair pair, the single most-recent buyback slot, the page buttons, the merchant
|
||||
-- bottom border — and the buyback page's pieces hidden.
|
||||
function BenillaMerchantFrame_UpdateMerchantInfo()
|
||||
local frame = BenillaMerchantFrame
|
||||
function MerchantFrame_UpdateMerchantInfo()
|
||||
local frame = MerchantFrame
|
||||
local page = frame.page or 1
|
||||
local numItems = GetMerchantNumItems()
|
||||
|
||||
for i = 1, BENILLA_MERCHANT_MAX do
|
||||
local button = getglobal("BenillaMerchantItem" .. i)
|
||||
local button = getglobal("MerchantItem" .. i)
|
||||
local index = (page - 1) * BENILLA_MERCHANT_MAX + i
|
||||
if index <= numItems then
|
||||
local name, texture, price, quantity, numAvailable, isUsable = GetMerchantItemInfo(index)
|
||||
@@ -355,32 +355,32 @@
|
||||
|
||||
-- Rows back to the merchant spacing (ref l.188-192; the buyback page spreads them to
|
||||
-- -15). SetPoint replaces the same-point anchor, the real client's semantics.
|
||||
getglobal("BenillaMerchantItem3"):SetPoint("TOPLEFT", getglobal("BenillaMerchantItem1"), "BOTTOMLEFT", 0, -8)
|
||||
getglobal("BenillaMerchantItem5"):SetPoint("TOPLEFT", getglobal("BenillaMerchantItem3"), "BOTTOMLEFT", 0, -8)
|
||||
getglobal("BenillaMerchantItem7"):SetPoint("TOPLEFT", getglobal("BenillaMerchantItem5"), "BOTTOMLEFT", 0, -8)
|
||||
getglobal("BenillaMerchantItem9"):SetPoint("TOPLEFT", getglobal("BenillaMerchantItem7"), "BOTTOMLEFT", 0, -8)
|
||||
getglobal("MerchantItem3"):SetPoint("TOPLEFT", getglobal("MerchantItem1"), "BOTTOMLEFT", 0, -8)
|
||||
getglobal("MerchantItem5"):SetPoint("TOPLEFT", getglobal("MerchantItem3"), "BOTTOMLEFT", 0, -8)
|
||||
getglobal("MerchantItem7"):SetPoint("TOPLEFT", getglobal("MerchantItem5"), "BOTTOMLEFT", 0, -8)
|
||||
getglobal("MerchantItem9"):SetPoint("TOPLEFT", getglobal("MerchantItem7"), "BOTTOMLEFT", 0, -8)
|
||||
|
||||
-- Buyback-page-only pieces off (ref l.180-186), merchant-only pieces on (ref l.175-178).
|
||||
getglobal("BenillaMerchantItem11"):Hide()
|
||||
getglobal("BenillaMerchantItem12"):Hide()
|
||||
getglobal("BenillaBuybackFrameTopLeft"):Hide()
|
||||
getglobal("BenillaBuybackFrameTopRight"):Hide()
|
||||
getglobal("BenillaBuybackFrameBotLeft"):Hide()
|
||||
getglobal("BenillaBuybackFrameBotRight"):Hide()
|
||||
getglobal("BenillaMerchantFrameBottomLeftBorder"):Show()
|
||||
getglobal("BenillaMerchantFrameBottomRightBorder"):Show()
|
||||
getglobal("MerchantItem11"):Hide()
|
||||
getglobal("MerchantItem12"):Hide()
|
||||
getglobal("BuybackFrameTopLeft"):Hide()
|
||||
getglobal("BuybackFrameTopRight"):Hide()
|
||||
getglobal("BuybackFrameBotLeft"):Hide()
|
||||
getglobal("BuybackFrameBotRight"):Hide()
|
||||
getglobal("MerchantFrameBottomLeftBorder"):Show()
|
||||
getglobal("MerchantFrameBottomRightBorder"):Show()
|
||||
|
||||
-- The repair pair shows iff this vendor repairs (ref l.121-130); the all-button
|
||||
-- enables iff there is damage to pay for (ref MerchantFrame_OnShow l.37-46 — our
|
||||
-- vertex-color gray stands in for its SetDesaturation, which the engine lacks).
|
||||
if CanMerchantRepair() then
|
||||
getglobal("BenillaMerchantRepairText"):Show()
|
||||
local allButton = getglobal("BenillaMerchantRepairAllButton")
|
||||
local itemButton = getglobal("BenillaMerchantRepairItemButton")
|
||||
getglobal("MerchantRepairText"):Show()
|
||||
local allButton = getglobal("MerchantRepairAllButton")
|
||||
local itemButton = getglobal("MerchantRepairItemButton")
|
||||
allButton:Show()
|
||||
itemButton:Show()
|
||||
local repairAllCost, canRepair = GetRepairAllCost()
|
||||
local icon = getglobal("BenillaMerchantRepairAllIcon")
|
||||
local icon = getglobal("MerchantRepairAllIcon")
|
||||
if canRepair then
|
||||
icon:SetVertexColor(1, 1, 1)
|
||||
allButton:Enable()
|
||||
@@ -389,14 +389,14 @@
|
||||
allButton:Disable()
|
||||
end
|
||||
else
|
||||
getglobal("BenillaMerchantRepairText"):Hide()
|
||||
getglobal("BenillaMerchantRepairAllButton"):Hide()
|
||||
getglobal("BenillaMerchantRepairItemButton"):Hide()
|
||||
getglobal("MerchantRepairText"):Hide()
|
||||
getglobal("MerchantRepairAllButton"):Hide()
|
||||
getglobal("MerchantRepairItemButton"):Hide()
|
||||
end
|
||||
|
||||
-- The single buyback slot: the MOST RECENT sale (ref l.132-152 —
|
||||
-- GetBuybackItemInfo(GetNumBuybackItems())).
|
||||
local slot = getglobal("BenillaMerchantBuyBackItem")
|
||||
local slot = getglobal("MerchantBuyBackItem")
|
||||
local bbName, bbTexture, bbPrice, bbQuantity = GetBuybackItemInfo(GetNumBuybackItems())
|
||||
if bbName then
|
||||
slot.icon:SetTexture(bbTexture or BENILLA_FALLBACK_ICON)
|
||||
@@ -406,13 +406,13 @@
|
||||
else
|
||||
slot.count:SetText("")
|
||||
end
|
||||
BenillaMoney_Set("BenillaMerchantBuyBackItemPrice", bbPrice, true)
|
||||
BenillaMoney_Set("MerchantBuyBackItemPrice", bbPrice, true)
|
||||
slot.slot:SetVertexColor(1, 1, 1)
|
||||
else
|
||||
slot.icon:SetTexture(nil)
|
||||
slot.name:SetText("")
|
||||
slot.count:SetText("")
|
||||
BenillaMoney_Clear("BenillaMerchantBuyBackItemPrice")
|
||||
BenillaMoney_Clear("MerchantBuyBackItemPrice")
|
||||
slot.slot:SetVertexColor(0.4, 0.4, 0.4)
|
||||
-- Hide the tooltip upon sale of the last slot (ref l.148-151).
|
||||
if GameTooltip:IsOwned(slot) then
|
||||
@@ -422,9 +422,9 @@
|
||||
slot:Show()
|
||||
|
||||
-- Paging (ref l.154-173): buttons + label only when the stock overflows one page.
|
||||
local prevButton = getglobal("BenillaMerchantPrevPageButton")
|
||||
local nextButton = getglobal("BenillaMerchantNextPageButton")
|
||||
local pageText = getglobal("BenillaMerchantPageText")
|
||||
local prevButton = getglobal("MerchantPrevPageButton")
|
||||
local nextButton = getglobal("MerchantNextPageButton")
|
||||
local pageText = getglobal("MerchantPageText")
|
||||
if numItems > BENILLA_MERCHANT_MAX then
|
||||
pageText:SetText(string.format("Page %d", page)) -- PAGE_NUMBER
|
||||
if page == 1 then prevButton:Disable() else prevButton:Enable() end
|
||||
@@ -445,34 +445,34 @@
|
||||
-- Title + portrait back to the vendor (ref l.67-68): re-bind the "npc" model bake the
|
||||
-- buyback page's raw SetTexture(UI-BuyBack-Icon) dropped.
|
||||
BenillaMerchantFrame_SetTitle(frame.vendorName)
|
||||
SetPortraitTexture(getglobal("BenillaMerchantFramePortrait"), "npc")
|
||||
SetPortraitTexture(getglobal("MerchantFramePortrait"), "npc")
|
||||
end
|
||||
|
||||
-- The buyback page (ref MerchantFrame_UpdateBuybackInfo l.195-273): 12 rows over the
|
||||
-- buyback slots, the wider -15 row spacing, the buyback corner art — and every
|
||||
-- merchant-only piece hidden.
|
||||
function BenillaMerchantFrame_UpdateBuybackInfo()
|
||||
getglobal("BenillaMerchantNameText"):SetText("Merchant Buyback") -- MERCHANT_BUYBACK
|
||||
function MerchantFrame_UpdateBuybackInfo()
|
||||
getglobal("MerchantNameText"):SetText("Merchant Buyback") -- MERCHANT_BUYBACK
|
||||
-- The buyback icon is drawn for the portrait ring — the ref sets it RAW (SetTexture,
|
||||
-- l.197), not through the portrait mask.
|
||||
getglobal("BenillaMerchantFramePortrait"):SetTexture("Interface\\MerchantFrame\\UI-BuyBack-Icon")
|
||||
getglobal("MerchantFramePortrait"):SetTexture("Interface\\MerchantFrame\\UI-BuyBack-Icon")
|
||||
|
||||
getglobal("BenillaMerchantItem11"):Show()
|
||||
getglobal("BenillaMerchantItem12"):Show()
|
||||
getglobal("BenillaBuybackFrameTopLeft"):Show()
|
||||
getglobal("BenillaBuybackFrameTopRight"):Show()
|
||||
getglobal("BenillaBuybackFrameBotLeft"):Show()
|
||||
getglobal("BenillaBuybackFrameBotRight"):Show()
|
||||
getglobal("MerchantItem11"):Show()
|
||||
getglobal("MerchantItem12"):Show()
|
||||
getglobal("BuybackFrameTopLeft"):Show()
|
||||
getglobal("BuybackFrameTopRight"):Show()
|
||||
getglobal("BuybackFrameBotLeft"):Show()
|
||||
getglobal("BuybackFrameBotRight"):Show()
|
||||
|
||||
-- The wider buyback row spacing (ref l.208-211).
|
||||
getglobal("BenillaMerchantItem3"):SetPoint("TOPLEFT", getglobal("BenillaMerchantItem1"), "BOTTOMLEFT", 0, -15)
|
||||
getglobal("BenillaMerchantItem5"):SetPoint("TOPLEFT", getglobal("BenillaMerchantItem3"), "BOTTOMLEFT", 0, -15)
|
||||
getglobal("BenillaMerchantItem7"):SetPoint("TOPLEFT", getglobal("BenillaMerchantItem5"), "BOTTOMLEFT", 0, -15)
|
||||
getglobal("BenillaMerchantItem9"):SetPoint("TOPLEFT", getglobal("BenillaMerchantItem7"), "BOTTOMLEFT", 0, -15)
|
||||
getglobal("MerchantItem3"):SetPoint("TOPLEFT", getglobal("MerchantItem1"), "BOTTOMLEFT", 0, -15)
|
||||
getglobal("MerchantItem5"):SetPoint("TOPLEFT", getglobal("MerchantItem3"), "BOTTOMLEFT", 0, -15)
|
||||
getglobal("MerchantItem7"):SetPoint("TOPLEFT", getglobal("MerchantItem5"), "BOTTOMLEFT", 0, -15)
|
||||
getglobal("MerchantItem9"):SetPoint("TOPLEFT", getglobal("MerchantItem7"), "BOTTOMLEFT", 0, -15)
|
||||
|
||||
local numBuyback = GetNumBuybackItems()
|
||||
for i = 1, BENILLA_BUYBACK_MAX do
|
||||
local button = getglobal("BenillaMerchantItem" .. i)
|
||||
local button = getglobal("MerchantItem" .. i)
|
||||
if i <= numBuyback then
|
||||
local name, texture, price, quantity, numAvailable, isUsable = GetBuybackItemInfo(i)
|
||||
-- Never soldOut here: the ref's buyback sold-out leg tests its merchant-page
|
||||
@@ -485,45 +485,45 @@
|
||||
end
|
||||
|
||||
-- Merchant-only pieces off (ref l.263-272).
|
||||
getglobal("BenillaMerchantRepairText"):Hide()
|
||||
getglobal("BenillaMerchantRepairAllButton"):Hide()
|
||||
getglobal("BenillaMerchantRepairItemButton"):Hide()
|
||||
getglobal("BenillaMerchantBuyBackItem"):Hide()
|
||||
getglobal("BenillaMerchantPrevPageButton"):Hide()
|
||||
getglobal("BenillaMerchantNextPageButton"):Hide()
|
||||
getglobal("BenillaMerchantPageText"):Hide()
|
||||
getglobal("BenillaMerchantFrameBottomLeftBorder"):Hide()
|
||||
getglobal("BenillaMerchantFrameBottomRightBorder"):Hide()
|
||||
getglobal("MerchantRepairText"):Hide()
|
||||
getglobal("MerchantRepairAllButton"):Hide()
|
||||
getglobal("MerchantRepairItemButton"):Hide()
|
||||
getglobal("MerchantBuyBackItem"):Hide()
|
||||
getglobal("MerchantPrevPageButton"):Hide()
|
||||
getglobal("MerchantNextPageButton"):Hide()
|
||||
getglobal("MerchantPageText"):Hide()
|
||||
getglobal("MerchantFrameBottomLeftBorder"):Hide()
|
||||
getglobal("MerchantFrameBottomRightBorder"):Hide()
|
||||
end
|
||||
|
||||
function BenillaMerchantFrame_Update()
|
||||
function MerchantFrame_Update()
|
||||
-- The tab dispatch (ref MerchantFrame_Update l.57-64) + the purse (the ref's money
|
||||
-- frame updates itself on PLAYER_MONEY; ours repaints with the window).
|
||||
if BenillaMerchantFrame.selectedTab == 2 then
|
||||
BenillaMerchantFrame_UpdateBuybackInfo()
|
||||
if MerchantFrame.selectedTab == 2 then
|
||||
MerchantFrame_UpdateBuybackInfo()
|
||||
else
|
||||
BenillaMerchantFrame_UpdateMerchantInfo()
|
||||
MerchantFrame_UpdateMerchantInfo()
|
||||
end
|
||||
BenillaMoney_Set("BenillaMerchantMoney", GetMoney())
|
||||
BenillaMoney_Set("MerchantMoney", GetMoney())
|
||||
end
|
||||
|
||||
-- The tab pair (ref MerchantFrameTab1/2 OnClick l.682-685/704-707): select + repaint.
|
||||
function BenillaMerchantFrameTab_OnClick(id)
|
||||
PanelTemplates_SetTab(BenillaMerchantFrame, id)
|
||||
BenillaMerchantFrame_Update()
|
||||
PanelTemplates_SetTab(MerchantFrame, id)
|
||||
MerchantFrame_Update()
|
||||
end
|
||||
|
||||
-- Paging clicks (ref MerchantPrevPageButton_OnClick/Next l.275-285).
|
||||
function BenillaMerchantPrevPageButton_OnClick()
|
||||
function MerchantPrevPageButton_OnClick()
|
||||
PlaySound("igMainMenuOptionCheckBoxOn")
|
||||
BenillaMerchantFrame.page = (BenillaMerchantFrame.page or 1) - 1
|
||||
BenillaMerchantFrame_Update()
|
||||
MerchantFrame.page = (MerchantFrame.page or 1) - 1
|
||||
MerchantFrame_Update()
|
||||
end
|
||||
|
||||
function BenillaMerchantNextPageButton_OnClick()
|
||||
function MerchantNextPageButton_OnClick()
|
||||
PlaySound("igMainMenuOptionCheckBoxOn")
|
||||
BenillaMerchantFrame.page = (BenillaMerchantFrame.page or 1) + 1
|
||||
BenillaMerchantFrame_Update()
|
||||
MerchantFrame.page = (MerchantFrame.page or 1) + 1
|
||||
MerchantFrame_Update()
|
||||
end
|
||||
|
||||
-- The single buyback slot's handlers (ref MerchantBuyBackItemItemButton l.516-541): a
|
||||
@@ -558,7 +558,8 @@
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaMerchantFrame_OnLoad(frame)
|
||||
function MerchantFrame_OnLoad()
|
||||
local frame = this
|
||||
-- The NPC portrait: the "npc" unit token → the vendor's live model bake
|
||||
-- (crates/benilla/src/portrait, InteractNpc). The buyback page overrides this ring with a
|
||||
-- raw SetTexture (which drops the binding); the vendor page restores it (see below).
|
||||
@@ -582,26 +583,27 @@
|
||||
if name == nil or name == "" then
|
||||
name = "Merchant"
|
||||
end
|
||||
getglobal("BenillaMerchantNameText"):SetText(name)
|
||||
getglobal("MerchantNameText"):SetText(name)
|
||||
end
|
||||
|
||||
function BenillaMerchantFrame_OnEvent(frame)
|
||||
function MerchantFrame_OnEvent()
|
||||
local frame = this
|
||||
if event == "PLAYER_MONEY" then
|
||||
BenillaMoney_Set("BenillaMerchantMoney", GetMoney())
|
||||
BenillaMoney_Set("MerchantMoney", GetMoney())
|
||||
elseif event == "MERCHANT_SHOW" then
|
||||
-- Fresh session: first page, merchant tab (ref OnEvent l.29 / OnShow l.47).
|
||||
frame.page = 1
|
||||
frame.vendorName = arg1
|
||||
PanelTemplates_SetTab(frame, 1)
|
||||
BenillaMerchantFrame_SetTitle(arg1)
|
||||
BenillaMerchantFrame_Update()
|
||||
MerchantFrame_Update()
|
||||
ShowUIPanel(frame)
|
||||
elseif event == "MERCHANT_UPDATE" then
|
||||
frame.vendorName = arg1
|
||||
if frame.selectedTab ~= 2 then
|
||||
BenillaMerchantFrame_SetTitle(arg1)
|
||||
end
|
||||
BenillaMerchantFrame_Update()
|
||||
MerchantFrame_Update()
|
||||
elseif event == "MERCHANT_CLOSED" then
|
||||
HideUIPanel(frame)
|
||||
end
|
||||
@@ -611,7 +613,7 @@
|
||||
-- vanilla's client-side close sends no packet; the app clears the merchant on this intent
|
||||
-- (which fires MERCHANT_CLOSED), and we hide immediately so there's no one-frame lag.
|
||||
CloseMerchant()
|
||||
HideUIPanel(getglobal("BenillaMerchantFrame"))
|
||||
HideUIPanel(getglobal("MerchantFrame"))
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
@@ -622,7 +624,7 @@
|
||||
the icon, and the dark label plate $parentNameFrame (Interface\MerchantFrame\UI-Merchant-
|
||||
LabelSlots, 128×78, LEFT of the slot -9,-18 — ref l.22-33) behind the name+price. Every one of
|
||||
the 10 slots always renders these; an empty slot dims the socket to 0.4 and clears its item
|
||||
(BenillaMerchantFrame_Update, ref MerchantFrame.lua l.112-118). The name (GameFontNormalSmall,
|
||||
(MerchantFrame_Update, ref MerchantFrame.lua l.112-118). The name (GameFontNormalSmall,
|
||||
LEFT — ref l.34) sits above the price coins, the real name-over-money stack. The pushed-state
|
||||
depress ring the ref repair buttons use (l.419) is a square-button texture that would smear
|
||||
across our wide whole-row button, so it is OMITTED; the highlight is the square ADD glow.
|
||||
@@ -786,7 +788,7 @@
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<Frame name="BenillaMerchantFrame" hidden="true">
|
||||
<Frame name="MerchantFrame" hidden="true">
|
||||
<!-- The real 384×512 window (ref l.117-119). No <Anchors> — ShowUIPanel/SetLeftFrame SetPoints
|
||||
this to the left slot on open. -->
|
||||
<Size><AbsDimension x="384" y="512"/></Size>
|
||||
@@ -830,20 +832,20 @@
|
||||
OnEvent handler sets it here, falling back to "Merchant" until it lands. CENTER +
|
||||
the (unsized, single-point) ~zero-width rect keeps the name centered on the
|
||||
window regardless of length. -->
|
||||
<FontString name="BenillaMerchantNameText" inherits="GameFontNormal" justifyH="CENTER" text="Merchant">
|
||||
<FontString name="MerchantNameText" inherits="GameFontNormal" justifyH="CENTER" text="Merchant">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativePoint="TOP"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<!-- "Repair Items" label beside the repair pair (ref MerchantRepairText l.199-207,
|
||||
REPAIR_ITEMS); shown/hidden with the pair by Update. -->
|
||||
<FontString name="BenillaMerchantRepairText" inherits="GameFontHighlightSmall" text="Repair Items" hidden="true">
|
||||
<FontString name="MerchantRepairText" inherits="GameFontHighlightSmall" text="Repair Items" hidden="true">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="26" y="103"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<!-- "Page N" between the page buttons (ref MerchantPageText l.187-198). -->
|
||||
<FontString name="BenillaMerchantPageText" inherits="GameFontNormal" justifyH="CENTER" hidden="true">
|
||||
<FontString name="MerchantPageText" inherits="GameFontNormal" justifyH="CENTER" hidden="true">
|
||||
<Size><AbsDimension x="104" y="0"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="-14" y="150"/></Offset></Anchor>
|
||||
@@ -853,34 +855,34 @@
|
||||
<!-- The buyback page's parchment (ref BuybackFrame* l.210-245): four slabs over the
|
||||
merchant page's item-grid art, shown only on the buyback tab. -->
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="BenillaBuybackFrameTopLeft" file="Interface\MerchantFrame\UI-BuyBack-TopLeft" hidden="true">
|
||||
<Texture name="BuybackFrameTopLeft" file="Interface\MerchantFrame\UI-BuyBack-TopLeft" hidden="true">
|
||||
<Size><AbsDimension x="256" y="256"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="19" y="-71"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
<Texture name="BenillaBuybackFrameTopRight" file="Interface\MerchantFrame\UI-BuyBack-TopRight" hidden="true">
|
||||
<Texture name="BuybackFrameTopRight" file="Interface\MerchantFrame\UI-BuyBack-TopRight" hidden="true">
|
||||
<Size><AbsDimension x="64" y="256"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBuybackFrameTopLeft" relativePoint="TOPRIGHT"/></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BuybackFrameTopLeft" relativePoint="TOPRIGHT"/></Anchors>
|
||||
</Texture>
|
||||
<Texture name="BenillaBuybackFrameBotLeft" file="Interface\MerchantFrame\UI-BuyBack-BotLeft" hidden="true">
|
||||
<Texture name="BuybackFrameBotLeft" file="Interface\MerchantFrame\UI-BuyBack-BotLeft" hidden="true">
|
||||
<Size><AbsDimension x="256" y="128"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBuybackFrameTopLeft" relativePoint="BOTTOMLEFT"/></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BuybackFrameTopLeft" relativePoint="BOTTOMLEFT"/></Anchors>
|
||||
</Texture>
|
||||
<Texture name="BenillaBuybackFrameBotRight" file="Interface\MerchantFrame\UI-BuyBack-BotRight" hidden="true">
|
||||
<Texture name="BuybackFrameBotRight" file="Interface\MerchantFrame\UI-BuyBack-BotRight" hidden="true">
|
||||
<Size><AbsDimension x="64" y="128"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaBuybackFrameTopLeft" relativePoint="BOTTOMRIGHT"/></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BuybackFrameTopLeft" relativePoint="BOTTOMRIGHT"/></Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<!-- The bottom band's engraved border over the repair/buyback strip (ref
|
||||
MerchantFrameBottomLeftBorder/RightBorder l.248-269) — merchant tab only. -->
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="BenillaMerchantFrameBottomLeftBorder" file="Interface\MerchantFrame\UI-Merchant-BottomBorder">
|
||||
<Texture name="MerchantFrameBottomLeftBorder" file="Interface\MerchantFrame\UI-Merchant-BottomBorder">
|
||||
<Size><AbsDimension x="256" y="61"/></Size>
|
||||
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="14" y="85"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0" right="1" top="0" bottom="0.4765625"/>
|
||||
</Texture>
|
||||
<Texture name="BenillaMerchantFrameBottomRightBorder" file="Interface\MerchantFrame\UI-Merchant-BottomBorder">
|
||||
<Texture name="MerchantFrameBottomRightBorder" file="Interface\MerchantFrame\UI-Merchant-BottomBorder">
|
||||
<Size><AbsDimension x="76" y="61"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMerchantFrameBottomLeftBorder" relativePoint="RIGHT"/></Anchors>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MerchantFrameBottomLeftBorder" relativePoint="RIGHT"/></Anchors>
|
||||
<TexCoords left="0" right="0.296875" top="0.4765625" bottom="0.953125"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
@@ -892,8 +894,8 @@
|
||||
starts at iconWidth — MoneyFrame.lua l.219), so the COIN edge the ref frame anchor
|
||||
implies is -40-13 = -53, bottom 67. Three position-slots chained right-to-left with
|
||||
the real 4px spacing, shrunk to content by the same BenillaMoney_Set fill as the row
|
||||
prices (prefix "BenillaMerchantMoney") — copper-first, right-flush off Coin1. -->
|
||||
<Frame name="BenillaMerchantMoneyCoin1">
|
||||
prices (prefix "MerchantMoney") — copper-first, right-flush off Coin1. -->
|
||||
<Frame name="MerchantMoneyCoin1">
|
||||
<Size><AbsDimension x="13" y="13"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-53" y="67"/></Offset></Anchor>
|
||||
@@ -912,10 +914,10 @@
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Frame>
|
||||
<Frame name="BenillaMerchantMoneyCoin2">
|
||||
<Frame name="MerchantMoneyCoin2">
|
||||
<Size><AbsDimension x="13" y="13"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativeTo="BenillaMerchantMoneyCoin1" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor>
|
||||
<Anchor point="RIGHT" relativeTo="MerchantMoneyCoin1" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
@@ -931,10 +933,10 @@
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Frame>
|
||||
<Frame name="BenillaMerchantMoneyCoin3">
|
||||
<Frame name="MerchantMoneyCoin3">
|
||||
<Size><AbsDimension x="13" y="13"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativeTo="BenillaMerchantMoneyCoin2" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor>
|
||||
<Anchor point="RIGHT" relativeTo="MerchantMoneyCoin2" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
@@ -953,54 +955,54 @@
|
||||
<!-- The real 2-column × 5-row grid (ref l.273-362): item 1 at (24, -80); the right column
|
||||
anchors to its left neighbour's TOPRIGHT +12; each new row to the left item's
|
||||
BOTTOMLEFT -8. Odd rows = left column, even rows = right column. -->
|
||||
<Button name="BenillaMerchantItem1" inherits="BenillaMerchantItemTemplate">
|
||||
<Button name="MerchantItem1" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="24" y="-80"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaMerchantItem_OnLoad(self, 1)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaMerchantItem2" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaMerchantItem1" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="MerchantItem2" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="MerchantItem1" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaMerchantItem_OnLoad(self, 2)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaMerchantItem3" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaMerchantItem1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-8"/></Offset></Anchor></Anchors>
|
||||
<Button name="MerchantItem3" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="MerchantItem1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-8"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaMerchantItem_OnLoad(self, 3)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaMerchantItem4" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaMerchantItem3" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="MerchantItem4" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="MerchantItem3" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaMerchantItem_OnLoad(self, 4)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaMerchantItem5" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaMerchantItem3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-8"/></Offset></Anchor></Anchors>
|
||||
<Button name="MerchantItem5" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="MerchantItem3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-8"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaMerchantItem_OnLoad(self, 5)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaMerchantItem6" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaMerchantItem5" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="MerchantItem6" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="MerchantItem5" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaMerchantItem_OnLoad(self, 6)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaMerchantItem7" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaMerchantItem5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-8"/></Offset></Anchor></Anchors>
|
||||
<Button name="MerchantItem7" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="MerchantItem5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-8"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaMerchantItem_OnLoad(self, 7)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaMerchantItem8" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaMerchantItem7" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="MerchantItem8" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="MerchantItem7" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaMerchantItem_OnLoad(self, 8)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaMerchantItem9" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaMerchantItem7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-8"/></Offset></Anchor></Anchors>
|
||||
<Button name="MerchantItem9" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="MerchantItem7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-8"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaMerchantItem_OnLoad(self, 9)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaMerchantItem10" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaMerchantItem9" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="MerchantItem10" inherits="BenillaMerchantItemTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="MerchantItem9" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaMerchantItem_OnLoad(self, 10)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<!-- Rows 11/12 exist for the BUYBACK page only (ref MerchantItem11/12 l.363-380; the
|
||||
merchant page hides them, the buyback page's 12 slots need them). -->
|
||||
<Button name="BenillaMerchantItem11" inherits="BenillaMerchantItemTemplate" hidden="true">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaMerchantItem9" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-15"/></Offset></Anchor></Anchors>
|
||||
<Button name="MerchantItem11" inherits="BenillaMerchantItemTemplate" hidden="true">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="MerchantItem9" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-15"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaMerchantItem_OnLoad(self, 11)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaMerchantItem12" inherits="BenillaMerchantItemTemplate" hidden="true">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaMerchantItem11" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Button name="MerchantItem12" inherits="BenillaMerchantItemTemplate" hidden="true">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="MerchantItem11" relativePoint="TOPRIGHT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaMerchantItem_OnLoad(self, 12)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<!-- The repair pair (ref MerchantRepairAllButton l.381-421 / MerchantRepairItemButton
|
||||
@@ -1008,14 +1010,14 @@
|
||||
UI-Merchant-RepairIcons sheet. Shown only when the vendor repairs. The ref's
|
||||
anchor: the ALL button's BOTTOMRIGHT at frame BOTTOMLEFT +(172,91); the ITEM
|
||||
button chains LEFT of it (-2). -->
|
||||
<Button name="BenillaMerchantRepairAllButton" hidden="true">
|
||||
<Button name="MerchantRepairAllButton" hidden="true">
|
||||
<Size><AbsDimension x="36" y="36"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="172" y="91"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BORDER">
|
||||
<Texture name="BenillaMerchantRepairAllIcon" file="Interface\MerchantFrame\UI-Merchant-RepairIcons">
|
||||
<Texture name="MerchantRepairAllIcon" file="Interface\MerchantFrame\UI-Merchant-RepairIcons">
|
||||
<TexCoords left="0.28125" right="0.5625" top="0" bottom="0.5625"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
@@ -1036,10 +1038,10 @@
|
||||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaMerchantRepairItemButton" hidden="true">
|
||||
<Button name="MerchantRepairItemButton" hidden="true">
|
||||
<Size><AbsDimension x="36" y="36"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativeTo="BenillaMerchantRepairAllButton" relativePoint="LEFT"><Offset><AbsDimension x="-2" y="0"/></Offset></Anchor>
|
||||
<Anchor point="RIGHT" relativeTo="MerchantRepairAllButton" relativePoint="LEFT"><Offset><AbsDimension x="-2" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BORDER">
|
||||
@@ -1064,10 +1066,10 @@
|
||||
MerchantBuyBackItem l.460-559): the same socket+plate row shape at 153×37, the
|
||||
shorter 64-tall label plate, anchored under row 10 (0,-53). One wide click
|
||||
button, our whole-row model. -->
|
||||
<Button name="BenillaMerchantBuyBackItem" hidden="true">
|
||||
<Button name="MerchantBuyBackItem" hidden="true">
|
||||
<Size><AbsDimension x="153" y="37"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="BenillaMerchantItem10" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-53"/></Offset></Anchor>
|
||||
<Anchor point="TOPLEFT" relativeTo="MerchantItem10" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-53"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
@@ -1190,7 +1192,7 @@
|
||||
<!-- Page turners (ref MerchantPrevPageButton l.569-610 / MerchantNextPageButton
|
||||
l.611-656): the spellbook page-corner buttons over the page background disc, with
|
||||
the Prev/Next labels beside them. Shown only when the stock overflows a page. -->
|
||||
<Button name="BenillaMerchantPrevPageButton" hidden="true">
|
||||
<Button name="MerchantPrevPageButton" hidden="true">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="37" y="156"/></Offset></Anchor>
|
||||
@@ -1211,10 +1213,10 @@
|
||||
<DisabledTexture file="Interface\Buttons\UI-SpellbookIcon-PrevPage-Disabled"/>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
|
||||
<Scripts>
|
||||
<OnClick>BenillaMerchantPrevPageButton_OnClick()</OnClick>
|
||||
<OnClick>MerchantPrevPageButton_OnClick()</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaMerchantNextPageButton" hidden="true">
|
||||
<Button name="MerchantNextPageButton" hidden="true">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="324" y="156"/></Offset></Anchor>
|
||||
@@ -1235,7 +1237,7 @@
|
||||
<DisabledTexture file="Interface\Buttons\UI-SpellbookIcon-NextPage-Disabled"/>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
|
||||
<Scripts>
|
||||
<OnClick>BenillaMerchantNextPageButton_OnClick()</OnClick>
|
||||
<OnClick>MerchantNextPageButton_OnClick()</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<!-- The Merchant / Buyback tab pair at the window's bottom edge (ref MerchantFrameTab1/2
|
||||
@@ -1259,7 +1261,7 @@
|
||||
</Button>
|
||||
<!-- Close: the real UIPanelCloseButton position (TOPRIGHT -30, -8; ref l.657-664), on the
|
||||
minimize-button art UIPanelCloseButton itself uses (pin § "The re-skin pass"). -->
|
||||
<Button name="BenillaMerchantCloseButton">
|
||||
<Button name="MerchantCloseButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-30" y="-8"/></Offset></Anchor>
|
||||
@@ -1273,19 +1275,19 @@
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaMerchantFrame_OnLoad(self)</OnLoad>
|
||||
<OnEvent>BenillaMerchantFrame_OnEvent(self)</OnEvent>
|
||||
<OnLoad>MerchantFrame_OnLoad()</OnLoad>
|
||||
<OnEvent>MerchantFrame_OnEvent()</OnEvent>
|
||||
<!-- While a buy-tab row is hovered, re-arm its cursor every frame (ref MerchantFrame
|
||||
OnUpdate l.725-731): the Inspect magnifier while Ctrl is held, else the coin —
|
||||
Buy if affordable, the grayed UnableBuy if not (ShowMerchantSellCursor's own gate).
|
||||
Polling (not a one-shot on enter) is what makes the Ctrl swap and a live money
|
||||
change track under a stationary cursor. The buyback slot arms in its OnEnter. -->
|
||||
<OnUpdate>
|
||||
if BenillaMerchantFrame.itemHover then
|
||||
if MerchantFrame.itemHover then
|
||||
if IsControlKeyDown() then
|
||||
ShowInspectCursor()
|
||||
else
|
||||
ShowMerchantSellCursor(BenillaMerchantFrame.itemHover)
|
||||
ShowMerchantSellCursor(MerchantFrame.itemHover)
|
||||
end
|
||||
end
|
||||
</OnUpdate>
|
||||
@@ -1307,7 +1309,7 @@
|
||||
<OnShow>OpenBackpack(); PlaySound("igCharacterInfoOpen")</OnShow>
|
||||
<!-- HideRepairCursor: closing the window must drop the repair-mode latch (the ref's
|
||||
OnHide ResetCursor, l.54 — its cursor semantics land with the sell-cursor slice). -->
|
||||
<OnHide>CloseMerchant(); CloseBackpack(); HideRepairCursor(); ResetCursor(); BenillaMerchantFrame.itemHover = nil; PlaySound("igCharacterInfoClose")</OnHide>
|
||||
<OnHide>CloseMerchant(); CloseBackpack(); HideRepairCursor(); ResetCursor(); MerchantFrame.itemHover = nil; PlaySound("igCharacterInfoClose")</OnHide>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
the action bar behind it, and an unguarded call would abort the rest of that OnShow. Same
|
||||
posture as UiPanels.xml's `CloseMenus and CloseMenus()` and the `if GameTooltip` guards.
|
||||
|
||||
Structure: the buttons are TOP-LEVEL frames anchored by name into BenillaActionBarArtFrame
|
||||
Structure: the buttons are TOP-LEVEL frames anchored by name into MainMenuBarArtFrame
|
||||
(this engine has no cross-file `parent=` attachment — SkillFrame.xml's header comment), then
|
||||
seated one frame level above the art by BenillaActionBarArt_SeatAbove (ActionBar.xml) so the
|
||||
bar plate doesn't paint over them. Same shape the bag bar next door already uses.
|
||||
@@ -153,10 +153,10 @@
|
||||
-- reference lists first. Its keyring arm has no frame here; the still-unwired Help button
|
||||
-- simply never pushes.
|
||||
function UpdateMicroButtons()
|
||||
BenillaMicroButton_SetPushed(CharacterMicroButton, BenillaMicro_PanelShown("BenillaCharacterFrame"))
|
||||
BenillaMicroButton_SetPushed(SpellbookMicroButton, BenillaMicro_PanelShown("BenillaSpellBookFrame"))
|
||||
BenillaMicroButton_SetPushed(CharacterMicroButton, BenillaMicro_PanelShown("CharacterFrame"))
|
||||
BenillaMicroButton_SetPushed(SpellbookMicroButton, BenillaMicro_PanelShown("SpellBookFrame"))
|
||||
BenillaMicroButton_SetPushed(TalentMicroButton, BenillaMicro_PanelShown("BenillaTalentFrame"))
|
||||
BenillaMicroButton_SetPushed(QuestLogMicroButton, BenillaMicro_PanelShown("BenillaQuestLogFrame"))
|
||||
BenillaMicroButton_SetPushed(QuestLogMicroButton, BenillaMicro_PanelShown("QuestLogFrame"))
|
||||
BenillaMicroButton_SetPushed(WorldMapMicroButton, BenillaMicro_PanelShown("WorldMapFrame"))
|
||||
BenillaMicroButton_SetPushed(MainMenuMicroButton, BenillaMicro_PanelShown("GameMenuFrame"))
|
||||
end
|
||||
@@ -249,7 +249,7 @@
|
||||
button's TOP -28. -->
|
||||
<Button name="CharacterMicroButton" inherits="BenillaMicroButtonTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="BenillaActionBarArtFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="552" y="2"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="MainMenuBarArtFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="552" y="2"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
@@ -262,7 +262,7 @@
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnLoad>CharacterMicroButton_OnLoad(self)</OnLoad>
|
||||
<OnClick>ToggleCharacter("BenillaPaperDollFrame")</OnClick>
|
||||
<OnClick>ToggleCharacter("PaperDollFrame")</OnClick>
|
||||
<OnUpdate>CharacterMicroButton_OnUpdate(self)</OnUpdate>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,601 @@
|
||||
<Ui>
|
||||
<!--
|
||||
The reference's MONEY FRAME kit — `MoneyFrameTemplate` / `SmallMoneyFrameTemplate` plus the
|
||||
`MoneyTypeInfo` table and the `MoneyFrame_*` API that drives them. Transcribed from the
|
||||
extracted 1.12 `Interface\FrameXML\MoneyFrame.lua` (356 lines) and `MoneyFrame.xml` (297),
|
||||
line numbers cited below as `l.N`.
|
||||
|
||||
WHY IT IS HERE: it is FrameXML's, not Rust's (decision 1190 — a name the shipped 1.12 UI
|
||||
defines is transcribed, never hardcoded), and the corpus asks for it by name.
|
||||
`MoneyFrame_SetType` is wanted by 11 addons, `MoneyFrame_Update` by 9, `SmallMoneyFrame_OnLoad`
|
||||
by 9, and 13 declare a frame `inherits="SmallMoneyFrameTemplate"`. `MoneyTypeInfo` was one of
|
||||
the top runtime walls the frame rename left standing.
|
||||
|
||||
── THE OTHER COIN KIT, and why there are two ────────────────────────────────────────────────
|
||||
benilla's own eight windows (merchant, bank, trade, mail, quest, quest log, taxi, trainer) do
|
||||
NOT use this file. They render coins through `BenillaMoney_Set`/`_Clear`/`_SetColor` in
|
||||
MerchantFrame.xml: a three-SLOT chain (`<prefix>Coin1..3`) that fills only the non-zero
|
||||
denominations in display order and re-flows, rather than three fixed GoldButton/SilverButton/
|
||||
CopperButton frames that hide themselves. That kit was quoted FROM this one (its header cites
|
||||
these same line numbers for the coin TexCoords, the collapse and the shrink) and its layout is
|
||||
the one the director has signed off across those eight windows.
|
||||
|
||||
So the two coexist deliberately, and the split is by AUDIENCE, not by accident:
|
||||
· `BenillaMoney_*` + `<prefix>Coin1..3` — what our own transcribed windows are laid out on.
|
||||
· `MoneyFrame_*` + `MoneyTypeInfo` here — the reference's names and structure, which is
|
||||
what an addon writes against and what an addon's `inherits="SmallMoneyFrameTemplate"`
|
||||
resolves to.
|
||||
The reconciliation — migrating those eight windows onto `SmallMoneyFrameTemplate` +
|
||||
`MoneyFrame_Update` and retiring `BenillaMoney_*` — is what the reference itself does and is
|
||||
the right end state; it is a re-layout of hand-authored art in eight files (an approved look),
|
||||
so it is named here rather than done in passing.
|
||||
|
||||
── WHAT IS NOT CARRIED, and why (each is guarded at its own site below) ─────────────────────
|
||||
· THE COIN PICKUP. `OpenCoinPickupFrame`/`UpdateCoinPickupFrame`/`CoinPickupFrame` live in the
|
||||
reference's CoinPickupFrame.lua/.xml, which we do not ship — and the engine verbs underneath
|
||||
it (`PickupPlayerMoney`, `DropCursorMoney`, `PickupTradeMoney`, `AddTradeMoney`) are not
|
||||
built either, so a picked-up coin would have nowhere to go. The templates' `<OnClick>` is
|
||||
the reference's, guarded on the function existing; a `canPickup` money frame therefore
|
||||
enables its mouse (the reference's own state) and clicking it does nothing.
|
||||
· `GetCursorMoney` (PLAYER's UpdateFunc, l.17) — the engine verb for coins held on the cursor.
|
||||
With no coin pickup there is no cursor money, so its term is not approximated away: it is
|
||||
structurally zero. Stated at the spot.
|
||||
· `GetSendMailMoney` / `GetSendMailCOD` (l.73, l.90) — 1.12 engine verbs we have not built. We
|
||||
ship their SETTERS (`SetSendMailMoney`/`SetSendMailCOD`, which MailFrame.xml drives); nothing
|
||||
reads the amount back yet. Guarded to 0 rather than left as a nil call.
|
||||
· `message()` (l.145, l.173, l.181) — the reference's one-line StaticPopup shim, which lives in
|
||||
BasicControls.xml, a file family we have not transcribed. Guarded, so a misuse of the API
|
||||
stays silent instead of erroring on the diagnostic.
|
||||
· `Button:GetTextWidth()` (l.202 etc.) — our Button has no such method yet. The button's own
|
||||
`<ButtonText name="$parentText">` FontString carries `GetStringWidth`, which is the same
|
||||
measurement, and that is what is called.
|
||||
· The four `Pickup*`/`Add*` mail verbs the reference's own MoneyTypeInfo closures name are not
|
||||
in 1.12's `_G` either (they are absent from reference/1.12-globals.tsv) — so those bodies are
|
||||
transcribed verbatim and are as unreachable here as they are there.
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
-- l.2-12, verbatim.
|
||||
MONEY_ICON_WIDTH = 19
|
||||
MONEY_ICON_WIDTH_SMALL = 13
|
||||
|
||||
MONEY_BUTTON_SPACING = -4
|
||||
MONEY_BUTTON_SPACING_SMALL = -4
|
||||
|
||||
COPPER_PER_SILVER = 100
|
||||
SILVER_PER_GOLD = 100
|
||||
COPPER_PER_GOLD = COPPER_PER_SILVER * SILVER_PER_GOLD
|
||||
|
||||
COIN_BUTTON_WIDTH = 32
|
||||
|
||||
-- l.14-103: the money TYPES. A money frame carries no amount of its own — it carries a type,
|
||||
-- and the type says where to read the number from (`UpdateFunc`, called with `this` set to the
|
||||
-- frame), whether zero denominations collapse away, whether the smaller coins show anyway, and
|
||||
-- whether the coins are click-to-pick-up. Addons read this table directly, so its shape is the
|
||||
-- contract, not just its behaviour.
|
||||
MoneyTypeInfo = { }
|
||||
MoneyTypeInfo["PLAYER"] = {
|
||||
UpdateFunc = function()
|
||||
-- Reference l.17: `GetMoney() - GetCursorMoney() - GetPlayerTradeMoney()`. The
|
||||
-- cursor term is dropped, not approximated: `GetCursorMoney` is the amount lifted onto
|
||||
-- the cursor by the coin-pickup frame, and benilla ships neither that frame nor the
|
||||
-- `PickupPlayerMoney` verb behind it, so it is structurally 0 here. The term returns
|
||||
-- the moment either lands.
|
||||
return GetMoney() - GetPlayerTradeMoney()
|
||||
end,
|
||||
|
||||
PickupFunc = function(amount)
|
||||
PickupPlayerMoney(amount)
|
||||
end,
|
||||
|
||||
DropFunc = function()
|
||||
DropCursorMoney()
|
||||
end,
|
||||
|
||||
collapse = 1,
|
||||
canPickup = 1,
|
||||
showSmallerCoins = "Backpack"
|
||||
}
|
||||
MoneyTypeInfo["STATIC"] = {
|
||||
UpdateFunc = function()
|
||||
return this.staticMoney
|
||||
end,
|
||||
|
||||
collapse = 1,
|
||||
}
|
||||
MoneyTypeInfo["AUCTION"] = {
|
||||
UpdateFunc = function()
|
||||
return this.staticMoney
|
||||
end,
|
||||
showSmallerCoins = "Backpack",
|
||||
fixedWidth = 1,
|
||||
collapse = 1,
|
||||
truncateSmallCoins = nil,
|
||||
}
|
||||
MoneyTypeInfo["PLAYER_TRADE"] = {
|
||||
UpdateFunc = function()
|
||||
return GetPlayerTradeMoney()
|
||||
end,
|
||||
|
||||
PickupFunc = function(amount)
|
||||
PickupTradeMoney(amount)
|
||||
end,
|
||||
|
||||
DropFunc = function()
|
||||
AddTradeMoney()
|
||||
end,
|
||||
|
||||
collapse = 1,
|
||||
canPickup = 1,
|
||||
}
|
||||
MoneyTypeInfo["TARGET_TRADE"] = {
|
||||
UpdateFunc = function()
|
||||
return GetTargetTradeMoney()
|
||||
end,
|
||||
|
||||
collapse = 1,
|
||||
}
|
||||
MoneyTypeInfo["SEND_MAIL"] = {
|
||||
UpdateFunc = function()
|
||||
-- `GetSendMailMoney` (ref l.73) is an engine verb we have not built — see the header.
|
||||
if not GetSendMailMoney then return 0 end
|
||||
return GetSendMailMoney()
|
||||
end,
|
||||
|
||||
PickupFunc = function(amount)
|
||||
PickupSendMailMoney(amount)
|
||||
end,
|
||||
|
||||
DropFunc = function()
|
||||
AddSendMailMoney()
|
||||
end,
|
||||
|
||||
collapse = nil,
|
||||
canPickup = 1,
|
||||
showSmallerCoins = "Backpack",
|
||||
}
|
||||
MoneyTypeInfo["SEND_MAIL_COD"] = {
|
||||
UpdateFunc = function()
|
||||
-- `GetSendMailCOD` (ref l.90) — same gap as SEND_MAIL above.
|
||||
if not GetSendMailCOD then return 0 end
|
||||
return GetSendMailCOD()
|
||||
end,
|
||||
|
||||
PickupFunc = function(amount)
|
||||
PickupSendMailCOD(amount)
|
||||
end,
|
||||
|
||||
DropFunc = function()
|
||||
AddSendMailCOD()
|
||||
end,
|
||||
|
||||
collapse = 1,
|
||||
canPickup = 1,
|
||||
}
|
||||
|
||||
-- l.105-122: the two OnLoads. Identical but for `this.small`, which is what picks the 13px
|
||||
-- coin + tight spacing over the 19px one in MoneyFrame_Update below. Both then take type
|
||||
-- PLAYER, so a frame that is never told otherwise shows the purse.
|
||||
function MoneyFrame_OnLoad()
|
||||
this:RegisterEvent("PLAYER_MONEY")
|
||||
this:RegisterEvent("PLAYER_TRADE_MONEY")
|
||||
this:RegisterEvent("TRADE_MONEY_CHANGED")
|
||||
this:RegisterEvent("SEND_MAIL_MONEY_CHANGED")
|
||||
this:RegisterEvent("SEND_MAIL_COD_CHANGED")
|
||||
MoneyFrame_SetType("PLAYER")
|
||||
end
|
||||
|
||||
function SmallMoneyFrame_OnLoad()
|
||||
this:RegisterEvent("PLAYER_MONEY")
|
||||
this:RegisterEvent("PLAYER_TRADE_MONEY")
|
||||
this:RegisterEvent("TRADE_MONEY_CHANGED")
|
||||
this:RegisterEvent("SEND_MAIL_MONEY_CHANGED")
|
||||
this:RegisterEvent("SEND_MAIL_COD_CHANGED")
|
||||
this.small = 1
|
||||
MoneyFrame_SetType("PLAYER")
|
||||
end
|
||||
|
||||
-- l.124-140, verbatim: every money frame hears every money event, and each decides for itself
|
||||
-- whether the one that arrived is about the number IT is showing.
|
||||
function MoneyFrame_OnEvent()
|
||||
if ( not this.info or not this:IsVisible() ) then
|
||||
return
|
||||
end
|
||||
|
||||
if ( event == "PLAYER_MONEY" and this.moneyType == "PLAYER" ) then
|
||||
MoneyFrame_UpdateMoney()
|
||||
elseif ( event == "PLAYER_TRADE_MONEY" and (this.moneyType == "PLAYER" or this.moneyType == "PLAYER_TRADE") ) then
|
||||
MoneyFrame_UpdateMoney()
|
||||
elseif ( event == "TRADE_MONEY_CHANGED" and this.moneyType == "TARGET_TRADE" ) then
|
||||
MoneyFrame_UpdateMoney()
|
||||
elseif ( event == "SEND_MAIL_MONEY_CHANGED" and (this.moneyType == "PLAYER" or this.moneyType == "SEND_MAIL") ) then
|
||||
MoneyFrame_UpdateMoney()
|
||||
elseif ( event == "SEND_MAIL_COD_CHANGED" and (this.moneyType == "PLAYER" or this.moneyType == "SEND_MAIL_COD") ) then
|
||||
MoneyFrame_UpdateMoney()
|
||||
end
|
||||
end
|
||||
|
||||
-- l.142-162: point a frame at a money type. Operates on `this`, not on a passed frame — the
|
||||
-- reference's convention here, and what the eleven corpus callers write.
|
||||
function MoneyFrame_SetType(type)
|
||||
local info = MoneyTypeInfo[type]
|
||||
if ( not info ) then
|
||||
-- ref l.145 `message("Invalid money type: "..type)` — `message` is BasicControls.xml's
|
||||
-- StaticPopup shim, not transcribed (see the header).
|
||||
if ( message ) then message("Invalid money type: "..type) end
|
||||
return
|
||||
end
|
||||
this.info = info
|
||||
this.moneyType = type
|
||||
local frameName = this:GetName()
|
||||
if ( info.canPickup ) then
|
||||
getglobal(frameName.."GoldButton"):EnableMouse(true)
|
||||
getglobal(frameName.."SilverButton"):EnableMouse(true)
|
||||
getglobal(frameName.."CopperButton"):EnableMouse(true)
|
||||
else
|
||||
getglobal(frameName.."GoldButton"):EnableMouse(false)
|
||||
getglobal(frameName.."SilverButton"):EnableMouse(false)
|
||||
getglobal(frameName.."CopperButton"):EnableMouse(false)
|
||||
end
|
||||
|
||||
MoneyFrame_UpdateMoney()
|
||||
end
|
||||
|
||||
-- l.164-175: re-read the number from the type and repaint. `this` again.
|
||||
function MoneyFrame_UpdateMoney()
|
||||
if ( this.info ) then
|
||||
local money = this.info.UpdateFunc()
|
||||
MoneyFrame_Update(this:GetName(), money)
|
||||
-- ref l.169-171 also drives the coin-pickup frame while one is open. `hasPickup` is
|
||||
-- only ever set by the templates' OnClick, which is guarded off (see the header), so
|
||||
-- this branch is unreachable today rather than removed.
|
||||
if ( this.hasPickup == 1 and UpdateCoinPickupFrame ) then
|
||||
UpdateCoinPickupFrame(money)
|
||||
end
|
||||
else
|
||||
if ( message ) then message("Error moneyType not set") end
|
||||
end
|
||||
end
|
||||
|
||||
-- The button's laid-out text width. The reference calls `Button:GetTextWidth()` (l.202 and
|
||||
-- kin); our Button has no such method, but the `<ButtonText name="$parentText">` FontString
|
||||
-- the templates below name does carry `GetStringWidth`, which measures the same string with
|
||||
-- the same font object. Falls back to 0 when the label has not resolved (headless, no atlas),
|
||||
-- which leaves the coin at exactly its icon width — the same shape, without the digits.
|
||||
local function moneyButtonTextWidth(buttonName)
|
||||
local text = getglobal(buttonName.."Text")
|
||||
if ( not text or not text.GetStringWidth ) then return 0 end
|
||||
return text:GetStringWidth() or 0
|
||||
end
|
||||
|
||||
-- l.177-270: THE PAINTER. Splits a copper amount into the three denominations, sizes each coin
|
||||
-- to its own digits + icon, and then — if the type collapses — hides the leading zeros and
|
||||
-- re-chains what is left so the group packs tight and the frame reports its true width.
|
||||
--
|
||||
-- Note that this one takes a frame NAME, not `this`: it is the only entry point in the family
|
||||
-- an owner can call about a frame that is not the one currently dispatching (`MoneyFrame_Update
|
||||
-- (frameName, money)` is how an addon paints a STATIC frame it just filled).
|
||||
function MoneyFrame_Update(frameName, money)
|
||||
local frame = getglobal(frameName)
|
||||
local info = frame.info
|
||||
if ( not info ) then
|
||||
if ( message ) then message("Error moneyType not set") end
|
||||
end
|
||||
|
||||
-- Breakdown the money into denominations
|
||||
local gold = floor(money / (COPPER_PER_SILVER * SILVER_PER_GOLD))
|
||||
local silver = floor((money - (gold * COPPER_PER_SILVER * SILVER_PER_GOLD)) / COPPER_PER_SILVER)
|
||||
local copper = mod(money, COPPER_PER_SILVER)
|
||||
|
||||
local goldButton = getglobal(frameName.."GoldButton")
|
||||
local silverButton = getglobal(frameName.."SilverButton")
|
||||
local copperButton = getglobal(frameName.."CopperButton")
|
||||
|
||||
local iconWidth = MONEY_ICON_WIDTH
|
||||
local spacing = MONEY_BUTTON_SPACING
|
||||
if ( frame.small ) then
|
||||
iconWidth = MONEY_ICON_WIDTH_SMALL
|
||||
spacing = MONEY_BUTTON_SPACING_SMALL
|
||||
end
|
||||
|
||||
-- Set values for each denomination
|
||||
goldButton:SetText(gold)
|
||||
goldButton:SetWidth(moneyButtonTextWidth(frameName.."GoldButton") + iconWidth)
|
||||
goldButton:Show()
|
||||
silverButton:SetText(silver)
|
||||
silverButton:SetWidth(moneyButtonTextWidth(frameName.."SilverButton") + iconWidth)
|
||||
silverButton:Show()
|
||||
copperButton:SetText(copper)
|
||||
copperButton:SetWidth(moneyButtonTextWidth(frameName.."CopperButton") + iconWidth)
|
||||
copperButton:Show()
|
||||
|
||||
-- Store how much money the frame is displaying
|
||||
frame.staticMoney = money
|
||||
|
||||
-- If not collapsable don't need to continue
|
||||
if ( not info.collapse ) then
|
||||
return
|
||||
end
|
||||
|
||||
local width = iconWidth
|
||||
local showLowerDenominations, truncateCopper
|
||||
if ( gold > 0 ) then
|
||||
width = width + goldButton:GetWidth()
|
||||
if ( info.showSmallerCoins ) then
|
||||
showLowerDenominations = 1
|
||||
end
|
||||
if ( info.truncateSmallCoins ) then
|
||||
truncateCopper = 1
|
||||
end
|
||||
else
|
||||
goldButton:Hide()
|
||||
end
|
||||
|
||||
if ( silver > 0 or showLowerDenominations ) then
|
||||
-- Exception if showLowerDenominations and fixedWidth
|
||||
if ( showLowerDenominations and info.fixedWidth ) then
|
||||
silverButton:SetWidth(COIN_BUTTON_WIDTH)
|
||||
end
|
||||
|
||||
width = width + silverButton:GetWidth()
|
||||
goldButton:SetPoint("RIGHT", frameName.."SilverButton", "LEFT", spacing, 0)
|
||||
if ( goldButton:IsVisible() ) then
|
||||
width = width - spacing
|
||||
end
|
||||
if ( info.showSmallerCoins ) then
|
||||
showLowerDenominations = 1
|
||||
end
|
||||
else
|
||||
silverButton:Hide()
|
||||
goldButton:SetPoint("RIGHT", frameName.."SilverButton", "RIGHT", 0, 0)
|
||||
end
|
||||
|
||||
-- Used if we're not showing lower denominations
|
||||
if ( (copper > 0 or showLowerDenominations or info.showSmallerCoins == "Backpack") and not truncateCopper) then
|
||||
-- Exception if showLowerDenominations and fixedWidth
|
||||
if ( showLowerDenominations and info.fixedWidth ) then
|
||||
copperButton:SetWidth(COIN_BUTTON_WIDTH)
|
||||
end
|
||||
|
||||
width = width + copperButton:GetWidth()
|
||||
silverButton:SetPoint("RIGHT", frameName.."CopperButton", "LEFT", spacing, 0)
|
||||
if ( silverButton:IsVisible() ) then
|
||||
width = width - spacing
|
||||
end
|
||||
else
|
||||
copperButton:Hide()
|
||||
silverButton:SetPoint("RIGHT", frameName.."CopperButton", "RIGHT", 0, 0)
|
||||
end
|
||||
|
||||
frame:SetWidth(width)
|
||||
end
|
||||
|
||||
-- l.272-346: a NO-OP in 1.12 — the reference's whole body is inside a `--[[ ]]` block, an
|
||||
-- earlier painter that MoneyFrame_Update replaced. Kept because the name is in the shipped
|
||||
-- client's `_G` and an addon that calls it gets exactly this there: nothing.
|
||||
function RefreshMoneyFrame(frameName, money, small, collapse, showSmallerCoins)
|
||||
end
|
||||
|
||||
-- l.348-356, verbatim: recolour the DIGITS (the coin icons keep their own texture) — how the
|
||||
-- reference reddens a price you cannot afford.
|
||||
function SetMoneyFrameColor(frameName, r, g, b)
|
||||
local goldButton = getglobal(frameName.."GoldButton")
|
||||
local silverButton = getglobal(frameName.."SilverButton")
|
||||
local copperButton = getglobal(frameName.."CopperButton")
|
||||
|
||||
goldButton:SetTextColor(r, g, b)
|
||||
silverButton:SetTextColor(r, g, b)
|
||||
copperButton:SetTextColor(r, g, b)
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
<!--
|
||||
MoneyFrameTemplate (ref MoneyFrame.xml l.3-149) — the LARGE kit: 192x28, 19px coins, the
|
||||
NumberFontNormalLarge digits. The three buttons chain RIGHT-to-LEFT from the copper end, which
|
||||
is what lets MoneyFrame_Update above re-point them past a hidden denomination.
|
||||
|
||||
The `<OnClick>` bodies are the reference's, guarded on `OpenCoinPickupFrame` — the coin-pickup
|
||||
frame is not transcribed (see the header). `<PushedTextOffset>` is carried for provenance; our
|
||||
loader does not model it and ignores it.
|
||||
-->
|
||||
<Frame name="MoneyFrameTemplate" virtual="true">
|
||||
<Size><AbsDimension x="192" y="28"/></Size>
|
||||
<Frames>
|
||||
<Button name="$parentCopperButton">
|
||||
<Size><AbsDimension x="47" y="19"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
local parent = this:GetParent()
|
||||
if ( OpenCoinPickupFrame ) then
|
||||
OpenCoinPickupFrame(1, MoneyTypeInfo[parent.moneyType].UpdateFunc(), parent)
|
||||
parent.hasPickup = 1
|
||||
end
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
<NormalTexture file="Interface\MoneyFrame\UI-MoneyIcons">
|
||||
<Size><AbsDimension x="19" y="19"/></Size>
|
||||
<Anchors><Anchor point="RIGHT"/></Anchors>
|
||||
<TexCoords left="0.5" right="0.75" top="0" bottom="1"/>
|
||||
</NormalTexture>
|
||||
<ButtonText name="$parentText">
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-19" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</ButtonText>
|
||||
<NormalFont inherits="NumberFontNormalLarge" justifyH="RIGHT"/>
|
||||
<PushedTextOffset><AbsDimension x="0" y="0"/></PushedTextOffset>
|
||||
</Button>
|
||||
<Button name="$parentSilverButton">
|
||||
<Size><AbsDimension x="47" y="19"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativeTo="$parentCopperButton" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
local parent = this:GetParent()
|
||||
if ( OpenCoinPickupFrame ) then
|
||||
OpenCoinPickupFrame(COPPER_PER_SILVER, MoneyTypeInfo[parent.moneyType].UpdateFunc(), parent)
|
||||
parent.hasPickup = 1
|
||||
end
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
<NormalTexture file="Interface\MoneyFrame\UI-MoneyIcons">
|
||||
<Size><AbsDimension x="19" y="19"/></Size>
|
||||
<Anchors><Anchor point="RIGHT"/></Anchors>
|
||||
<TexCoords left="0.25" right="0.5" top="0" bottom="1"/>
|
||||
</NormalTexture>
|
||||
<ButtonText name="$parentText">
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-19" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</ButtonText>
|
||||
<NormalFont inherits="NumberFontNormalLarge" justifyH="RIGHT"/>
|
||||
<PushedTextOffset><AbsDimension x="0" y="0"/></PushedTextOffset>
|
||||
</Button>
|
||||
<Button name="$parentGoldButton">
|
||||
<Size><AbsDimension x="47" y="19"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativeTo="$parentSilverButton" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
local parent = this:GetParent()
|
||||
if ( OpenCoinPickupFrame ) then
|
||||
OpenCoinPickupFrame(COPPER_PER_GOLD, MoneyTypeInfo[parent.moneyType].UpdateFunc(), parent)
|
||||
parent.hasPickup = 1
|
||||
end
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
<NormalTexture file="Interface\MoneyFrame\UI-MoneyIcons">
|
||||
<Size><AbsDimension x="19" y="19"/></Size>
|
||||
<Anchors><Anchor point="RIGHT"/></Anchors>
|
||||
<TexCoords left="0" right="0.25" top="0" bottom="1"/>
|
||||
</NormalTexture>
|
||||
<ButtonText name="$parentText">
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-19" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</ButtonText>
|
||||
<NormalFont inherits="NumberFontNormalLarge" justifyH="RIGHT"/>
|
||||
<PushedTextOffset><AbsDimension x="0" y="0"/></PushedTextOffset>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>MoneyFrame_OnLoad()</OnLoad>
|
||||
<OnEvent>MoneyFrame_OnEvent()</OnEvent>
|
||||
<OnShow>MoneyFrame_UpdateMoney()</OnShow>
|
||||
<OnHide>
|
||||
if ( this.hasPickup == 1 and CoinPickupFrame ) then
|
||||
CoinPickupFrame:Hide()
|
||||
this.hasPickup = 0
|
||||
end
|
||||
</OnHide>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
<!--
|
||||
SmallMoneyFrameTemplate (ref MoneyFrame.xml l.150-296) — the same kit at 128x13 with 13px
|
||||
coins and NumberFontNormal digits, and `SmallMoneyFrame_OnLoad` in place of `MoneyFrame_OnLoad`
|
||||
so `this.small` is set. This is the one the shipped UI uses nearly everywhere (merchant prices,
|
||||
the purse, mail postage, trade offers) and the one 13 corpus addons inherit.
|
||||
-->
|
||||
<Frame name="SmallMoneyFrameTemplate" virtual="true">
|
||||
<Size><AbsDimension x="128" y="13"/></Size>
|
||||
<Frames>
|
||||
<Button name="$parentCopperButton">
|
||||
<Size><AbsDimension x="32" y="13"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
local parent = this:GetParent()
|
||||
if ( OpenCoinPickupFrame ) then
|
||||
OpenCoinPickupFrame(1, MoneyTypeInfo[parent.moneyType].UpdateFunc(), parent)
|
||||
parent.hasPickup = 1
|
||||
end
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
<NormalTexture file="Interface\MoneyFrame\UI-MoneyIcons">
|
||||
<Size><AbsDimension x="13" y="13"/></Size>
|
||||
<Anchors><Anchor point="RIGHT"/></Anchors>
|
||||
<TexCoords left="0.5" right="0.75" top="0" bottom="1"/>
|
||||
</NormalTexture>
|
||||
<ButtonText name="$parentText">
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</ButtonText>
|
||||
<NormalFont inherits="NumberFontNormal" justifyH="RIGHT"/>
|
||||
<PushedTextOffset><AbsDimension x="0" y="0"/></PushedTextOffset>
|
||||
</Button>
|
||||
<Button name="$parentSilverButton">
|
||||
<Size><AbsDimension x="32" y="13"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativeTo="$parentCopperButton" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
local parent = this:GetParent()
|
||||
if ( OpenCoinPickupFrame ) then
|
||||
OpenCoinPickupFrame(COPPER_PER_SILVER, MoneyTypeInfo[parent.moneyType].UpdateFunc(), parent)
|
||||
parent.hasPickup = 1
|
||||
end
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
<NormalTexture file="Interface\MoneyFrame\UI-MoneyIcons">
|
||||
<Size><AbsDimension x="13" y="13"/></Size>
|
||||
<Anchors><Anchor point="RIGHT"/></Anchors>
|
||||
<TexCoords left="0.25" right="0.5" top="0" bottom="1"/>
|
||||
</NormalTexture>
|
||||
<ButtonText name="$parentText">
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</ButtonText>
|
||||
<NormalFont inherits="NumberFontNormal" justifyH="RIGHT"/>
|
||||
<PushedTextOffset><AbsDimension x="0" y="0"/></PushedTextOffset>
|
||||
</Button>
|
||||
<Button name="$parentGoldButton">
|
||||
<Size><AbsDimension x="32" y="13"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativeTo="$parentSilverButton" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
local parent = this:GetParent()
|
||||
if ( OpenCoinPickupFrame ) then
|
||||
OpenCoinPickupFrame(COPPER_PER_GOLD, MoneyTypeInfo[parent.moneyType].UpdateFunc(), parent)
|
||||
parent.hasPickup = 1
|
||||
end
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
<NormalTexture file="Interface\MoneyFrame\UI-MoneyIcons">
|
||||
<Size><AbsDimension x="13" y="13"/></Size>
|
||||
<Anchors><Anchor point="RIGHT"/></Anchors>
|
||||
<TexCoords left="0" right="0.25" top="0" bottom="1"/>
|
||||
</NormalTexture>
|
||||
<ButtonText name="$parentText">
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</ButtonText>
|
||||
<NormalFont inherits="NumberFontNormal" justifyH="RIGHT"/>
|
||||
<PushedTextOffset><AbsDimension x="0" y="0"/></PushedTextOffset>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>SmallMoneyFrame_OnLoad()</OnLoad>
|
||||
<OnEvent>MoneyFrame_OnEvent()</OnEvent>
|
||||
<OnShow>MoneyFrame_UpdateMoney()</OnShow>
|
||||
<OnHide>
|
||||
if ( this.hasPickup == 1 and CoinPickupFrame ) then
|
||||
CoinPickupFrame:Hide()
|
||||
this.hasPickup = 0
|
||||
end
|
||||
</OnHide>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
@@ -15,7 +15,7 @@
|
||||
as per-button data.
|
||||
- Buttons reuse BenillaActionButtonTemplate + the whole ActionBar.xml handler set: same
|
||||
click/drag/keyless-use, same feedback layer (state/usable/cooldown/flash), fanned from
|
||||
BenillaActionBar's bar-level events via the BENILLA_ACTION_BUTTONS roster each button
|
||||
MainMenuBar's bar-level events via the BENILLA_ACTION_BUTTONS roster each button
|
||||
joins at OnLoad.
|
||||
|
||||
Ref divergences (deliberate, mirroring ActionBar.xml's SCOPE notes):
|
||||
@@ -36,24 +36,30 @@
|
||||
BENILLA_MULTIBAR_BOTTOMLEFT_BASE = 60
|
||||
BENILLA_MULTIBAR_BOTTOMRIGHT_BASE = 48
|
||||
|
||||
-- The MULTIACTIONBAR keybind pair — ref MultiActionBars.lua:24-45 MultiActionButtonDown/
|
||||
-- Up, transcribed onto our frame names (`bar` is "BottomLeft"/"BottomRight", the ref
|
||||
-- passes "MultiBarBottomLeft"); the same NORMAL→PUSHED debounce dance as the main bar's
|
||||
-- BenillaActionButtonDown/Up (ActionBar.xml — the ref's MacroFrame_SaveMacro hook and
|
||||
-- onSelf argument are not modeled there either). Driven by the binding dispatch
|
||||
-- (crate::bindings, Kind::EdgeUpDown — decision 1008).
|
||||
function BenillaMultiActionButtonDown(bar, id)
|
||||
local button = getglobal("BenillaMultiBar" .. bar .. "Button" .. id)
|
||||
-- The MULTIACTIONBAR keybind pair — ref MultiActionBars.lua:24-45 MultiActionButtonDown/Up,
|
||||
-- the same NORMAL→PUSHED debounce dance as the main bar's ActionButtonDown/Up
|
||||
-- (ActionBar.xml — the ref's MacroFrame_SaveMacro hook is not modeled there either).
|
||||
-- Driven by the binding dispatch (crate::bindings, Kind::EdgeUpDown — decision 1008).
|
||||
--
|
||||
-- `bar` IS THE WHOLE FRAME-NAME PREFIX, not a suffix (decision 1199). The reference resolves
|
||||
-- `getglobal(bar.."Button"..id)` and its own Bindings.xml passes "MultiBarBottomLeft"; ours
|
||||
-- used to pass "BottomLeft" and splice "MultiBar" on in here. Same arity, different MEANING
|
||||
-- — the silent kind of wrong shape: zBar calls `MultiActionButtonDown("zBar"..barid, id)`
|
||||
-- and under the old splice built the nonexistent frame name "MultiBarzBar1Button1". The
|
||||
-- caller now names the bar in full, exactly as the reference's binding does, so a third-
|
||||
-- party bar can address these at all. `onSelf` rides to UseAction like ActionButtonUp's.
|
||||
function MultiActionButtonDown(bar, id)
|
||||
local button = getglobal(bar .. "Button" .. id)
|
||||
if ( button:GetButtonState() == "NORMAL" ) then
|
||||
button:SetButtonState("PUSHED")
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaMultiActionButtonUp(bar, id)
|
||||
local button = getglobal("BenillaMultiBar" .. bar .. "Button" .. id)
|
||||
function MultiActionButtonUp(bar, id, onSelf)
|
||||
local button = getglobal(bar .. "Button" .. id)
|
||||
if ( button:GetButtonState() == "PUSHED" ) then
|
||||
button:SetButtonState("NORMAL")
|
||||
UseAction(BenillaActionButton_Action(button))
|
||||
UseAction(BenillaActionButton_Action(button), 0, onSelf)
|
||||
-- Ref MultiActionButtonUp:39-43: the checked ring reflects the use immediately.
|
||||
BenillaActionButton_UpdateState(button)
|
||||
end
|
||||
@@ -65,116 +71,116 @@
|
||||
both sides by design — and both sides carry deep nested button trees, so co-tenanting one
|
||||
stratum is exactly the level-outranks-insertion collision decision 0597 fixed for the party
|
||||
frame. HIGH is what keeps the bars above an open bag. -->
|
||||
<Frame name="BenillaMultiBarBottomLeft" frameStrata="HIGH">
|
||||
<Frame name="MultiBarBottomLeft" frameStrata="HIGH">
|
||||
<Size><AbsDimension x="500" y="38"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="BenillaActionButton1" relativePoint="TOPLEFT"><Offset><AbsDimension x="0" y="17"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="ActionButton1" relativePoint="TOPLEFT"><Offset><AbsDimension x="0" y="17"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Frames>
|
||||
<CheckButton name="BenillaMultiBarBottomLeftButton1" inherits="BenillaActionButtonTemplate">
|
||||
<CheckButton name="MultiBarBottomLeftButton1" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 1, BENILLA_MULTIBAR_BOTTOMLEFT_BASE, "MULTIACTIONBAR1BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomLeftButton2" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomLeftButton1" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomLeftButton2" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomLeftButton1" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 2, BENILLA_MULTIBAR_BOTTOMLEFT_BASE, "MULTIACTIONBAR1BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomLeftButton3" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomLeftButton2" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomLeftButton3" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomLeftButton2" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 3, BENILLA_MULTIBAR_BOTTOMLEFT_BASE, "MULTIACTIONBAR1BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomLeftButton4" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomLeftButton3" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomLeftButton4" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomLeftButton3" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 4, BENILLA_MULTIBAR_BOTTOMLEFT_BASE, "MULTIACTIONBAR1BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomLeftButton5" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomLeftButton4" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomLeftButton5" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomLeftButton4" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 5, BENILLA_MULTIBAR_BOTTOMLEFT_BASE, "MULTIACTIONBAR1BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomLeftButton6" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomLeftButton5" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomLeftButton6" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomLeftButton5" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 6, BENILLA_MULTIBAR_BOTTOMLEFT_BASE, "MULTIACTIONBAR1BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomLeftButton7" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomLeftButton6" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomLeftButton7" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomLeftButton6" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 7, BENILLA_MULTIBAR_BOTTOMLEFT_BASE, "MULTIACTIONBAR1BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomLeftButton8" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomLeftButton7" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomLeftButton8" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomLeftButton7" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 8, BENILLA_MULTIBAR_BOTTOMLEFT_BASE, "MULTIACTIONBAR1BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomLeftButton9" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomLeftButton8" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomLeftButton9" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomLeftButton8" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 9, BENILLA_MULTIBAR_BOTTOMLEFT_BASE, "MULTIACTIONBAR1BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomLeftButton10" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomLeftButton9" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomLeftButton10" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomLeftButton9" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 10, BENILLA_MULTIBAR_BOTTOMLEFT_BASE, "MULTIACTIONBAR1BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomLeftButton11" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomLeftButton10" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomLeftButton11" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomLeftButton10" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 11, BENILLA_MULTIBAR_BOTTOMLEFT_BASE, "MULTIACTIONBAR1BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomLeftButton12" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomLeftButton11" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomLeftButton12" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomLeftButton11" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 12, BENILLA_MULTIBAR_BOTTOMLEFT_BASE, "MULTIACTIONBAR1BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- HIGH, same as BottomLeft above (ref `MultiActionBars.xml` l.151). -->
|
||||
<Frame name="BenillaMultiBarBottomRight" frameStrata="HIGH">
|
||||
<Frame name="MultiBarBottomRight" frameStrata="HIGH">
|
||||
<Size><AbsDimension x="500" y="38"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="BenillaMultiBarBottomLeft" relativePoint="RIGHT"><Offset><AbsDimension x="10" y="0"/></Offset></Anchor>
|
||||
<Anchor point="LEFT" relativeTo="MultiBarBottomLeft" relativePoint="RIGHT"><Offset><AbsDimension x="10" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Frames>
|
||||
<CheckButton name="BenillaMultiBarBottomRightButton1" inherits="BenillaActionButtonTemplate">
|
||||
<CheckButton name="MultiBarBottomRightButton1" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 1, BENILLA_MULTIBAR_BOTTOMRIGHT_BASE, "MULTIACTIONBAR2BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomRightButton2" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomRightButton1" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomRightButton2" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomRightButton1" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 2, BENILLA_MULTIBAR_BOTTOMRIGHT_BASE, "MULTIACTIONBAR2BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomRightButton3" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomRightButton2" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomRightButton3" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomRightButton2" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 3, BENILLA_MULTIBAR_BOTTOMRIGHT_BASE, "MULTIACTIONBAR2BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomRightButton4" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomRightButton3" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomRightButton4" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomRightButton3" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 4, BENILLA_MULTIBAR_BOTTOMRIGHT_BASE, "MULTIACTIONBAR2BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomRightButton5" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomRightButton4" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomRightButton5" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomRightButton4" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 5, BENILLA_MULTIBAR_BOTTOMRIGHT_BASE, "MULTIACTIONBAR2BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomRightButton6" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomRightButton5" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomRightButton6" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomRightButton5" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 6, BENILLA_MULTIBAR_BOTTOMRIGHT_BASE, "MULTIACTIONBAR2BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomRightButton7" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomRightButton6" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomRightButton7" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomRightButton6" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 7, BENILLA_MULTIBAR_BOTTOMRIGHT_BASE, "MULTIACTIONBAR2BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomRightButton8" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomRightButton7" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomRightButton8" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomRightButton7" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 8, BENILLA_MULTIBAR_BOTTOMRIGHT_BASE, "MULTIACTIONBAR2BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomRightButton9" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomRightButton8" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomRightButton9" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomRightButton8" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 9, BENILLA_MULTIBAR_BOTTOMRIGHT_BASE, "MULTIACTIONBAR2BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomRightButton10" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomRightButton9" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomRightButton10" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomRightButton9" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 10, BENILLA_MULTIBAR_BOTTOMRIGHT_BASE, "MULTIACTIONBAR2BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomRightButton11" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomRightButton10" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomRightButton11" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomRightButton10" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 11, BENILLA_MULTIBAR_BOTTOMRIGHT_BASE, "MULTIACTIONBAR2BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaMultiBarBottomRightButton12" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaMultiBarBottomRightButton11" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="MultiBarBottomRightButton12" inherits="BenillaActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="MultiBarBottomRightButton11" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 12, BENILLA_MULTIBAR_BOTTOMRIGHT_BASE, "MULTIACTIONBAR2BUTTON")</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
</Frames>
|
||||
|
||||
@@ -326,7 +326,7 @@
|
||||
function OptionsSearch_Words(text)
|
||||
local up = string.upper(text)
|
||||
local words = { up }
|
||||
for w in string.gmatch(up, "([^, ]+)") do
|
||||
for w in string.gfind(up, "([^, ]+)") do
|
||||
table.insert(words, w)
|
||||
end
|
||||
return words
|
||||
@@ -2373,7 +2373,7 @@
|
||||
<Scripts><OnLoad>
|
||||
OptionsGlobalRow_OnLoad(self, "SHOW_BUFF_DURATIONS",
|
||||
"Buff Durations", "OPTION_TOOLTIP_SHOW_BUFF_DURATION")
|
||||
self.applyFunc = BenillaBuffButtons_UpdatePositions
|
||||
self.applyFunc = BuffButtons_UpdatePositions
|
||||
</OnLoad></Scripts>
|
||||
</Frame>
|
||||
<Frame name="$parentRowAutoQuestWatch" inherits="OptionsCheckboxRowTemplate">
|
||||
@@ -2685,14 +2685,14 @@
|
||||
seating). The wheel handler is NOT here: a spin over a row
|
||||
bubbles up the PARENT chain (pointer.rs), and this frame is
|
||||
the rows' sibling — it lives on the body below. -->
|
||||
<Frame name="$parentScrollFrame" inherits="BenillaFauxScrollFrameTemplate">
|
||||
<Frame name="$parentScrollFrame" inherits="FauxScrollFrameTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-47"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-32" y="28"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
BenillaFauxScrollFrame_OnLoad(this)
|
||||
ScrollFrame_OnLoad()
|
||||
this.updateFunc = KeyBindingsPage_Update
|
||||
</OnLoad>
|
||||
</Scripts>
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
<Ui>
|
||||
<!--
|
||||
The reference's shared OPTIONS widget kit — Interface\FrameXML\OptionsFrameTemplates.xml,
|
||||
transcribed from the extracted 1.12.1 file (line numbers below are that file's), plus the
|
||||
one template from UIOptionsFrame.xml that addons actually name.
|
||||
|
||||
These are UIPanelTemplates.xml's descendants: `OptionsCheckButtonTemplate` inherits
|
||||
`UICheckButtonTemplate` and `OptionsButtonTemplate` inherits `UIPanelButtonTemplate`, so
|
||||
this file must load after it (the .toc seats them together).
|
||||
|
||||
WHY THE UIOptionsFrame ONE LIVES HERE. `UIOptionsCheckButtonTemplate` is 8 `CreateFrame`
|
||||
call sites in the corpus — the fourth most-wanted template we lacked — and in the reference
|
||||
it is declared in UIOptionsFrame.xml (l.6-10) as a two-line size override on
|
||||
`OptionsCheckButtonTemplate`. We do not transcribe UIOptionsFrame (benilla's own options
|
||||
window is OptionsFrame.xml, a different design), so the one template that file contributes
|
||||
to the shared namespace sits with its parent rather than in a file of its own. Its
|
||||
provenance is on the declaration.
|
||||
|
||||
Every name here is in `reference/1.12-globals.tsv`; none is invented. What this supersedes
|
||||
but does not delete: nothing — OptionsFrame.xml's own `Options*Template` family
|
||||
(`OptionsRedButtonTemplate`, `OptionsCheckboxRowTemplate`, `OptionsSliderRowTemplate`, …)
|
||||
are benilla's row widgets for benilla's window, distinct names and a distinct shape.
|
||||
-->
|
||||
|
||||
<!-- ref l.3-34. The titled group box: a tooltip-border backdrop with its caption hung above the
|
||||
top-left corner, greyed by its own OnLoad. -->
|
||||
<Frame name="OptionFrameBoxTemplate" virtual="true">
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<FontString name="$parentTitle" inherits="GameFontHighlight">
|
||||
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="9" y="0"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
|
||||
<EdgeSize><AbsValue val="16"/></EdgeSize>
|
||||
<TileSize><AbsValue val="16"/></TileSize>
|
||||
<BackgroundInsets><AbsInset left="5" right="5" top="5" bottom="5"/></BackgroundInsets>
|
||||
</Backdrop>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
this:SetBackdropBorderColor(0.4, 0.4, 0.4)
|
||||
this:SetBackdropColor(0.5, 0.5, 0.5)
|
||||
</OnLoad>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
<!-- ref l.35-39. The panel button at the options window's own 90x21. -->
|
||||
<Button name="OptionsButtonTemplate" inherits="UIPanelButtonTemplate" virtual="true">
|
||||
<Size><AbsDimension x="90" y="21"/></Size>
|
||||
</Button>
|
||||
|
||||
<!-- ref l.40-66. The checkbox with the options window's manners: a 100px hit rect to the right
|
||||
so the LABEL is clickable too, the on/off click sounds, and the tooltipText /
|
||||
tooltipRequirement hover pair an instance sets as plain fields. -->
|
||||
<CheckButton name="OptionsCheckButtonTemplate" inherits="UICheckButtonTemplate" virtual="true">
|
||||
<HitRectInsets><AbsInset left="0" right="-100" top="0" bottom="0"/></HitRectInsets>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
if (this:GetChecked()) then
|
||||
PlaySound("igMainMenuOptionCheckBoxOn")
|
||||
else
|
||||
PlaySound("igMainMenuOptionCheckBoxOff")
|
||||
end
|
||||
</OnClick>
|
||||
<OnEnter>
|
||||
if (this.tooltipText) then
|
||||
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
|
||||
GameTooltip:SetText(this.tooltipText, nil, nil, nil, nil, 1)
|
||||
end
|
||||
if (this.tooltipRequirement) then
|
||||
GameTooltip:AddLine(this.tooltipRequirement, "", 1.0, 1.0, 1.0)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
</OnEnter>
|
||||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||||
</Scripts>
|
||||
</CheckButton>
|
||||
|
||||
<!-- ref UIOptionsFrame.xml l.6-10 — see the header. A 26x26 override of the above, and the
|
||||
template the corpus's nameplate addons build their whole options panel out of. -->
|
||||
<CheckButton name="UIOptionsCheckButtonTemplate" inherits="OptionsCheckButtonTemplate" virtual="true">
|
||||
<Size><AbsDimension x="26" y="26"/></Size>
|
||||
</CheckButton>
|
||||
|
||||
<!-- ref l.67-132. The horizontal options slider: a SliderBar backdrop with three labels
|
||||
(caption above, LOW/HIGH under the ends) and the horizontal knob. `text="LOW"`/`"HIGH"` are
|
||||
global-string keys, resolved by the loader's `resolve_text` against GlobalStrings. -->
|
||||
<Slider name="OptionsSliderTemplate" orientation="HORIZONTAL" virtual="true" enableMouse="true">
|
||||
<Size><AbsDimension x="128" y="17"/></Size>
|
||||
<HitRectInsets><AbsInset left="0" right="0" top="-10" bottom="-10"/></HitRectInsets>
|
||||
<Backdrop bgFile="Interface\Buttons\UI-SliderBar-Background" edgeFile="Interface\Buttons\UI-SliderBar-Border" tile="true">
|
||||
<EdgeSize><AbsValue val="8"/></EdgeSize>
|
||||
<TileSize><AbsValue val="8"/></TileSize>
|
||||
<BackgroundInsets><AbsInset left="3" right="3" top="6" bottom="6"/></BackgroundInsets>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="$parentText" inherits="GameFontNormalSmall">
|
||||
<Anchors><Anchor point="BOTTOM" relativePoint="TOP"/></Anchors>
|
||||
</FontString>
|
||||
<FontString name="$parentLow" inherits="GameFontHighlightSmall" text="LOW">
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="2" y="3"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<FontString name="$parentHigh" inherits="GameFontHighlightSmall" text="HIGH">
|
||||
<Anchors><Anchor point="TOPRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-2" y="3"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnEnter>
|
||||
if (this.tooltipText) then
|
||||
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
|
||||
GameTooltip:SetText(this.tooltipText, nil, nil, nil, nil, 1)
|
||||
end
|
||||
if (this.tooltipRequirement) then
|
||||
GameTooltip:AddLine(this.tooltipRequirement, "", 1.0, 1.0, 1.0)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
</OnEnter>
|
||||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||||
</Scripts>
|
||||
<ThumbTexture name="$parentThumb" file="Interface\Buttons\UI-SliderBar-Button-Horizontal">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
</ThumbTexture>
|
||||
</Slider>
|
||||
</Ui>
|
||||
@@ -144,14 +144,14 @@
|
||||
frame.healthBar:SetMinMaxValues(0, UnitHealthMax(unit))
|
||||
frame.healthBar:SetValue(UnitHealth(unit))
|
||||
|
||||
local maxPower = UnitPowerMax(unit)
|
||||
local maxPower = UnitManaMax(unit)
|
||||
if maxPower > 0 then
|
||||
frame.manaBar:Show()
|
||||
local _, token = UnitPowerType(unit)
|
||||
local c = BENILLA_POWER_COLORS[token] or BENILLA_POWER_COLORS.MANA
|
||||
frame.manaBar:SetStatusBarColor(c.r, c.g, c.b)
|
||||
frame.manaBar:SetMinMaxValues(0, maxPower)
|
||||
frame.manaBar:SetValue(UnitPower(unit))
|
||||
frame.manaBar:SetValue(UnitMana(unit))
|
||||
else
|
||||
frame.manaBar:Hide()
|
||||
end
|
||||
@@ -201,7 +201,7 @@
|
||||
end
|
||||
local hp, hpMax = UnitHealth(unit), UnitHealthMax(unit)
|
||||
if hpMax > 0 and hp > 0 and hp / hpMax <= 0.2 then
|
||||
local t = math.fmod(frame.flashTimer + elapsed, 1.0)
|
||||
local t = mod(frame.flashTimer + elapsed, 1.0)
|
||||
frame.flashTimer = t
|
||||
local a
|
||||
if t < 0.5 then
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
strips (256x44 at TOPLEFT with TexCoords 0..1 / 0.015625..0.359375, then 184x44 to its
|
||||
right with 0..0.71875 / 0.375..0.71875); buttons 30x30, button1 at frame BOTTOMLEFT +(36,2),
|
||||
chained +8 (with the ref's own +7 between 6 and 7); the 54x54 UI-Quickslot2 ring CENTER
|
||||
(0,-1). The bar rides `BenillaActionBar` BOTTOMLEFT — our MainMenuBar, the same 1024x53
|
||||
(0,-1). The bar rides `MainMenuBar` BOTTOMLEFT — our MainMenuBar, the same 1024x53
|
||||
frame at screen BOTTOM the ref anchors to — at the managed PETACTIONBAR_YPOS (UIParent.xml
|
||||
declares the row already).
|
||||
|
||||
@@ -222,13 +222,14 @@
|
||||
-- and the reference immediately clears that (`this:SetChecked(0)`) — nothing about this bar
|
||||
-- is locally latched, because the server owns every bit of it. Left = run the slot, unless
|
||||
-- the pet is already on the attack, in which case it calls off; right = flip autocast.
|
||||
function BenillaPetActionButton_OnClick(button, mouseButton)
|
||||
function PetActionButton_OnClick(mouseButton)
|
||||
local button = this
|
||||
button:SetChecked(0)
|
||||
-- Shift outranks the button fork entirely (ref l.253-255): a shift-click on the pet bar
|
||||
-- is a pick-up whichever mouse button it came from, and it never toggles autocast.
|
||||
if IsShiftKeyDown() then
|
||||
PickupPetAction(button.index)
|
||||
BenillaPetActionBar_Update()
|
||||
PetActionBar_Update()
|
||||
elseif mouseButton == "RightButton" then
|
||||
TogglePetAutocast(button.index)
|
||||
elseif IsPetAttackActive(button.index) then
|
||||
@@ -255,15 +256,15 @@
|
||||
-- latch lit, so click and key both simply order the attack. The split is visible on a
|
||||
-- POSSESS bar (Mind Control, Eye of Kilrogg, Eyes of the Beast): there a click sends
|
||||
-- PetStopAttack and CTRL-1 re-issues the order instead.
|
||||
function BenillaPetActionButtonDown(id)
|
||||
local button = getglobal("BenillaPetActionButton" .. id)
|
||||
function PetActionButtonDown(id)
|
||||
local button = getglobal("PetActionButton" .. id)
|
||||
if ( button:GetButtonState() == "NORMAL" ) then
|
||||
button:SetButtonState("PUSHED")
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaPetActionButtonUp(id)
|
||||
local button = getglobal("BenillaPetActionButton" .. id)
|
||||
function PetActionButtonUp(id)
|
||||
local button = getglobal("PetActionButton" .. id)
|
||||
if ( button:GetButtonState() == "PUSHED" ) then
|
||||
button:SetButtonState("NORMAL")
|
||||
CastPetAction(id)
|
||||
@@ -278,22 +279,25 @@
|
||||
-- Both also honour the lock (the ref wraps each body in `if LOCK_ACTIONBAR ~= "1" then …`).
|
||||
-- The shift-click in OnClick above is unguarded on this bar too, exactly as the reference
|
||||
-- leaves it (l.253-255): the lock stops the drag, shift-click stays the way through.
|
||||
function BenillaPetActionButton_OnDragStart(button)
|
||||
function PetActionButton_OnDragStart()
|
||||
local button = this
|
||||
if LOCK_ACTIONBAR == "1" then return end
|
||||
PickupPetAction(button.index)
|
||||
BenillaPetActionBar_Update()
|
||||
PetActionBar_Update()
|
||||
end
|
||||
|
||||
function BenillaPetActionButton_OnReceiveDrag(button)
|
||||
function PetActionButton_OnReceiveDrag()
|
||||
local button = this
|
||||
if LOCK_ACTIONBAR == "1" then return end
|
||||
PickupPetAction(button.index)
|
||||
BenillaPetActionBar_Update()
|
||||
PetActionBar_Update()
|
||||
end
|
||||
|
||||
-- Ref PetActionButton_OnEnter. A token slot has no spell to look up, so its tooltip is
|
||||
-- built from the two strings the info call already returned (the ref's own `isToken` fork);
|
||||
-- a spell slot goes through the engine's tooltip channel.
|
||||
function BenillaPetActionButton_OnEnter(button)
|
||||
function PetActionButton_OnEnter()
|
||||
local button = this
|
||||
if not GameTooltip then return end -- engine tests load file subsets
|
||||
if not button.tooltipName then return end
|
||||
GameTooltip_SetDefaultAnchor(GameTooltip, button)
|
||||
@@ -308,7 +312,8 @@
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaPetActionButton_OnLeave(button)
|
||||
function PetActionButton_OnLeave()
|
||||
local button = this
|
||||
if GameTooltip then GameTooltip:Hide() end
|
||||
end
|
||||
|
||||
@@ -318,10 +323,10 @@
|
||||
-- header). Every branch below is the reference's, including the two that are easy to lose:
|
||||
-- an unnamed slot HIDES its button, and a slot with no texture swaps the button's ring from
|
||||
-- the filled UI-Quickslot2 to the empty UI-Quickslot.
|
||||
function BenillaPetActionBar_Update()
|
||||
function PetActionBar_Update()
|
||||
local usable = GetPetActionsUsable()
|
||||
for i = 1, BENILLA_NUM_PET_ACTION_SLOTS do
|
||||
local button = getglobal("BenillaPetActionButton" .. i)
|
||||
local button = getglobal("PetActionButton" .. i)
|
||||
local name, subtext, texture, isToken, isActive, autoCastAllowed, autoCastEnabled = GetPetActionInfo(i)
|
||||
button.isToken = isToken
|
||||
button.tooltipSubtext = subtext
|
||||
@@ -341,7 +346,7 @@
|
||||
-- An unnamed slot hides — UNLESS the drop grid is up, which is the whole point of
|
||||
-- the grid: while a pet action rides the cursor every slot shows, so you can see
|
||||
-- where it may land (ref PetActionBar_Update l.122-128 + PetActionBar_ShowGrid).
|
||||
if name or (BenillaPetActionBarFrame.showgrid or 0) > 0 then
|
||||
if name or (PetActionBarFrame.showgrid or 0) > 0 then
|
||||
button:Show()
|
||||
else
|
||||
button:Hide()
|
||||
@@ -361,7 +366,8 @@
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaPetActionBar_OnLoad(frame)
|
||||
function PetActionBar_OnLoad()
|
||||
local frame = this
|
||||
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
frame:RegisterEvent("PET_BAR_UPDATE")
|
||||
-- The drop grid (decision 1010). A counter rather than a flag, ref-verbatim
|
||||
@@ -372,21 +378,26 @@
|
||||
frame:RegisterEvent("PET_BAR_HIDEGRID")
|
||||
frame.showgrid = 0
|
||||
frame.clock = 0
|
||||
BenillaPetActionBar_OnEvent(frame)
|
||||
PetActionBar_OnEvent()
|
||||
end
|
||||
|
||||
-- The bar exists exactly while PetHasActionBar() does. Its show/hide re-fires the managed
|
||||
-- bottom-stack pass (UIParent.xml, decision 0272) — the pet term of that stack is what
|
||||
-- lifts the cast bar and the chat frame clear of it, which is the ref's own
|
||||
-- PetActionBarFrame.xml:205-210.
|
||||
function BenillaPetActionBar_OnEvent(frame, eventName)
|
||||
if eventName == "PET_BAR_SHOWGRID" then
|
||||
-- Declares nothing and reads the ambient `event`, exactly as the reference does
|
||||
-- (PetActionBarFrame.lua:33 / .xml's bare `PetActionBar_OnEvent();`). PetActionBar_OnLoad's
|
||||
-- own tail call therefore lands with `event` nil, which is the no-grid-edge path — the same
|
||||
-- thing the old explicit `eventName` argument did when omitted.
|
||||
function PetActionBar_OnEvent()
|
||||
local frame = this
|
||||
if event == "PET_BAR_SHOWGRID" then
|
||||
frame.showgrid = frame.showgrid + 1
|
||||
elseif eventName == "PET_BAR_HIDEGRID" and frame.showgrid > 0 then
|
||||
elseif event == "PET_BAR_HIDEGRID" and frame.showgrid > 0 then
|
||||
frame.showgrid = frame.showgrid - 1
|
||||
end
|
||||
if PetHasActionBar() then
|
||||
BenillaPetActionBar_Update()
|
||||
PetActionBar_Update()
|
||||
frame:Show()
|
||||
else
|
||||
frame:Hide()
|
||||
@@ -401,7 +412,7 @@
|
||||
frame.clock = frame.clock - BENILLA_PET_AUTOCAST_PERIOD
|
||||
end
|
||||
for i = 1, BENILLA_NUM_PET_ACTION_SLOTS do
|
||||
BenillaPetAutocast_Update(getglobal("BenillaPetActionButton" .. i), frame.clock)
|
||||
BenillaPetAutocast_Update(getglobal("PetActionButton" .. i), frame.clock)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -519,15 +530,15 @@
|
||||
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
|
||||
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
|
||||
<Scripts>
|
||||
<OnClick>BenillaPetActionButton_OnClick(self, arg1)</OnClick>
|
||||
<OnDragStart>BenillaPetActionButton_OnDragStart(self)</OnDragStart>
|
||||
<OnReceiveDrag>BenillaPetActionButton_OnReceiveDrag(self)</OnReceiveDrag>
|
||||
<OnEnter>BenillaPetActionButton_OnEnter(self)</OnEnter>
|
||||
<OnLeave>BenillaPetActionButton_OnLeave(self)</OnLeave>
|
||||
<OnClick>PetActionButton_OnClick(arg1)</OnClick>
|
||||
<OnDragStart>PetActionButton_OnDragStart()</OnDragStart>
|
||||
<OnReceiveDrag>PetActionButton_OnReceiveDrag()</OnReceiveDrag>
|
||||
<OnEnter>PetActionButton_OnEnter()</OnEnter>
|
||||
<OnLeave>PetActionButton_OnLeave()</OnLeave>
|
||||
</Scripts>
|
||||
</CheckButton>
|
||||
|
||||
<Frame name="BenillaPetActionBarFrame" hidden="true">
|
||||
<Frame name="PetActionBarFrame" hidden="true">
|
||||
<Size><AbsDimension x="509" y="43"/></Size>
|
||||
<!-- This anchor is the LOAD-TIME value only: UIParent_ManageFramePositions owns the real
|
||||
one and re-seats the frame on every pass (OnShow/OnHide below fire it), because the
|
||||
@@ -540,68 +551,68 @@
|
||||
the same arriving seated as it does arriving in 90 ms (the stance bar's own posture,
|
||||
decision 0270). -->
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="BenillaActionBar" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="36" y="97"/></Offset></Anchor>
|
||||
<Anchor point="TOPLEFT" relativeTo="MainMenuBar" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="36" y="97"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="BenillaSlidingActionBarTexture0" file="Interface\PetActionBar\UI-PetBar">
|
||||
<Texture name="SlidingActionBarTexture0" file="Interface\PetActionBar\UI-PetBar">
|
||||
<Size><AbsDimension x="256" y="44"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0.015625" bottom="0.359375"/>
|
||||
</Texture>
|
||||
<Texture name="BenillaSlidingActionBarTexture1" file="Interface\PetActionBar\UI-PetBar">
|
||||
<Texture name="SlidingActionBarTexture1" file="Interface\PetActionBar\UI-PetBar">
|
||||
<Size><AbsDimension x="184" y="44"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaSlidingActionBarTexture0" relativePoint="RIGHT"/></Anchors>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="SlidingActionBarTexture0" relativePoint="RIGHT"/></Anchors>
|
||||
<TexCoords left="0" right="0.71875" top="0.375" bottom="0.71875"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<CheckButton name="BenillaPetActionButton1" inherits="BenillaPetActionButtonTemplate">
|
||||
<CheckButton name="PetActionButton1" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="36" y="2"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaPetActionButton_OnLoad(self, 1)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaPetActionButton2" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaPetActionButton1" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="PetActionButton2" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="PetActionButton1" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaPetActionButton_OnLoad(self, 2)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaPetActionButton3" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaPetActionButton2" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="PetActionButton3" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="PetActionButton2" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaPetActionButton_OnLoad(self, 3)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaPetActionButton4" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaPetActionButton3" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="PetActionButton4" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="PetActionButton3" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaPetActionButton_OnLoad(self, 4)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaPetActionButton5" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaPetActionButton4" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="PetActionButton5" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="PetActionButton4" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaPetActionButton_OnLoad(self, 5)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaPetActionButton6" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaPetActionButton5" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="PetActionButton6" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="PetActionButton5" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaPetActionButton_OnLoad(self, 6)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<!-- The ref's own +7 here, not +8 (PetActionBarFrame.xml l.158-166). -->
|
||||
<CheckButton name="BenillaPetActionButton7" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaPetActionButton6" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="PetActionButton7" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="PetActionButton6" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaPetActionButton_OnLoad(self, 7)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaPetActionButton8" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaPetActionButton7" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="PetActionButton8" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="PetActionButton7" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaPetActionButton_OnLoad(self, 8)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaPetActionButton9" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaPetActionButton8" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="PetActionButton9" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="PetActionButton8" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaPetActionButton_OnLoad(self, 9)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaPetActionButton10" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaPetActionButton9" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="PetActionButton10" inherits="BenillaPetActionButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="PetActionButton9" relativePoint="RIGHT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaPetActionButton_OnLoad(self, 10)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaPetActionBar_OnLoad(self)</OnLoad>
|
||||
<OnEvent>BenillaPetActionBar_OnEvent(self, event)</OnEvent>
|
||||
<OnLoad>PetActionBar_OnLoad()</OnLoad>
|
||||
<OnEvent>PetActionBar_OnEvent()</OnEvent>
|
||||
<OnUpdate>BenillaPetActionBar_OnUpdate(self, arg1)</OnUpdate>
|
||||
<OnShow>UIParent_ManageFramePositions()</OnShow>
|
||||
<OnHide>UIParent_ManageFramePositions()</OnHide>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
**Its own top-level file, loaded straight after `CharacterFrame.xml`** — the SkillFrame.xml
|
||||
precedent: this engine reads no XML `parent=` attribute, so a page ships as a sibling pinned
|
||||
to `BenillaCharacterFrame`'s TOPLEFT by name, and `BENILLA_CHARACTERFRAME_SUBFRAMES` (which
|
||||
to `CharacterFrame`'s TOPLEFT by name, and `BENILLA_CHARACTERFRAME_SUBFRAMES` (which
|
||||
this page joins as entry 2) is what hides it when the whole window closes.
|
||||
|
||||
**Almost none of the page's Lua lives here, and that is the point.** The reference's own
|
||||
@@ -19,10 +19,10 @@
|
||||
l.470-473/l.541-544 — the pet page has no ranged rows, so the ref no-ops rather than
|
||||
nil-indexing). So this file adds only what is genuinely pet-only: the stats/resistances pair
|
||||
that reads the pet's own frames, the XP bar, loyalty, training points and the diet icon.
|
||||
`prefix` here is `"BenillaPet"`, so `prefix.."ArmorFrame"` resolves `BenillaPetArmorFrame`,
|
||||
exactly as `"BenillaCharacter"` resolves the sibling page's.
|
||||
`prefix` here is `"Pet"`, so `prefix.."ArmorFrame"` resolves `PetArmorFrame`,
|
||||
exactly as `"Character"` resolves the sibling page's.
|
||||
|
||||
**The tab is conditional, and `BenillaPetTab_Update` is the whole mechanism** (ref
|
||||
**The tab is conditional, and `PetTab_Update` is the whole mechanism** (ref
|
||||
`PetPaperDollFrame.lua:189-198`): no pet UI ⇒ tab 2 hides and tab 3 re-anchors onto tab 2's
|
||||
LEFT, closing the gap; a pet ⇒ tab 2 shows and tab 3 goes back to the -16 chain. Ours is the
|
||||
ref's arithmetic unchanged; only WHICH page tab 3 is differs (Skills here, Reputation there —
|
||||
@@ -55,7 +55,7 @@
|
||||
below dropped their existence guards. The ref's OWN `if ( UnitCreatureFamily("pet") )`
|
||||
guard stays — nil is still reachable (a template with no family; the creature query not
|
||||
yet answered) and a blank level line is the ref's answer to it.
|
||||
- `BenillaPetPaperDollFrame_SetResistances` reads the school ids from the shared
|
||||
- `PetPaperDollFrame_SetResistances` reads the school ids from the shared
|
||||
`BENILLA_MAGICRES_IDS` table where the ref reads each frame's own `id=` attr
|
||||
(`frame:GetID()`, ref l.112). Same five ids in the same order (6/2/3/4/5); the table is
|
||||
what the sibling page already uses, and one convention across the two pages beats
|
||||
@@ -90,12 +90,13 @@
|
||||
-- page's OnLoad carries the why: this engine's template expansion APPENDS children, so a
|
||||
-- re-declared $parentLabel makes a second anchorless FontString instead of merging).
|
||||
-- `SetTextStatusBarTextPrefix(…, XP)` is dropped with the bar's numerals (header).
|
||||
function BenillaPetPaperDollFrame_OnLoad(frame)
|
||||
getglobal("BenillaPetAttackFrameLabel"):SetText(ATTACK_COLON)
|
||||
getglobal("BenillaPetDamageFrameLabel"):SetText(DAMAGE_COLON)
|
||||
getglobal("BenillaPetAttackPowerFrameLabel"):SetText(ATTACK_POWER_COLON)
|
||||
getglobal("BenillaPetDefenseFrameLabel"):SetText(DEFENSE_COLON)
|
||||
getglobal("BenillaPetArmorFrameLabel"):SetText(ARMOR_COLON)
|
||||
function PetPaperDollFrame_OnLoad()
|
||||
local frame = this
|
||||
getglobal("PetAttackFrameLabel"):SetText(ATTACK_COLON)
|
||||
getglobal("PetDamageFrameLabel"):SetText(DAMAGE_COLON)
|
||||
getglobal("PetAttackPowerFrameLabel"):SetText(ATTACK_POWER_COLON)
|
||||
getglobal("PetDefenseFrameLabel"):SetText(DEFENSE_COLON)
|
||||
getglobal("PetArmorFrameLabel"):SetText(ARMOR_COLON)
|
||||
frame:RegisterEvent("PET_UI_UPDATE")
|
||||
frame:RegisterEvent("PET_BAR_UPDATE")
|
||||
frame:RegisterEvent("PET_UI_CLOSE")
|
||||
@@ -113,55 +114,56 @@
|
||||
frame:RegisterEvent("UNIT_DEFENSE")
|
||||
frame:RegisterEvent("UNIT_ATTACK")
|
||||
frame:RegisterEvent("UNIT_PET_TRAINING_POINTS")
|
||||
BenillaPetTab_Update()
|
||||
PetTab_Update()
|
||||
end
|
||||
|
||||
-- ref PetPaperDollFrame_OnEvent (l.31-48), all four arms. NOTE the shape: unlike the
|
||||
-- character page, this one does NOT gate on visibility — the first arm has to run while the
|
||||
-- page is HIDDEN, because that is what keeps the tab itself in step with whether a pet
|
||||
-- exists. Only the last arm (a plain "the pet's numbers moved") is unit-token gated.
|
||||
function BenillaPetPaperDollFrame_OnEvent(frame)
|
||||
function PetPaperDollFrame_OnEvent()
|
||||
local frame = this
|
||||
if event == "PET_UI_UPDATE" or event == "PET_BAR_UPDATE"
|
||||
or (event == "UNIT_PET" and arg1 == "player") then
|
||||
-- The pet left while its own page was open: close the window (ToggleCharacter on
|
||||
-- the visible page is the ref's own way of saying HideUIPanel), then re-fit the tab.
|
||||
if frame:IsVisible() and not HasPetUI() then
|
||||
ToggleCharacter("BenillaPetPaperDollFrame")
|
||||
ToggleCharacter("PetPaperDollFrame")
|
||||
end
|
||||
BenillaPetTab_Update()
|
||||
BenillaPetPaperDollFrame_Update()
|
||||
PetTab_Update()
|
||||
PetPaperDollFrame_Update()
|
||||
elseif event == "PET_UI_CLOSE" then
|
||||
if frame:IsVisible() then
|
||||
ToggleCharacter("BenillaPetPaperDollFrame")
|
||||
ToggleCharacter("PetPaperDollFrame")
|
||||
end
|
||||
BenillaPetTab_Update()
|
||||
PetTab_Update()
|
||||
elseif event == "UNIT_PET_EXPERIENCE" then
|
||||
BenillaPetExpBar_Update()
|
||||
PetExpBar_Update()
|
||||
elseif arg1 == "pet" then
|
||||
BenillaPetPaperDollFrame_Update()
|
||||
PetPaperDollFrame_Update()
|
||||
end
|
||||
end
|
||||
|
||||
-- ref PetPaperDollFrame_OnShow/_OnHide (l.50-60): the window's ONE name line is shared —
|
||||
-- the pet page borrows it and puts the player's back on the way out.
|
||||
function BenillaPetPaperDollFrame_OnShow()
|
||||
getglobal("BenillaCharacterNameText"):Hide()
|
||||
local petName = getglobal("BenillaPetNameText")
|
||||
function PetPaperDollFrame_OnShow()
|
||||
getglobal("CharacterNameText"):Hide()
|
||||
local petName = getglobal("PetNameText")
|
||||
petName:Show()
|
||||
petName:SetText(UnitName("pet"))
|
||||
BenillaPetPaperDollFrame_Update()
|
||||
PetPaperDollFrame_Update()
|
||||
end
|
||||
|
||||
function BenillaPetPaperDollFrame_OnHide()
|
||||
getglobal("BenillaCharacterNameText"):Show()
|
||||
getglobal("BenillaPetNameText"):Hide()
|
||||
function PetPaperDollFrame_OnHide()
|
||||
getglobal("CharacterNameText"):Show()
|
||||
getglobal("PetNameText"):Hide()
|
||||
end
|
||||
|
||||
-- ref PetPaperDollFrame_Update (l.62-94). The `hasPetUI` early-out is load-bearing, not
|
||||
-- defensive: every setter below reads the "pet" token, and with no pet those all answer the
|
||||
-- absent shape — repainting from it would blank a page the ref leaves showing its last state
|
||||
-- until the tab itself goes.
|
||||
function BenillaPetPaperDollFrame_Update()
|
||||
function PetPaperDollFrame_Update()
|
||||
local hasPetUI, canGainXP = HasPetUI()
|
||||
if not hasPetUI then return end
|
||||
-- ref l.67 `PetModelFrame:SetUnit("pet")` — no analogue: the app's `petdoll` booth
|
||||
@@ -174,34 +176,34 @@
|
||||
-- the binding twice for one answer.
|
||||
local family = UnitCreatureFamily("pet")
|
||||
if family then
|
||||
getglobal("BenillaPetLevelText"):SetText(
|
||||
getglobal("PetLevelText"):SetText(
|
||||
format(UNIT_LEVEL_TEMPLATE, UnitLevel("pet")) .. " " .. family)
|
||||
end
|
||||
getglobal("BenillaPetLoyaltyText"):SetText(GetPetLoyalty())
|
||||
BenillaPetExpBar_Update()
|
||||
BenillaPetPaperDollFrame_SetResistances()
|
||||
BenillaPetPaperDollFrame_SetStats()
|
||||
BenillaPaperDollFrame_SetDamage("pet", "BenillaPet")
|
||||
BenillaPaperDollFrame_SetRangedDamage("pet", "BenillaPet")
|
||||
BenillaPaperDollFrame_SetAttackPower("pet", "BenillaPet")
|
||||
BenillaPaperDollFrame_SetRangedAttackPower("pet", "BenillaPet")
|
||||
BenillaPaperDollFrame_SetArmor("pet", "BenillaPet")
|
||||
BenillaPaperDollFrame_SetAttackBothHands("pet", "BenillaPet")
|
||||
BenillaPaperDollFrame_SetDefense("pet", "BenillaPet")
|
||||
getglobal("PetLoyaltyText"):SetText(GetPetLoyalty())
|
||||
PetExpBar_Update()
|
||||
PetPaperDollFrame_SetResistances()
|
||||
PetPaperDollFrame_SetStats()
|
||||
PaperDollFrame_SetDamage("pet", "Pet")
|
||||
PaperDollFrame_SetRangedDamage("pet", "Pet")
|
||||
PaperDollFrame_SetAttackPower("pet", "Pet")
|
||||
PaperDollFrame_SetRangedAttackPower("pet", "Pet")
|
||||
PaperDollFrame_SetArmor("pet", "Pet")
|
||||
PaperDollFrame_SetAttackBothHands("pet", "Pet")
|
||||
PaperDollFrame_SetDefense("pet", "Pet")
|
||||
|
||||
-- The hunter-only block (ref l.83-93): the diet icon and the training-point line are
|
||||
-- the two things a warlock's minion never has. `canGainXP` is HasPetUI's second return.
|
||||
if canGainXP then
|
||||
getglobal("BenillaPetPaperDollPetInfo"):Show()
|
||||
getglobal("PetPaperDollPetInfo"):Show()
|
||||
local totalPoints, spent = GetPetTrainingPoints()
|
||||
local text = getglobal("BenillaPetTrainingPointText")
|
||||
local text = getglobal("PetTrainingPointText")
|
||||
text:SetText(totalPoints - spent)
|
||||
text:Show()
|
||||
getglobal("BenillaPetTrainingPointLabel"):Show()
|
||||
getglobal("PetTrainingPointLabel"):Show()
|
||||
else
|
||||
getglobal("BenillaPetPaperDollPetInfo"):Hide()
|
||||
getglobal("BenillaPetTrainingPointText"):Hide()
|
||||
getglobal("BenillaPetTrainingPointLabel"):Hide()
|
||||
getglobal("PetPaperDollPetInfo"):Hide()
|
||||
getglobal("PetTrainingPointText"):Hide()
|
||||
getglobal("PetTrainingPointLabel"):Hide()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -209,11 +211,11 @@
|
||||
-- version: the pet's has no resistance-quality subtext (no `RESISTANCE_TOOLTIP_SUBTEXT`
|
||||
-- line at all) and colours the NUMBER off its own sign rather than off pos-vs-neg
|
||||
-- magnitude — two real differences in the reference, transcribed rather than unified.
|
||||
function BenillaPetPaperDollFrame_SetResistances()
|
||||
function PetPaperDollFrame_SetResistances()
|
||||
for i = 1, BENILLA_NUM_PET_RESISTANCE_TYPES do
|
||||
local resistId = BENILLA_MAGICRES_IDS[i]
|
||||
local text = getglobal("BenillaPetMagicResText" .. i)
|
||||
local frame = getglobal("BenillaPetMagicResFrame" .. i)
|
||||
local text = getglobal("PetMagicResText" .. i)
|
||||
local frame = getglobal("PetMagicResFrame" .. i)
|
||||
local base, resistance, positive, negative = UnitResistance("pet", resistId)
|
||||
frame.tooltip = getglobal("RESISTANCE" .. resistId .. "_NAME")
|
||||
-- Resistances can be negative: red below zero, green above, plain white at zero.
|
||||
@@ -241,11 +243,11 @@
|
||||
-- _SetStats with the pet's own frame names and the "pet" token (the ref carries the same
|
||||
-- duplication; unifying them would mean inventing a prefix the reference's own
|
||||
-- `PetStatFrame`/`CharacterStatFrame` naming doesn't share).
|
||||
function BenillaPetPaperDollFrame_SetStats()
|
||||
function PetPaperDollFrame_SetStats()
|
||||
for i = 1, BENILLA_NUM_PET_STATS do
|
||||
local label = getglobal("BenillaPetStatFrame" .. i .. "Label")
|
||||
local text = getglobal("BenillaPetStatFrame" .. i .. "StatText")
|
||||
local frame = getglobal("BenillaPetStatFrame" .. i)
|
||||
local label = getglobal("PetStatFrame" .. i .. "Label")
|
||||
local text = getglobal("PetStatFrame" .. i .. "StatText")
|
||||
local frame = getglobal("PetStatFrame" .. i)
|
||||
local statName = getglobal("SPELL_STAT" .. (i - 1) .. "_NAME")
|
||||
label:SetText(statName .. ":")
|
||||
local stat, effectiveStat, posBuff, negBuff = UnitStat("pet", i)
|
||||
@@ -279,9 +281,9 @@
|
||||
|
||||
-- ref PetExpBar_Update (l.183-187). `min(0, currXP)` is the ref's own; XP can't be negative,
|
||||
-- so it is just 0 — quoted, not simplified.
|
||||
function BenillaPetExpBar_Update()
|
||||
function PetExpBar_Update()
|
||||
local currXP, nextXP = GetPetExperience()
|
||||
local bar = getglobal("BenillaPetPaperDollFrameExpBar")
|
||||
local bar = getglobal("PetPaperDollFrameExpBar")
|
||||
bar:SetMinMaxValues(math.min(0, currXP), nextXP)
|
||||
bar:SetValue(currXP)
|
||||
end
|
||||
@@ -289,27 +291,27 @@
|
||||
-- ref PetTab_Update (l.189-198) — the conditional tab, whole. The re-anchor is what keeps
|
||||
-- the row gapless: with tab 2 hidden, tab 3 sits at tab 2's own LEFT (i.e. exactly where
|
||||
-- tab 2 would have started) instead of -16 past its RIGHT.
|
||||
function BenillaPetTab_Update()
|
||||
local tab2 = getglobal("BenillaCharacterFrameTab2")
|
||||
local tab3 = getglobal("BenillaCharacterFrameTab3")
|
||||
function PetTab_Update()
|
||||
local tab2 = getglobal("CharacterFrameTab2")
|
||||
local tab3 = getglobal("CharacterFrameTab3")
|
||||
-- Nil-guarded like CharacterFrame_ShowSubFrame's own loop: a test subset may load this
|
||||
-- file without the window's tab row.
|
||||
if not tab2 or not tab3 then return end
|
||||
if not HasPetUI() then
|
||||
tab2:Hide()
|
||||
tab3:SetPoint("LEFT", "BenillaCharacterFrameTab2", "LEFT", 0, 0)
|
||||
tab3:SetPoint("LEFT", "CharacterFrameTab2", "LEFT", 0, 0)
|
||||
else
|
||||
tab2:Show()
|
||||
tab3:SetPoint("LEFT", "BenillaCharacterFrameTab2", "RIGHT", -16, 0)
|
||||
tab3:SetPoint("LEFT", "CharacterFrameTab2", "RIGHT", -16, 0)
|
||||
end
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
<Frame name="BenillaPetPaperDollFrame" enableMouse="true" parent="BenillaCharacterFrame" hidden="true" id="2">
|
||||
<Frame name="PetPaperDollFrame" enableMouse="true" parent="CharacterFrame" hidden="true" id="2">
|
||||
<!-- The real 384×512 page (ref l.4, `setAllPoints="true"`); pinned to the window's TOPLEFT
|
||||
by name, the SkillFrame.xml precedent (no cross-file `parent=` in this engine). -->
|
||||
<Size><AbsDimension x="384" y="512"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterFrame" relativePoint="TOPLEFT"/></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterFrame" relativePoint="TOPLEFT"/></Anchors>
|
||||
<HitRectInsets><AbsInset left="0" right="30" top="0" bottom="75"/></HitRectInsets>
|
||||
<Layers>
|
||||
<!-- The quadrant background (ref l.9-57): the "General" top halves every
|
||||
@@ -334,28 +336,28 @@
|
||||
</Texture>
|
||||
<!-- The pet's name (ref l.58-69), sitting exactly where the window's own player
|
||||
name line does — the two swap on this page's Show/Hide. -->
|
||||
<FontString name="BenillaPetNameText" inherits="GameFontHighlight">
|
||||
<FontString name="PetNameText" inherits="GameFontHighlight">
|
||||
<Size><AbsDimension x="109" y="16"/></Size>
|
||||
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="6" y="232"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<!-- "Level %d <Family>" (ref l.70-78) and the loyalty level under it (l.79-87). -->
|
||||
<FontString name="BenillaPetLevelText" inherits="GameFontNormalSmall">
|
||||
<FontString name="PetLevelText" inherits="GameFontNormalSmall">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeTo="BenillaPetNameText" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-6"/></Offset></Anchor>
|
||||
<Anchor point="TOP" relativeTo="PetNameText" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-6"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="BenillaPetLoyaltyText" inherits="GameFontNormalSmall">
|
||||
<FontString name="PetLoyaltyText" inherits="GameFontNormalSmall">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeTo="BenillaPetLevelText" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
|
||||
<Anchor point="TOP" relativeTo="PetLevelText" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<!-- Unspent training points (ref l.88-105) — shown for hunter pets only. -->
|
||||
<FontString name="BenillaPetTrainingPointText" inherits="GameFontHighlightSmall">
|
||||
<FontString name="PetTrainingPointText" inherits="GameFontHighlightSmall">
|
||||
<Anchors><Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-135" y="86"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<FontString name="BenillaPetTrainingPointLabel" inherits="GameFontNormalSmall" text="TRAINING_POINTS">
|
||||
<FontString name="PetTrainingPointLabel" inherits="GameFontNormalSmall" text="TRAINING_POINTS">
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativeTo="BenillaPetTrainingPointText" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor>
|
||||
<Anchor point="RIGHT" relativeTo="PetTrainingPointText" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
@@ -367,19 +369,19 @@
|
||||
<!-- No `enableMouse`: the ref's bar takes the mouse only to show its numerals on hover
|
||||
(l.166-172), and those are the deferral above — a mouse-enabled bar with nothing to
|
||||
show would just eat clicks. It comes back with the numerals. -->
|
||||
<StatusBar name="BenillaPetPaperDollFrameExpBar">
|
||||
<StatusBar name="PetPaperDollFrameExpBar">
|
||||
<Size><AbsDimension x="319" y="11"/></Size>
|
||||
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="23" y="105"/></Offset></Anchor></Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="BenillaPetPaperDollXPBar1" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Texture name="PetPaperDollXPBar1" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Size><AbsDimension x="160" y="13"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
||||
<TexCoords left="0.203125" right="0.8046875" top="0.2890625" bottom="0.33984375"/>
|
||||
</Texture>
|
||||
<Texture file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
|
||||
<Size><AbsDimension x="159" y="13"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaPetPaperDollXPBar1" relativePoint="RIGHT"/></Anchors>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="PetPaperDollXPBar1" relativePoint="RIGHT"/></Anchors>
|
||||
<TexCoords left="0.203125" right="0.8046875" top="0.2890625" bottom="0.33984375"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
@@ -390,14 +392,14 @@
|
||||
<!-- The ref's OnEvent is the TextStatusBar CVar hook plus PetExpBar_Update; only
|
||||
the latter has anything to do here, and the page's own UNIT_PET_EXPERIENCE
|
||||
arm already drives it, so no per-bar registration ships. -->
|
||||
<OnLoad>BenillaPetExpBar_Update()</OnLoad>
|
||||
<OnLoad>PetExpBar_Update()</OnLoad>
|
||||
</Scripts>
|
||||
</StatusBar>
|
||||
|
||||
<!-- The model pane (ref PlayerModel PetModelFrame, l.177-244): 318×224 at TOPLEFT
|
||||
(25,-78) — wider than the character page's 233 because there is no equipment column
|
||||
beside it. A plain Frame sampling the `petdoll` booth bake (header). -->
|
||||
<Frame name="BenillaPetModelFrame">
|
||||
<Frame name="PetModelFrame">
|
||||
<Size><AbsDimension x="318" y="224"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="25" y="-78"/></Offset></Anchor></Anchors>
|
||||
<Layers>
|
||||
@@ -423,26 +425,26 @@
|
||||
<!-- Rotate buttons (ref l.201-242): 35×35. NOTE the ref anchors the left one to
|
||||
the PAGE, not to the pane (l.206) — (23,-76), two pixels up and left of the
|
||||
pane's own corner — and the right one chains off its TOPRIGHT. -->
|
||||
<Button name="BenillaPetModelFrameRotateLeftButton">
|
||||
<Button name="PetModelFrameRotateLeftButton">
|
||||
<Size><AbsDimension x="35" y="35"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaPetPaperDollFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="23" y="-76"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="PetPaperDollFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="23" y="-76"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-RotationLeft-Button-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-RotationLeft-Button-Down"/>
|
||||
<HighlightTexture file="Interface\Buttons\ButtonHilight-Round" alphaMode="ADD"/>
|
||||
<Scripts>
|
||||
<OnLoad>self:RegisterForClicks("LeftButtonDown", "LeftButtonUp")</OnLoad>
|
||||
<OnClick>BenillaPaperDollModel_RotateLeft(getglobal("BenillaPetModelFrame"))</OnClick>
|
||||
<OnClick>BenillaPaperDollModel_RotateLeft(getglobal("PetModelFrame"))</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaPetModelFrameRotateRightButton">
|
||||
<Button name="PetModelFrameRotateRightButton">
|
||||
<Size><AbsDimension x="35" y="35"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaPetModelFrameRotateLeftButton" relativePoint="TOPRIGHT"/></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="PetModelFrameRotateLeftButton" relativePoint="TOPRIGHT"/></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-RotationRight-Button-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-RotationRight-Button-Down"/>
|
||||
<HighlightTexture file="Interface\Buttons\ButtonHilight-Round" alphaMode="ADD"/>
|
||||
<Scripts>
|
||||
<OnLoad>self:RegisterForClicks("LeftButtonDown", "LeftButtonUp")</OnLoad>
|
||||
<OnClick>BenillaPaperDollModel_RotateRight(getglobal("BenillaPetModelFrame"))</OnClick>
|
||||
<OnClick>BenillaPaperDollModel_RotateRight(getglobal("PetModelFrame"))</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
@@ -467,10 +469,10 @@
|
||||
(its tooltip lists what it eats), not a happiness readout; the happiness face that
|
||||
does move lives on the pet unit frame (decision 1005/0990). Shown for hunter pets
|
||||
only. -->
|
||||
<Frame name="BenillaPetPaperDollPetInfo">
|
||||
<Frame name="PetPaperDollPetInfo">
|
||||
<Size><AbsDimension x="24" y="23"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="BenillaPetModelFrameRotateLeftButton" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor>
|
||||
<Anchor point="TOPLEFT" relativeTo="PetModelFrameRotateLeftButton" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
@@ -505,7 +507,7 @@
|
||||
|
||||
<!-- The page's own Close button (ref PetPaperDollCloseButton, l.276-292) — the panel
|
||||
button art/size/font inline, since no UIPanelButtonTemplate ships (header). -->
|
||||
<Button name="BenillaPetPaperDollCloseButton" text="CLOSE">
|
||||
<Button name="PetPaperDollCloseButton" text="CLOSE">
|
||||
<Size><AbsDimension x="80" y="22"/></Size>
|
||||
<Anchors><Anchor point="CENTER" relativePoint="TOPLEFT"><Offset><AbsDimension x="305" y="-422"/></Offset></Anchor></Anchors>
|
||||
<NormalFont inherits="GameFontNormal"/>
|
||||
@@ -519,7 +521,7 @@
|
||||
cross-file parenting, so the page is not really the window's child (header).
|
||||
CLOSE is the ref's own literal `text="CLOSE"` attribute — the GlobalStrings
|
||||
key, resolved by the loader's text lookup like every other one here. -->
|
||||
<OnClick>HideUIPanel(getglobal("BenillaCharacterFrame"))</OnClick>
|
||||
<OnClick>HideUIPanel(getglobal("CharacterFrame"))</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
@@ -527,7 +529,7 @@
|
||||
(67,-300), two 114×78 stat-background plates side by side. The pet's plate is the
|
||||
SIMPLE two-column one — the character page's is three stacked 3-piece columns
|
||||
because it also carries a ranged block, which a pet has none of. -->
|
||||
<Frame name="BenillaPetAttributesFrame">
|
||||
<Frame name="PetAttributesFrame">
|
||||
<Size><AbsDimension x="230" y="78"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="67" y="-300"/></Offset></Anchor></Anchors>
|
||||
<Layers>
|
||||
@@ -547,51 +549,51 @@
|
||||
<Frames>
|
||||
<!-- The 5 primaries (ref l.335-419): TOPLEFT (6,-5), chained BOTTOMLEFT with a
|
||||
1px gap. Labels are the template's $parentLabel, filled by _SetStats. -->
|
||||
<Frame name="BenillaPetStatFrame1" inherits="BenillaStatRowTemplate">
|
||||
<Frame name="PetStatFrame1" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="6" y="-5"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>self:EnableMouse(true)</OnLoad>
|
||||
<OnEnter>BenillaPaperDollStatTooltip(self, "STRENGTH", "pet")</OnEnter>
|
||||
<OnEnter>PaperDollStatTooltip("pet", "STRENGTH")</OnEnter>
|
||||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
<Frame name="BenillaPetStatFrame2" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaPetStatFrame1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Frame name="PetStatFrame2" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="PetStatFrame1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>self:EnableMouse(true)</OnLoad>
|
||||
<OnEnter>BenillaPaperDollStatTooltip(self, "AGILITY", "pet")</OnEnter>
|
||||
<OnEnter>PaperDollStatTooltip("pet", "AGILITY")</OnEnter>
|
||||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
<Frame name="BenillaPetStatFrame3" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaPetStatFrame2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Frame name="PetStatFrame3" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="PetStatFrame2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>self:EnableMouse(true)</OnLoad>
|
||||
<OnEnter>BenillaPaperDollStatTooltip(self, "STAMINA", "pet")</OnEnter>
|
||||
<OnEnter>PaperDollStatTooltip("pet", "STAMINA")</OnEnter>
|
||||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
<Frame name="BenillaPetStatFrame4" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaPetStatFrame3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Frame name="PetStatFrame4" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="PetStatFrame3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>self:EnableMouse(true)</OnLoad>
|
||||
<OnEnter>BenillaPaperDollStatTooltip(self, "INTELLECT", "pet")</OnEnter>
|
||||
<OnEnter>PaperDollStatTooltip("pet", "INTELLECT")</OnEnter>
|
||||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
<Frame name="BenillaPetStatFrame5" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaPetStatFrame4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Frame name="PetStatFrame5" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="PetStatFrame4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>self:EnableMouse(true)</OnLoad>
|
||||
<OnEnter>BenillaPaperDollStatTooltip(self, "SPIRIT", "pet")</OnEnter>
|
||||
<OnEnter>PaperDollStatTooltip("pet", "SPIRIT")</OnEnter>
|
||||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
<!-- The right-hand column (ref l.420-514): attack rating, attack power, damage,
|
||||
defense, armor — the second plate's own chain, rooted at (122,-5) of the
|
||||
attributes frame. -->
|
||||
<Frame name="BenillaPetAttackFrame" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaPetAttributesFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="122" y="-5"/></Offset></Anchor></Anchors>
|
||||
<Frame name="PetAttackFrame" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="PetAttributesFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="122" y="-5"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>self:EnableMouse(true)</OnLoad>
|
||||
<!-- ref l.429-433: the pet's attack row shows the BARE tooltip title, with
|
||||
@@ -605,8 +607,8 @@
|
||||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
<Frame name="BenillaPetAttackPowerFrame" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaPetAttackFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Frame name="PetAttackPowerFrame" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="PetAttackFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>self:EnableMouse(true)</OnLoad>
|
||||
<OnEnter>
|
||||
@@ -618,19 +620,19 @@
|
||||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
<Frame name="BenillaPetDamageFrame" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaPetAttackPowerFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Frame name="PetDamageFrame" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="PetAttackPowerFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>self:EnableMouse(true)</OnLoad>
|
||||
<!-- ref l.468-470: the pet's damage row reuses the CHARACTER damage
|
||||
hover verbatim — it reads `self.damage`/`self.dps`, which
|
||||
_SetDamage filled with the pet's numbers. -->
|
||||
<OnEnter>BenillaCharacterDamageFrame_OnEnter(self)</OnEnter>
|
||||
<OnEnter>CharacterDamageFrame_OnEnter()</OnEnter>
|
||||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
<Frame name="BenillaPetDefenseFrame" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaPetDamageFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Frame name="PetDefenseFrame" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="PetDamageFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>self:EnableMouse(true)</OnLoad>
|
||||
<OnEnter>
|
||||
@@ -641,8 +643,8 @@
|
||||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
<Frame name="BenillaPetArmorFrame" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaPetDefenseFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Frame name="PetArmorFrame" inherits="BenillaStatRowTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="PetDefenseFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>self:EnableMouse(true)</OnLoad>
|
||||
<OnEnter>
|
||||
@@ -662,72 +664,72 @@
|
||||
297, because the pet page's wider model pane pushes it out. The five icons carry
|
||||
their own texcoords into the shared ResistanceIcons sheet, in the ref's own school
|
||||
order (arcane/fire/nature/frost/shadow = BENILLA_MAGICRES_IDS). -->
|
||||
<Frame name="BenillaPetResistanceFrame">
|
||||
<Frame name="PetResistanceFrame">
|
||||
<Size><AbsDimension x="32" y="160"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" relativeTo="BenillaPetPaperDollFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="347" y="-77"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT" relativeTo="PetPaperDollFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="347" y="-77"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Frames>
|
||||
<Frame name="BenillaPetMagicResFrame1" inherits="BenillaMagicResistanceFrameTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="BenillaPetResistanceFrame" relativePoint="TOP"/></Anchors>
|
||||
<Frame name="PetMagicResFrame1" inherits="BenillaMagicResistanceFrameTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="PetResistanceFrame" relativePoint="TOP"/></Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture file="Interface\PaperDollInfoFrame\UI-Character-ResistanceIcons">
|
||||
<TexCoords left="0" right="1.0" top="0.2265625" bottom="0.33984375"/>
|
||||
</Texture>
|
||||
<FontString name="BenillaPetMagicResText1" inherits="GameFontHighlightSmall" text="X">
|
||||
<FontString name="PetMagicResText1" inherits="GameFontHighlightSmall" text="X">
|
||||
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Frame>
|
||||
<Frame name="BenillaPetMagicResFrame2" inherits="BenillaMagicResistanceFrameTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="BenillaPetMagicResFrame1" relativePoint="BOTTOM"/></Anchors>
|
||||
<Frame name="PetMagicResFrame2" inherits="BenillaMagicResistanceFrameTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="PetMagicResFrame1" relativePoint="BOTTOM"/></Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture file="Interface\PaperDollInfoFrame\UI-Character-ResistanceIcons">
|
||||
<TexCoords left="0" right="1.0" top="0" bottom="0.11328125"/>
|
||||
</Texture>
|
||||
<FontString name="BenillaPetMagicResText2" inherits="GameFontHighlightSmall" text="X">
|
||||
<FontString name="PetMagicResText2" inherits="GameFontHighlightSmall" text="X">
|
||||
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Frame>
|
||||
<Frame name="BenillaPetMagicResFrame3" inherits="BenillaMagicResistanceFrameTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="BenillaPetMagicResFrame2" relativePoint="BOTTOM"/></Anchors>
|
||||
<Frame name="PetMagicResFrame3" inherits="BenillaMagicResistanceFrameTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="PetMagicResFrame2" relativePoint="BOTTOM"/></Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture file="Interface\PaperDollInfoFrame\UI-Character-ResistanceIcons">
|
||||
<TexCoords left="0" right="1.0" top="0.11328125" bottom="0.2265625"/>
|
||||
</Texture>
|
||||
<FontString name="BenillaPetMagicResText3" inherits="GameFontHighlightSmall" text="X">
|
||||
<FontString name="PetMagicResText3" inherits="GameFontHighlightSmall" text="X">
|
||||
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Frame>
|
||||
<Frame name="BenillaPetMagicResFrame4" inherits="BenillaMagicResistanceFrameTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="BenillaPetMagicResFrame3" relativePoint="BOTTOM"/></Anchors>
|
||||
<Frame name="PetMagicResFrame4" inherits="BenillaMagicResistanceFrameTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="PetMagicResFrame3" relativePoint="BOTTOM"/></Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture file="Interface\PaperDollInfoFrame\UI-Character-ResistanceIcons">
|
||||
<TexCoords left="0" right="1.0" top="0.33984375" bottom="0.453125"/>
|
||||
</Texture>
|
||||
<FontString name="BenillaPetMagicResText4" inherits="GameFontHighlightSmall" text="X">
|
||||
<FontString name="PetMagicResText4" inherits="GameFontHighlightSmall" text="X">
|
||||
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Frame>
|
||||
<Frame name="BenillaPetMagicResFrame5" inherits="BenillaMagicResistanceFrameTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="BenillaPetMagicResFrame4" relativePoint="BOTTOM"/></Anchors>
|
||||
<Frame name="PetMagicResFrame5" inherits="BenillaMagicResistanceFrameTemplate">
|
||||
<Anchors><Anchor point="TOP" relativeTo="PetMagicResFrame4" relativePoint="BOTTOM"/></Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture file="Interface\PaperDollInfoFrame\UI-Character-ResistanceIcons">
|
||||
<TexCoords left="0" right="1.0" top="0.453125" bottom="0.56640625"/>
|
||||
</Texture>
|
||||
<FontString name="BenillaPetMagicResText5" inherits="GameFontHighlightSmall" text="X">
|
||||
<FontString name="PetMagicResText5" inherits="GameFontHighlightSmall" text="X">
|
||||
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
@@ -737,10 +739,10 @@
|
||||
</Frame>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaPetPaperDollFrame_OnLoad(self)</OnLoad>
|
||||
<OnEvent>BenillaPetPaperDollFrame_OnEvent(self)</OnEvent>
|
||||
<OnShow>BenillaPetPaperDollFrame_OnShow()</OnShow>
|
||||
<OnHide>BenillaPetPaperDollFrame_OnHide()</OnHide>
|
||||
<OnLoad>PetPaperDollFrame_OnLoad()</OnLoad>
|
||||
<OnEvent>PetPaperDollFrame_OnEvent()</OnEvent>
|
||||
<OnShow>PetPaperDollFrame_OnShow()</OnShow>
|
||||
<OnHide>PetPaperDollFrame_OnHide()</OnHide>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -24,14 +24,18 @@
|
||||
getglobal(name), and the ref's own Lua below reaches its rows by
|
||||
getglobal("QuestTimer"..i.."Text"). Prefixing would silently unseat the frame and break
|
||||
the row lookup at once.
|
||||
- Lua 5.0 `arg`/`arg.n` varargs replaced with select("#", ...)/select(i, ...) —
|
||||
LUA_COMPAT_VARARG isn't shipped (TalentFrame.xml / WorldMapFrame.xml's precedent).
|
||||
- Lua 5.0 `arg`/`arg.n` varargs written as select("#", ...)/select(i, ...), the 5.1-native
|
||||
spelling and this tree's house style (TalentFrame.xml / WorldMapFrame.xml's precedent).
|
||||
- The ref's XML anchor (TOPRIGHT of MinimapCluster +10,0) is transcribed and, like every
|
||||
managed HUD frame, DEAD ON ARRIVAL: UIParent_ManageFramePositions re-seats it at
|
||||
-CONTAINER_OFFSET_X on every pass. Kept verbatim anyway so the file reads as the ref's.
|
||||
- `toplevel="true"` is dropped: it is the ref's raise-on-click behaviour for movable
|
||||
dialogs, which this engine has no verb for and this frame never uses (it is not movable
|
||||
and its click opens the quest log rather than raising anything).
|
||||
- `toplevel="true"` is dropped, and the reason given here was half wrong: the raise's real
|
||||
triggers are **Show**, drag-start and mouse-down, not "raise-on-click for movable dialogs"
|
||||
(wow-re `ui/scratch/toplevel-raise.md`). The verb now exists (`script::object::toplevel`),
|
||||
so the standing reason is only the second one — this frame is a managed HUD strip that
|
||||
UIParent_ManageFramePositions re-seats and that overlaps nothing in its own stratum, so
|
||||
the occlusion gate would decline every raise anyway. Re-adding it is one word if that
|
||||
stops being true.
|
||||
- QuestTimerFrame_OnShow/OnHide call the manage pass, as the ref does — that is what lets
|
||||
the durability guy and the quest tracker drop below this window when a timer appears.
|
||||
- The ref's own commented-out QuestTimerFrame_UpdatePosition (its pre-manage-pass
|
||||
@@ -112,7 +116,7 @@
|
||||
if not index then
|
||||
return
|
||||
end
|
||||
ShowUIPanel(getglobal("BenillaQuestLogFrame"))
|
||||
ShowUIPanel(getglobal("QuestLogFrame"))
|
||||
SelectQuestLogEntry(index)
|
||||
BenillaQuestLogFrame_Update()
|
||||
end
|
||||
|
||||
@@ -64,6 +64,25 @@
|
||||
-- repaint calls _Update below, which SetValue()s the thumb back to offset*itemHeight — a value
|
||||
-- that maps to the SAME offset, so this handler no-ops and the cycle stops after one hop (the
|
||||
-- Slider's own SetValue change-gate stops the value side, decision 0250).
|
||||
--
|
||||
-- THE SECOND HALF IS FOR ADDONS, and it is the reference's own wiring. The ref's
|
||||
-- `UIPanelScrollBarTemplate` routes its value through the ScrollFrame it rides
|
||||
-- (`<OnValueChanged>this:GetParent():SetVerticalScroll(arg1)</OnValueChanged>`,
|
||||
-- ref-UIPanelTemplates.xml l.156-158), and that frame's `<OnVerticalScroll>` is where an
|
||||
-- addon puts `FauxScrollFrame_OnVerticalScroll(itemHeight, MyUpdate)`. Our OWN faux frames
|
||||
-- are plain `<Frame>`s (the 0251 divergence in the header above), so they have no
|
||||
-- SetVerticalScroll and this line never fires for them — but an addon's is declared
|
||||
-- `<ScrollFrame ... inherits="FauxScrollFrameTemplate">`, and the instance's own tag wins
|
||||
-- over the template's on inherit (framexml.rs `merge`), so it really is one. Duck-typed for
|
||||
-- exactly that reason.
|
||||
--
|
||||
-- GAP, stated where it bites: `SetVerticalScroll` clamps into `[0, GetVerticalScrollRange()]`
|
||||
-- (scrollframe.rs), and that range comes from the frame's SCROLL CHILD — which the ref's
|
||||
-- `FauxScrollFrameTemplate` declares as `<ScrollChild><Frame name="$parentScrollChildFrame">`
|
||||
-- and our XML loader has no `<ScrollChild>` element for. Until it does, an addon frame's
|
||||
-- range reads 0, so the handler fires with arg1 = 0 and its list stays at the top. The
|
||||
-- offset our own kit computes above is unaffected — it comes straight off the Slider, which
|
||||
-- is the whole point of 0251.
|
||||
function BenillaFauxScrollBar_OnValueChanged(scrollBar, value)
|
||||
local frame = scrollBar:GetParent()
|
||||
local step = scrollBar:GetValueStep()
|
||||
@@ -74,25 +93,53 @@
|
||||
if frame.updateFunc then frame.updateFunc() end
|
||||
end
|
||||
BenillaScrollBar_UpdateArrows(scrollBar)
|
||||
if frame.SetVerticalScroll then frame:SetVerticalScroll(value) end
|
||||
end
|
||||
|
||||
-- OnLoad: a fresh faux frame is at the top with an empty range (ref ScrollFrame_OnLoad).
|
||||
function BenillaFauxScrollFrame_OnLoad(frame)
|
||||
frame.offset = 0
|
||||
local scrollBar = getglobal(frame:GetName() .. "ScrollBar")
|
||||
-- OnLoad: a fresh scroll frame is at the top with an empty range and both arrows greyed
|
||||
-- (ref UIPanelTemplates.lua l.244-252, verbatim but for the one added line). The ref's own
|
||||
-- name, because this is the ref's own function: `FauxScrollFrameTemplate` inherits
|
||||
-- `UIPanelScrollFrameTemplate`, whose `<OnLoad>` is exactly this.
|
||||
--
|
||||
-- The added line is `SetValueStep(1)`: our bar's step is also its ARROW/WHEEL step
|
||||
-- (BenillaScrollBar_Step), so a bar that has not been through an _Update yet would step by
|
||||
-- nothing. The ref has no such coupling — its arrows move by half the bar's height.
|
||||
function ScrollFrame_OnLoad()
|
||||
getglobal(this:GetName() .. "ScrollBarScrollDownButton"):Disable()
|
||||
getglobal(this:GetName() .. "ScrollBarScrollUpButton"):Disable()
|
||||
|
||||
local scrollBar = getglobal(this:GetName() .. "ScrollBar")
|
||||
scrollBar:SetMinMaxValues(0, 0)
|
||||
scrollBar:SetValueStep(1)
|
||||
scrollBar:SetValue(0)
|
||||
this.offset = 0
|
||||
end
|
||||
|
||||
-- The offset getters/setters the owner's list code reads (ref FauxScrollFrame_GetOffset /
|
||||
-- _SetOffset). _SetOffset syncs the thumb too, so a programmatic reset (top-of-list on open)
|
||||
-- moves the bar.
|
||||
function BenillaFauxScrollFrame_GetOffset(frame)
|
||||
-- The frame's `<OnVerticalScroll>` body, as the reference writes it (UIPanelTemplates.lua
|
||||
-- l.228-233, verbatim): the frame scrolled, so seat the bar at the new pixel value, round it
|
||||
-- to a row, and repaint. `this`/`arg1` are the handler globals our dispatcher sets
|
||||
-- (script/event.rs `invoke_with_globals`), so the ref's body runs unchanged.
|
||||
--
|
||||
-- Nothing in benilla's own FrameXML calls this — our six faux lists take the offset straight
|
||||
-- off the Slider (BenillaFauxScrollBar_OnValueChanged above). It is here because it is the
|
||||
-- shape 27 corpus addons write into their own XML, and it is reached through the
|
||||
-- SetVerticalScroll passthrough there, whose one gap is documented at that spot.
|
||||
function FauxScrollFrame_OnVerticalScroll(itemHeight, updateFunction)
|
||||
local scrollbar = getglobal(this:GetName() .. "ScrollBar")
|
||||
scrollbar:SetValue(arg1)
|
||||
this.offset = floor((arg1 / itemHeight) + 0.5)
|
||||
updateFunction()
|
||||
end
|
||||
|
||||
-- The offset getters/setters the owner's list code reads (ref UIPanelTemplates.lua
|
||||
-- l.235-241). _SetOffset diverges by one line, deliberately: the ref writes `frame.offset`
|
||||
-- and leaves the thumb where it was, which is a latent desync until the next _Update; ours
|
||||
-- syncs the bar, so a programmatic reset (top-of-list on open) moves it there and then.
|
||||
function FauxScrollFrame_GetOffset(frame)
|
||||
return frame.offset or 0
|
||||
end
|
||||
|
||||
function BenillaFauxScrollFrame_SetOffset(frame, offset)
|
||||
function FauxScrollFrame_SetOffset(frame, offset)
|
||||
frame.offset = offset
|
||||
local scrollBar = getglobal(frame:GetName() .. "ScrollBar")
|
||||
scrollBar:SetValue(offset * scrollBar:GetValueStep())
|
||||
@@ -152,27 +199,93 @@
|
||||
end
|
||||
end
|
||||
|
||||
-- Reconcile the bar to a freshly-sized list (ref FauxScrollFrame_Update): clamp the offset into
|
||||
-- range, set the pixel range + step, seat the thumb at the current offset, and show the bar only
|
||||
-- when the list overflows the visible rows. Called by the owner's list-repaint each pass.
|
||||
function BenillaFauxScrollFrame_Update(frame, numItems, numToDisplay, itemHeight)
|
||||
local scrollBar = getglobal(frame:GetName() .. "ScrollBar")
|
||||
-- Reconcile the bar to a freshly-sized list — the reference's own `FauxScrollFrame_Update`
|
||||
-- (UIPanelTemplates.lua l.160-226), whose signature and return value are transcribed whole
|
||||
-- because 28 corpus addons call it with all ten arguments and read the result:
|
||||
--
|
||||
-- frame, numItems, numToDisplay, valueStep the list itself (our four)
|
||||
-- button, smallWidth, bigWidth a `<name>1..numToDisplay` row family to
|
||||
-- NARROW when the bar takes its gutter
|
||||
-- highlightFrame, smallHighlightWidth, and the moving highlight over them
|
||||
-- bigHighlightWidth
|
||||
-- → showScrollBar 1 when the bar is up, nil when it is not
|
||||
--
|
||||
-- `valueStep` is what benilla called `itemHeight` before this landed; same number, the ref's
|
||||
-- name. The shrink/widen tail is pure ref and no benilla window passes it — our lists are
|
||||
-- laid out to their with-bar width from the start.
|
||||
--
|
||||
-- TWO DELIBERATE DIVERGENCES from the ref body, both named where they are:
|
||||
--
|
||||
-- 1 · The ref gates its whole middle section on `frame:IsVisible()` right after showing or
|
||||
-- hiding the frame — reading back a state it just wrote. That is a quirk, not a
|
||||
-- mechanism: it is true exactly when `showScrollBar` is, EXCEPT when an ancestor is
|
||||
-- hidden, in which case the range/step/value silently do not get set and the bar is
|
||||
-- stale the next time the window opens. benilla repaints lists while their window is
|
||||
-- closed (a feed arriving is not a click), so we gate on `showScrollBar` — what the ref
|
||||
-- means — and the offset clamp runs unconditionally.
|
||||
-- 2 · The bar is shown/hidden through BenillaScrollBar_SetShown, not by the frame alone:
|
||||
-- ours also carries the TROUGH (see its own note above; the ref has no trough in this
|
||||
-- kit at all). The frame's own Show/Hide is kept as well, because it is what an addon
|
||||
-- reads back — and because for us it is the same pixels either way, the bar being the
|
||||
-- only thing a faux frame contains.
|
||||
function FauxScrollFrame_Update(frame, numItems, numToDisplay, valueStep, button, smallWidth, bigWidth, highlightFrame, smallHighlightWidth, bigHighlightWidth)
|
||||
local frameName = frame:GetName()
|
||||
local scrollBar = getglobal(frameName .. "ScrollBar")
|
||||
|
||||
local maxOffset = numItems - numToDisplay
|
||||
if maxOffset < 0 then maxOffset = 0 end
|
||||
if not frame.offset or frame.offset > maxOffset then frame.offset = maxOffset end
|
||||
if frame.offset < 0 then frame.offset = 0 end
|
||||
|
||||
scrollBar:SetValueStep(itemHeight)
|
||||
scrollBar:SetMinMaxValues(0, maxOffset * itemHeight)
|
||||
scrollBar:SetValue(frame.offset * itemHeight)
|
||||
local showScrollBar
|
||||
if numItems > numToDisplay then showScrollBar = 1 end
|
||||
|
||||
scrollBar:SetValueStep(valueStep)
|
||||
scrollBar:SetMinMaxValues(0, maxOffset * valueStep)
|
||||
scrollBar:SetValue(frame.offset * valueStep)
|
||||
BenillaScrollBar_UpdateArrows(scrollBar)
|
||||
|
||||
BenillaScrollBar_SetShown(scrollBar, numItems > numToDisplay)
|
||||
if showScrollBar then frame:Show() else frame:Hide() end
|
||||
BenillaScrollBar_SetShown(scrollBar, showScrollBar)
|
||||
|
||||
-- The ref sizes the scroll child to the whole list (l.179-191) — which is what gives a
|
||||
-- real ScrollFrame its scroll range. GUARDED, because our loader has no `<ScrollChild>`
|
||||
-- element, so `$parentScrollChildFrame` exists only if the owner declared one itself.
|
||||
-- (This is the same gap BenillaFauxScrollBar_OnValueChanged documents; closing it is a
|
||||
-- loader change, not a FrameXML one.)
|
||||
local scrollChildFrame = getglobal(frameName .. "ScrollChildFrame")
|
||||
if scrollChildFrame then
|
||||
if numItems > 0 then
|
||||
scrollChildFrame:SetHeight(numItems * valueStep)
|
||||
scrollChildFrame:Show()
|
||||
else
|
||||
scrollChildFrame:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
-- Shrink because the bar is shown / widen because it is hidden (ref l.205-223).
|
||||
if showScrollBar then
|
||||
if highlightFrame then highlightFrame:SetWidth(smallHighlightWidth) end
|
||||
if button then
|
||||
for i = 1, numToDisplay do getglobal(button .. i):SetWidth(smallWidth) end
|
||||
end
|
||||
else
|
||||
if highlightFrame then highlightFrame:SetWidth(bigHighlightWidth) end
|
||||
if button then
|
||||
for i = 1, numToDisplay do getglobal(button .. i):SetWidth(bigWidth) end
|
||||
end
|
||||
end
|
||||
|
||||
return showScrollBar
|
||||
end
|
||||
|
||||
-- A wheel spin over the list → one row (WoW convention: +1 up, −1 down). Called from the
|
||||
-- owner window's OnMouseWheel (the engine bubbles the spin up the parent chain, so the handler
|
||||
-- lives on the window, not a child — TrainerFrame.xml's own note).
|
||||
--
|
||||
-- KEEPS the `Benilla` prefix on purpose: the reference has no faux-scroll wheel function to
|
||||
-- take the name of. Its `ScrollFrameTemplate_OnMouseWheel` is the REAL-scroll one and moves
|
||||
-- by half the bar's height; its faux owners each write a wheel body of their own inline.
|
||||
function BenillaFauxScrollFrame_OnMouseWheel(frame, delta)
|
||||
local scrollBar = getglobal(frame:GetName() .. "ScrollBar")
|
||||
BenillaScrollBar_Step(scrollBar, -delta)
|
||||
@@ -388,7 +501,7 @@
|
||||
UIPanelScrollFrameTemplate's scrollbar anchors). An owner instantiates this and, in its own
|
||||
OnLoad, sets `frame.updateFunc` to its list-repaint.
|
||||
-->
|
||||
<Frame name="BenillaFauxScrollFrameTemplate" virtual="true">
|
||||
<Frame name="FauxScrollFrameTemplate" virtual="true">
|
||||
<Frames>
|
||||
<Slider name="$parentScrollBar" inherits="BenillaScrollBarTemplate">
|
||||
<Anchors>
|
||||
@@ -398,7 +511,7 @@
|
||||
</Slider>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaFauxScrollFrame_OnLoad(self)</OnLoad>
|
||||
<OnLoad>ScrollFrame_OnLoad()</OnLoad>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
|
||||
@@ -8,20 +8,20 @@
|
||||
`Expand`/`CollapseSkillHeader`/`SetSelectedSkill`/`GetSelectedSkill`, decision 0437's skills
|
||||
seam, `crates/benilla-ui/src/script/skills.rs`, built alongside this file).
|
||||
|
||||
A CHILD PAGE of `BenillaCharacterFrame` (ref: `parent="CharacterFrame"`, `id="4"`), shown/
|
||||
hidden by `CharacterFrame_ShowSubFrame` exactly like `BenillaPaperDollFrame` — but NOT a
|
||||
A CHILD PAGE of `CharacterFrame` (ref: `parent="CharacterFrame"`, `id="4"`), shown/
|
||||
hidden by `CharacterFrame_ShowSubFrame` exactly like `PaperDollFrame` — but NOT a
|
||||
physically nested `<Frame>` the way PaperDollFrame is: PaperDollFrame is declared INSIDE
|
||||
CharacterFrame.xml's own `<Frames>` tree (a real engine parent/child), while this page is a
|
||||
SEPARATE file's top-level `<Frame>` (CharacterFrame.xml is shared-tree with another session
|
||||
this pass, so only its tab machinery is touched here — not its frame tree). `parent=` is
|
||||
transcribed for fidelity but is a NO-OP in this engine (grepped `benilla-ui/src/loader`: the
|
||||
XML `parent` attribute is read nowhere — only physical `<Frames>` nesting creates a real
|
||||
parent). Position instead rides `<Anchors relativeTo="BenillaCharacterFrame">` (a NAMED
|
||||
parent). Position instead rides `<Anchors relativeTo="CharacterFrame">` (a NAMED
|
||||
lookup, works regardless of tree position, the same mechanism every sibling-anchor in this
|
||||
codebase already uses) — loaded right after `CharacterFrame.xml` in the load list so it
|
||||
paints ON TOP of it (later file = higher z, the established house convention every top-level
|
||||
window already relies on implicitly). Because a true engine Hide-cascade doesn't reach a
|
||||
non-child, `CharacterFrame.xml`'s own `BenillaCharacterFrame_OnHide` was extended (its own
|
||||
non-child, `CharacterFrame.xml`'s own `CharacterFrame_OnHide` was extended (its own
|
||||
header comment) to loop-hide every `BENILLA_CHARACTERFRAME_SUBFRAMES` entry — the fix this
|
||||
page's non-nested shape requires, done once so later non-nested tabs (Pet/Reputation/Honor)
|
||||
inherit it for free.
|
||||
@@ -29,7 +29,7 @@
|
||||
NO OWN PORTRAIT (matches the ref: `ref-ui/SkillFrame.xml` l.130-213 draws no portrait Texture
|
||||
at all): the shared quadrant background art below (`UI-Character-General-TopLeft/TopRight`,
|
||||
the SAME "General" variant the ref uses, l.136-159) carries the SAME baked-in portrait-ring
|
||||
cutout every PaperDollInfoFrame quadrant variant shares, so `BenillaCharacterFrame`'s own
|
||||
cutout every PaperDollInfoFrame quadrant variant shares, so `CharacterFrame`'s own
|
||||
portrait/name/level (drawn at a lower z, underneath) shows through unchanged — exactly the
|
||||
ref's own real-client compositing (SkillFrame is a nested child there too, relying on the
|
||||
same cutout rather than redrawing the portrait itself).
|
||||
@@ -42,10 +42,10 @@
|
||||
|
||||
v1 DEVIATIONS (each re-cited at its own site below):
|
||||
1. THE DETAIL PANE IS MINIMAL-PLUS-DESCRIPTION-PLUS-ABANDON: the selected skill's rank
|
||||
StatusBar + name + the DESCRIPTION BODY (`BenillaSkillDetailDescriptionText` — ref
|
||||
StatusBar + name + the DESCRIPTION BODY (`SkillDetailDescriptionText` — ref
|
||||
`SkillDetailDescriptionText`/`SKILL_DESCRIPTION`, real since `GetSkillLineInfo`'s 13th
|
||||
return carries `SkillLine.dbc` col 12) + the ABANDON BUTTON
|
||||
(`BenillaSkillDetailUnlearnButton` → the `UNLEARN_SKILL` confirm → `AbandonSkill` →
|
||||
(`SkillDetailUnlearnButton` → the `UNLEARN_SKILL` confirm → `AbandonSkill` →
|
||||
`CMSG_UNLEARN_SKILL`; the isAbandonable predicate is `SkillRaceClassInfo.flags & 0x20`
|
||||
— the server's own gate, real 5875 data: primary professions only). STILL OMITTED:
|
||||
the training-point spend row (the ref's `LeftArrow`/`RightArrow`/`LearnSkillButton`/
|
||||
@@ -58,8 +58,8 @@
|
||||
3. NO ACCEPT/CANCEL BUTTONS (ref l.318-357's `SkillFrameAcceptButton`/`SkillFrameCancelButton`,
|
||||
the latter ALSO commented dead in the ref): both back the training-point economy (Accept/
|
||||
CancelSkillUps) this pane doesn't ship, AND this page has no window of its own to close —
|
||||
`BenillaCharacterFrame`'s single shared close X (CharacterFrame.xml) already covers every
|
||||
tab, matching `BenillaPaperDollFrame`'s own lack of a per-page close button.
|
||||
`CharacterFrame`'s single shared close X (CharacterFrame.xml) already covers every
|
||||
tab, matching `PaperDollFrame`'s own lack of a per-page close button.
|
||||
4. THE RANK TEXT FORMAT IS THE REF'S BYTE-EXACT ONE, not the task brief's own paraphrase:
|
||||
`ref-ui/SkillFrame.lua` l.178-186 builds `rank .. " (" .. color .. modifier .. close ..
|
||||
")/" .. max` (e.g. "57 (|cff20ff20+3|r)/75") — transcribed verbatim (the
|
||||
@@ -75,7 +75,7 @@
|
||||
7. `LockHighlight`/`UnlockHighlight` ENGINE GAP (TalentFrame.xml/TradeSkillFrame.xml's own
|
||||
"no engine equivalent" posture, grepped `benilla-ui/src/script/button.rs` — ships
|
||||
texture-pairs/SetText/Enable/Disable/RegisterForClicks/SetButtonState/Click only): the
|
||||
persistent selection glow is `BenillaSkillHighlight`, the separate overlay Frame
|
||||
persistent selection glow is `SkillHighlight`, the separate overlay Frame
|
||||
Trainer/TradeSkillFrame already use, moved onto the selected row's StatusBar.
|
||||
8. GlobalStrings this window itself needs: NONE beyond the tab label `SKILLS` (already
|
||||
declared in CharacterFrame.xml, quoted there byte-exact with its GlobalStrings.lua line
|
||||
@@ -182,9 +182,9 @@
|
||||
-- same fill (ref l.220-226): shown only for an abandonable line, carrying the name the
|
||||
-- confirm popup formats and the index its accept abandons.
|
||||
function BenillaSkillFrame_UpdateDetail()
|
||||
local bar = getglobal("BenillaSkillDetailBar")
|
||||
local desc = getglobal("BenillaSkillDetailDescriptionText")
|
||||
local unlearn = getglobal("BenillaSkillDetailUnlearnButton")
|
||||
local bar = getglobal("SkillDetailBar")
|
||||
local desc = getglobal("SkillDetailDescriptionText")
|
||||
local unlearn = getglobal("SkillDetailUnlearnButton")
|
||||
local index = GetSelectedSkill()
|
||||
if index == 0 then
|
||||
bar:Hide()
|
||||
@@ -252,8 +252,8 @@
|
||||
-- skillCostType coloring + stepCost/rankCost training-up arrows never apply here, since
|
||||
-- GetSkillLineInfo always reports them nil/0, this file's header comment deviation 1).
|
||||
function BenillaSkillFrame_SetRow(i, index, numRows)
|
||||
local bar = getglobal("BenillaSkillRankFrame" .. i)
|
||||
local label = getglobal("BenillaSkillTypeLabel" .. i)
|
||||
local bar = getglobal("SkillRankFrame" .. i)
|
||||
local label = getglobal("SkillTypeLabel" .. i)
|
||||
|
||||
if index > numRows then
|
||||
bar:Hide()
|
||||
@@ -292,11 +292,11 @@
|
||||
-- TradeSkillFrame.xml "ref's own dead code is dropped" precedent).
|
||||
function BenillaSkillFrame_Update()
|
||||
local numRows = GetNumSkillLines()
|
||||
local scrollFrame = getglobal("BenillaSkillListScrollFrame")
|
||||
BenillaFauxScrollFrame_Update(scrollFrame, numRows, SKILLFRAME_SKILLS_DISPLAYED, SKILLFRAME_SKILL_HEIGHT)
|
||||
local offset = BenillaFauxScrollFrame_GetOffset(scrollFrame)
|
||||
local scrollFrame = getglobal("SkillListScrollFrame")
|
||||
FauxScrollFrame_Update(scrollFrame, numRows, SKILLFRAME_SKILLS_DISPLAYED, SKILLFRAME_SKILL_HEIGHT)
|
||||
local offset = FauxScrollFrame_GetOffset(scrollFrame)
|
||||
|
||||
local highlight = getglobal("BenillaSkillHighlight")
|
||||
local highlight = getglobal("SkillHighlight")
|
||||
highlight:Hide()
|
||||
local selected = GetSelectedSkill()
|
||||
|
||||
@@ -304,7 +304,7 @@
|
||||
local index = i + offset
|
||||
BenillaSkillFrame_SetRow(i, index, numRows)
|
||||
if selected == index and index <= numRows then
|
||||
highlight:SetPoint("TOPLEFT", getglobal("BenillaSkillRankFrame" .. i), "TOPLEFT", -5, 0)
|
||||
highlight:SetPoint("TOPLEFT", getglobal("SkillRankFrame" .. i), "TOPLEFT", -5, 0)
|
||||
highlight:Show()
|
||||
end
|
||||
end
|
||||
@@ -317,7 +317,7 @@
|
||||
allExpanded = false
|
||||
end
|
||||
end
|
||||
local collapseAll = getglobal("BenillaSkillCollapseAllButton")
|
||||
local collapseAll = getglobal("SkillCollapseAllButton")
|
||||
if numRows == 0 then
|
||||
collapseAll:Disable()
|
||||
else
|
||||
@@ -370,12 +370,12 @@
|
||||
-- driven the TrainerFrame.xml BenillaTrainerCollapseAllButton way since this engine's
|
||||
-- CollapseSkillHeader(0)/ExpandSkillHeader(0) already fold/unfold every group at once).
|
||||
function BenillaSkillCollapseAllButton_OnClick()
|
||||
local b = getglobal("BenillaSkillCollapseAllButton")
|
||||
local b = getglobal("SkillCollapseAllButton")
|
||||
if b.collapsed then
|
||||
ExpandSkillHeader(0)
|
||||
else
|
||||
CollapseSkillHeader(0)
|
||||
BenillaFauxScrollFrame_SetOffset(getglobal("BenillaSkillListScrollFrame"), 0)
|
||||
FauxScrollFrame_SetOffset(getglobal("SkillListScrollFrame"), 0)
|
||||
end
|
||||
BenillaSkillFrame_Update()
|
||||
end
|
||||
@@ -383,27 +383,29 @@
|
||||
-- The window's wheel handler (bubbled up the parent chain from a row) → one row via the faux
|
||||
-- kit (TrainerFrame.xml's own note: the engine bubbles a spin up the parent chain).
|
||||
function BenillaSkillFrame_OnMouseWheel(delta)
|
||||
BenillaFauxScrollFrame_OnMouseWheel(getglobal("BenillaSkillListScrollFrame"), delta)
|
||||
BenillaFauxScrollFrame_OnMouseWheel(getglobal("SkillListScrollFrame"), delta)
|
||||
end
|
||||
|
||||
-- ── Lifecycle (ref SkillFrame_OnLoad/_OnShow/_OnEvent, l.4-22, collapsed to the events this
|
||||
-- page's feed actually fires: CHARACTER_POINTS_CHANGED backs the training-point economy this
|
||||
-- pane doesn't ship, this file's header comment deviation 1). ─────────────────────────────────
|
||||
function BenillaSkillFrame_OnLoad(frame)
|
||||
function SkillFrame_OnLoad()
|
||||
local frame = this
|
||||
frame:RegisterEvent("SKILL_LINES_CHANGED")
|
||||
getglobal("BenillaSkillListScrollFrame").updateFunc = BenillaSkillFrame_Update
|
||||
getglobal("SkillListScrollFrame").updateFunc = BenillaSkillFrame_Update
|
||||
-- The description body hangs under the detail bar — anchored here, not in XML,
|
||||
-- because the bar is a child frame declared after the page's layers and anchors
|
||||
-- resolve at SetPoint time (an XML forward reference silently lands on the page;
|
||||
-- the MailFrame error-text OnLoad precedent, ref MailFrame.xml l.1040).
|
||||
BenillaSkillDetailDescriptionText:SetPoint("TOPLEFT", "BenillaSkillDetailBar", "BOTTOMLEFT", -2, -10)
|
||||
SkillDetailDescriptionText:SetPoint("TOPLEFT", "SkillDetailBar", "BOTTOMLEFT", -2, -10)
|
||||
end
|
||||
|
||||
function BenillaSkillFrame_OnShow()
|
||||
function SkillFrame_OnShow()
|
||||
BenillaSkillFrame_Update()
|
||||
end
|
||||
|
||||
function BenillaSkillFrame_OnEvent(frame)
|
||||
function SkillFrame_OnEvent(event)
|
||||
local frame = this
|
||||
if event == "SKILL_LINES_CHANGED" and frame:IsVisible() then
|
||||
BenillaSkillFrame_Update()
|
||||
end
|
||||
@@ -470,11 +472,11 @@
|
||||
Reputation (the ref's 3) is not built — the same deviation CharacterFrame.xml's tab row
|
||||
already flags. `character_tests.rs` pins every page's id against its slot in
|
||||
BENILLA_CHARACTERFRAME_SUBFRAMES, so the day Reputation lands this is loud, not silent. -->
|
||||
<Frame name="BenillaSkillFrame" enableMouse="true" parent="BenillaCharacterFrame" hidden="true" id="3">
|
||||
<Frame name="SkillFrame" enableMouse="true" parent="CharacterFrame" hidden="true" id="3">
|
||||
<!-- The real 384×512 page. No <Anchors relativeTo> substitute for true parenting exists
|
||||
(this file's header comment) — pinned to BenillaCharacterFrame's own TOPLEFT by name. -->
|
||||
(this file's header comment) — pinned to CharacterFrame's own TOPLEFT by name. -->
|
||||
<Size><AbsDimension x="384" y="512"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterFrame" relativePoint="TOPLEFT"/></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterFrame" relativePoint="TOPLEFT"/></Anchors>
|
||||
<!-- Transcribed for fidelity (ref l.131-133); no loader wiring exists yet for the hit-rect
|
||||
shrink (TradeSkillFrame.xml's own "transcribe the hook even if nothing drives it yet"
|
||||
posture, TalentFrame.xml's precedent). -->
|
||||
@@ -506,14 +508,14 @@
|
||||
last row sits slightly lower than the ref's fixed-slot geometry — a pixel-fit
|
||||
adjustment, not a structural deviation). -->
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="BenillaSkillHorizontalBarLeft" file="Interface\ClassTrainerFrame\UI-ClassTrainer-HorizontalBar">
|
||||
<Texture name="SkillHorizontalBarLeft" file="Interface\ClassTrainerFrame\UI-ClassTrainer-HorizontalBar">
|
||||
<Size><AbsDimension x="256" y="16"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="15" y="-305"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0" right="1.0" top="0" bottom="0.25"/>
|
||||
</Texture>
|
||||
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-HorizontalBar">
|
||||
<Size><AbsDimension x="75" y="16"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaSkillHorizontalBarLeft" relativePoint="RIGHT"/></Anchors>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="SkillHorizontalBarLeft" relativePoint="RIGHT"/></Anchors>
|
||||
<TexCoords left="0" right="0.29296875" top="0.25" bottom="0.5"/>
|
||||
</Texture>
|
||||
<!-- The detail pane's description body (ref SkillDetailDescriptionText, l.579-591:
|
||||
@@ -521,9 +523,9 @@
|
||||
its SkillDetailScrollChildFrame; this page's flat layout (header comment) has
|
||||
no such child, so it hangs under the detail bar instead — the same pane
|
||||
position, minus the (never-needed for one sentence) scroll machinery. The
|
||||
anchor is set in BenillaSkillFrame_OnLoad, NOT here: the bar is a child frame
|
||||
anchor is set in SkillFrame_OnLoad, NOT here: the bar is a child frame
|
||||
declared below and an XML forward reference silently lands on the page. -->
|
||||
<FontString name="BenillaSkillDetailDescriptionText" inherits="GameFontHighlightSmall" justifyH="LEFT" justifyV="TOP" hidden="true">
|
||||
<FontString name="SkillDetailDescriptionText" inherits="GameFontHighlightSmall" justifyH="LEFT" justifyV="TOP" hidden="true">
|
||||
<Size><AbsDimension x="275" y="0"/></Size>
|
||||
</FontString>
|
||||
</Layer>
|
||||
@@ -532,11 +534,11 @@
|
||||
<!-- The scroll-wheel catcher (TrainerFrame.xml's own note) — MUST be first so every real
|
||||
row below out-ranks it in hit-testing, and sized to the LIST REGION ONLY (rows span
|
||||
y -79..-295, x 22..309), NEVER the whole page: unlike the trainer's standalone
|
||||
window, this page is BenillaCharacterFrame's LAST child, so a full-page catcher
|
||||
window, this page is CharacterFrame's LAST child, so a full-page catcher
|
||||
out-ranks every EARLIER CharacterFrame child — it ate the tab row and the close X
|
||||
whenever Skills was up (the 2026-07-17 "can't switch back to Character" report;
|
||||
character_tests.rs's tab_round_trip_with_a_selected_skill_by_point is the repro). -->
|
||||
<Button name="BenillaSkillWheelCatcher">
|
||||
<Button name="SkillWheelCatcher">
|
||||
<Size><AbsDimension x="290" y="222"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="20" y="-76"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnMouseWheel>BenillaSkillFrame_OnMouseWheel(arg1)</OnMouseWheel></Scripts>
|
||||
@@ -544,18 +546,18 @@
|
||||
|
||||
<!-- The expand/collapse-all tab (ref SkillFrameExpandButtonFrame, l.242-317 — the SAME
|
||||
QuestLogSortTab art TrainerFrame.xml's own BenillaTrainerExpandButtonFrame uses). -->
|
||||
<Frame name="BenillaSkillExpandButtonFrame">
|
||||
<Frame name="SkillExpandButtonFrame">
|
||||
<Size><AbsDimension x="54" y="32"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="70" y="-49"/></Offset></Anchor></Anchors>
|
||||
<Layers><Layer level="BACKGROUND">
|
||||
<Texture name="BenillaSkillExpandTabLeft" file="Interface\QuestFrame\UI-QuestLogSortTab-Left"><Size><AbsDimension x="8" y="32"/></Size><Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="0" y="6"/></Offset></Anchor></Anchors></Texture>
|
||||
<Texture name="BenillaSkillExpandTabMiddle" file="Interface\QuestFrame\UI-QuestLogSortTab-Middle"><Size><AbsDimension x="38" y="32"/></Size><Anchors><Anchor point="LEFT" relativeTo="BenillaSkillExpandTabLeft" relativePoint="RIGHT"/></Anchors></Texture>
|
||||
<Texture file="Interface\QuestFrame\UI-QuestLogSortTab-Right"><Size><AbsDimension x="8" y="32"/></Size><Anchors><Anchor point="LEFT" relativeTo="BenillaSkillExpandTabMiddle" relativePoint="RIGHT"/></Anchors></Texture>
|
||||
<Texture name="SkillExpandTabLeft" file="Interface\QuestFrame\UI-QuestLogSortTab-Left"><Size><AbsDimension x="8" y="32"/></Size><Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="0" y="6"/></Offset></Anchor></Anchors></Texture>
|
||||
<Texture name="SkillExpandTabMiddle" file="Interface\QuestFrame\UI-QuestLogSortTab-Middle"><Size><AbsDimension x="38" y="32"/></Size><Anchors><Anchor point="LEFT" relativeTo="SkillExpandTabLeft" relativePoint="RIGHT"/></Anchors></Texture>
|
||||
<Texture file="Interface\QuestFrame\UI-QuestLogSortTab-Right"><Size><AbsDimension x="8" y="32"/></Size><Anchors><Anchor point="LEFT" relativeTo="SkillExpandTabMiddle" relativePoint="RIGHT"/></Anchors></Texture>
|
||||
</Layer></Layers>
|
||||
<Frames>
|
||||
<Button name="BenillaSkillCollapseAllButton" text="ALL">
|
||||
<Button name="SkillCollapseAllButton" text="ALL">
|
||||
<Size><AbsDimension x="40" y="22"/></Size>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaSkillExpandTabLeft" relativePoint="RIGHT"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="SkillExpandTabLeft" relativePoint="RIGHT"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-MinusButton-Up"><Size><AbsDimension x="16" y="16"/></Size><Anchors><Anchor point="LEFT" relativePoint="LEFT"/></Anchors></NormalTexture>
|
||||
<HighlightTexture file="Interface\Buttons\UI-PlusButton-Hilight" alphaMode="ADD"><Size><AbsDimension x="16" y="16"/></Size><Anchors><Anchor point="LEFT" relativePoint="LEFT"/></Anchors></HighlightTexture>
|
||||
<ButtonText><Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="18" y="0"/></Offset></Anchor></Anchors></ButtonText>
|
||||
@@ -568,11 +570,11 @@
|
||||
<!-- The persistent selection highlight (Trainer/TradeSkillFrame's own precedent, this
|
||||
file's header comment deviation 7): 281×15, ARTWORK-layer UI-Listbox-Highlight2.
|
||||
Declared BEFORE the rows so the row text paints OVER the glow (same z-tiebreak). -->
|
||||
<Frame name="BenillaSkillHighlightFrame" hidden="true">
|
||||
<Frame name="SkillHighlightFrame" hidden="true">
|
||||
<Size><AbsDimension x="281" y="15"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
|
||||
<Layers><Layer level="ARTWORK">
|
||||
<Texture name="BenillaSkillHighlight" file="Interface\Buttons\UI-Listbox-Highlight2">
|
||||
<Texture name="SkillHighlight" file="Interface\Buttons\UI-Listbox-Highlight2">
|
||||
<Anchors><Anchor point="TOPLEFT"/><Anchor point="BOTTOMRIGHT"/></Anchors>
|
||||
</Texture>
|
||||
</Layer></Layers>
|
||||
@@ -580,44 +582,44 @@
|
||||
|
||||
<!-- 12 header-row slots (ref SkillTypeLabel1..12, l.358-465): 285×14, row1 LEFT of
|
||||
TOPLEFT(22,-86); rows 2-12 chain by (0,-18). -->
|
||||
<Button name="BenillaSkillTypeLabel1" inherits="BenillaSkillHeaderTemplate">
|
||||
<Button name="SkillTypeLabel1" inherits="BenillaSkillHeaderTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="22" y="-86"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="BenillaSkillTypeLabel2" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="BenillaSkillTypeLabel1"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="BenillaSkillTypeLabel3" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="BenillaSkillTypeLabel2"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="BenillaSkillTypeLabel4" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="BenillaSkillTypeLabel3"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="BenillaSkillTypeLabel5" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="BenillaSkillTypeLabel4"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="BenillaSkillTypeLabel6" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="BenillaSkillTypeLabel5"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="BenillaSkillTypeLabel7" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="BenillaSkillTypeLabel6"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="BenillaSkillTypeLabel8" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="BenillaSkillTypeLabel7"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="BenillaSkillTypeLabel9" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="BenillaSkillTypeLabel8"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="BenillaSkillTypeLabel10" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="BenillaSkillTypeLabel9"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="BenillaSkillTypeLabel11" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="BenillaSkillTypeLabel10"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="BenillaSkillTypeLabel12" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="BenillaSkillTypeLabel11"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="SkillTypeLabel2" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel1"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="SkillTypeLabel3" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel2"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="SkillTypeLabel4" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel3"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="SkillTypeLabel5" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel4"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="SkillTypeLabel6" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel5"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="SkillTypeLabel7" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel6"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="SkillTypeLabel8" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel7"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="SkillTypeLabel9" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel8"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="SkillTypeLabel10" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel9"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="SkillTypeLabel11" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel10"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
<Button name="SkillTypeLabel12" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel11"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
|
||||
|
||||
<!-- 12 rank-bar slots (ref SkillRankFrame1..12, l.746-853): 271×15, row1 TOPLEFT(38,-79);
|
||||
rows 2-12 chain BOTTOMLEFT→TOPLEFT by (0,-3) — the SAME 18px pitch as the label chain
|
||||
(15 bar height + 3 gap). -->
|
||||
<StatusBar name="BenillaSkillRankFrame1" inherits="BenillaSkillRankTemplate">
|
||||
<StatusBar name="SkillRankFrame1" inherits="BenillaSkillRankTemplate">
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="38" y="-79"/></Offset></Anchor></Anchors>
|
||||
</StatusBar>
|
||||
<StatusBar name="BenillaSkillRankFrame2" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSkillRankFrame1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="BenillaSkillRankFrame3" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSkillRankFrame2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="BenillaSkillRankFrame4" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSkillRankFrame3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="BenillaSkillRankFrame5" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSkillRankFrame4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="BenillaSkillRankFrame6" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSkillRankFrame5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="BenillaSkillRankFrame7" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSkillRankFrame6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="BenillaSkillRankFrame8" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSkillRankFrame7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="BenillaSkillRankFrame9" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSkillRankFrame8" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="BenillaSkillRankFrame10" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSkillRankFrame9" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="BenillaSkillRankFrame11" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSkillRankFrame10" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="BenillaSkillRankFrame12" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSkillRankFrame11" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="SkillRankFrame2" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="SkillRankFrame3" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="SkillRankFrame4" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="SkillRankFrame5" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="SkillRankFrame6" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="SkillRankFrame7" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="SkillRankFrame8" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="SkillRankFrame9" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame8" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="SkillRankFrame10" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame9" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="SkillRankFrame11" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame10" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
<StatusBar name="SkillRankFrame12" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame11" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
|
||||
|
||||
<!-- The faux-scroll frame (BenillaFauxScrollFrameTemplate, ScrollTemplates.xml): invisible
|
||||
<!-- The faux-scroll frame (FauxScrollFrameTemplate, ScrollTemplates.xml): invisible
|
||||
container over the 12 rows; only its right-hand Slider scroll bar renders. Sized to the
|
||||
12-row span (SKILLFRAME_SKILLS_DISPLAYED × 18px pitch), TrainerFrame.xml's own
|
||||
same-TOPLEFT-as-row1 convention. updateFunc set in BenillaSkillFrame_OnLoad. -->
|
||||
<Frame name="BenillaSkillListScrollFrame" inherits="BenillaFauxScrollFrameTemplate">
|
||||
same-TOPLEFT-as-row1 convention. updateFunc set in SkillFrame_OnLoad. -->
|
||||
<Frame name="SkillListScrollFrame" inherits="FauxScrollFrameTemplate">
|
||||
<Size><AbsDimension x="296" y="216"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="22" y="-79"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
@@ -626,7 +628,7 @@
|
||||
skill's own rank StatusBar + name — no description/abandon/spend row. Reuses the SAME
|
||||
rank template the list rows use (its Border click is harmless here: no .skillIndex is
|
||||
ever set on this instance, so BenillaSkillRankFrame_OnClick's guard no-ops it). -->
|
||||
<StatusBar name="BenillaSkillDetailBar" inherits="BenillaSkillRankTemplate" hidden="true">
|
||||
<StatusBar name="SkillDetailBar" inherits="BenillaSkillRankTemplate" hidden="true">
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="38" y="-325"/></Offset></Anchor></Anchors>
|
||||
<Frames>
|
||||
<!-- The unlearn (abandon) button (ref $parentUnlearnButton, ref SkillFrame.xml
|
||||
@@ -635,10 +637,10 @@
|
||||
transcribed-inert (no loader wiring — CharacterFrame.xml's engine-gap note).
|
||||
Handlers are chunk functions (the chunk-boundary rule — OnEnter reads the
|
||||
chunk-local UNLEARN_SKILL_TOOLTIP). -->
|
||||
<Button name="BenillaSkillDetailUnlearnButton" hidden="true">
|
||||
<Button name="SkillDetailUnlearnButton" hidden="true">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="BenillaSkillDetailBarBorder" relativePoint="RIGHT">
|
||||
<Anchor point="LEFT" relativeTo="SkillDetailBarBorder" relativePoint="RIGHT">
|
||||
<Offset><AbsDimension x="-2" y="-1"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
@@ -656,9 +658,9 @@
|
||||
</StatusBar>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaSkillFrame_OnLoad(self)</OnLoad>
|
||||
<OnShow>BenillaSkillFrame_OnShow()</OnShow>
|
||||
<OnEvent>BenillaSkillFrame_OnEvent(self)</OnEvent>
|
||||
<OnLoad>SkillFrame_OnLoad()</OnLoad>
|
||||
<OnShow>SkillFrame_OnShow()</OnShow>
|
||||
<OnEvent>SkillFrame_OnEvent(event)</OnEvent>
|
||||
<OnMouseWheel>BenillaSkillFrame_OnMouseWheel(arg1)</OnMouseWheel>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
@@ -8,16 +8,17 @@
|
||||
line cites below are into that extraction's own `ref-SpellBookFrame.xml`/`.lua`).
|
||||
|
||||
Docks the left UIPanel slot exactly like Character/Merchant/Gossip/Quest/QuestLog
|
||||
(`UiPanels.xml`'s `UIPanelWindows["BenillaSpellBookFrame"] = { area = "left", pushable =
|
||||
(`UiPanels.xml`'s `UIPanelWindows["SpellBookFrame"] = { area = "left", pushable =
|
||||
0 }`, mirroring the ref's own `SpellBookFrame` row) — so it competes for/replaces those
|
||||
windows, never stacks with them. `ToggleSpellBook(bookType)` (ref `SpellBookFrame.lua:
|
||||
9-50`, kept BARE — not Benilla-prefixed — matching this codebase's own `ToggleCharacter`/
|
||||
9-50`) is the addon-facing entry point; like every frame name here it is the reference's own
|
||||
`ToggleQuestLog` precedent) is the show/hide entry point; the app's 'P' binding calls it
|
||||
with `BOOKTYPE_SPELL` (`ui_script/input.rs`, ref `Bindings.xml:582-583`).
|
||||
|
||||
ONE deliberate naming deviation from the ref, applied consistently: every frame this file
|
||||
defines is Benilla-prefixed (`BenillaSpellBookFrame`, `BenillaSpellButton1`, …) — the
|
||||
same posture every other window in this codebase already takes.
|
||||
Every frame this file defines wears the REFERENCE's name (`SpellBookFrame`, `SpellButton1`,
|
||||
…), because addons address them by it — the `Benilla` prefix these carried until the
|
||||
reference-naming pass is gone. A child with no counterpart in the 1.12 namespace keeps the
|
||||
bare stripped name so `$parent`/`GetName()..` composition inside the family stays coherent.
|
||||
|
||||
The book MODEL (tabs = skill lines, the flat slot list, offsets) is built entirely by the
|
||||
app from `PlayerActions.spells` through the `Spell.dbc`/`SkillLine.dbc` join
|
||||
@@ -36,7 +37,7 @@
|
||||
`crate::ui_pet_book`'s feed of `SMSG_PET_SPELLS`' own spell list. Two reference QUIRKS are
|
||||
reproduced on purpose here and each carries a do-not-fix note at its site (the standing
|
||||
rule from decision 1030): the pet page **ignores its page number**, and its page arrows
|
||||
write the *spell* book's page counter. See `BenillaSpellBook_GetSpellID` and the two
|
||||
write the *spell* book's page counter. See `SpellBook_GetSpellID` and the two
|
||||
page-turn handlers.
|
||||
|
||||
Named deferrals/OMITs (each re-cited at its own site below):
|
||||
@@ -96,7 +97,7 @@
|
||||
-- codebase's own ToggleCharacter/ToggleQuestLog precedent. The ref's own DUPLICATED
|
||||
-- prev/next enable/disable (once inside the `currentPage > maxPages` clamp branch, once
|
||||
-- again unconditionally right after — both produce the identical end state) collapses to
|
||||
-- the ONE BenillaSpellBook_UpdatePageArrows() call already shared by every other
|
||||
-- the ONE SpellBook_UpdatePageArrows() call already shared by every other
|
||||
-- page-turn site below — a redundant-code cleanup, not a behavior change.
|
||||
function ToggleSpellBook(bookType)
|
||||
-- ref l.11-14: asking for a pet book you haven't got does NOTHING — not even close a
|
||||
@@ -104,29 +105,30 @@
|
||||
if bookType == BOOKTYPE_PET and not HasPetSpells() then
|
||||
return
|
||||
end
|
||||
local isVisible = BenillaSpellBookFrame:IsVisible()
|
||||
HideUIPanel(BenillaSpellBookFrame)
|
||||
if (not isVisible or (BenillaSpellBookFrame.bookType ~= bookType)) then
|
||||
BenillaSpellBookFrame.bookType = bookType
|
||||
ShowUIPanel(BenillaSpellBookFrame)
|
||||
local isVisible = SpellBookFrame:IsVisible()
|
||||
HideUIPanel(SpellBookFrame)
|
||||
if (not isVisible or (SpellBookFrame.bookType ~= bookType)) then
|
||||
SpellBookFrame.bookType = bookType
|
||||
ShowUIPanel(SpellBookFrame)
|
||||
end
|
||||
BenillaSpellBook_ClampPage()
|
||||
BenillaSpellBook_UpdatePageArrows()
|
||||
BenillaSpellBookPageText:SetText(format(PAGE_NUMBER, BenillaSpellBook_GetCurrentPage()))
|
||||
SpellBook_UpdatePageArrows()
|
||||
SpellBookPageText:SetText(format(PAGE_NUMBER, SpellBook_GetCurrentPage()))
|
||||
end
|
||||
|
||||
-- Clamp the selected tab's page back onto range (ref's own inline logic inside
|
||||
-- ToggleSpellBook, l.22-26) — split out so both ToggleSpellBook and a future re-entry
|
||||
-- share it, rather than inlining it twice.
|
||||
function BenillaSpellBook_ClampPage()
|
||||
local currentPage, maxPages = BenillaSpellBook_GetCurrentPage()
|
||||
local currentPage, maxPages = SpellBook_GetCurrentPage()
|
||||
if currentPage > maxPages then
|
||||
SPELLBOOK_PAGENUMBERS[BenillaSpellBookFrame.selectedSkillLine] = maxPages
|
||||
SPELLBOOK_PAGENUMBERS[SpellBookFrame.selectedSkillLine] = maxPages
|
||||
BenillaUpdateSpells()
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaSpellBookFrame_OnLoad(frame)
|
||||
function SpellBookFrame_OnLoad()
|
||||
local frame = this
|
||||
frame:RegisterEvent("SPELLS_CHANGED")
|
||||
-- LEARNED_SPELL_IN_TAB OMITTED — this file's header comment.
|
||||
frame.bookType = BOOKTYPE_SPELL
|
||||
@@ -134,45 +136,46 @@
|
||||
SPELLBOOK_PAGENUMBERS[i] = 1
|
||||
end
|
||||
SPELLBOOK_PAGENUMBERS["pet"] = 1 -- ref l.66; read by GetCurrentPage, written by nothing
|
||||
BenillaSpellBookSkillLineTab_OnClick(1)
|
||||
SpellBookSkillLineTab_OnClick(1)
|
||||
end
|
||||
|
||||
-- ref SpellBookFrame_OnEvent (l.75-92), the SPELLS_CHANGED branch only (LEARNED_SPELL_IN_TAB
|
||||
-- OMITTED, header comment).
|
||||
function BenillaSpellBookFrame_OnEvent(frame)
|
||||
function SpellBookFrame_OnEvent()
|
||||
local frame = this
|
||||
if event == "SPELLS_CHANGED" and frame:IsVisible() then
|
||||
BenillaSpellBookFrame_Update()
|
||||
BenillaSpellBook_UpdatePageArrows()
|
||||
SpellBookFrame_Update()
|
||||
SpellBook_UpdatePageArrows()
|
||||
end
|
||||
end
|
||||
|
||||
-- ref SpellBookFrame_OnShow (l.94-105): MultiActionBar_ShowAllGrids OMITTED (header
|
||||
-- comment); the tab-flash UIFrameFlash call goes with LEARNED_SPELL_IN_TAB.
|
||||
-- UpdateMicroButtons is LIVE as of the micro-menu slice (MicroMenu.xml).
|
||||
function BenillaSpellBookFrame_OnShow()
|
||||
function SpellBookFrame_OnShow()
|
||||
if UpdateMicroButtons then UpdateMicroButtons() end
|
||||
BenillaSpellBookFrame_Update(1)
|
||||
SpellBookFrame_Update(1)
|
||||
end
|
||||
|
||||
-- ref SpellBookFrame_Update (l.107-164), whole. Three things happen here and they are
|
||||
-- easy to conflate: the skill-line strip is gated on the book TYPE (the pet book has no
|
||||
-- tabs at all — GetSpellTabInfo never sees it), the Spell/Pet toggle row is gated on
|
||||
-- HasPetSpells(), and the title + open sound fork on which book is up.
|
||||
function BenillaSpellBookFrame_Update(showing)
|
||||
function SpellBookFrame_Update(showing)
|
||||
if showing then
|
||||
BenillaSpellBookSkillLineTab_OnClick(BenillaSpellBookFrame.selectedSkillLine)
|
||||
SpellBookSkillLineTab_OnClick(SpellBookFrame.selectedSkillLine)
|
||||
BenillaUpdateSpells()
|
||||
end
|
||||
local isPet = BenillaSpellBookFrame.bookType == BOOKTYPE_PET
|
||||
local isPet = SpellBookFrame.bookType == BOOKTYPE_PET
|
||||
local numSkillLineTabs = GetNumSpellTabs()
|
||||
for i = 1, MAX_SKILLLINE_TABS do
|
||||
local skillLineTab = getglobal("BenillaSpellBookSkillLineTab" .. i)
|
||||
local skillLineTab = getglobal("SpellBookSkillLineTab" .. i)
|
||||
if i <= numSkillLineTabs and not isPet then
|
||||
local name, texture = GetSpellTabInfo(i)
|
||||
skillLineTab:SetNormalTexture(texture)
|
||||
skillLineTab.tooltip = name
|
||||
skillLineTab:Show()
|
||||
skillLineTab:SetChecked(BenillaSpellBookFrame.selectedSkillLine == i)
|
||||
skillLineTab:SetChecked(SpellBookFrame.selectedSkillLine == i)
|
||||
else
|
||||
skillLineTab:Hide()
|
||||
end
|
||||
@@ -181,26 +184,26 @@
|
||||
-- ref l.108-111 + l.141-151: hide the row first, then raise exactly the tabs that
|
||||
-- exist. Tab 3 never appears in 1.12 (no third book) and is hidden by the template's
|
||||
-- own `hidden="true"`; it is kept so the anchor chain reads like the ref's.
|
||||
BenillaSpellBookFrameTabButton1:Hide()
|
||||
BenillaSpellBookFrameTabButton2:Hide()
|
||||
SpellBookFrameTabButton1:Hide()
|
||||
SpellBookFrameTabButton2:Hide()
|
||||
local hasPetSpells, petToken = HasPetSpells()
|
||||
BenillaSpellBookFrame.petTitle = nil
|
||||
SpellBookFrame.petTitle = nil
|
||||
if hasPetSpells then
|
||||
BenillaSpellBookFrame_SetTabType(BenillaSpellBookFrameTabButton1, BOOKTYPE_SPELL)
|
||||
BenillaSpellBookFrame_SetTabType(BenillaSpellBookFrameTabButton2, BOOKTYPE_PET, petToken)
|
||||
SpellBookFrame_SetTabType(SpellBookFrameTabButton1, BOOKTYPE_SPELL)
|
||||
SpellBookFrame_SetTabType(SpellBookFrameTabButton2, BOOKTYPE_PET, petToken)
|
||||
elseif isPet then
|
||||
-- The pet died/was dismissed while its book was open: the ref closes the window
|
||||
-- and reverts, rather than showing an empty pet page (l.147-151).
|
||||
HideUIPanel(BenillaSpellBookFrame)
|
||||
BenillaSpellBookFrame.bookType = BOOKTYPE_SPELL
|
||||
HideUIPanel(SpellBookFrame)
|
||||
SpellBookFrame.bookType = BOOKTYPE_SPELL
|
||||
isPet = false
|
||||
end
|
||||
|
||||
if not isPet then
|
||||
BenillaSpellBookTitleText:SetText(SPELLBOOK)
|
||||
SpellBookTitleText:SetText(SPELLBOOK)
|
||||
if showing then PlaySound("igSpellBookOpen") end
|
||||
else
|
||||
BenillaSpellBookTitleText:SetText(BenillaSpellBookFrame.petTitle)
|
||||
SpellBookTitleText:SetText(SpellBookFrame.petTitle)
|
||||
-- ref l.161: the pet book opens on the ABILITY kit, not the spellbook one. Both
|
||||
-- ship in the real SoundEntries.dbc.
|
||||
if showing then PlaySound("igAbilityOpen") end
|
||||
@@ -210,7 +213,7 @@
|
||||
-- ref SpellBookFrame_SetTabType (l.166-183). `binding` is what the tab's tooltip appends
|
||||
-- the bound key to (MicroButtonTooltipText); `petTitle` is set here as a side effect,
|
||||
-- exactly as the ref does, because this is the only place the token is in hand.
|
||||
function BenillaSpellBookFrame_SetTabType(tabButton, bookType, token)
|
||||
function SpellBookFrame_SetTabType(tabButton, bookType, token)
|
||||
if bookType == BOOKTYPE_SPELL then
|
||||
tabButton.bookType = BOOKTYPE_SPELL
|
||||
tabButton:SetText(SPELLBOOK)
|
||||
@@ -220,12 +223,12 @@
|
||||
local label = getglobal("PET_TYPE_" .. (token or "PET")) or PET_TYPE_PET
|
||||
tabButton:SetText(label)
|
||||
tabButton.binding = "TOGGLEPETBOOK"
|
||||
BenillaSpellBookFrame.petTitle = label
|
||||
SpellBookFrame.petTitle = label
|
||||
end
|
||||
-- The tab for the book you are ON is disabled, which is also what draws it as the
|
||||
-- selected (Tab1-Selected) plate — the ref uses <DisabledTexture> for the "current"
|
||||
-- look rather than a checked state.
|
||||
if BenillaSpellBookFrame.bookType == bookType then
|
||||
if SpellBookFrame.bookType == bookType then
|
||||
tabButton:Disable()
|
||||
else
|
||||
tabButton:Enable()
|
||||
@@ -244,16 +247,16 @@
|
||||
frame.clock = frame.clock - BENILLA_PET_AUTOCAST_PERIOD
|
||||
end
|
||||
for i = 1, SPELLS_PER_PAGE do
|
||||
BenillaPetAutocast_Update(getglobal("BenillaSpellButton" .. i), frame.clock)
|
||||
BenillaPetAutocast_Update(getglobal("SpellButton" .. i), frame.clock)
|
||||
end
|
||||
end
|
||||
|
||||
-- ref SpellBookFrame_OnHide (l.186-204): MultiActionBar_HideAllGrids and the tab-flash
|
||||
-- cleanup OMITTED (header comment); always the spell-book close kit (no pet book to branch
|
||||
-- on). UpdateMicroButtons is LIVE as of the micro-menu slice (MicroMenu.xml).
|
||||
function BenillaSpellBookFrame_OnHide()
|
||||
function SpellBookFrame_OnHide()
|
||||
if UpdateMicroButtons then UpdateMicroButtons() end
|
||||
if BenillaSpellBookFrame.bookType == BOOKTYPE_PET then
|
||||
if SpellBookFrame.bookType == BOOKTYPE_PET then
|
||||
PlaySound("igAbilityClose") -- ref l.191
|
||||
else
|
||||
PlaySound("igSpellBookClose")
|
||||
@@ -266,7 +269,7 @@
|
||||
-- page (this file's own convention: explicit button params, not ambient `this`).
|
||||
function BenillaUpdateSpells()
|
||||
for i = 1, SPELLS_PER_PAGE do
|
||||
BenillaSpellButton_UpdateButton(getglobal("BenillaSpellButton" .. i))
|
||||
BenillaSpellButton_UpdateButton(getglobal("SpellButton" .. i))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -277,7 +280,8 @@
|
||||
-- PET_BAR_UPDATE is LIVE (ref l.214 + l.227-231) and it is the pet page's whole repaint
|
||||
-- wire: the pet book's cooldowns, autocast bits and active ring all move without the BOOK
|
||||
-- changing, so SPELLS_CHANGED alone would leave a stale page.
|
||||
function BenillaSpellButton_OnLoad(button)
|
||||
function SpellButton_OnLoad()
|
||||
local button = this
|
||||
button:RegisterEvent("SPELLS_CHANGED")
|
||||
button:RegisterEvent("CURRENT_SPELL_CAST_CHANGED")
|
||||
button:RegisterEvent("SPELL_UPDATE_COOLDOWN")
|
||||
@@ -293,13 +297,14 @@
|
||||
BenillaSpellButton_UpdateButton(button)
|
||||
end
|
||||
|
||||
function BenillaSpellButton_OnEvent(button)
|
||||
function SpellButton_OnEvent(event)
|
||||
local button = this
|
||||
if event == "SPELLS_CHANGED" or event == "SPELL_UPDATE_COOLDOWN" then
|
||||
BenillaSpellButton_UpdateButton(button)
|
||||
elseif event == "CURRENT_SPELL_CAST_CHANGED" then
|
||||
BenillaSpellButton_UpdateSelection(button)
|
||||
elseif event == "PET_BAR_UPDATE" then
|
||||
if BenillaSpellBookFrame.bookType == BOOKTYPE_PET then
|
||||
if SpellBookFrame.bookType == BOOKTYPE_PET then
|
||||
BenillaSpellButton_UpdateButton(button)
|
||||
end
|
||||
end
|
||||
@@ -312,8 +317,8 @@
|
||||
-- The ref's out-of-tab bounds fork collapses into the one SetChecked: IsCurrentCast
|
||||
-- answers nil past the book's end already.
|
||||
function BenillaSpellButton_UpdateSelection(button)
|
||||
local id = BenillaSpellBook_GetSpellID(button:GetID())
|
||||
button:SetChecked(IsCurrentCast(id, BenillaSpellBookFrame.bookType))
|
||||
local id = SpellBook_GetSpellID(button:GetID())
|
||||
button:SetChecked(IsCurrentCast(id, SpellBookFrame.bookType))
|
||||
end
|
||||
|
||||
-- ref SpellButton_OnEnter (l.235-244): the engine's SetSpell — the byte-verified spell
|
||||
@@ -322,12 +327,12 @@
|
||||
-- verified showRank=0).
|
||||
function BenillaSpellButton_OnEnter(button)
|
||||
if not GameTooltip then return end -- engine tests load file subsets
|
||||
local id = BenillaSpellBook_GetSpellID(button:GetID())
|
||||
if not GetSpellName(id, BenillaSpellBookFrame.bookType) then
|
||||
local id = SpellBook_GetSpellID(button:GetID())
|
||||
if not GetSpellName(id, SpellBookFrame.bookType) then
|
||||
return
|
||||
end
|
||||
GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
|
||||
GameTooltip:SetSpell(id, BenillaSpellBookFrame.bookType)
|
||||
GameTooltip:SetSpell(id, SpellBookFrame.bookType)
|
||||
end
|
||||
|
||||
-- ref SpellButton_OnClick (l.263-290): the MacroFrame branch and the right-click pet
|
||||
@@ -336,17 +341,22 @@
|
||||
-- `id > MAX_SPELLS` guard is dropped too: GetSpellName/CastSpell/PickupSpell already
|
||||
-- answer nil/no-op for any id past the book's real length, so the extra guard checks
|
||||
-- nothing a MAX_SPELLS constant would add here.
|
||||
function BenillaSpellButton_OnClick(button, drag, mouseButton)
|
||||
local id = BenillaSpellBook_GetSpellID(button:GetID())
|
||||
function SpellButton_OnClick(drag)
|
||||
local button = this
|
||||
-- The mouse button is the ambient `arg1`, exactly as the reference reads it (l.284) —
|
||||
-- its XML passes nothing on OnClick and a bare `1` on the two drag slots. Only the
|
||||
-- `drag` falsy arm below reaches it, so the drag slots never read a stale arg1.
|
||||
local mouseButton = arg1
|
||||
local id = SpellBook_GetSpellID(button:GetID())
|
||||
button:SetChecked("false")
|
||||
if drag then
|
||||
PickupSpell(id, BenillaSpellBookFrame.bookType)
|
||||
PickupSpell(id, SpellBookFrame.bookType)
|
||||
elseif not IsShiftKeyDown() and mouseButton and mouseButton ~= "LeftButton"
|
||||
and BenillaSpellBookFrame.bookType == BOOKTYPE_PET then
|
||||
and SpellBookFrame.bookType == BOOKTYPE_PET then
|
||||
-- ref l.284-285: on the PET page a non-left click flips autocast instead of
|
||||
-- casting. Shift still outranks it — the ref tests shift first, so a shift
|
||||
-- right-click on a pet spell picks it up.
|
||||
ToggleSpellAutocast(id, BenillaSpellBookFrame.bookType)
|
||||
ToggleSpellAutocast(id, SpellBookFrame.bookType)
|
||||
elseif IsShiftKeyDown() then
|
||||
-- ref SpellButton_OnClick l.271-283 (LIVE since decision 0983): with the macro
|
||||
-- editor open a shift-click APPENDS `/cast <name>[(<rank>)]` to the body instead of
|
||||
@@ -355,8 +365,8 @@
|
||||
-- The `/cast` alias comes from the shipped strings (SLASH_CAST1), never typed here.
|
||||
local macroFrame = getglobal("BenillaMacroFrame")
|
||||
if macroFrame and macroFrame:IsVisible() then
|
||||
local spellName, subSpellName = GetSpellName(id, BenillaSpellBookFrame.bookType)
|
||||
if spellName and not IsSpellPassive(id, BenillaSpellBookFrame.bookType) then
|
||||
local spellName, subSpellName = GetSpellName(id, SpellBookFrame.bookType)
|
||||
if spellName and not IsSpellPassive(id, SpellBookFrame.bookType) then
|
||||
local line = (SLASH_CAST1 or "/cast") .. " " .. spellName
|
||||
if subSpellName and string.len(subSpellName) > 0 then
|
||||
line = line .. "(" .. subSpellName .. ")"
|
||||
@@ -364,10 +374,10 @@
|
||||
BenillaMacroFrame_AddMacroLine(line)
|
||||
end
|
||||
else
|
||||
PickupSpell(id, BenillaSpellBookFrame.bookType)
|
||||
PickupSpell(id, SpellBookFrame.bookType)
|
||||
end
|
||||
else
|
||||
CastSpell(id, BenillaSpellBookFrame.bookType)
|
||||
CastSpell(id, SpellBookFrame.bookType)
|
||||
BenillaSpellButton_UpdateSelection(button) -- ref l.288: the ring re-reads at once
|
||||
end
|
||||
end
|
||||
@@ -383,15 +393,15 @@
|
||||
if GameTooltip:IsOwned(button) then
|
||||
BenillaSpellButton_OnEnter(button)
|
||||
end
|
||||
if not BenillaSpellBookFrame.selectedSkillLine then
|
||||
BenillaSpellBookFrame.selectedSkillLine = 1
|
||||
if not SpellBookFrame.selectedSkillLine then
|
||||
SpellBookFrame.selectedSkillLine = 1
|
||||
end
|
||||
local isPet = BenillaSpellBookFrame.bookType == BOOKTYPE_PET
|
||||
local temp, tabTexture, offset, numSpells = GetSpellTabInfo(BenillaSpellBookFrame.selectedSkillLine)
|
||||
local isPet = SpellBookFrame.bookType == BOOKTYPE_PET
|
||||
local temp, tabTexture, offset, numSpells = GetSpellTabInfo(SpellBookFrame.selectedSkillLine)
|
||||
offset = offset or 0
|
||||
numSpells = numSpells or 0
|
||||
BenillaSpellBookFrame.selectedSkillLineOffset = offset
|
||||
local id = BenillaSpellBook_GetSpellID(button:GetID())
|
||||
SpellBookFrame.selectedSkillLineOffset = offset
|
||||
local id = SpellBook_GetSpellID(button:GetID())
|
||||
local name = button:GetName()
|
||||
local iconTexture = getglobal(name .. "IconTexture")
|
||||
local spellString = getglobal(name .. "SpellName")
|
||||
@@ -416,7 +426,7 @@
|
||||
else
|
||||
button:Enable()
|
||||
end
|
||||
local texture = GetSpellTexture(id, BenillaSpellBookFrame.bookType)
|
||||
local texture = GetSpellTexture(id, SpellBookFrame.bookType)
|
||||
local highlightTexture = getglobal(name .. "Highlight")
|
||||
local normalTexture = getglobal(name .. "NormalTexture")
|
||||
if (not texture) or (texture == "") then
|
||||
@@ -434,7 +444,7 @@
|
||||
-- The cooldown sweep + the enable-keyed icon dim (ref l.359-365): enable == 0 is an
|
||||
-- on-hold cooldown (Stealth/Feign Death parked until SMSG_COOLDOWN_EVENT) — the
|
||||
-- widget stays hidden (CooldownFrame_SetTimer's own gate) and the icon reads 40%.
|
||||
local start, duration, enable = GetSpellCooldown(id, BenillaSpellBookFrame.bookType)
|
||||
local start, duration, enable = GetSpellCooldown(id, SpellBookFrame.bookType)
|
||||
CooldownFrame_SetTimer(cooldown, start, duration, enable)
|
||||
if enable == 1 then
|
||||
iconTexture:SetVertexColor(1.0, 1.0, 1.0)
|
||||
@@ -444,11 +454,11 @@
|
||||
-- ref l.367-377: the autocast pair. `GetSpellAutocast` answers (nil, nil) for the
|
||||
-- player book by the engine's own short-circuit, so this needs no isPet guard — the
|
||||
-- overlay and the sparkle simply never come on outside the pet page.
|
||||
local autoCastAllowed, autoCastEnabled = GetSpellAutocast(id, BenillaSpellBookFrame.bookType)
|
||||
local autoCastAllowed, autoCastEnabled = GetSpellAutocast(id, SpellBookFrame.bookType)
|
||||
if autoCastAllowed then autocastable:Show() else autocastable:Hide() end
|
||||
button.autocasting = autoCastEnabled
|
||||
local spellName, subSpellName = GetSpellName(id, BenillaSpellBookFrame.bookType)
|
||||
local isPassive = IsSpellPassive(id, BenillaSpellBookFrame.bookType)
|
||||
local spellName, subSpellName = GetSpellName(id, SpellBookFrame.bookType)
|
||||
local isPassive = IsSpellPassive(id, SpellBookFrame.bookType)
|
||||
if isPassive then
|
||||
normalTexture:SetVertexColor(0, 0, 0)
|
||||
highlightTexture:SetTexture("Interface\\Buttons\\UI-PassiveHighlight")
|
||||
@@ -479,47 +489,47 @@
|
||||
-- reads `SPELLBOOK_PAGENUMBERS["pet"]` for the pet one — a key nothing writes. So on the
|
||||
-- pet page the arrows turn nothing, the page text says a number the page does not honour,
|
||||
-- and the *spell* book's page counter moves behind your back. All three are the reference's
|
||||
-- and all three are reproduced; `BenillaSpellBook_GetSpellID`'s note is the other half.
|
||||
-- and all three are reproduced; `SpellBook_GetSpellID`'s note is the other half.
|
||||
function BenillaSpellBookPrevPageButton_OnClick()
|
||||
local pageNum = BenillaSpellBook_GetCurrentPage() - 1
|
||||
SPELLBOOK_PAGENUMBERS[BenillaSpellBookFrame.selectedSkillLine] = pageNum
|
||||
BenillaSpellBook_UpdatePageArrows()
|
||||
BenillaSpellBookPageText:SetText(format(PAGE_NUMBER, pageNum))
|
||||
local pageNum = SpellBook_GetCurrentPage() - 1
|
||||
SPELLBOOK_PAGENUMBERS[SpellBookFrame.selectedSkillLine] = pageNum
|
||||
SpellBook_UpdatePageArrows()
|
||||
SpellBookPageText:SetText(format(PAGE_NUMBER, pageNum))
|
||||
BenillaUpdateSpells()
|
||||
-- ref l.414-417: the pet page re-asserts its own title, because the spell book's page
|
||||
-- text handler is shared and the title is not.
|
||||
if BenillaSpellBookFrame.bookType == BOOKTYPE_PET then
|
||||
BenillaSpellBookTitleText:SetText(BenillaSpellBookFrame.petTitle)
|
||||
if SpellBookFrame.bookType == BOOKTYPE_PET then
|
||||
SpellBookTitleText:SetText(SpellBookFrame.petTitle)
|
||||
end
|
||||
PlaySound("igAbiliityPageTurn")
|
||||
end
|
||||
|
||||
function BenillaSpellBookNextPageButton_OnClick()
|
||||
local pageNum = BenillaSpellBook_GetCurrentPage() + 1
|
||||
SPELLBOOK_PAGENUMBERS[BenillaSpellBookFrame.selectedSkillLine] = pageNum
|
||||
BenillaSpellBook_UpdatePageArrows()
|
||||
BenillaSpellBookPageText:SetText(format(PAGE_NUMBER, pageNum))
|
||||
local pageNum = SpellBook_GetCurrentPage() + 1
|
||||
SPELLBOOK_PAGENUMBERS[SpellBookFrame.selectedSkillLine] = pageNum
|
||||
SpellBook_UpdatePageArrows()
|
||||
SpellBookPageText:SetText(format(PAGE_NUMBER, pageNum))
|
||||
BenillaUpdateSpells()
|
||||
if BenillaSpellBookFrame.bookType == BOOKTYPE_PET then
|
||||
BenillaSpellBookTitleText:SetText(BenillaSpellBookFrame.petTitle)
|
||||
if SpellBookFrame.bookType == BOOKTYPE_PET then
|
||||
SpellBookTitleText:SetText(SpellBookFrame.petTitle)
|
||||
end
|
||||
PlaySound("igAbiliityPageTurn")
|
||||
end
|
||||
|
||||
-- ── The skill-line tabs (ref SpellBookSkillLineTab_OnClick, l.436-457) ───────────────────
|
||||
function BenillaSpellBookSkillLineTab_OnClick(id)
|
||||
function SpellBookSkillLineTab_OnClick(id)
|
||||
local update
|
||||
if not id then
|
||||
update = 1
|
||||
id = this:GetID()
|
||||
end
|
||||
BenillaSpellBookFrame.selectedSkillLine = id
|
||||
SpellBookFrame.selectedSkillLine = id
|
||||
local name, texture, offset, numSpells = GetSpellTabInfo(id)
|
||||
BenillaSpellBookFrame.selectedSkillLineOffset = offset or 0
|
||||
BenillaSpellBookFrame.selectedSkillLineNumSpells = numSpells or 0
|
||||
BenillaSpellBook_UpdatePageArrows()
|
||||
BenillaSpellBookFrame_Update()
|
||||
BenillaSpellBookPageText:SetText(format(PAGE_NUMBER, BenillaSpellBook_GetCurrentPage()))
|
||||
SpellBookFrame.selectedSkillLineOffset = offset or 0
|
||||
SpellBookFrame.selectedSkillLineNumSpells = numSpells or 0
|
||||
SpellBook_UpdatePageArrows()
|
||||
SpellBookFrame_Update()
|
||||
SpellBookPageText:SetText(format(PAGE_NUMBER, SpellBook_GetCurrentPage()))
|
||||
if update then
|
||||
BenillaUpdateSpells()
|
||||
end
|
||||
@@ -533,26 +543,26 @@
|
||||
-- always shows book entries 1..12 no matter what page number the window is displaying. A
|
||||
-- well-trained hunter pet can know more than twelve spells, so this is visible, and it is
|
||||
-- still the reference's behaviour. Its sibling quirk is in the two page-turn handlers.
|
||||
function BenillaSpellBook_GetSpellID(id)
|
||||
if BenillaSpellBookFrame.bookType == BOOKTYPE_PET then
|
||||
function SpellBook_GetSpellID(id)
|
||||
if SpellBookFrame.bookType == BOOKTYPE_PET then
|
||||
return id
|
||||
end
|
||||
return id + (BenillaSpellBookFrame.selectedSkillLineOffset or 0)
|
||||
+ (SPELLS_PER_PAGE * ((SPELLBOOK_PAGENUMBERS[BenillaSpellBookFrame.selectedSkillLine] or 1) - 1))
|
||||
return id + (SpellBookFrame.selectedSkillLineOffset or 0)
|
||||
+ (SPELLS_PER_PAGE * ((SPELLBOOK_PAGENUMBERS[SpellBookFrame.selectedSkillLine] or 1) - 1))
|
||||
end
|
||||
|
||||
-- ref SpellBook_UpdatePageArrows (l.467-479).
|
||||
function BenillaSpellBook_UpdatePageArrows()
|
||||
local currentPage, maxPages = BenillaSpellBook_GetCurrentPage()
|
||||
function SpellBook_UpdatePageArrows()
|
||||
local currentPage, maxPages = SpellBook_GetCurrentPage()
|
||||
if currentPage == 1 then
|
||||
BenillaSpellBookPrevPageButton:Disable()
|
||||
SpellBookPrevPageButton:Disable()
|
||||
else
|
||||
BenillaSpellBookPrevPageButton:Enable()
|
||||
SpellBookPrevPageButton:Enable()
|
||||
end
|
||||
if currentPage == maxPages then
|
||||
BenillaSpellBookNextPageButton:Disable()
|
||||
SpellBookNextPageButton:Disable()
|
||||
else
|
||||
BenillaSpellBookNextPageButton:Enable()
|
||||
SpellBookNextPageButton:Enable()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -564,15 +574,15 @@
|
||||
-- The pet branch reads `SPELLBOOK_PAGENUMBERS["pet"]`, which is the counter NOTHING ever
|
||||
-- writes — see the page-turn handlers. So it is always 1, and with it `maxPages > 1` the
|
||||
-- Next arrow stays enabled forever on a big pet book. Reference behaviour; see the
|
||||
-- do-not-fix note on `BenillaSpellBook_GetSpellID`.
|
||||
function BenillaSpellBook_GetCurrentPage()
|
||||
-- do-not-fix note on `SpellBook_GetSpellID`.
|
||||
function SpellBook_GetCurrentPage()
|
||||
local numPetSpells = HasPetSpells()
|
||||
if numPetSpells and BenillaSpellBookFrame.bookType == BOOKTYPE_PET then
|
||||
if numPetSpells and SpellBookFrame.bookType == BOOKTYPE_PET then
|
||||
return SPELLBOOK_PAGENUMBERS["pet"] or 1,
|
||||
max(ceil(numPetSpells / SPELLS_PER_PAGE), 1)
|
||||
end
|
||||
local currentPage = SPELLBOOK_PAGENUMBERS[BenillaSpellBookFrame.selectedSkillLine] or 1
|
||||
local temp, texture, offset, numSpells = GetSpellTabInfo(BenillaSpellBookFrame.selectedSkillLine)
|
||||
local currentPage = SPELLBOOK_PAGENUMBERS[SpellBookFrame.selectedSkillLine] or 1
|
||||
local temp, texture, offset, numSpells = GetSpellTabInfo(SpellBookFrame.selectedSkillLine)
|
||||
local maxPages = max(ceil((numSpells or 0) / SPELLS_PER_PAGE), 1)
|
||||
return currentPage, maxPages
|
||||
end
|
||||
@@ -601,7 +611,7 @@
|
||||
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
|
||||
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
|
||||
<Scripts>
|
||||
<OnClick>BenillaSpellBookSkillLineTab_OnClick()</OnClick>
|
||||
<OnClick>SpellBookSkillLineTab_OnClick()</OnClick>
|
||||
<OnEnter>
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
if self.tooltip then
|
||||
@@ -671,14 +681,14 @@
|
||||
</Cooldown>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaSpellButton_OnLoad(self)</OnLoad>
|
||||
<OnEvent>BenillaSpellButton_OnEvent(self)</OnEvent>
|
||||
<OnClick>BenillaSpellButton_OnClick(self, nil, arg1)</OnClick>
|
||||
<OnLoad>SpellButton_OnLoad()</OnLoad>
|
||||
<OnEvent>SpellButton_OnEvent(event)</OnEvent>
|
||||
<OnClick>SpellButton_OnClick()</OnClick>
|
||||
<OnShow>BenillaSpellButton_UpdateButton(self)</OnShow>
|
||||
<!-- The reference routes both drag entries to the plain click path with drag=1
|
||||
(SpellButtonTemplate l.169-174) — the bag/doll/action slots' own precedent. -->
|
||||
<OnDragStart>BenillaSpellButton_OnClick(self, 1)</OnDragStart>
|
||||
<OnReceiveDrag>BenillaSpellButton_OnClick(self, 1)</OnReceiveDrag>
|
||||
<OnDragStart>SpellButton_OnClick(1)</OnDragStart>
|
||||
<OnReceiveDrag>SpellButton_OnClick(1)</OnReceiveDrag>
|
||||
<OnEnter>BenillaSpellButton_OnEnter(self)</OnEnter>
|
||||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||||
</Scripts>
|
||||
@@ -726,7 +736,7 @@
|
||||
<HighlightTexture file="Interface\SpellBook\UI-SpellbookPanel-Tab-Highlight" alphaMode="ADD"/>
|
||||
</Button>
|
||||
|
||||
<Frame name="BenillaSpellBookFrame" hidden="true">
|
||||
<Frame name="SpellBookFrame" hidden="true">
|
||||
<!-- The real 384x512 window (ref l.202-205). No <Anchors> — ShowUIPanel/SetLeftFrame
|
||||
SetPoints this to the left slot on open (UiPanels.xml's own convention). -->
|
||||
<Size><AbsDimension x="384" y="512"/></Size>
|
||||
@@ -762,12 +772,12 @@
|
||||
<Size><AbsDimension x="128" y="256"/></Size>
|
||||
<Anchors><Anchor point="BOTTOMRIGHT"/></Anchors>
|
||||
</Texture>
|
||||
<FontString name="BenillaSpellBookTitleText" inherits="GameFontNormal" text="SPELLBOOK">
|
||||
<FontString name="SpellBookTitleText" inherits="GameFontNormal" text="SPELLBOOK">
|
||||
<Anchors>
|
||||
<Anchor point="CENTER"><Offset><AbsDimension x="6" y="230"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="BenillaSpellBookPageText" inherits="GameFontNormal">
|
||||
<FontString name="SpellBookPageText" inherits="GameFontNormal">
|
||||
<Size><AbsDimension x="102" y="0"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM"><Offset><AbsDimension x="-14" y="96"/></Offset></Anchor>
|
||||
@@ -779,53 +789,53 @@
|
||||
<!-- Close: the house minimize-button art (no UIPanelCloseButton template ships —
|
||||
Gossip/Bag/Merchant/CharacterFrame's own precedent), the same TOPRIGHT(-44,-25)
|
||||
position as every other window's close button. -->
|
||||
<Button name="BenillaSpellBookFrameCloseButton">
|
||||
<Button name="SpellBookFrameCloseButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors><Anchor point="CENTER" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-44" y="-25"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Down"/>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Highlight" alphaMode="ADD"/>
|
||||
<Scripts>
|
||||
<OnClick>HideUIPanel(BenillaSpellBookFrame)</OnClick>
|
||||
<OnClick>HideUIPanel(SpellBookFrame)</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- ref l.288-317. Tab 3 exists in the ref's own XML and is never raised in 1.12
|
||||
(there is no third book); kept so the anchor chain reads like the ref's. -->
|
||||
<Button name="BenillaSpellBookFrameTabButton1" inherits="BenillaSpellBookFrameTabButtonTemplate">
|
||||
<Button name="SpellBookFrameTabButton1" inherits="BenillaSpellBookFrameTabButtonTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="79" y="61"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<DisabledTexture file="Interface\SpellBook\UI-SpellBook-Tab1-Selected"/>
|
||||
</Button>
|
||||
<Button name="BenillaSpellBookFrameTabButton2" inherits="BenillaSpellBookFrameTabButtonTemplate">
|
||||
<Button name="SpellBookFrameTabButton2" inherits="BenillaSpellBookFrameTabButtonTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="BenillaSpellBookFrameTabButton1" relativePoint="RIGHT">
|
||||
<Anchor point="LEFT" relativeTo="SpellBookFrameTabButton1" relativePoint="RIGHT">
|
||||
<Offset><AbsDimension x="-20" y="0"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<DisabledTexture file="Interface\SpellBook\UI-SpellBook-Tab1-Selected"/>
|
||||
</Button>
|
||||
<Button name="BenillaSpellBookFrameTabButton3" inherits="BenillaSpellBookFrameTabButtonTemplate">
|
||||
<Button name="SpellBookFrameTabButton3" inherits="BenillaSpellBookFrameTabButtonTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="BenillaSpellBookFrameTabButton2" relativePoint="RIGHT">
|
||||
<Anchor point="LEFT" relativeTo="SpellBookFrameTabButton2" relativePoint="RIGHT">
|
||||
<Offset><AbsDimension x="-20" y="0"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<DisabledTexture file="Interface\SpellBook\UI-SpellBook-Tab3-Selected"/>
|
||||
</Button>
|
||||
|
||||
<Button name="BenillaSpellBookPrevPageButton">
|
||||
<Button name="SpellBookPrevPageButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativeTo="BenillaSpellBookFrame" relativePoint="BOTTOMLEFT">
|
||||
<Anchor point="CENTER" relativeTo="SpellBookFrame" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="50" y="105"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<FontString inherits="GameFontNormal" justifyH="LEFT" text="PREV">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaSpellBookPrevPageButton" relativePoint="RIGHT"/></Anchors>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="SpellBookPrevPageButton" relativePoint="RIGHT"/></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
@@ -835,17 +845,17 @@
|
||||
<DisabledTexture file="Interface\Buttons\UI-SpellbookIcon-PrevPage-Disabled"/>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
|
||||
</Button>
|
||||
<Button name="BenillaSpellBookNextPageButton">
|
||||
<Button name="SpellBookNextPageButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativeTo="BenillaSpellBookFrame" relativePoint="BOTTOMLEFT">
|
||||
<Anchor point="CENTER" relativeTo="SpellBookFrame" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="314" y="105"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<FontString inherits="GameFontNormal" justifyH="RIGHT" text="NEXT">
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="BenillaSpellBookNextPageButton" relativePoint="LEFT"/></Anchors>
|
||||
<Anchors><Anchor point="RIGHT" relativeTo="SpellBookNextPageButton" relativePoint="LEFT"/></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
@@ -860,74 +870,74 @@
|
||||
ref's own column-major pattern (col1 ids 1-6, col2 ids 7-12) — SpellBook_GetSpellID
|
||||
reads a button's `id=` as its within-page/within-tab 1-based position, not its
|
||||
screen order. -->
|
||||
<CheckButton name="BenillaSpellButton1" inherits="BenillaSpellButtonTemplate" id="1">
|
||||
<CheckButton name="SpellButton1" inherits="BenillaSpellButtonTemplate" id="1">
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="34" y="-85"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellButton2" inherits="BenillaSpellButtonTemplate" id="7">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellButton1"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellButton2" inherits="BenillaSpellButtonTemplate" id="7">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton1"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellButton3" inherits="BenillaSpellButtonTemplate" id="2">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellButton1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellButton3" inherits="BenillaSpellButtonTemplate" id="2">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellButton4" inherits="BenillaSpellButtonTemplate" id="8">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellButton3"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellButton4" inherits="BenillaSpellButtonTemplate" id="8">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton3"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellButton5" inherits="BenillaSpellButtonTemplate" id="3">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellButton3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellButton5" inherits="BenillaSpellButtonTemplate" id="3">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellButton6" inherits="BenillaSpellButtonTemplate" id="9">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellButton5"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellButton6" inherits="BenillaSpellButtonTemplate" id="9">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton5"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellButton7" inherits="BenillaSpellButtonTemplate" id="4">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellButton5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellButton7" inherits="BenillaSpellButtonTemplate" id="4">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellButton8" inherits="BenillaSpellButtonTemplate" id="10">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellButton7"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellButton8" inherits="BenillaSpellButtonTemplate" id="10">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton7"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellButton9" inherits="BenillaSpellButtonTemplate" id="5">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellButton7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellButton9" inherits="BenillaSpellButtonTemplate" id="5">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellButton10" inherits="BenillaSpellButtonTemplate" id="11">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellButton9"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellButton10" inherits="BenillaSpellButtonTemplate" id="11">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton9"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellButton11" inherits="BenillaSpellButtonTemplate" id="6">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellButton9" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellButton11" inherits="BenillaSpellButtonTemplate" id="6">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton9" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellButton12" inherits="BenillaSpellButtonTemplate" id="12">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellButton11"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellButton12" inherits="BenillaSpellButtonTemplate" id="12">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton11"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
|
||||
<!-- The right-edge skill-line tabs, up to MAX_SKILLLINE_TABS (8) — ref l.495-566. -->
|
||||
<CheckButton name="BenillaSpellBookSkillLineTab1" inherits="BenillaSpellBookSkillLineTabTemplate" id="1">
|
||||
<CheckButton name="SpellBookSkillLineTab1" inherits="BenillaSpellBookSkillLineTabTemplate" id="1">
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-32" y="-65"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellBookSkillLineTab2" inherits="BenillaSpellBookSkillLineTabTemplate" id="2">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellBookSkillLineTab1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellBookSkillLineTab2" inherits="BenillaSpellBookSkillLineTabTemplate" id="2">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellBookSkillLineTab1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellBookSkillLineTab3" inherits="BenillaSpellBookSkillLineTabTemplate" id="3">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellBookSkillLineTab2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellBookSkillLineTab3" inherits="BenillaSpellBookSkillLineTabTemplate" id="3">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellBookSkillLineTab2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellBookSkillLineTab4" inherits="BenillaSpellBookSkillLineTabTemplate" id="4">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellBookSkillLineTab3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellBookSkillLineTab4" inherits="BenillaSpellBookSkillLineTabTemplate" id="4">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellBookSkillLineTab3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellBookSkillLineTab5" inherits="BenillaSpellBookSkillLineTabTemplate" id="5">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellBookSkillLineTab4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellBookSkillLineTab5" inherits="BenillaSpellBookSkillLineTabTemplate" id="5">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellBookSkillLineTab4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellBookSkillLineTab6" inherits="BenillaSpellBookSkillLineTabTemplate" id="6">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellBookSkillLineTab5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellBookSkillLineTab6" inherits="BenillaSpellBookSkillLineTabTemplate" id="6">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellBookSkillLineTab5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellBookSkillLineTab7" inherits="BenillaSpellBookSkillLineTabTemplate" id="7">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellBookSkillLineTab6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellBookSkillLineTab7" inherits="BenillaSpellBookSkillLineTabTemplate" id="7">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellBookSkillLineTab6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaSpellBookSkillLineTab8" inherits="BenillaSpellBookSkillLineTabTemplate" id="8">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaSpellBookSkillLineTab7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="SpellBookSkillLineTab8" inherits="BenillaSpellBookSkillLineTabTemplate" id="8">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellBookSkillLineTab7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||||
</CheckButton>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaSpellBookFrame_OnLoad(self)</OnLoad>
|
||||
<OnEvent>BenillaSpellBookFrame_OnEvent(self)</OnEvent>
|
||||
<OnShow>BenillaSpellBookFrame_OnShow()</OnShow>
|
||||
<OnHide>BenillaSpellBookFrame_OnHide()</OnHide>
|
||||
<OnLoad>SpellBookFrame_OnLoad()</OnLoad>
|
||||
<OnEvent>SpellBookFrame_OnEvent()</OnEvent>
|
||||
<OnShow>SpellBookFrame_OnShow()</OnShow>
|
||||
<OnHide>SpellBookFrame_OnHide()</OnHide>
|
||||
<OnUpdate>BenillaSpellBookFrame_OnUpdate(self, arg1)</OnUpdate>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
-- ContainerFrameItemButton_OnClick's own closure). maxStack < 2 hides instead of showing (a
|
||||
-- 1-stack has nothing to split) — the ref's own guard.
|
||||
function OpenStackSplitFrame(maxStack, owner, anchor, anchorTo)
|
||||
local frame = BenillaStackSplitFrame
|
||||
local frame = StackSplitFrame
|
||||
if frame.owner then
|
||||
frame.owner.hasStackSplit = nil
|
||||
end
|
||||
@@ -51,9 +51,9 @@
|
||||
frame.owner = owner
|
||||
owner.hasStackSplit = true
|
||||
frame.split = 1
|
||||
BenillaStackSplitText:SetText(frame.split)
|
||||
BenillaStackSplitLeftButton:Disable()
|
||||
BenillaStackSplitRightButton:Enable()
|
||||
StackSplitText:SetText(frame.split)
|
||||
StackSplitLeftButton:Disable()
|
||||
StackSplitRightButton:Enable()
|
||||
-- ref l.21-22: ClearAllPoints first — SetPoint only replaces an anchor of the SAME
|
||||
-- point, and the anchor callers choose (BagFrame.xml passes "BOTTOMRIGHT") differs from
|
||||
-- this frame's authored placeholder ("CENTER"), so skipping this would leave both live.
|
||||
@@ -67,7 +67,7 @@
|
||||
-- (no caller has a live reason to resize an open spinner today) — kept so a later caller
|
||||
-- (the paper doll / bank slice) drops in without a new function.
|
||||
function UpdateStackSplitFrame(maxStack)
|
||||
local frame = BenillaStackSplitFrame
|
||||
local frame = StackSplitFrame
|
||||
frame.maxStack = maxStack
|
||||
if frame.maxStack < 2 then
|
||||
if frame.owner then
|
||||
@@ -78,44 +78,44 @@
|
||||
end
|
||||
if frame.split > frame.maxStack then
|
||||
frame.split = frame.maxStack
|
||||
BenillaStackSplitText:SetText(frame.split)
|
||||
StackSplitText:SetText(frame.split)
|
||||
end
|
||||
if frame.split == frame.maxStack then
|
||||
BenillaStackSplitRightButton:Disable()
|
||||
StackSplitRightButton:Disable()
|
||||
else
|
||||
BenillaStackSplitRightButton:Enable()
|
||||
StackSplitRightButton:Enable()
|
||||
end
|
||||
if frame.split == 1 then
|
||||
BenillaStackSplitLeftButton:Disable()
|
||||
StackSplitLeftButton:Disable()
|
||||
else
|
||||
BenillaStackSplitLeftButton:Enable()
|
||||
StackSplitLeftButton:Enable()
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaStackSplitLeft_Click()
|
||||
local frame = BenillaStackSplitFrame
|
||||
local frame = StackSplitFrame
|
||||
if frame.split == 1 then return end
|
||||
frame.split = frame.split - 1
|
||||
BenillaStackSplitText:SetText(frame.split)
|
||||
StackSplitText:SetText(frame.split)
|
||||
if frame.split == 1 then
|
||||
BenillaStackSplitLeftButton:Disable()
|
||||
StackSplitLeftButton:Disable()
|
||||
end
|
||||
BenillaStackSplitRightButton:Enable()
|
||||
StackSplitRightButton:Enable()
|
||||
end
|
||||
|
||||
function BenillaStackSplitRight_Click()
|
||||
local frame = BenillaStackSplitFrame
|
||||
local frame = StackSplitFrame
|
||||
if frame.split == frame.maxStack then return end
|
||||
frame.split = frame.split + 1
|
||||
BenillaStackSplitText:SetText(frame.split)
|
||||
StackSplitText:SetText(frame.split)
|
||||
if frame.split == frame.maxStack then
|
||||
BenillaStackSplitRightButton:Disable()
|
||||
StackSplitRightButton:Disable()
|
||||
end
|
||||
BenillaStackSplitLeftButton:Enable()
|
||||
StackSplitLeftButton:Enable()
|
||||
end
|
||||
|
||||
function BenillaStackSplitOkay_Click()
|
||||
local frame = BenillaStackSplitFrame
|
||||
local frame = StackSplitFrame
|
||||
if frame.owner then
|
||||
frame.owner.SplitStack(frame.owner, frame.split)
|
||||
end
|
||||
@@ -123,7 +123,7 @@
|
||||
end
|
||||
|
||||
function BenillaStackSplitCancel_Click()
|
||||
BenillaStackSplitFrame:Hide()
|
||||
StackSplitFrame:Hide()
|
||||
end
|
||||
|
||||
-- OnHide (ref l.158-162): ANY hide — Okay, Cancel, or ToggleGameMenu's chain hook below —
|
||||
@@ -131,8 +131,8 @@
|
||||
-- l.35-39, checks `this.hasStackSplit` to auto-close the spinner if the SLOT itself goes away
|
||||
-- mid-split — no consumer needs that half here, but the flag's own clear is this function's
|
||||
-- job regardless of which button caused the hide).
|
||||
function BenillaStackSplitFrame_OnHide()
|
||||
local frame = BenillaStackSplitFrame
|
||||
function StackSplitFrame_OnHide()
|
||||
local frame = StackSplitFrame
|
||||
if frame.owner then
|
||||
frame.owner.hasStackSplit = nil
|
||||
end
|
||||
@@ -152,7 +152,7 @@
|
||||
<HighlightTexture file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></HighlightTexture>
|
||||
</Button>
|
||||
|
||||
<Frame name="BenillaStackSplitFrame" frameStrata="HIGH" hidden="true">
|
||||
<Frame name="StackSplitFrame" frameStrata="HIGH" hidden="true">
|
||||
<!-- ref StackSplitFrame.xml l.4-7: 172×96. The CENTER anchor here is only the pre-first-open
|
||||
placeholder — OpenStackSplitFrame always repositions before the first Show. -->
|
||||
<Size><AbsDimension x="172" y="96"/></Size>
|
||||
@@ -167,7 +167,7 @@
|
||||
<TexCoords left="0" right="0.671875" top="0" bottom="0.75"/>
|
||||
</Texture>
|
||||
<!-- ref l.16-24: the count, GameFontHighlight, right-justified, RIGHT -50/+18. -->
|
||||
<FontString name="BenillaStackSplitText" inherits="GameFontHighlight" justifyH="RIGHT">
|
||||
<FontString name="StackSplitText" inherits="GameFontHighlight" justifyH="RIGHT">
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativePoint="RIGHT"><Offset><AbsDimension x="-50" y="18"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
@@ -176,7 +176,7 @@
|
||||
</Layers>
|
||||
<Frames>
|
||||
<!-- ref l.28-47: the left/right arrows, 16×16, RIGHT/LEFT-of-CENTER ±59/64, +18. -->
|
||||
<Button name="BenillaStackSplitLeftButton">
|
||||
<Button name="StackSplitLeftButton">
|
||||
<Size><AbsDimension x="16" y="16"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativePoint="CENTER"><Offset><AbsDimension x="-59" y="18"/></Offset></Anchor>
|
||||
@@ -188,7 +188,7 @@
|
||||
<OnClick>BenillaStackSplitLeft_Click()</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaStackSplitRightButton">
|
||||
<Button name="StackSplitRightButton">
|
||||
<Size><AbsDimension x="16" y="16"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativePoint="CENTER"><Offset><AbsDimension x="64" y="18"/></Offset></Anchor>
|
||||
@@ -201,7 +201,7 @@
|
||||
</Scripts>
|
||||
</Button>
|
||||
<!-- ref l.68-84: Okay, RIGHT-of-BOTTOM -3/+32. -->
|
||||
<Button name="BenillaStackSplitButton1" inherits="BenillaStackSplitButtonTemplate" text="OKAY">
|
||||
<Button name="StackSplitButton1" inherits="BenillaStackSplitButtonTemplate" text="OKAY">
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativePoint="BOTTOM"><Offset><AbsDimension x="-3" y="32"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
@@ -210,7 +210,7 @@
|
||||
</Scripts>
|
||||
</Button>
|
||||
<!-- ref l.85-101: Cancel, LEFT-of-BOTTOM +5/+32. -->
|
||||
<Button name="BenillaStackSplitButton2" inherits="BenillaStackSplitButtonTemplate" text="CANCEL">
|
||||
<Button name="StackSplitButton2" inherits="BenillaStackSplitButtonTemplate" text="CANCEL">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativePoint="BOTTOM"><Offset><AbsDimension x="5" y="32"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
@@ -220,7 +220,7 @@
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnHide>BenillaStackSplitFrame_OnHide()</OnHide>
|
||||
<OnHide>StackSplitFrame_OnHide()</OnHide>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
|
||||
@@ -78,15 +78,15 @@
|
||||
-- bar to the form count (hide the whole frame at 0 — a formless class never shows it),
|
||||
-- then paint each button — icon, checked (active), the 1.0/0.4 castable tint (ref l.210-214),
|
||||
-- cooldown swipe (CooldownFrame_SetTimer, Cooldown.xml's shared transcription).
|
||||
function BenillaShapeshiftBar_Update()
|
||||
function ShapeshiftBar_Update()
|
||||
local numForms = GetNumShapeshiftForms()
|
||||
if numForms > 0 then
|
||||
BenillaShapeshiftBarFrame:Show()
|
||||
ShapeshiftBarFrame:Show()
|
||||
else
|
||||
BenillaShapeshiftBarFrame:Hide()
|
||||
ShapeshiftBarFrame:Hide()
|
||||
end
|
||||
for i = 1, NUM_SHAPESHIFT_SLOTS do
|
||||
local button = getglobal("BenillaShapeshiftButton" .. i)
|
||||
local button = getglobal("ShapeshiftButton" .. i)
|
||||
if i <= numForms then
|
||||
local texture, name, isActive, isCastable = GetShapeshiftFormInfo(i)
|
||||
button.icon:SetTexture(texture or BENILLA_FALLBACK_ICON)
|
||||
@@ -109,14 +109,16 @@
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaShapeshiftBar_OnLoad(frame)
|
||||
function ShapeshiftBar_OnLoad()
|
||||
local frame = this
|
||||
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
frame:RegisterEvent("UPDATE_SHAPESHIFT_FORMS")
|
||||
BenillaShapeshiftBar_Update()
|
||||
ShapeshiftBar_Update()
|
||||
end
|
||||
|
||||
function BenillaShapeshiftBar_OnEvent(frame)
|
||||
BenillaShapeshiftBar_Update()
|
||||
function ShapeshiftBar_OnEvent()
|
||||
local frame = this
|
||||
ShapeshiftBar_Update()
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
@@ -157,56 +159,56 @@
|
||||
</Scripts>
|
||||
</CheckButton>
|
||||
|
||||
<Frame name="BenillaShapeshiftBarFrame" hidden="true">
|
||||
<Frame name="ShapeshiftBarFrame" hidden="true">
|
||||
<Size><AbsDimension x="29" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="BenillaActionBar" relativePoint="TOPLEFT"><Offset><AbsDimension x="30" y="45"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="MainMenuBar" relativePoint="TOPLEFT"><Offset><AbsDimension x="30" y="45"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Frames>
|
||||
<CheckButton name="BenillaShapeshiftButton1" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<CheckButton name="ShapeshiftButton1" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="11" y="3"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaShapeshiftButton_OnLoad(self, 1)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaShapeshiftButton2" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaShapeshiftButton1" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ShapeshiftButton2" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ShapeshiftButton1" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaShapeshiftButton_OnLoad(self, 2)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaShapeshiftButton3" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaShapeshiftButton2" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ShapeshiftButton3" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ShapeshiftButton2" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaShapeshiftButton_OnLoad(self, 3)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaShapeshiftButton4" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaShapeshiftButton3" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ShapeshiftButton4" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ShapeshiftButton3" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaShapeshiftButton_OnLoad(self, 4)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaShapeshiftButton5" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaShapeshiftButton4" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ShapeshiftButton5" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ShapeshiftButton4" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaShapeshiftButton_OnLoad(self, 5)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaShapeshiftButton6" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaShapeshiftButton5" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ShapeshiftButton6" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ShapeshiftButton5" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaShapeshiftButton_OnLoad(self, 6)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaShapeshiftButton7" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaShapeshiftButton6" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ShapeshiftButton7" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ShapeshiftButton6" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaShapeshiftButton_OnLoad(self, 7)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaShapeshiftButton8" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaShapeshiftButton7" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ShapeshiftButton8" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ShapeshiftButton7" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaShapeshiftButton_OnLoad(self, 8)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaShapeshiftButton9" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaShapeshiftButton8" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ShapeshiftButton9" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ShapeshiftButton8" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaShapeshiftButton_OnLoad(self, 9)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
<CheckButton name="BenillaShapeshiftButton10" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaShapeshiftButton9" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<CheckButton name="ShapeshiftButton10" inherits="BenillaShapeshiftButtonTemplate">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="ShapeshiftButton9" relativePoint="RIGHT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor></Anchors>
|
||||
<Scripts><OnLoad>BenillaShapeshiftButton_OnLoad(self, 10)</OnLoad></Scripts>
|
||||
</CheckButton>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaShapeshiftBar_OnLoad(self)</OnLoad>
|
||||
<OnEvent>BenillaShapeshiftBar_OnEvent(self)</OnEvent>
|
||||
<OnLoad>ShapeshiftBar_OnLoad()</OnLoad>
|
||||
<OnEvent>ShapeshiftBar_OnEvent()</OnEvent>
|
||||
<!-- The stance bar is the pet/stance term of the managed bottom-stack positions
|
||||
(UIParent.xml, decision 0272): its show/hide re-fires the pass, exactly the
|
||||
ref's BonusActionBarFrame.xml:350-355. -->
|
||||
|
||||
@@ -147,8 +147,8 @@
|
||||
- Bare `min`/`max`/`select` (used verbatim in `_DrawLines`/`_SetPrereqs` below): CONFIRMED
|
||||
available — `min`/`max` are house-stdlib aliases for `math.min`/`math.max`
|
||||
(`benilla-ui/src/script/stdlib.rs`), `select` is stock Lua 5.1. The ref's own `arg`/`arg.n`
|
||||
Lua-5.0 vararg table (`_SetPrereqs`) has no equivalent here — `LUA_COMPAT_VARARG` isn't
|
||||
shipped (WorldMapFrame.xml's own identical note) — replaced with `select("#", ...)`/
|
||||
Lua-5.0 vararg table (`_SetPrereqs`) is written the 5.1-native way
|
||||
(WorldMapFrame.xml's own identical note) — replaced with `select("#", ...)`/
|
||||
`select(i, ...)` over a trailing `...`, WorldMapFrame.xml's own precedent for the identical
|
||||
substitution.
|
||||
- `GetTalentTabInfo` called TWICE in a row by the ref's own `TalentFrame_Update` (l.152-154:
|
||||
@@ -1049,7 +1049,7 @@
|
||||
<Anchors><Anchor point="TOPRIGHT"><Offset><AbsDimension x="-65" y="-77"/></Offset></Anchor></Anchors>
|
||||
<Frames>
|
||||
<!-- The scrollbar: BenillaScrollBarTemplate's own geometry (ScrollTemplates.xml's
|
||||
BenillaFauxScrollFrameTemplate, whose Slider anchor shape is reused directly
|
||||
FauxScrollFrameTemplate, whose Slider anchor shape is reused directly
|
||||
here without the Frame wrapper around it — this file's header comment). Its
|
||||
inherited OnValueChanged is REPLACED below (not chained — verified). -->
|
||||
<Slider name="BenillaTalentFrameScrollBar" inherits="BenillaScrollBarTemplate">
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
demand. `BENILLA_TAXI_MAX_NODES = 50` below comfortably covers the real 5875
|
||||
`TaxiNodes.dbc`'s per-continent max (43 nodes on Eastern Kingdoms, 39 on Kalimdor —
|
||||
counted this session, `crate::ui_taxi`'s module doc). The route-line textures stay
|
||||
genuinely dynamic (`BenillaTaxiRouteMap:CreateTexture(...)`, ref l.101/142) — plain
|
||||
genuinely dynamic (`TaxiRouteMap:CreateTexture(...)`, ref l.101/142) — plain
|
||||
region creation carries no template/inherits, so THAT half of the reference runs
|
||||
completely unmodified.
|
||||
2. THE TITLE READS AN EVENT ARG, not `UnitName("npc")` (ref TaxiFrame.lua:27). This
|
||||
@@ -41,13 +41,13 @@
|
||||
3. THE "NO SINGLE-HOP DESTINATION" REFUSAL (`DrawOneHopLines`, ref l.161-164) calls
|
||||
`BenillaErrorsFrame_AddMessage` (`ErrorsFrame.xml`'s real Lua-callable seam, byte-exact
|
||||
`ERR_TAXINOPATHS` text below) rather than the reference's `UIErrorsFrame:AddMessage` —
|
||||
this engine's error toast is `BenillaErrorsFrame`, not an intrinsic `MessageFrame`
|
||||
this engine's error toast is `UIErrorsFrame`, not an intrinsic `MessageFrame`
|
||||
object, so the ref's OO call becomes the plain function call. `ErrorsFrame.xml` loads
|
||||
well before this file in the shipped list (`ui_script/mod.rs`), so the global already
|
||||
exists by the time this ever runs.
|
||||
|
||||
`<TaxiRouteFrame>` (an intrinsic type this engine doesn't have) becomes a plain `<Frame>`
|
||||
(`BenillaTaxiRouteMap`) per the house rule for every non-shipped intrinsic (decision 0246's
|
||||
(`TaxiRouteMap`) per the house rule for every non-shipped intrinsic (decision 0246's
|
||||
patch-chain rule; the `Cooldown`/`GameTooltip`/etc. precedents) — it carries no behavior of
|
||||
its own beyond being a Frame that owns child regions/buttons, so the plain type loses
|
||||
nothing.
|
||||
@@ -86,7 +86,8 @@
|
||||
-- (U+2019, "don\226\128\153t"), not a straight one.
|
||||
local ERR_TAXINOPATHS = "You don\226\128\153t know any flight locations connected to this one."
|
||||
|
||||
function BenillaTaxiFrame_OnLoad(frame)
|
||||
function TaxiFrame_OnLoad()
|
||||
local frame = this
|
||||
-- The "npc" portrait token (decision 0421): the open taxi session's flight master
|
||||
-- bakes into the ring, resolved exactly like Gossip/Merchant/Trainer's own OnLoad.
|
||||
SetPortraitTexture(getglobal(frame:GetName() .. "Portrait"), "npc")
|
||||
@@ -97,29 +98,30 @@
|
||||
-- Deviation 2 above: the flight master's name rides TAXIMAP_OPENED's arg1 (the app
|
||||
-- resolves it through the NameCache) rather than a live UnitName("npc") read.
|
||||
function BenillaTaxiFrame_SetTitle(name)
|
||||
getglobal("BenillaTaxiNameText"):SetText(name or "")
|
||||
getglobal("TaxiNameText"):SetText(name or "")
|
||||
end
|
||||
|
||||
-- ref TaxiFrame.lua:24-80, restructured only at the node-button loop (deviation 1 — a
|
||||
-- fixed pool walked 1..BENILLA_TAXI_MAX_NODES instead of grown via CreateFrame).
|
||||
function BenillaTaxiFrame_OnEvent(frame)
|
||||
function TaxiFrame_OnEvent(event)
|
||||
local frame = this
|
||||
if event == "TAXIMAP_OPENED" then
|
||||
BenillaTaxiFrame_SetTitle(arg1)
|
||||
|
||||
-- Set the texture coords on the map, then hand the region to the app's art pick.
|
||||
BenillaTaxiMap:SetTexCoord(0, 1, 0, 1)
|
||||
SetTaxiMap(BenillaTaxiMap)
|
||||
TaxiMap:SetTexCoord(0, 1, 0, 1)
|
||||
SetTaxiMap(TaxiMap)
|
||||
|
||||
-- Draw nodes: every pool slot up to the pushed count shows its type's icon at its
|
||||
-- pushed position; the rest hide (ref l.44-63, the static-pool shape).
|
||||
local num_nodes = NumTaxiNodes()
|
||||
for index = 1, BENILLA_TAXI_MAX_NODES do
|
||||
local button = getglobal("BenillaTaxiButton" .. index)
|
||||
local button = getglobal("TaxiButton" .. index)
|
||||
local ntype = TaxiNodeGetType(index)
|
||||
if index <= num_nodes and ntype ~= "NONE" then
|
||||
local x, y = TaxiNodePosition(button:GetID())
|
||||
button:ClearAllPoints()
|
||||
button:SetPoint("CENTER", "BenillaTaxiMap", "BOTTOMLEFT", x * TAXI_MAP_WIDTH, y * TAXI_MAP_HEIGHT)
|
||||
button:SetPoint("CENTER", "TaxiMap", "BOTTOMLEFT", x * TAXI_MAP_WIDTH, y * TAXI_MAP_HEIGHT)
|
||||
button:SetNormalTexture(TaxiButtonTypes[ntype].file)
|
||||
button:Show()
|
||||
else
|
||||
@@ -153,8 +155,8 @@
|
||||
local numRoutes = GetNumRoutes(index)
|
||||
local line
|
||||
local sX, sY, dX, dY
|
||||
local w = BenillaTaxiRouteMap:GetWidth()
|
||||
local h = BenillaTaxiRouteMap:GetHeight()
|
||||
local w = TaxiRouteMap:GetWidth()
|
||||
local h = TaxiRouteMap:GetHeight()
|
||||
|
||||
local ntype = TaxiNodeGetType(index)
|
||||
if ntype == "REACHABLE" then
|
||||
@@ -163,7 +165,7 @@
|
||||
|
||||
if numRoutes > NUM_TAXI_ROUTES then
|
||||
for i = NUM_TAXI_ROUTES + 1, numRoutes do
|
||||
line = BenillaTaxiRouteMap:CreateTexture("TaxiRoute" .. i, "BACKGROUND")
|
||||
line = TaxiRouteMap:CreateTexture("TaxiRoute" .. i, "BACKGROUND")
|
||||
line:SetTexture("Interface\\TaxiFrame\\UI-Taxi-Line")
|
||||
end
|
||||
NUM_TAXI_ROUTES = numRoutes
|
||||
@@ -176,7 +178,7 @@
|
||||
sY = TaxiGetSrcY(index, i) * h
|
||||
dX = TaxiGetDestX(index, i) * w
|
||||
dY = TaxiGetDestY(index, i) * h
|
||||
DrawRouteLine(line, "BenillaTaxiRouteMap", sX, sY, dX, dY, 32)
|
||||
DrawRouteLine(line, "TaxiRouteMap", sX, sY, dX, dY, 32)
|
||||
line:Show()
|
||||
else
|
||||
line:Hide()
|
||||
@@ -193,14 +195,14 @@
|
||||
NUM_TAXI_ROUTES = 0
|
||||
|
||||
-- Draw all flightpaths within one hop of current location — ref TaxiFrame.lua:129-165,
|
||||
-- verbatim except the canvas name ("TaxiRouteMap" -> "BenillaTaxiRouteMap") and the
|
||||
-- verbatim except the canvas name ("TaxiRouteMap" -> "TaxiRouteMap") and the
|
||||
-- no-single-hop refusal (deviation 3 above: BenillaErrorsFrame_AddMessage, not
|
||||
-- UIErrorsFrame:AddMessage).
|
||||
function DrawOneHopLines()
|
||||
local line
|
||||
local sX, sY, dX, dY
|
||||
local w = BenillaTaxiRouteMap:GetWidth()
|
||||
local h = BenillaTaxiRouteMap:GetHeight()
|
||||
local w = TaxiRouteMap:GetWidth()
|
||||
local h = TaxiRouteMap:GetHeight()
|
||||
local numNodes = NumTaxiNodes()
|
||||
local numLines = 0
|
||||
local numSingleHops = 0
|
||||
@@ -209,7 +211,7 @@
|
||||
numSingleHops = numSingleHops + 1
|
||||
numLines = numLines + 1
|
||||
if numLines > NUM_TAXI_ROUTES then
|
||||
line = BenillaTaxiRouteMap:CreateTexture("TaxiRoute" .. numLines, "BACKGROUND")
|
||||
line = TaxiRouteMap:CreateTexture("TaxiRoute" .. numLines, "BACKGROUND")
|
||||
line:SetTexture("Interface\\TaxiFrame\\UI-Taxi-Line")
|
||||
NUM_TAXI_ROUTES = numLines
|
||||
else
|
||||
@@ -220,7 +222,7 @@
|
||||
sY = TaxiGetSrcY(i, 1) * h
|
||||
dX = TaxiGetDestX(i, 1) * w
|
||||
dY = TaxiGetDestY(i, 1) * h
|
||||
DrawRouteLine(line, "BenillaTaxiRouteMap", sX, sY, dX, dY, 32)
|
||||
DrawRouteLine(line, "TaxiRouteMap", sX, sY, dX, dY, 32)
|
||||
line:Show()
|
||||
end
|
||||
end
|
||||
@@ -230,7 +232,7 @@
|
||||
end
|
||||
if numSingleHops == 0 then
|
||||
BenillaErrorsFrame_AddMessage(ERR_TAXINOPATHS, 1.0, 0.1, 0.1)
|
||||
HideUIPanel(BenillaTaxiFrame)
|
||||
HideUIPanel(TaxiFrame)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -303,7 +305,7 @@
|
||||
-- vanilla's client-side close sends no packet; the app clears TaxiState on this
|
||||
-- intent (which fires TAXIMAP_CLOSED), and we hide immediately so there's no lag.
|
||||
CloseTaxiMap()
|
||||
HideUIPanel(BenillaTaxiFrame)
|
||||
HideUIPanel(TaxiFrame)
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
@@ -347,7 +349,7 @@
|
||||
</HighlightTexture>
|
||||
</Button>
|
||||
|
||||
<Frame name="BenillaTaxiFrame" hidden="true">
|
||||
<Frame name="TaxiFrame" hidden="true">
|
||||
<!-- The real 384×512 window (ref l.34-36). No <Anchors> — ShowUIPanel/SetLeftFrame
|
||||
SetPoints this to the left slot on open, exactly like Gossip/Merchant/Trainer. -->
|
||||
<Size><AbsDimension x="384" y="512"/></Size>
|
||||
@@ -384,7 +386,7 @@
|
||||
</Texture>
|
||||
<!-- The flight master's name banner (ref TaxiMerchant, l.104-115): GameFontNormal,
|
||||
CENTER, TOP -17, 192 wide — deviation 2 above (arg1, not UnitName("npc")). -->
|
||||
<FontString name="BenillaTaxiNameText" inherits="GameFontNormal" justifyH="CENTER">
|
||||
<FontString name="TaxiNameText" inherits="GameFontNormal" justifyH="CENTER">
|
||||
<Size><AbsDimension x="192" y="0"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor>
|
||||
@@ -394,7 +396,7 @@
|
||||
<!-- The continent map art (ref TaxiMap, l.117-129): 316×352, TOP -13,-75. No file —
|
||||
SetTaxiMap assigns the app's TAXIMAP<continentId> pick (byte-verified — decision 0496). -->
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="BenillaTaxiMap">
|
||||
<Texture name="TaxiMap">
|
||||
<Size><AbsDimension x="316" y="352"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP"><Offset><AbsDimension x="-13" y="-75"/></Offset></Anchor>
|
||||
@@ -406,7 +408,7 @@
|
||||
<!-- Close: the real UIPanelCloseButton position (TOPRIGHT -29,-8; ref l.133-141), on
|
||||
the minimize-button art (this engine ships no UIPanelCloseButton — the Gossip/
|
||||
Trainer/Merchant precedent). -->
|
||||
<Button name="BenillaTaxiCloseButton">
|
||||
<Button name="TaxiCloseButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-29" y="-8"/></Offset></Anchor>
|
||||
@@ -419,73 +421,73 @@
|
||||
|
||||
<!-- `<TaxiRouteFrame>` (ref l.142-153) — an intrinsic type this engine doesn't have —
|
||||
becomes a plain <Frame> (see the header note): 316×352, TOP -13,-75, exactly
|
||||
overlaid on BenillaTaxiMap. Houses the node-button pool + the dynamically-created
|
||||
overlaid on TaxiMap. Houses the node-button pool + the dynamically-created
|
||||
route-line textures, both parented here exactly like the reference (ref l.39,101). -->
|
||||
<Frame name="BenillaTaxiRouteMap">
|
||||
<Frame name="TaxiRouteMap">
|
||||
<Size><AbsDimension x="316" y="352"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP"><Offset><AbsDimension x="-13" y="-75"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Frames>
|
||||
<Button name="BenillaTaxiButton1" inherits="BenillaTaxiButtonTemplate" id="1"/>
|
||||
<Button name="BenillaTaxiButton2" inherits="BenillaTaxiButtonTemplate" id="2"/>
|
||||
<Button name="BenillaTaxiButton3" inherits="BenillaTaxiButtonTemplate" id="3"/>
|
||||
<Button name="BenillaTaxiButton4" inherits="BenillaTaxiButtonTemplate" id="4"/>
|
||||
<Button name="BenillaTaxiButton5" inherits="BenillaTaxiButtonTemplate" id="5"/>
|
||||
<Button name="BenillaTaxiButton6" inherits="BenillaTaxiButtonTemplate" id="6"/>
|
||||
<Button name="BenillaTaxiButton7" inherits="BenillaTaxiButtonTemplate" id="7"/>
|
||||
<Button name="BenillaTaxiButton8" inherits="BenillaTaxiButtonTemplate" id="8"/>
|
||||
<Button name="BenillaTaxiButton9" inherits="BenillaTaxiButtonTemplate" id="9"/>
|
||||
<Button name="BenillaTaxiButton10" inherits="BenillaTaxiButtonTemplate" id="10"/>
|
||||
<Button name="BenillaTaxiButton11" inherits="BenillaTaxiButtonTemplate" id="11"/>
|
||||
<Button name="BenillaTaxiButton12" inherits="BenillaTaxiButtonTemplate" id="12"/>
|
||||
<Button name="BenillaTaxiButton13" inherits="BenillaTaxiButtonTemplate" id="13"/>
|
||||
<Button name="BenillaTaxiButton14" inherits="BenillaTaxiButtonTemplate" id="14"/>
|
||||
<Button name="BenillaTaxiButton15" inherits="BenillaTaxiButtonTemplate" id="15"/>
|
||||
<Button name="BenillaTaxiButton16" inherits="BenillaTaxiButtonTemplate" id="16"/>
|
||||
<Button name="BenillaTaxiButton17" inherits="BenillaTaxiButtonTemplate" id="17"/>
|
||||
<Button name="BenillaTaxiButton18" inherits="BenillaTaxiButtonTemplate" id="18"/>
|
||||
<Button name="BenillaTaxiButton19" inherits="BenillaTaxiButtonTemplate" id="19"/>
|
||||
<Button name="BenillaTaxiButton20" inherits="BenillaTaxiButtonTemplate" id="20"/>
|
||||
<Button name="BenillaTaxiButton21" inherits="BenillaTaxiButtonTemplate" id="21"/>
|
||||
<Button name="BenillaTaxiButton22" inherits="BenillaTaxiButtonTemplate" id="22"/>
|
||||
<Button name="BenillaTaxiButton23" inherits="BenillaTaxiButtonTemplate" id="23"/>
|
||||
<Button name="BenillaTaxiButton24" inherits="BenillaTaxiButtonTemplate" id="24"/>
|
||||
<Button name="BenillaTaxiButton25" inherits="BenillaTaxiButtonTemplate" id="25"/>
|
||||
<Button name="BenillaTaxiButton26" inherits="BenillaTaxiButtonTemplate" id="26"/>
|
||||
<Button name="BenillaTaxiButton27" inherits="BenillaTaxiButtonTemplate" id="27"/>
|
||||
<Button name="BenillaTaxiButton28" inherits="BenillaTaxiButtonTemplate" id="28"/>
|
||||
<Button name="BenillaTaxiButton29" inherits="BenillaTaxiButtonTemplate" id="29"/>
|
||||
<Button name="BenillaTaxiButton30" inherits="BenillaTaxiButtonTemplate" id="30"/>
|
||||
<Button name="BenillaTaxiButton31" inherits="BenillaTaxiButtonTemplate" id="31"/>
|
||||
<Button name="BenillaTaxiButton32" inherits="BenillaTaxiButtonTemplate" id="32"/>
|
||||
<Button name="BenillaTaxiButton33" inherits="BenillaTaxiButtonTemplate" id="33"/>
|
||||
<Button name="BenillaTaxiButton34" inherits="BenillaTaxiButtonTemplate" id="34"/>
|
||||
<Button name="BenillaTaxiButton35" inherits="BenillaTaxiButtonTemplate" id="35"/>
|
||||
<Button name="BenillaTaxiButton36" inherits="BenillaTaxiButtonTemplate" id="36"/>
|
||||
<Button name="BenillaTaxiButton37" inherits="BenillaTaxiButtonTemplate" id="37"/>
|
||||
<Button name="BenillaTaxiButton38" inherits="BenillaTaxiButtonTemplate" id="38"/>
|
||||
<Button name="BenillaTaxiButton39" inherits="BenillaTaxiButtonTemplate" id="39"/>
|
||||
<Button name="BenillaTaxiButton40" inherits="BenillaTaxiButtonTemplate" id="40"/>
|
||||
<Button name="BenillaTaxiButton41" inherits="BenillaTaxiButtonTemplate" id="41"/>
|
||||
<Button name="BenillaTaxiButton42" inherits="BenillaTaxiButtonTemplate" id="42"/>
|
||||
<Button name="BenillaTaxiButton43" inherits="BenillaTaxiButtonTemplate" id="43"/>
|
||||
<Button name="BenillaTaxiButton44" inherits="BenillaTaxiButtonTemplate" id="44"/>
|
||||
<Button name="BenillaTaxiButton45" inherits="BenillaTaxiButtonTemplate" id="45"/>
|
||||
<Button name="BenillaTaxiButton46" inherits="BenillaTaxiButtonTemplate" id="46"/>
|
||||
<Button name="BenillaTaxiButton47" inherits="BenillaTaxiButtonTemplate" id="47"/>
|
||||
<Button name="BenillaTaxiButton48" inherits="BenillaTaxiButtonTemplate" id="48"/>
|
||||
<Button name="BenillaTaxiButton49" inherits="BenillaTaxiButtonTemplate" id="49"/>
|
||||
<Button name="BenillaTaxiButton50" inherits="BenillaTaxiButtonTemplate" id="50"/>
|
||||
<Button name="TaxiButton1" inherits="BenillaTaxiButtonTemplate" id="1"/>
|
||||
<Button name="TaxiButton2" inherits="BenillaTaxiButtonTemplate" id="2"/>
|
||||
<Button name="TaxiButton3" inherits="BenillaTaxiButtonTemplate" id="3"/>
|
||||
<Button name="TaxiButton4" inherits="BenillaTaxiButtonTemplate" id="4"/>
|
||||
<Button name="TaxiButton5" inherits="BenillaTaxiButtonTemplate" id="5"/>
|
||||
<Button name="TaxiButton6" inherits="BenillaTaxiButtonTemplate" id="6"/>
|
||||
<Button name="TaxiButton7" inherits="BenillaTaxiButtonTemplate" id="7"/>
|
||||
<Button name="TaxiButton8" inherits="BenillaTaxiButtonTemplate" id="8"/>
|
||||
<Button name="TaxiButton9" inherits="BenillaTaxiButtonTemplate" id="9"/>
|
||||
<Button name="TaxiButton10" inherits="BenillaTaxiButtonTemplate" id="10"/>
|
||||
<Button name="TaxiButton11" inherits="BenillaTaxiButtonTemplate" id="11"/>
|
||||
<Button name="TaxiButton12" inherits="BenillaTaxiButtonTemplate" id="12"/>
|
||||
<Button name="TaxiButton13" inherits="BenillaTaxiButtonTemplate" id="13"/>
|
||||
<Button name="TaxiButton14" inherits="BenillaTaxiButtonTemplate" id="14"/>
|
||||
<Button name="TaxiButton15" inherits="BenillaTaxiButtonTemplate" id="15"/>
|
||||
<Button name="TaxiButton16" inherits="BenillaTaxiButtonTemplate" id="16"/>
|
||||
<Button name="TaxiButton17" inherits="BenillaTaxiButtonTemplate" id="17"/>
|
||||
<Button name="TaxiButton18" inherits="BenillaTaxiButtonTemplate" id="18"/>
|
||||
<Button name="TaxiButton19" inherits="BenillaTaxiButtonTemplate" id="19"/>
|
||||
<Button name="TaxiButton20" inherits="BenillaTaxiButtonTemplate" id="20"/>
|
||||
<Button name="TaxiButton21" inherits="BenillaTaxiButtonTemplate" id="21"/>
|
||||
<Button name="TaxiButton22" inherits="BenillaTaxiButtonTemplate" id="22"/>
|
||||
<Button name="TaxiButton23" inherits="BenillaTaxiButtonTemplate" id="23"/>
|
||||
<Button name="TaxiButton24" inherits="BenillaTaxiButtonTemplate" id="24"/>
|
||||
<Button name="TaxiButton25" inherits="BenillaTaxiButtonTemplate" id="25"/>
|
||||
<Button name="TaxiButton26" inherits="BenillaTaxiButtonTemplate" id="26"/>
|
||||
<Button name="TaxiButton27" inherits="BenillaTaxiButtonTemplate" id="27"/>
|
||||
<Button name="TaxiButton28" inherits="BenillaTaxiButtonTemplate" id="28"/>
|
||||
<Button name="TaxiButton29" inherits="BenillaTaxiButtonTemplate" id="29"/>
|
||||
<Button name="TaxiButton30" inherits="BenillaTaxiButtonTemplate" id="30"/>
|
||||
<Button name="TaxiButton31" inherits="BenillaTaxiButtonTemplate" id="31"/>
|
||||
<Button name="TaxiButton32" inherits="BenillaTaxiButtonTemplate" id="32"/>
|
||||
<Button name="TaxiButton33" inherits="BenillaTaxiButtonTemplate" id="33"/>
|
||||
<Button name="TaxiButton34" inherits="BenillaTaxiButtonTemplate" id="34"/>
|
||||
<Button name="TaxiButton35" inherits="BenillaTaxiButtonTemplate" id="35"/>
|
||||
<Button name="TaxiButton36" inherits="BenillaTaxiButtonTemplate" id="36"/>
|
||||
<Button name="TaxiButton37" inherits="BenillaTaxiButtonTemplate" id="37"/>
|
||||
<Button name="TaxiButton38" inherits="BenillaTaxiButtonTemplate" id="38"/>
|
||||
<Button name="TaxiButton39" inherits="BenillaTaxiButtonTemplate" id="39"/>
|
||||
<Button name="TaxiButton40" inherits="BenillaTaxiButtonTemplate" id="40"/>
|
||||
<Button name="TaxiButton41" inherits="BenillaTaxiButtonTemplate" id="41"/>
|
||||
<Button name="TaxiButton42" inherits="BenillaTaxiButtonTemplate" id="42"/>
|
||||
<Button name="TaxiButton43" inherits="BenillaTaxiButtonTemplate" id="43"/>
|
||||
<Button name="TaxiButton44" inherits="BenillaTaxiButtonTemplate" id="44"/>
|
||||
<Button name="TaxiButton45" inherits="BenillaTaxiButtonTemplate" id="45"/>
|
||||
<Button name="TaxiButton46" inherits="BenillaTaxiButtonTemplate" id="46"/>
|
||||
<Button name="TaxiButton47" inherits="BenillaTaxiButtonTemplate" id="47"/>
|
||||
<Button name="TaxiButton48" inherits="BenillaTaxiButtonTemplate" id="48"/>
|
||||
<Button name="TaxiButton49" inherits="BenillaTaxiButtonTemplate" id="49"/>
|
||||
<Button name="TaxiButton50" inherits="BenillaTaxiButtonTemplate" id="50"/>
|
||||
</Frames>
|
||||
</Frame>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
BenillaTaxiFrame_OnLoad(self)
|
||||
TaxiFrame_OnLoad()
|
||||
</OnLoad>
|
||||
<OnEvent>
|
||||
BenillaTaxiFrame_OnEvent(self)
|
||||
TaxiFrame_OnEvent(event)
|
||||
</OnEvent>
|
||||
<!-- ref l.162-169, verbatim: the MainMenu sound kit (not Merchant/Trainer's
|
||||
CharacterInfo kit — the reference genuinely uses a different pair here). -->
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
BENILLA_MAX_TRADE_ITEMS = 7 -- MAX_TRADE_ITEMS (ref TradeFrame.lua l.1)
|
||||
|
||||
-- ── Load / event dispatch (ref TradeFrame_OnLoad l.4 / TradeFrame_OnEvent l.18) ───────────
|
||||
function BenillaTradeFrame_OnLoad(frame)
|
||||
function TradeFrame_OnLoad()
|
||||
local frame = this
|
||||
frame:RegisterEvent("TRADE_SHOW")
|
||||
frame:RegisterEvent("TRADE_UPDATE")
|
||||
frame:RegisterEvent("TRADE_CLOSED")
|
||||
@@ -37,71 +38,72 @@
|
||||
frame:RegisterEvent("PLAYER_TRADE_MONEY")
|
||||
end
|
||||
|
||||
function BenillaTradeFrame_OnEvent(frame)
|
||||
function TradeFrame_OnEvent()
|
||||
local frame = this
|
||||
if event == "TRADE_SHOW" or event == "TRADE_UPDATE" then
|
||||
ShowUIPanel(frame)
|
||||
if not frame:IsVisible() then
|
||||
CloseTrade()
|
||||
return
|
||||
end
|
||||
BenillaTradeFrameTradeButton:Enable()
|
||||
BenillaTradeFrame_Update()
|
||||
TradeFrameTradeButton:Enable()
|
||||
TradeFrame_Update()
|
||||
elseif event == "PLAYER_TRADE_MONEY" then
|
||||
BenillaMoneyInput_SetCopper("BenillaTradePlayerInputMoney", GetPlayerTradeMoney())
|
||||
BenillaMoneyInput_SetCopper("TradePlayerInputMoney", GetPlayerTradeMoney())
|
||||
elseif event == "TRADE_CLOSED" then
|
||||
HideUIPanel(frame)
|
||||
elseif event == "TRADE_PLAYER_ITEM_CHANGED" then
|
||||
BenillaTradeFrame_UpdatePlayerItem(arg1)
|
||||
TradeFrame_UpdatePlayerItem(arg1)
|
||||
elseif event == "TRADE_TARGET_ITEM_CHANGED" then
|
||||
BenillaTradeFrame_UpdateTargetItem(arg1)
|
||||
TradeFrame_UpdateTargetItem(arg1)
|
||||
elseif event == "TRADE_ACCEPT_UPDATE" then
|
||||
BenillaTradeFrame_SetAcceptState(arg1, arg2)
|
||||
TradeFrame_SetAcceptState(arg1, arg2)
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaTradeFrame_OnShow()
|
||||
BenillaTradeFrame.acceptState = 0
|
||||
function TradeFrame_OnShow()
|
||||
TradeFrame.acceptState = 0
|
||||
end
|
||||
|
||||
function BenillaTradeFrame_OnHide()
|
||||
function TradeFrame_OnHide()
|
||||
-- The client-side close verb (ref TradeFrame_OnHide l.144) → CMSG_CANCEL_TRADE + a local
|
||||
-- clear (the app's drain); clear our money entry so a later trade opens fresh (ref l.146).
|
||||
BenillaMoneyInput_ResetMoney("BenillaTradePlayerInputMoney")
|
||||
BenillaMoneyInput_ResetMoney("TradePlayerInputMoney")
|
||||
CloseTrade()
|
||||
end
|
||||
|
||||
-- A keystroke in our money input (ref TradeFrame_UpdateMoney l.162): read the copper total,
|
||||
-- clamp to what we actually carry, and offer it → SetTradeMoney → CMSG_SET_TRADE_GOLD.
|
||||
function BenillaTradeFrame_UpdateMoney()
|
||||
local copper = BenillaMoneyInput_GetCopper("BenillaTradePlayerInputMoney")
|
||||
function TradeFrame_UpdateMoney()
|
||||
local copper = BenillaMoneyInput_GetCopper("TradePlayerInputMoney")
|
||||
if GetMoney and copper > GetMoney() then
|
||||
copper = GetPlayerTradeMoney()
|
||||
BenillaMoneyInput_SetCopper("BenillaTradePlayerInputMoney", copper)
|
||||
BenillaMoneyInput_SetCopper("TradePlayerInputMoney", copper)
|
||||
end
|
||||
SetTradeMoney(copper)
|
||||
end
|
||||
|
||||
-- ── Full repaint (ref TradeFrame_Update l.39): portraits, names, all 7 slots each side, the
|
||||
-- two money displays, the accept highlights hidden. ────────────────────────────────────
|
||||
function BenillaTradeFrame_Update()
|
||||
SetPortraitTexture(BenillaTradeFramePlayerPortrait, "player")
|
||||
function TradeFrame_Update()
|
||||
SetPortraitTexture(TradeFramePlayerPortrait, "player")
|
||||
-- The partner rides the shared "npc" portrait booth (the app points it at the partner
|
||||
-- entity, decision 0592); the name comes from GetTradePartnerName() in place of the
|
||||
-- reference's UnitName("NPC").
|
||||
SetPortraitTexture(BenillaTradeFrameRecipientPortrait, "npc")
|
||||
BenillaTradeFramePlayerNameText:SetText(UnitName("player") or "")
|
||||
BenillaTradeFrameRecipientNameText:SetText(GetTradePartnerName() or "")
|
||||
SetPortraitTexture(TradeFrameRecipientPortrait, "npc")
|
||||
TradeFramePlayerNameText:SetText(UnitName("player") or "")
|
||||
TradeFrameRecipientNameText:SetText(GetTradePartnerName() or "")
|
||||
for i = 1, BENILLA_MAX_TRADE_ITEMS do
|
||||
BenillaTradeFrame_UpdatePlayerItem(i)
|
||||
BenillaTradeFrame_UpdateTargetItem(i)
|
||||
TradeFrame_UpdatePlayerItem(i)
|
||||
TradeFrame_UpdateTargetItem(i)
|
||||
end
|
||||
-- Our gold is the editable input, reflected via PLAYER_TRADE_MONEY (not repainted here, so
|
||||
-- the repaint never clobbers mid-typing); only the partner's read-only gold repaints.
|
||||
BenillaMoney_Set("BenillaTradeRecipientMoneyFrame", GetTargetTradeMoney())
|
||||
BenillaTradeHighlightPlayer:Hide()
|
||||
BenillaTradeHighlightRecipient:Hide()
|
||||
BenillaTradeHighlightPlayerEnchant:Hide()
|
||||
BenillaTradeHighlightRecipientEnchant:Hide()
|
||||
BenillaMoney_Set("TradeRecipientMoneyFrame", GetTargetTradeMoney())
|
||||
TradeHighlightPlayer:Hide()
|
||||
TradeHighlightRecipient:Hide()
|
||||
TradeHighlightPlayerEnchant:Hide()
|
||||
TradeHighlightRecipientEnchant:Hide()
|
||||
end
|
||||
|
||||
-- One slot's paint, shared by both columns (ref TradeFrame_UpdatePlayerItem l.54 /
|
||||
@@ -152,14 +154,14 @@
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaTradeFrame_UpdatePlayerItem(id)
|
||||
function TradeFrame_UpdatePlayerItem(id)
|
||||
local name, texture, numItems, isUsable, enchantment = GetTradePlayerItemInfo(id)
|
||||
BenillaTradeSlot_SetItem("BenillaTradePlayerItem"..id, id, name, texture, numItems, isUsable, enchantment, nil)
|
||||
BenillaTradeSlot_SetItem("TradePlayerItem"..id, id, name, texture, numItems, isUsable, enchantment, nil)
|
||||
end
|
||||
|
||||
function BenillaTradeFrame_UpdateTargetItem(id)
|
||||
function TradeFrame_UpdateTargetItem(id)
|
||||
local name, texture, numItems, quality, isUsable, enchantment = GetTradeTargetItemInfo(id)
|
||||
BenillaTradeSlot_SetItem("BenillaTradeRecipientItem"..id, id, name, texture, numItems, isUsable, enchantment, 1)
|
||||
BenillaTradeSlot_SetItem("TradeRecipientItem"..id, id, name, texture, numItems, isUsable, enchantment, 1)
|
||||
end
|
||||
|
||||
-- A trade slot's item button as a cursor drop target (decision 0592 P2, ref ClickTradeButton /
|
||||
@@ -178,33 +180,33 @@
|
||||
|
||||
-- The accept glow (ref TradeFrame_SetAcceptState l.116): each side's column + enchant-slot
|
||||
-- highlight tracks who has pressed Trade; our own accept disables the Trade button.
|
||||
function BenillaTradeFrame_SetAcceptState(playerState, targetState)
|
||||
BenillaTradeFrame.acceptState = playerState
|
||||
function TradeFrame_SetAcceptState(playerState, targetState)
|
||||
TradeFrame.acceptState = playerState
|
||||
if playerState == 1 then
|
||||
BenillaTradeHighlightPlayer:Show()
|
||||
BenillaTradeHighlightPlayerEnchant:Show()
|
||||
BenillaTradeFrameTradeButton:Disable()
|
||||
TradeHighlightPlayer:Show()
|
||||
TradeHighlightPlayerEnchant:Show()
|
||||
TradeFrameTradeButton:Disable()
|
||||
else
|
||||
BenillaTradeHighlightPlayer:Hide()
|
||||
BenillaTradeHighlightPlayerEnchant:Hide()
|
||||
BenillaTradeFrameTradeButton:Enable()
|
||||
TradeHighlightPlayer:Hide()
|
||||
TradeHighlightPlayerEnchant:Hide()
|
||||
TradeFrameTradeButton:Enable()
|
||||
end
|
||||
if targetState == 1 then
|
||||
BenillaTradeHighlightRecipient:Show()
|
||||
BenillaTradeHighlightRecipientEnchant:Show()
|
||||
TradeHighlightRecipient:Show()
|
||||
TradeHighlightRecipientEnchant:Show()
|
||||
else
|
||||
BenillaTradeHighlightRecipient:Hide()
|
||||
BenillaTradeHighlightRecipientEnchant:Hide()
|
||||
TradeHighlightRecipient:Hide()
|
||||
TradeHighlightRecipientEnchant:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
-- Cancel (ref TradeFrameCancelButton_OnClick l.136): with our accept up, drop it (stay in the
|
||||
-- trade); otherwise close the window (→ OnHide → CloseTrade).
|
||||
function BenillaTradeFrameCancelButton_OnClick()
|
||||
if BenillaTradeFrame.acceptState == 1 then
|
||||
function TradeFrameCancelButton_OnClick()
|
||||
if TradeFrame.acceptState == 1 then
|
||||
CancelTradeAccept()
|
||||
else
|
||||
HideUIPanel(BenillaTradeFrame)
|
||||
HideUIPanel(TradeFrame)
|
||||
end
|
||||
end
|
||||
]]></Script>
|
||||
@@ -304,18 +306,18 @@
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<Frame name="BenillaTradeFrame" toplevel="true" movable="true" enableMouse="true" hidden="true">
|
||||
<Frame name="TradeFrame" toplevel="true" movable="true" enableMouse="true" hidden="true">
|
||||
<!-- ref TradeFrame l.164: 384×512; ShowUIPanel slots it left. -->
|
||||
<Size><AbsDimension x="384" y="512"/></Size>
|
||||
<HitRectInsets><AbsInset left="0" right="35" top="0" bottom="72"/></HitRectInsets>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<!-- ref l.180: the two 60×60 unit portraits (recipient right, player left). -->
|
||||
<Texture name="BenillaTradeFrameRecipientPortrait">
|
||||
<Texture name="TradeFrameRecipientPortrait">
|
||||
<Size><AbsDimension x="60" y="60"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="183" y="-6"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
<Texture name="BenillaTradeFramePlayerPortrait">
|
||||
<Texture name="TradeFramePlayerPortrait">
|
||||
<Size><AbsDimension x="60" y="60"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="7" y="-6"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
@@ -338,92 +340,92 @@
|
||||
<Size><AbsDimension x="128" y="256"/></Size>
|
||||
<Anchors><Anchor point="BOTTOMRIGHT"/></Anchors>
|
||||
</Texture>
|
||||
<FontString name="BenillaTradeFrameRecipientNameText" inherits="GameFontNormal" text="">
|
||||
<FontString name="TradeFrameRecipientNameText" inherits="GameFontNormal" text="">
|
||||
<Size><AbsDimension x="80" y="12"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradeFrame"><Offset><AbsDimension x="245" y="-17"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradeFrame"><Offset><AbsDimension x="245" y="-17"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<FontString name="BenillaTradeFramePlayerNameText" inherits="GameFontNormal" text="">
|
||||
<FontString name="TradeFramePlayerNameText" inherits="GameFontNormal" text="">
|
||||
<Size><AbsDimension x="100" y="12"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradeFrame"><Offset><AbsDimension x="75" y="-17"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradeFrame"><Offset><AbsDimension x="75" y="-17"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<!-- ref l.262: the "Will Be Enchanted" column labels above the enchant slots. -->
|
||||
<FontString name="BenillaTradeFramePlayerEnchantText" inherits="GameFontHighlightSmall" text="Will Be Enchanted">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradeFrame"><Offset><AbsDimension x="26" y="-374"/></Offset></Anchor></Anchors>
|
||||
<FontString name="TradeFramePlayerEnchantText" inherits="GameFontHighlightSmall" text="Will Be Enchanted">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradeFrame"><Offset><AbsDimension x="26" y="-374"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
<FontString name="BenillaTradeFrameRecipientEnchantText" inherits="GameFontHighlightSmall" text="Will Be Enchanted">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaTradeFramePlayerEnchantText"><Offset><AbsDimension x="170" y="0"/></Offset></Anchor></Anchors>
|
||||
<FontString name="TradeFrameRecipientEnchantText" inherits="GameFontHighlightSmall" text="Will Be Enchanted">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="TradeFramePlayerEnchantText"><Offset><AbsDimension x="170" y="0"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<!-- The accept-glow highlights, one per column + its enchant slot (ref l.283). -->
|
||||
<Frame name="BenillaTradeHighlightPlayer" inherits="BenillaTradeHighlightTemplate" hidden="true">
|
||||
<Frame name="TradeHighlightPlayer" inherits="BenillaTradeHighlightTemplate" hidden="true">
|
||||
<Size><AbsDimension x="161" y="266"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradeFrame"><Offset><AbsDimension x="19" y="-100"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradeFrame"><Offset><AbsDimension x="19" y="-100"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaTradeHighlightRecipient" inherits="BenillaTradeHighlightTemplate" hidden="true">
|
||||
<Frame name="TradeHighlightRecipient" inherits="BenillaTradeHighlightTemplate" hidden="true">
|
||||
<Size><AbsDimension x="161" y="266"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="189" y="-100"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaTradeHighlightPlayerEnchant" inherits="BenillaTradeHighlightTemplate" hidden="true">
|
||||
<Frame name="TradeHighlightPlayerEnchant" inherits="BenillaTradeHighlightTemplate" hidden="true">
|
||||
<Size><AbsDimension x="161" y="61"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradeHighlightPlayer" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradeHighlightPlayer" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaTradeHighlightRecipientEnchant" inherits="BenillaTradeHighlightTemplate" hidden="true">
|
||||
<Frame name="TradeHighlightRecipientEnchant" inherits="BenillaTradeHighlightTemplate" hidden="true">
|
||||
<Size><AbsDimension x="161" y="61"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradeHighlightRecipient" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradeHighlightRecipient" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
|
||||
<!-- The partner's 6 trade slots + the 7th enchant slot (ref TradeRecipientItem1..7 l.331). -->
|
||||
<Frame name="BenillaTradeRecipientItem1" inherits="BenillaTradeItemTemplate" id="1">
|
||||
<Frame name="TradeRecipientItem1" inherits="BenillaTradeItemTemplate" id="1">
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="195" y="-104"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaTradeRecipientItem2" inherits="BenillaTradeItemTemplate" id="2">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradeRecipientItem1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Frame name="TradeRecipientItem2" inherits="BenillaTradeItemTemplate" id="2">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradeRecipientItem1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaTradeRecipientItem3" inherits="BenillaTradeItemTemplate" id="3">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradeRecipientItem2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Frame name="TradeRecipientItem3" inherits="BenillaTradeItemTemplate" id="3">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradeRecipientItem2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaTradeRecipientItem4" inherits="BenillaTradeItemTemplate" id="4">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradeRecipientItem3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Frame name="TradeRecipientItem4" inherits="BenillaTradeItemTemplate" id="4">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradeRecipientItem3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaTradeRecipientItem5" inherits="BenillaTradeItemTemplate" id="5">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradeRecipientItem4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Frame name="TradeRecipientItem5" inherits="BenillaTradeItemTemplate" id="5">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradeRecipientItem4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaTradeRecipientItem6" inherits="BenillaTradeItemTemplate" id="6">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradeRecipientItem5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Frame name="TradeRecipientItem6" inherits="BenillaTradeItemTemplate" id="6">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradeRecipientItem5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaTradeRecipientItem7" inherits="BenillaTradeEnchantItemTemplate" id="7">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradeRecipientItem6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-28"/></Offset></Anchor></Anchors>
|
||||
<Frame name="TradeRecipientItem7" inherits="BenillaTradeEnchantItemTemplate" id="7">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradeRecipientItem6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-28"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
|
||||
<!-- Our 6 trade slots + the 7th enchant slot (ref TradePlayerItem1..7 l.410). -->
|
||||
<Frame name="BenillaTradePlayerItem1" inherits="BenillaTradeItemTemplate" id="1">
|
||||
<Frame name="TradePlayerItem1" inherits="BenillaTradeItemTemplate" id="1">
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="26" y="-104"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaTradePlayerItem2" inherits="BenillaTradeItemTemplate" id="2">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradePlayerItem1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Frame name="TradePlayerItem2" inherits="BenillaTradeItemTemplate" id="2">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradePlayerItem1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaTradePlayerItem3" inherits="BenillaTradeItemTemplate" id="3">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradePlayerItem2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Frame name="TradePlayerItem3" inherits="BenillaTradeItemTemplate" id="3">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradePlayerItem2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaTradePlayerItem4" inherits="BenillaTradeItemTemplate" id="4">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradePlayerItem3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Frame name="TradePlayerItem4" inherits="BenillaTradeItemTemplate" id="4">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradePlayerItem3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaTradePlayerItem5" inherits="BenillaTradeItemTemplate" id="5">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradePlayerItem4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Frame name="TradePlayerItem5" inherits="BenillaTradeItemTemplate" id="5">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradePlayerItem4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaTradePlayerItem6" inherits="BenillaTradeItemTemplate" id="6">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradePlayerItem5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
<Frame name="TradePlayerItem6" inherits="BenillaTradeItemTemplate" id="6">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradePlayerItem5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
<Frame name="BenillaTradePlayerItem7" inherits="BenillaTradeEnchantItemTemplate" id="7">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradePlayerItem6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-28"/></Offset></Anchor></Anchors>
|
||||
<Frame name="TradePlayerItem7" inherits="BenillaTradeEnchantItemTemplate" id="7">
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradePlayerItem6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-28"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
|
||||
<!-- The Trade button (ref l.502, UIPanelButtonTemplate inlined): OnClick → AcceptTrade. -->
|
||||
<Button name="BenillaTradeFrameTradeButton" text="Trade">
|
||||
<Button name="TradeFrameTradeButton" text="Trade">
|
||||
<Size><AbsDimension x="85" y="22"/></Size>
|
||||
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaTradeFrame" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-113" y="55"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="TradeFrame" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-113" y="55"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-Panel-Button-Up"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></NormalTexture>
|
||||
<PushedTexture file="Interface\Buttons\UI-Panel-Button-Down"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></PushedTexture>
|
||||
<DisabledTexture file="Interface\Buttons\UI-Panel-Button-Disabled"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></DisabledTexture>
|
||||
@@ -432,32 +434,32 @@
|
||||
<NormalFont inherits="GameFontNormal"/><HighlightFont inherits="GameFontHighlight"/><DisabledFont inherits="GameFontDisable"/>
|
||||
<Scripts><OnClick>AcceptTrade()</OnClick></Scripts>
|
||||
</Button>
|
||||
<Button name="BenillaTradeFrameCancelButton" text="Cancel">
|
||||
<Button name="TradeFrameCancelButton" text="Cancel">
|
||||
<Size><AbsDimension x="77" y="22"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradeFrameTradeButton" relativePoint="TOPRIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="TOPLEFT" relativeTo="TradeFrameTradeButton" relativePoint="TOPRIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-Panel-Button-Up"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></NormalTexture>
|
||||
<PushedTexture file="Interface\Buttons\UI-Panel-Button-Down"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></PushedTexture>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></HighlightTexture>
|
||||
<ButtonText name="$parentText"/>
|
||||
<NormalFont inherits="GameFontNormal"/><HighlightFont inherits="GameFontHighlight"/><DisabledFont inherits="GameFontDisable"/>
|
||||
<Scripts><OnClick>BenillaTradeFrameCancelButton_OnClick()</OnClick></Scripts>
|
||||
<Scripts><OnClick>TradeFrameCancelButton_OnClick()</OnClick></Scripts>
|
||||
</Button>
|
||||
<!-- The window close X (ref l.544, UIPanelCloseButton inlined). -->
|
||||
<Button name="BenillaTradeFrameCloseButton">
|
||||
<Button name="TradeFrameCloseButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors><Anchor point="TOPRIGHT" relativeTo="BenillaTradeFrame" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-25" y="-8"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="TOPRIGHT" relativeTo="TradeFrame" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-25" y="-8"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Down"/>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Highlight" alphaMode="ADD"/>
|
||||
<Scripts><OnClick>HideUIPanel(BenillaTradeFrame)</OnClick></Scripts>
|
||||
<Scripts><OnClick>HideUIPanel(TradeFrame)</OnClick></Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- Our gold — EDITABLE (decision 0592 P2, ref TradePlayerInputMoneyFrame l.553): the shared
|
||||
BenillaMoneyInput trio (three numeric edit boxes Gold/Silver/Copper). A keystroke drives
|
||||
BenillaTradeFrame_UpdateMoney → SetTradeMoney → CMSG_SET_TRADE_GOLD; the server's echo is
|
||||
TradeFrame_UpdateMoney → SetTradeMoney → CMSG_SET_TRADE_GOLD; the server's echo is
|
||||
reflected back through PLAYER_TRADE_MONEY (below). Box layout copied from the proven
|
||||
send-mail widget (MailFrame.xml). -->
|
||||
<Frame name="BenillaTradePlayerInputMoney">
|
||||
<Frame name="TradePlayerInputMoney">
|
||||
<Size><AbsDimension x="176" y="18"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="26" y="-73"/></Offset></Anchor></Anchors>
|
||||
<Frames>
|
||||
@@ -476,7 +478,7 @@
|
||||
<OnTabPressed>getglobal(this:GetParent():GetName().."Silver"):SetFocus()</OnTabPressed>
|
||||
<OnEnterPressed>getglobal(this:GetParent():GetName().."Silver"):SetFocus()</OnEnterPressed>
|
||||
<OnEscapePressed>this:ClearFocus()</OnEscapePressed>
|
||||
<OnTextChanged>if ( BenillaMoneyInput_Consume("BenillaTradePlayerInputMoney") ) then BenillaTradeFrame_UpdateMoney() end</OnTextChanged>
|
||||
<OnTextChanged>if ( BenillaMoneyInput_Consume("TradePlayerInputMoney") ) then TradeFrame_UpdateMoney() end</OnTextChanged>
|
||||
<OnEditFocusLost>this:HighlightText(0, 0)</OnEditFocusLost>
|
||||
<OnEditFocusGained>this:HighlightText()</OnEditFocusGained>
|
||||
</Scripts>
|
||||
@@ -497,7 +499,7 @@
|
||||
<OnTabPressed>getglobal(this:GetParent():GetName().."Copper"):SetFocus()</OnTabPressed>
|
||||
<OnEnterPressed>getglobal(this:GetParent():GetName().."Copper"):SetFocus()</OnEnterPressed>
|
||||
<OnEscapePressed>this:ClearFocus()</OnEscapePressed>
|
||||
<OnTextChanged>if ( BenillaMoneyInput_Consume("BenillaTradePlayerInputMoney") ) then BenillaTradeFrame_UpdateMoney() end</OnTextChanged>
|
||||
<OnTextChanged>if ( BenillaMoneyInput_Consume("TradePlayerInputMoney") ) then TradeFrame_UpdateMoney() end</OnTextChanged>
|
||||
<OnEditFocusLost>this:HighlightText(0, 0)</OnEditFocusLost>
|
||||
<OnEditFocusGained>this:HighlightText()</OnEditFocusGained>
|
||||
</Scripts>
|
||||
@@ -516,7 +518,7 @@
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnEscapePressed>this:ClearFocus()</OnEscapePressed>
|
||||
<OnTextChanged>if ( BenillaMoneyInput_Consume("BenillaTradePlayerInputMoney") ) then BenillaTradeFrame_UpdateMoney() end</OnTextChanged>
|
||||
<OnTextChanged>if ( BenillaMoneyInput_Consume("TradePlayerInputMoney") ) then TradeFrame_UpdateMoney() end</OnTextChanged>
|
||||
<OnEditFocusLost>this:HighlightText(0, 0)</OnEditFocusLost>
|
||||
<OnEditFocusGained>this:HighlightText()</OnEditFocusGained>
|
||||
</Scripts>
|
||||
@@ -525,7 +527,7 @@
|
||||
</Frames>
|
||||
</Frame>
|
||||
<!-- The partner's gold (read-only — GetTargetTradeMoney; ref TradeRecipientMoneyFrame l.574). -->
|
||||
<Frame name="BenillaTradeRecipientMoneyFrame">
|
||||
<Frame name="TradeRecipientMoneyFrame">
|
||||
<Size><AbsDimension x="13" y="13"/></Size>
|
||||
<Anchors><Anchor point="TOPRIGHT"><Offset><AbsDimension x="-40" y="-80"/></Offset></Anchor></Anchors>
|
||||
<Frames>
|
||||
@@ -548,10 +550,10 @@
|
||||
</Frame>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaTradeFrame_OnLoad(self)</OnLoad>
|
||||
<OnEvent>BenillaTradeFrame_OnEvent(self)</OnEvent>
|
||||
<OnShow>BenillaTradeFrame_OnShow(); PlaySound("igCharacterInfoOpen")</OnShow>
|
||||
<OnHide>BenillaTradeFrame_OnHide(); PlaySound("igCharacterInfoClose")</OnHide>
|
||||
<OnLoad>TradeFrame_OnLoad()</OnLoad>
|
||||
<OnEvent>TradeFrame_OnEvent()</OnEvent>
|
||||
<OnShow>TradeFrame_OnShow(); PlaySound("igCharacterInfoOpen")</OnShow>
|
||||
<OnHide>TradeFrame_OnHide(); PlaySound("igCharacterInfoClose")</OnHide>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
token those NPC-session windows use), the UIPanel slot system (carries NO hardcoded
|
||||
<Anchors> — ShowUIPanel/SetLeftFrame SetPoints it), OnShow/OnHide PlaySound pair.
|
||||
- TrainerFrame.xml — the FLAT layout constraint (below), the faux-scroll list kit
|
||||
(BenillaFauxScrollFrameTemplate, ScrollTemplates.xml), the locally-reconstructed
|
||||
(FauxScrollFrameTemplate, ScrollTemplates.xml), the locally-reconstructed
|
||||
UIPanelButtonTemplate/close-button/item-button idioms (no such templates ship in this house
|
||||
dialect — TalentFrame.xml's own ENGINE API note), the persistent selection-highlight Frame.
|
||||
|
||||
@@ -127,8 +127,8 @@
|
||||
`GetText("*_ABBR", nil, n)` locale-pluralization dispatch collapses to English
|
||||
singular/plural directly — `UiPanels.xml`'s `StaticPopupTimeUnit` is the identical-shaped
|
||||
precedent for this SAME `GetText` call elsewhere in the reference.
|
||||
- Lua-5.0 `arg`/`arg.n` varargs have no equivalent (`LUA_COMPAT_VARARG` isn't shipped,
|
||||
WorldMapFrame.xml/TalentFrame.xml's own identical note) — `BuildColoredListString`'s
|
||||
- Lua-5.0 `arg`/`arg.n` varargs are written the 5.1-native way
|
||||
(WorldMapFrame.xml/TalentFrame.xml's own identical note) — `BuildColoredListString`'s
|
||||
varargs use `select("#", ...)`/`select(i, ...)`, their own precedent for the
|
||||
substitution; the new `TradeSkillFilterFrame_LoadSubClasses`/`_LoadInvSlots` (ref l.331/
|
||||
377) use the same substitution.
|
||||
@@ -412,8 +412,8 @@
|
||||
end
|
||||
|
||||
local scrollFrame = getglobal("BenillaTradeSkillListScrollFrame")
|
||||
BenillaFauxScrollFrame_Update(scrollFrame, numTradeSkills, TRADE_SKILLS_DISPLAYED, TRADE_SKILL_HEIGHT)
|
||||
local skillOffset = BenillaFauxScrollFrame_GetOffset(scrollFrame)
|
||||
FauxScrollFrame_Update(scrollFrame, numTradeSkills, TRADE_SKILLS_DISPLAYED, TRADE_SKILL_HEIGHT)
|
||||
local skillOffset = FauxScrollFrame_GetOffset(scrollFrame)
|
||||
|
||||
local highlight = getglobal("BenillaTradeSkillHighlight")
|
||||
highlight:Hide()
|
||||
@@ -545,7 +545,7 @@
|
||||
-- doc), using the button's own `.collapsed` flag (re-derived every Update() from the real
|
||||
-- header states above, so it stays honest regardless of how a fold happened). The ref's raw
|
||||
-- `TradeSkillListScrollFrameScrollBar:SetValue(0)` becomes this file's own
|
||||
-- `BenillaFauxScrollFrame_SetOffset(...,0)` wrapper (already this file's own Show/OnEvent
|
||||
-- `FauxScrollFrame_SetOffset(...,0)` wrapper (already this file's own Show/OnEvent
|
||||
-- convention). No direct `BenillaTradeSkillFrame_Update()` call — the ref doesn't call one
|
||||
-- either; `CollapseTradeSkillSubClass`/`ExpandTradeSkillSubClass` raise the engine's touched
|
||||
-- flag, answered with `TRADE_SKILL_UPDATE` the same frame (header comment, deviation 1). ──────
|
||||
@@ -555,7 +555,7 @@
|
||||
ExpandTradeSkillSubClass(0)
|
||||
else
|
||||
self.collapsed = true
|
||||
BenillaFauxScrollFrame_SetOffset(getglobal("BenillaTradeSkillListScrollFrame"), 0)
|
||||
FauxScrollFrame_SetOffset(getglobal("BenillaTradeSkillListScrollFrame"), 0)
|
||||
CollapseTradeSkillSubClass(0)
|
||||
end
|
||||
end
|
||||
@@ -781,7 +781,7 @@
|
||||
else
|
||||
BenillaTradeSkillFrame_SetSelection(GetTradeSkillSelectionIndex())
|
||||
end
|
||||
BenillaFauxScrollFrame_SetOffset(getglobal("BenillaTradeSkillListScrollFrame"), 0)
|
||||
FauxScrollFrame_SetOffset(getglobal("BenillaTradeSkillListScrollFrame"), 0)
|
||||
SetPortraitTexture(getglobal("BenillaTradeSkillFramePortrait"), "player")
|
||||
BenillaTradeSkillFrame_Update()
|
||||
ShowUIPanel(frame)
|
||||
@@ -797,7 +797,7 @@
|
||||
BenillaTradeSkillFrame_SetSelection(selection)
|
||||
elseif GetNumTradeSkills() > 0 then
|
||||
BenillaTradeSkillFrame_SetSelection(GetFirstTradeSkill())
|
||||
BenillaFauxScrollFrame_SetOffset(getglobal("BenillaTradeSkillListScrollFrame"), 0)
|
||||
FauxScrollFrame_SetOffset(getglobal("BenillaTradeSkillListScrollFrame"), 0)
|
||||
end
|
||||
BenillaTradeSkillFrame_Update()
|
||||
elseif event == "TRADE_SKILL_CLOSE" then
|
||||
@@ -1131,11 +1131,11 @@
|
||||
<Button name="BenillaTradeSkillSkill7" inherits="BenillaTradeSkillSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradeSkillSkill6" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="BenillaTradeSkillSkill8" inherits="BenillaTradeSkillSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTradeSkillSkill7" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
|
||||
<!-- The faux-scroll frame (BenillaFauxScrollFrameTemplate, ScrollTemplates.xml): invisible
|
||||
<!-- The faux-scroll frame (FauxScrollFrameTemplate, ScrollTemplates.xml): invisible
|
||||
container over the 8 rows; only its right-hand Slider scroll bar renders. Sized to the
|
||||
8-row span (TRADE_SKILLS_DISPLAYED × TRADE_SKILL_HEIGHT), TrainerFrame.xml's own
|
||||
same-TOPLEFT-as-row1 convention. updateFunc set in BenillaTradeSkillFrame_OnLoad. -->
|
||||
<Frame name="BenillaTradeSkillListScrollFrame" inherits="BenillaFauxScrollFrameTemplate">
|
||||
<Frame name="BenillaTradeSkillListScrollFrame" inherits="FauxScrollFrameTemplate">
|
||||
<Size><AbsDimension x="293" y="128"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="22" y="-96"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
the shipped 1.12.1 Blizzard_TrainerUI.lua:6-9's own values — restored from
|
||||
benilla/saved-variables.lua at load, and pushed into the engine on show because the list
|
||||
builder resets its mask on every packet.
|
||||
- THE SCROLL BAR IS REAL. BenillaTrainerListScrollFrame (BenillaFauxScrollFrameTemplate): a
|
||||
- THE SCROLL BAR IS REAL. BenillaTrainerListScrollFrame (FauxScrollFrameTemplate): a
|
||||
draggable Slider bar + up/down step arrows over the fixed 11 rows (the faux-scroll re-binds
|
||||
rows to a moving offset — it never really scrolls a child). The wheel still lives on the
|
||||
WINDOW (the engine bubbles a spin up the parent chain, so a spin over a row reaches here).
|
||||
@@ -258,8 +258,8 @@
|
||||
|
||||
-- The faux-scroll bar reconciles to the row count, then hands back the top-row offset.
|
||||
local scrollFrame = getglobal("BenillaTrainerListScrollFrame")
|
||||
BenillaFauxScrollFrame_Update(scrollFrame, numServices, BENILLA_TRAINER_ROWS, BENILLA_TRAINER_ROW_HEIGHT)
|
||||
local skillOffset = BenillaFauxScrollFrame_GetOffset(scrollFrame)
|
||||
FauxScrollFrame_Update(scrollFrame, numServices, BENILLA_TRAINER_ROWS, BENILLA_TRAINER_ROW_HEIGHT)
|
||||
local skillOffset = FauxScrollFrame_GetOffset(scrollFrame)
|
||||
|
||||
getglobal("BenillaTrainerSkillHighlight"):Hide()
|
||||
|
||||
@@ -445,7 +445,7 @@
|
||||
end
|
||||
setglobal("TRAINER_FILTER_" .. strupper(this.value), on)
|
||||
SetTrainerServiceTypeFilter(this.value, on)
|
||||
BenillaFauxScrollFrame_SetOffset(getglobal("BenillaTrainerListScrollFrame"), 0)
|
||||
FauxScrollFrame_SetOffset(getglobal("BenillaTrainerListScrollFrame"), 0)
|
||||
BenillaTrainerFrame_Update()
|
||||
end
|
||||
|
||||
@@ -459,7 +459,7 @@
|
||||
ExpandTrainerSkillLine(0)
|
||||
else
|
||||
b.collapsed = 1
|
||||
BenillaFauxScrollFrame_SetOffset(getglobal("BenillaTrainerListScrollFrame"), 0)
|
||||
FauxScrollFrame_SetOffset(getglobal("BenillaTrainerListScrollFrame"), 0)
|
||||
CollapseTrainerSkillLine(0)
|
||||
end
|
||||
BenillaTrainerFrame_Update()
|
||||
@@ -503,7 +503,7 @@
|
||||
-- Fresh session: top of the list, a first selection (ref ClassTrainerFrame_Show).
|
||||
BenillaTrainerFrame_ApplyFilter()
|
||||
BenillaTrainerFrame_SetTitle(arg1)
|
||||
BenillaFauxScrollFrame_SetOffset(getglobal("BenillaTrainerListScrollFrame"), 0)
|
||||
FauxScrollFrame_SetOffset(getglobal("BenillaTrainerListScrollFrame"), 0)
|
||||
if GetTrainerSelectionIndex() == 0 then
|
||||
selectFirstService()
|
||||
end
|
||||
@@ -750,11 +750,11 @@
|
||||
<Button name="BenillaTrainerService10" inherits="BenillaTrainerServiceTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTrainerService9" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
<Button name="BenillaTrainerService11" inherits="BenillaTrainerServiceTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTrainerService10" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||||
|
||||
<!-- The faux-scroll frame (BenillaFauxScrollFrameTemplate, ScrollTemplates.xml): invisible
|
||||
<!-- The faux-scroll frame (FauxScrollFrameTemplate, ScrollTemplates.xml): invisible
|
||||
container over the 11 rows; only its right-hand Slider scroll bar renders. Sized to the
|
||||
11-row span so the bar sits in the window's right gutter. updateFunc is set in
|
||||
BenillaTrainerFrame_OnLoad. -->
|
||||
<Frame name="BenillaTrainerListScrollFrame" inherits="BenillaFauxScrollFrameTemplate">
|
||||
<Frame name="BenillaTrainerListScrollFrame" inherits="FauxScrollFrameTemplate">
|
||||
<Size><AbsDimension x="293" y="176"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="22" y="-100"/></Offset></Anchor></Anchors>
|
||||
</Frame>
|
||||
|
||||
@@ -15,9 +15,17 @@
|
||||
CloseDropDownMenus(level), Disable/EnableButton, SetButtonText, SetAnchor, JustifyText,
|
||||
GetCurrentDropDown, the 2-second hover-away auto-close counting through the level chain.
|
||||
|
||||
THE COLOR SWATCH ROW came in with the colour picker (ColorPickerFrame.xml): the full
|
||||
`hasColorSwatch` / `r` / `g` / `b` / `swatchFunc` / `hasOpacity` / `opacity` / `opacityFunc` /
|
||||
`cancelFunc` field set, the `$parentColorSwatch` button on the row template, and
|
||||
`UIDropDownMenuButton_OpenColorPicker` (ref UIDropDownMenu.lua l.801-815). It is the second half
|
||||
of one feature, not a separate one: `hasColorSwatch` draws the square, clicking the square is
|
||||
the ONLY way the reference's colour picker ever opens, and `hasColorSwatch` is **145 files /
|
||||
82 corpus addons**.
|
||||
|
||||
OUT (each waits for its first customer): menu level 3 (UIDROPDOWNMENU_MAXLEVELS is 2 — the
|
||||
unit popups' deepest law; the XML gains DropDownList3 the day a customer needs it), color
|
||||
swatches + the color picker + opacity, info.textHeight's per-row SetFont, the newbie
|
||||
unit popups' deepest law; the XML gains DropDownList3 the day a customer needs it),
|
||||
info.textHeight's per-row SetFont, the newbie
|
||||
tooltips on rows (the ref gates them behind a default-off cvar), message() on the row/level
|
||||
overflow guards (no dev-error dialog surface; the guard returns silently). The uiScale
|
||||
correction came IN with its first scaled customer (0992, the options window's dropdown
|
||||
@@ -41,8 +49,9 @@
|
||||
- MENU displayMode hides the capsule art trio when present but skips the ref's toggle-button
|
||||
texture blanking + re-anchor (every MENU customer here is a born-hidden 10x10 frame whose
|
||||
capsule never draws).
|
||||
- The disabled-row InvisibleButton spans to the row's RIGHT (the ref stops it at the color
|
||||
swatch, which is OUT).
|
||||
- (Was: "the disabled-row InvisibleButton spans to the row's RIGHT — the ref stops it at the
|
||||
color swatch, which is OUT". The swatch is IN now, so the ref's own anchor is restored:
|
||||
`RIGHT relativeTo="$parentColorSwatch" relativePoint="LEFT"`.)
|
||||
- The list OnShow resets showTimer AND isCounting (the ref writes `showTime`, a field
|
||||
nothing reads — transcribed as the intent, not the typo).
|
||||
-->
|
||||
@@ -97,8 +106,10 @@
|
||||
end
|
||||
|
||||
-- The ref's width law, run from the list's OnUpdate (the header's WIDTH SETTLE): rows
|
||||
-- measure text + 60, +10 for an arrow, +10 for an icon, -30 when notCheckable; the list
|
||||
-- takes max + 15. Waits until every text row has a real (nonzero) measure.
|
||||
-- measure text + 60, +10 for an arrow OR a colour swatch, +10 for an icon, -30 when
|
||||
-- notCheckable; the list takes max + 15. Waits until every text row has a real (nonzero)
|
||||
-- measure. (The ref's `if ( info.hasArrow or info.hasColorSwatch )` is ONE +10, not two —
|
||||
-- the two ride the same right-hand slot, and a row never has both.)
|
||||
function UIDropDownMenu_SettleWidth(listFrame)
|
||||
local listFrameName = listFrame:GetName();
|
||||
if ( listFrame.fixedWidth ) then
|
||||
@@ -116,7 +127,7 @@
|
||||
if ( width > 0 ) then
|
||||
seen = seen + 1;
|
||||
width = width + 60;
|
||||
if ( button.hasArrow ) then
|
||||
if ( button.hasArrow or button.hasColorSwatch ) then
|
||||
width = width + 10;
|
||||
end
|
||||
if ( button.notCheckable ) then
|
||||
@@ -296,6 +307,15 @@
|
||||
|
||||
button.func = info.func;
|
||||
button.owner = info.owner;
|
||||
-- The colour-swatch half of the info table (ref l.229-233), passed to the row so the
|
||||
-- swatch's OnClick can hand it all to ColorPickerFrame. `swatchFunc` becomes the
|
||||
-- picker's `func`, which is why it is the picker's LIVE-preview callback and not an
|
||||
-- on-accept one.
|
||||
button.hasOpacity = info.hasOpacity;
|
||||
button.opacity = info.opacity;
|
||||
button.opacityFunc = info.opacityFunc;
|
||||
button.cancelFunc = info.cancelFunc;
|
||||
button.swatchFunc = info.swatchFunc;
|
||||
button.keepShownOnClick = info.keepShownOnClick;
|
||||
button.tooltipTitle = info.tooltipTitle;
|
||||
button.tooltipText = info.tooltipText;
|
||||
@@ -376,6 +396,20 @@
|
||||
end
|
||||
button.checked = info.checked;
|
||||
|
||||
-- The colour square (ref l.318-328). Its NormalTexture is the swatch art, tinted to the
|
||||
-- row's colour; the row keeps r/g/b so the swatch's OnClick can seed the picker.
|
||||
local colorSwatch = getglobal(listFrameName.."Button"..index.."ColorSwatch");
|
||||
if ( info.hasColorSwatch ) then
|
||||
getglobal(colorSwatch:GetName().."NormalTexture"):SetVertexColor(info.r, info.g, info.b);
|
||||
button.r = info.r;
|
||||
button.g = info.g;
|
||||
button.b = info.b;
|
||||
colorSwatch:Show();
|
||||
else
|
||||
colorSwatch:Hide();
|
||||
end
|
||||
button.hasColorSwatch = info.hasColorSwatch;
|
||||
|
||||
-- notClickable disables white; isTitle disables gold (ref order — title wins).
|
||||
if ( info.notClickable ) then
|
||||
info.disabled = 1;
|
||||
@@ -743,6 +777,26 @@
|
||||
return getglobal(this:GetName().."NormalText"):GetText();
|
||||
end
|
||||
|
||||
-- ref l.801-815, verbatim. THE entry point to the colour picker: every colour option in
|
||||
-- every FrameXML dropdown and every Ace2/Dewdrop menu row lands here. Note what it does
|
||||
-- that the picker itself does not — `previousValues` is captured HERE, and it is the table
|
||||
-- `cancelFunc` is handed, so Cancel restoring the old colour is this line's doing.
|
||||
function UIDropDownMenuButton_OpenColorPicker(button)
|
||||
CloseMenus();
|
||||
if ( not button ) then
|
||||
button = this;
|
||||
end
|
||||
UIDROPDOWNMENU_MENU_VALUE = button.value;
|
||||
ColorPickerFrame.func = button.swatchFunc;
|
||||
ColorPickerFrame.hasOpacity = button.hasOpacity;
|
||||
ColorPickerFrame.opacityFunc = button.opacityFunc;
|
||||
ColorPickerFrame.opacity = button.opacity;
|
||||
ColorPickerFrame:SetColorRGB(button.r, button.g, button.b);
|
||||
ColorPickerFrame.previousValues = {r = button.r, g = button.g, b = button.b, opacity = button.opacity};
|
||||
ColorPickerFrame.cancelFunc = button.cancelFunc;
|
||||
ShowUIPanel(ColorPickerFrame);
|
||||
end
|
||||
|
||||
function UIDropDownMenu_DisableButton(level, id)
|
||||
getglobal("DropDownList"..level.."Button"..id):Disable();
|
||||
end
|
||||
@@ -781,6 +835,41 @@
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<!-- ref UIDropDownMenuTemplates.xml l.38-83. The colour square: a 16x16 button whose
|
||||
NormalTexture is the ChatFrameColorSwatch art, tinted per row, over a 14x14
|
||||
`$parentSwatchBg` that the hover recolours (gold in, white out) — that background
|
||||
is the row's only hover feedback, since the square itself is the row's colour.
|
||||
Clicking it is the ONLY thing in the whole reference UI that opens the colour
|
||||
picker. -->
|
||||
<Button name="$parentColorSwatch" hidden="true">
|
||||
<Size><AbsDimension x="16" y="16"/></Size>
|
||||
<Anchors><Anchor point="RIGHT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor></Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentSwatchBg">
|
||||
<Size><AbsDimension x="14" y="14"/></Size>
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
<Color r="1.0" g="1.0" b="1.0"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
CloseMenus();
|
||||
UIDropDownMenuButton_OpenColorPicker(this:GetParent());
|
||||
</OnClick>
|
||||
<OnEnter>
|
||||
CloseDropDownMenus(this:GetParent():GetParent():GetID() + 1);
|
||||
getglobal(this:GetName().."SwatchBg"):SetVertexColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b);
|
||||
UIDropDownMenu_StopCounting(this:GetParent():GetParent());
|
||||
</OnEnter>
|
||||
<OnLeave>
|
||||
getglobal(this:GetName().."SwatchBg"):SetVertexColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b);
|
||||
UIDropDownMenu_StartCounting(this:GetParent():GetParent());
|
||||
</OnLeave>
|
||||
</Scripts>
|
||||
<NormalTexture name="$parentNormalTexture" file="Interface\ChatFrame\ChatFrameColorSwatch"/>
|
||||
</Button>
|
||||
<Button name="$parentExpandArrow" hidden="true">
|
||||
<Size><AbsDimension x="16" y="16"/></Size>
|
||||
<Anchors><Anchor point="RIGHT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor></Anchors>
|
||||
@@ -799,12 +888,13 @@
|
||||
<NormalTexture file="Interface\ChatFrame\ChatFrameExpandArrow"/>
|
||||
</Button>
|
||||
<!-- Disabled rows don't get mouse events; this transparent cover keeps the hover
|
||||
alive (timeout + deeper-level close). Spans to the row's RIGHT (header note). -->
|
||||
alive (timeout + deeper-level close). It stops at the colour swatch's LEFT edge
|
||||
(ref l.111-113) so a disabled row's swatch is still clickable. -->
|
||||
<Button name="$parentInvisibleButton" hidden="true">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"/>
|
||||
<Anchor point="BOTTOMLEFT"/>
|
||||
<Anchor point="RIGHT"/>
|
||||
<Anchor point="RIGHT" relativeTo="$parentColorSwatch" relativePoint="LEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnEnter>
|
||||
|
||||
@@ -0,0 +1,433 @@
|
||||
<Ui>
|
||||
<!--
|
||||
The reference's SHARED widget kit — Interface\FrameXML\UIPanelTemplates.xml, transcribed
|
||||
from the extracted 1.12.1 file (line numbers below are that file's).
|
||||
|
||||
WHY THIS FILE EXISTS AT ALL. Every window benilla has built so far declared its own private
|
||||
copy of these — `AddonListPanelButtonTemplate`, `BenillaTrainerPanelButtonTemplate`,
|
||||
`BenillaMacroPanelButtonTemplate` and a dozen siblings, each the same four state textures on
|
||||
the same UI-Panel-Button art. That was fine while the only consumer was our own XML. It
|
||||
stopped being fine the day `CreateFrame`'s fourth argument started working (decision 1203):
|
||||
an addon writes `CreateFrame("Button", "MyBtn", UIParent, "UIPanelButtonTemplate")` and
|
||||
nothing else, and 185 of the corpus's 218 addons' template call sites name templates we had
|
||||
never declared. These are those templates, under the reference's own names — the 1190 rule
|
||||
(a name the shipped 1.12 UI defines is FrameXML's, and belongs in `assets/ui`) applied to
|
||||
the ranked list `addon_harness` now prints.
|
||||
|
||||
Every name here is in `reference/1.12-globals.tsv`; none is invented.
|
||||
|
||||
NO DEVIATIONS — the state textures below are the reference's, `<NormalTexture
|
||||
inherits="UIPanelButtonUpTexture"/>` and all. That is worth a note only because it was not
|
||||
true when this file first landed: the loader expanded `inherits=` on `<Layers>` regions and
|
||||
on nested `<Frames>` children but not on a Button's state-texture or a Slider's thumb, so a
|
||||
faithful transcription produced buttons with no art AND NO ERROR. The first version carried
|
||||
the resolved art inline as a workaround; both gaps are now fixed in
|
||||
`loader/widgets.rs` (`apply_button`/`apply_slider` call `expand_region`, the same call a
|
||||
`<Layers>` region gets) and the workaround is gone. Reverting either fix makes
|
||||
`a_panel_button_from_the_template_labels_and_paints` and
|
||||
`a_scroll_frame_from_the_template_wires_its_bar_two_parents_deep` fail.
|
||||
|
||||
NOT CARRIED, deliberately:
|
||||
· `FauxScrollFrameTemplate` (ref l.213-225) — benilla already ships one under the reference's
|
||||
own name in ScrollTemplates.xml, built as a plain `<Frame>` over `BenillaScrollBarTemplate`
|
||||
(decisions 0247/0250/0251). Two declarations of one name would race; re-pointing ours at
|
||||
the reference's `<ScrollFrame>` shape is its own job with its own risk, not this pass's.
|
||||
· `TabButtonTemplate` (ref l.303-402) — already transcribed verbatim in UiPanels.xml, with
|
||||
its fit/highlight deviations recorded there (decision 1004).
|
||||
(`GameMenuButtonTemplate`, ref l.403-410, was on this list as "already in GameMenuFrame.xml"
|
||||
until the colour picker needed it from above that file's LAST-in-the-toc seat. It is below
|
||||
now, in the reference's own file for it, and GameMenuFrame.xml's private copy is gone.)
|
||||
|
||||
AND WHAT THIS SUPERSEDES BUT DOES NOT DELETE: `UIPanelButtonTemplate` below is the shared
|
||||
original of `AddonListPanelButtonTemplate`, `BenillaQuestPanelButtonTemplate`,
|
||||
`BenillaCraftPanelButtonTemplate`, `BenillaQuestLogPanelButtonTemplate`,
|
||||
`BenillaFriendsPanelButtonTemplate`, `BenillaTrainerPanelButtonTemplate`,
|
||||
`BenillaMacroPanelButtonTemplate`, `BenillaTradeSkillPanelButtonTemplate` and
|
||||
`BenillaDressUpPanelButtonTemplate` — nine copies of the same eight lines. Re-pointing those
|
||||
windows at this template is a separate, mechanical pass; nothing here changes them.
|
||||
-->
|
||||
|
||||
<Script><![CDATA[
|
||||
-- The two scroll-frame handlers `UIPanelScrollFrameTemplate` below calls by name (ref
|
||||
-- UIPanelTemplates.lua l.150-157 and l.254-305). `ScrollFrame_OnLoad` and the whole
|
||||
-- `FauxScrollFrame_*` family already live in ScrollTemplates.xml, which loads immediately
|
||||
-- before this file; these two are the remainder of that kit.
|
||||
|
||||
-- ref l.150-157, verbatim.
|
||||
function ScrollFrameTemplate_OnMouseWheel(value)
|
||||
local scrollBar = getglobal(this:GetName() .. "ScrollBar")
|
||||
if (value > 0) then
|
||||
scrollBar:SetValue(scrollBar:GetValue() - (scrollBar:GetHeight() / 2))
|
||||
else
|
||||
scrollBar:SetValue(scrollBar:GetValue() + (scrollBar:GetHeight() / 2))
|
||||
end
|
||||
end
|
||||
|
||||
-- ref l.254-305, verbatim — including `getglobal(scrollbar:GetName().."ThumbTexture")`,
|
||||
-- which is what `<ThumbTexture name="$parentThumbTexture">` publishes. (That publication
|
||||
-- did not exist when this file was first written: `apply_slider` applied a named thumb's
|
||||
-- layout and never set the global, the one asymmetry with a Button's state textures. It
|
||||
-- was the reference's own line that found the gap, and the gap is fixed rather than
|
||||
-- routed around.)
|
||||
function ScrollFrame_OnScrollRangeChanged(scrollrange)
|
||||
local scrollbar = getglobal(this:GetName() .. "ScrollBar")
|
||||
if (not scrollrange) then
|
||||
scrollrange = this:GetVerticalScrollRange()
|
||||
end
|
||||
local value = scrollbar:GetValue()
|
||||
if (value > scrollrange) then
|
||||
value = scrollrange
|
||||
end
|
||||
scrollbar:SetMinMaxValues(0, scrollrange)
|
||||
scrollbar:SetValue(value)
|
||||
if (floor(scrollrange) == 0) then
|
||||
if (this.scrollBarHideable) then
|
||||
scrollbar:Hide()
|
||||
getglobal(scrollbar:GetName() .. "ScrollDownButton"):Hide()
|
||||
getglobal(scrollbar:GetName() .. "ScrollUpButton"):Hide()
|
||||
else
|
||||
getglobal(scrollbar:GetName() .. "ScrollDownButton"):Disable()
|
||||
getglobal(scrollbar:GetName() .. "ScrollUpButton"):Disable()
|
||||
getglobal(scrollbar:GetName() .. "ScrollDownButton"):Show()
|
||||
getglobal(scrollbar:GetName() .. "ScrollUpButton"):Show()
|
||||
end
|
||||
getglobal(scrollbar:GetName() .. "ThumbTexture"):Hide()
|
||||
else
|
||||
getglobal(scrollbar:GetName() .. "ScrollDownButton"):Show()
|
||||
getglobal(scrollbar:GetName() .. "ScrollUpButton"):Show()
|
||||
scrollbar:Show()
|
||||
getglobal(scrollbar:GetName() .. "ScrollDownButton"):Enable()
|
||||
getglobal(scrollbar:GetName() .. "ThumbTexture"):Show()
|
||||
end
|
||||
|
||||
-- Hide/show scrollframe borders (ref l.285-304). Named regions an OWNER declares, so
|
||||
-- all three are optional.
|
||||
local top = getglobal(this:GetName() .. "Top")
|
||||
local bottom = getglobal(this:GetName() .. "Bottom")
|
||||
local middle = getglobal(this:GetName() .. "Middle")
|
||||
if (top and bottom and this.scrollBarHideable) then
|
||||
if (this:GetVerticalScrollRange() == 0) then
|
||||
top:Hide()
|
||||
bottom:Hide()
|
||||
else
|
||||
top:Show()
|
||||
bottom:Show()
|
||||
end
|
||||
end
|
||||
if (middle and this.scrollBarHideable) then
|
||||
if (this:GetVerticalScrollRange() == 0) then
|
||||
middle:Hide()
|
||||
else
|
||||
middle:Show()
|
||||
end
|
||||
end
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
<!-- The five shared panel-button faces (ref l.3-17). Declared under their own names because
|
||||
they ARE 1.12 globals an addon's XML may inherit; the state textures below also carry the
|
||||
resolved art inline, for the reason in the header. -->
|
||||
<Texture name="UIPanelButtonUpTexture" file="Interface\Buttons\UI-Panel-Button-Up" virtual="true">
|
||||
<TexCoords left="0" right="0.625" top="0" bottom="0.6875"/>
|
||||
</Texture>
|
||||
<Texture name="UIPanelButtonDownTexture" file="Interface\Buttons\UI-Panel-Button-Down" virtual="true">
|
||||
<TexCoords left="0" right="0.625" top="0" bottom="0.6875"/>
|
||||
</Texture>
|
||||
<Texture name="UIPanelButtonDisabledTexture" file="Interface\Buttons\UI-Panel-Button-Disabled" virtual="true">
|
||||
<TexCoords left="0" right="0.625" top="0" bottom="0.6875"/>
|
||||
</Texture>
|
||||
<Texture name="UIPanelButtonDisabledDownTexture" file="Interface\Buttons\UI-Panel-Button-Disabled-Down" virtual="true">
|
||||
<TexCoords left="0" right="0.625" top="0" bottom="0.6875"/>
|
||||
</Texture>
|
||||
<Texture name="UIPanelButtonHighlightTexture" file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD" virtual="true">
|
||||
<TexCoords left="0" right="0.625" top="0" bottom="0.6875"/>
|
||||
</Texture>
|
||||
|
||||
<!-- ref l.18-27. THE panel button: 19 call sites across 3 corpus addons, and the shared
|
||||
original of nine private copies in our own assets/ui. Note it declares no `<Size>` — an
|
||||
instance sets its own, and a bare CreateFrame of it comes out 0x0 until the addon does. -->
|
||||
<Button name="UIPanelButtonTemplate" virtual="true">
|
||||
<ButtonText name="$parentText"/>
|
||||
<NormalFont inherits="GameFontNormal"/>
|
||||
<HighlightFont inherits="GameFontHighlight"/>
|
||||
<DisabledFont inherits="GameFontDisable"/>
|
||||
<NormalTexture inherits="UIPanelButtonUpTexture"/>
|
||||
<PushedTexture inherits="UIPanelButtonDownTexture"/>
|
||||
<DisabledTexture inherits="UIPanelButtonDisabledTexture"/>
|
||||
<HighlightTexture inherits="UIPanelButtonHighlightTexture"/>
|
||||
</Button>
|
||||
|
||||
<!-- ref l.403-410, verbatim. The ESC menu's button: a 144x21 UIPanelButtonTemplate whose label
|
||||
trio is overridden to GameFontHighlight/GameFontHighlight/GameFontDisable — the menu's
|
||||
labels are WHITE, not the gold a normal panel button wears. It lived in GameMenuFrame.xml
|
||||
with its art inlined until ColorPickerFrame.xml needed it too (the reference's own colour
|
||||
picker builds its Okay/Cancel from this template, and loads far ahead of the game menu,
|
||||
which is deliberately LAST in the toc). Moving it here is where the reference has it. -->
|
||||
<Button name="GameMenuButtonTemplate" inherits="UIPanelButtonTemplate" virtual="true">
|
||||
<Size><AbsDimension x="144" y="21"/></Size>
|
||||
<NormalFont inherits="GameFontHighlight"/>
|
||||
<HighlightFont inherits="GameFontHighlight"/>
|
||||
<DisabledFont inherits="GameFontDisable"/>
|
||||
</Button>
|
||||
|
||||
<!-- ref l.28-81. The THREE-SLICE panel button: same art, cut 12/stretch/12 so it can be any
|
||||
width without the end caps smearing, and its own OnMouseDown/OnMouseUp swap the three
|
||||
slices to the Down sheet (there is no PushedTexture on this one — the layer regions ARE
|
||||
the pressed state). The slices are `<Layers>` regions, so their `$parent` names publish
|
||||
and the handlers' getglobal finds them. -->
|
||||
<Button name="UIPanelButtonTemplate2" virtual="true">
|
||||
<Size><AbsDimension x="40" y="22"/></Size>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentLeft" file="Interface\Buttons\UI-Panel-Button-Up">
|
||||
<Size><AbsDimension x="12" y="22"/></Size>
|
||||
<Anchors><Anchor point="LEFT"/></Anchors>
|
||||
<TexCoords left="0" right="0.09375" top="0" bottom="0.6875"/>
|
||||
</Texture>
|
||||
<Texture name="$parentRight" file="Interface\Buttons\UI-Panel-Button-Up">
|
||||
<Size><AbsDimension x="12" y="22"/></Size>
|
||||
<Anchors><Anchor point="RIGHT"/></Anchors>
|
||||
<TexCoords left="0.53125" right="0.625" top="0" bottom="0.6875"/>
|
||||
</Texture>
|
||||
<Texture name="$parentMiddle" file="Interface\Buttons\UI-Panel-Button-Up">
|
||||
<Size><AbsDimension x="12" y="22"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="$parentLeft" relativePoint="RIGHT"/>
|
||||
<Anchor point="RIGHT" relativeTo="$parentRight" relativePoint="LEFT"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0.09375" right="0.53125" top="0" bottom="0.6875"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnMouseDown>
|
||||
getglobal(this:GetName() .. "Left"):SetTexture("Interface\\Buttons\\UI-Panel-Button-Down")
|
||||
getglobal(this:GetName() .. "Middle"):SetTexture("Interface\\Buttons\\UI-Panel-Button-Down")
|
||||
getglobal(this:GetName() .. "Right"):SetTexture("Interface\\Buttons\\UI-Panel-Button-Down")
|
||||
</OnMouseDown>
|
||||
<OnMouseUp>
|
||||
getglobal(this:GetName() .. "Left"):SetTexture("Interface\\Buttons\\UI-Panel-Button-Up")
|
||||
getglobal(this:GetName() .. "Middle"):SetTexture("Interface\\Buttons\\UI-Panel-Button-Up")
|
||||
getglobal(this:GetName() .. "Right"):SetTexture("Interface\\Buttons\\UI-Panel-Button-Up")
|
||||
</OnMouseUp>
|
||||
</Scripts>
|
||||
<ButtonText name="$parentText"/>
|
||||
<NormalFont inherits="GameFontNormal"/>
|
||||
<HighlightFont inherits="GameFontHighlight"/>
|
||||
<DisabledFont inherits="GameFontDisable"/>
|
||||
<HighlightTexture inherits="UIPanelButtonHighlightTexture"/>
|
||||
</Button>
|
||||
|
||||
<!-- ref l.82-90. The permanently-greyed variant: the Disabled sheet in the Normal slot, white
|
||||
label. Used for a button that reads as unavailable but still takes clicks. -->
|
||||
<Button name="UIPanelButtonGrayTemplate" virtual="true">
|
||||
<NormalFont inherits="GameFontHighlight"/>
|
||||
<HighlightFont inherits="GameFontHighlight"/>
|
||||
<DisabledFont inherits="GameFontDisable"/>
|
||||
<NormalTexture inherits="UIPanelButtonDisabledTexture"/>
|
||||
<PushedTexture inherits="UIPanelButtonDisabledDownTexture"/>
|
||||
<DisabledTexture inherits="UIPanelButtonDisabledTexture"/>
|
||||
<HighlightTexture inherits="UIPanelButtonHighlightTexture"/>
|
||||
</Button>
|
||||
|
||||
<!-- ref l.91-103. The round X in a window's top-right corner — 8 call sites across 4 corpus
|
||||
addons, the most-wanted template after the checkbox and the panel button. Note the name has
|
||||
no `Template` suffix; that is the reference's own spelling, and the reference-globals table
|
||||
agrees. Its OnClick is `HideUIPanel(this:GetParent())`, the verb UiPanels.xml defines: a
|
||||
Lua global resolved at click time, so the load order between the two files is free. -->
|
||||
<Button name="UIPanelCloseButton" virtual="true">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Scripts>
|
||||
<OnClick>HideUIPanel(this:GetParent())</OnClick>
|
||||
</Scripts>
|
||||
<NormalTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Down"/>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Highlight" alphaMode="ADD"/>
|
||||
</Button>
|
||||
|
||||
<!-- ref l.104-124. The scroll-arrow kit. `UIPanelScrollBarButton` is the shared crop — the
|
||||
1.12 arrow art is 32x32 with the glyph in the centre 16, which is what the 0.25..0.75
|
||||
TexCoords take (ScrollTemplates.xml's own bar applies the identical crop). -->
|
||||
<Texture name="UIPanelScrollBarButton" virtual="true">
|
||||
<TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/>
|
||||
</Texture>
|
||||
<Button name="UIPanelScrollUpButtonTemplate" virtual="true">
|
||||
<Size><AbsDimension x="16" y="16"/></Size>
|
||||
<NormalTexture inherits="UIPanelScrollBarButton" file="Interface\Buttons\UI-ScrollBar-ScrollUpButton-Up"/>
|
||||
<PushedTexture inherits="UIPanelScrollBarButton" file="Interface\Buttons\UI-ScrollBar-ScrollUpButton-Down"/>
|
||||
<DisabledTexture inherits="UIPanelScrollBarButton" file="Interface\Buttons\UI-ScrollBar-ScrollUpButton-Disabled"/>
|
||||
<HighlightTexture inherits="UIPanelScrollBarButton" file="Interface\Buttons\UI-ScrollBar-ScrollUpButton-Highlight" alphaMode="ADD"/>
|
||||
</Button>
|
||||
<Button name="UIPanelScrollDownButtonTemplate" virtual="true">
|
||||
<Size><AbsDimension x="16" y="16"/></Size>
|
||||
<NormalTexture inherits="UIPanelScrollBarButton" file="Interface\Buttons\UI-ScrollBar-ScrollDownButton-Up"/>
|
||||
<PushedTexture inherits="UIPanelScrollBarButton" file="Interface\Buttons\UI-ScrollBar-ScrollDownButton-Down"/>
|
||||
<DisabledTexture inherits="UIPanelScrollBarButton" file="Interface\Buttons\UI-ScrollBar-ScrollDownButton-Disabled"/>
|
||||
<HighlightTexture inherits="UIPanelScrollBarButton" file="Interface\Buttons\UI-ScrollBar-ScrollDownButton-Highlight" alphaMode="ADD"/>
|
||||
</Button>
|
||||
|
||||
<!-- ref l.125-165. The scroll bar itself: a Slider carrying its two step buttons as nested
|
||||
frames, so `$parentScrollUpButton`/`$parentScrollDownButton` publish under the OWNER's
|
||||
name two levels down (`MyScrollScrollBarScrollUpButton`) — which is exactly what
|
||||
`ScrollFrame_OnLoad` and `FauxScrollFrame_Update` getglobal for. The value model here is
|
||||
the REFERENCE's: `OnValueChanged` pushes the value straight into the parent ScrollFrame's
|
||||
`SetVerticalScroll`, i.e. pixels of pan. (benilla's own `BenillaScrollBarTemplate` in
|
||||
ScrollTemplates.xml is the faux twin of this — same art and same child names, but its
|
||||
OnValueChanged drives a row offset instead. Both are needed: ours serves our windows, this
|
||||
serves an addon that names the reference template.) -->
|
||||
<Slider name="UIPanelScrollBarTemplate" virtual="true">
|
||||
<Size><AbsDimension x="16" y="0"/></Size>
|
||||
<Frames>
|
||||
<Button name="$parentScrollUpButton" inherits="UIPanelScrollUpButtonTemplate">
|
||||
<Anchors><Anchor point="BOTTOM" relativePoint="TOP"/></Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
local parent = this:GetParent()
|
||||
parent:SetValue(parent:GetValue() - (parent:GetHeight() / 2))
|
||||
PlaySound("UChatScrollButton")
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="$parentScrollDownButton" inherits="UIPanelScrollDownButtonTemplate">
|
||||
<Anchors><Anchor point="TOP" relativePoint="BOTTOM"/></Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
local parent = this:GetParent()
|
||||
parent:SetValue(parent:GetValue() + (parent:GetHeight() / 2))
|
||||
PlaySound("UChatScrollButton")
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnValueChanged>this:GetParent():SetVerticalScroll(arg1)</OnValueChanged>
|
||||
</Scripts>
|
||||
<ThumbTexture name="$parentThumbTexture" inherits="UIPanelScrollBarButton" file="Interface\Buttons\UI-ScrollBar-Knob">
|
||||
<Size><AbsDimension x="16" y="16"/></Size>
|
||||
<TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/>
|
||||
</ThumbTexture>
|
||||
</Slider>
|
||||
|
||||
<!-- ref l.166-212. The REAL scroll frame: an engine `<ScrollFrame>` that pans and clips a
|
||||
scroll child, with the bar above pinned down its right edge (inset 16 top and bottom so
|
||||
the arrow buttons sit inside the track span). 6 call sites across 2 corpus addons.
|
||||
|
||||
`ScrollFrame_OnLoad` is ScrollTemplates.xml's (the reference's own function, already
|
||||
transcribed there); the other two handlers are this file's script block. An instance
|
||||
supplies its own `<ScrollChild>` — without one the frame has no scroll range at all, which
|
||||
the loader now says out loud. -->
|
||||
<ScrollFrame name="UIPanelScrollFrameTemplate" virtual="true">
|
||||
<Frames>
|
||||
<Slider name="$parentScrollBar" inherits="UIPanelScrollBarTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="6" y="-16"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="6" y="16"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Slider>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>ScrollFrame_OnLoad()</OnLoad>
|
||||
<OnScrollRangeChanged>ScrollFrame_OnScrollRangeChanged(arg2)</OnScrollRangeChanged>
|
||||
<!-- The reference's own comment here reads "The code to disable the ScrollDownButton is
|
||||
pretty hacked, but it works". Transcribed as it stands. -->
|
||||
<OnVerticalScroll>
|
||||
local scrollbar = getglobal(this:GetName() .. "ScrollBar")
|
||||
scrollbar:SetValue(arg1)
|
||||
local min
|
||||
local max
|
||||
min, max = scrollbar:GetMinMaxValues()
|
||||
if (arg1 == 0) then
|
||||
getglobal(scrollbar:GetName() .. "ScrollUpButton"):Disable()
|
||||
else
|
||||
getglobal(scrollbar:GetName() .. "ScrollUpButton"):Enable()
|
||||
end
|
||||
if ((scrollbar:GetValue() - max) == 0) then
|
||||
getglobal(scrollbar:GetName() .. "ScrollDownButton"):Disable()
|
||||
else
|
||||
getglobal(scrollbar:GetName() .. "ScrollDownButton"):Enable()
|
||||
end
|
||||
</OnVerticalScroll>
|
||||
<OnMouseWheel>ScrollFrameTemplate_OnMouseWheel(arg1)</OnMouseWheel>
|
||||
</Scripts>
|
||||
</ScrollFrame>
|
||||
|
||||
<!-- ref l.226-279. The standard text box: a three-slice Common-Input-Border under an EditBox,
|
||||
with the reference's escape/focus trio. 5 call sites across 3 corpus addons. The
|
||||
direct-child `<FontString>` is the box's TEXT region (the engine's LoadXML slot, not a
|
||||
layer) — `loader/regions.rs`'s `apply_special_fontstrings`. -->
|
||||
<EditBox name="InputBoxTemplate" enableMouse="true" virtual="true">
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentLeft" file="Interface\Common\Common-Input-Border">
|
||||
<Size><AbsDimension x="8" y="20"/></Size>
|
||||
<Anchors><Anchor point="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0" right="0.0625" top="0" bottom="0.625"/>
|
||||
</Texture>
|
||||
<Texture name="$parentRight" file="Interface\Common\Common-Input-Border">
|
||||
<Size><AbsDimension x="8" y="20"/></Size>
|
||||
<Anchors><Anchor point="RIGHT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0.9375" right="1.0" top="0" bottom="0.625"/>
|
||||
</Texture>
|
||||
<Texture name="$parentMiddle" file="Interface\Common\Common-Input-Border">
|
||||
<Size><AbsDimension x="10" y="20"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="$parentLeft" relativePoint="RIGHT"/>
|
||||
<Anchor point="RIGHT" relativeTo="$parentRight" relativePoint="LEFT"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0.0625" right="0.9375" top="0" bottom="0.625"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnEscapePressed>this:ClearFocus()</OnEscapePressed>
|
||||
<OnEditFocusLost>this:HighlightText(0, 0)</OnEditFocusLost>
|
||||
<OnEditFocusGained>this:HighlightText()</OnEditFocusGained>
|
||||
</Scripts>
|
||||
<FontString inherits="ChatFontNormal"/>
|
||||
</EditBox>
|
||||
|
||||
<!-- ref l.280-302. THE most-wanted template in the whole corpus: 69 `CreateFrame` call sites.
|
||||
A 32x32 checkbox with its label hung off the right edge, and no scripts of its own — the
|
||||
checked flag toggles in the engine BEFORE `OnClick` fires (script/button.rs), so an addon
|
||||
that sets only an OnClick reads the new state there, which is what every one of those 69
|
||||
sites assumes. -->
|
||||
<CheckButton name="UICheckButtonTemplate" virtual="true">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="$parentText" inherits="GameFontNormalSmall">
|
||||
<Anchors><Anchor point="LEFT" relativePoint="RIGHT"><Offset><AbsDimension x="-2" y="0"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<NormalTexture file="Interface\Buttons\UI-CheckBox-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-CheckBox-Down"/>
|
||||
<HighlightTexture file="Interface\Buttons\UI-CheckBox-Highlight" alphaMode="ADD"/>
|
||||
<CheckedTexture file="Interface\Buttons\UI-CheckBox-Check"/>
|
||||
<DisabledCheckedTexture file="Interface\Buttons\UI-CheckBox-Check-Disabled"/>
|
||||
</CheckButton>
|
||||
|
||||
<!-- ref l.411-437. The radio button — the same CheckButton machinery on the four-frame
|
||||
UI-RadioButton strip (normal / checked / highlight are three crops of one 64x16 sheet).
|
||||
Carried because it is the checkbox's sibling in the same file and costs nothing; no corpus
|
||||
addon names it today. -->
|
||||
<CheckButton name="UIRadioButtonTemplate" virtual="true">
|
||||
<Size><AbsDimension x="16" y="16"/></Size>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<FontString name="$parentText" inherits="GameFontNormalSmall">
|
||||
<Anchors><Anchor point="LEFT" relativePoint="RIGHT"><Offset><AbsDimension x="5" y="0"/></Offset></Anchor></Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<NormalTexture file="Interface\Buttons\UI-RadioButton">
|
||||
<TexCoords left="0" right="0.25" top="0" bottom="1"/>
|
||||
</NormalTexture>
|
||||
<HighlightTexture file="Interface\Buttons\UI-RadioButton" alphaMode="ADD">
|
||||
<TexCoords left="0.5" right="0.75" top="0" bottom="1"/>
|
||||
</HighlightTexture>
|
||||
<CheckedTexture file="Interface\Buttons\UI-RadioButton">
|
||||
<TexCoords left="0.25" right="0.5" top="0" bottom="1"/>
|
||||
</CheckedTexture>
|
||||
</CheckButton>
|
||||
</Ui>
|
||||
@@ -17,6 +17,29 @@
|
||||
-->
|
||||
<Frame name="UIParent" setAllPoints="true" frameStrata="MEDIUM"/>
|
||||
|
||||
<!--
|
||||
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
|
||||
@@ -36,17 +59,22 @@
|
||||
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).
|
||||
- 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 )`.
|
||||
- MultiBarLeft/MultiBarRight (the vertical bars) and ReputationWatchBar don't exist yet:
|
||||
nil-guarded (the ref reads them bare only because it always creates them). MainMenuExpBar
|
||||
and MainMenuBarMaxLevelBar DO exist now, so the `reputation` and `maxLevel` flags are
|
||||
real reads — `reputation` still cannot fire (it needs ReputationWatchBar too) and
|
||||
`maxLevel` fires exactly when the level-60 rail replaces the XP bar, which is the ref's
|
||||
own rule.
|
||||
- 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.
|
||||
@@ -161,8 +189,8 @@
|
||||
|
||||
-- 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()
|
||||
local bottomLeftShown = MultiBarBottomLeft and MultiBarBottomLeft:IsShown()
|
||||
local bottomRightShown = MultiBarBottomRight and MultiBarBottomRight:IsShown()
|
||||
if ( bottomLeftShown or bottomRightShown ) then
|
||||
table.insert(yOffsetFrames, "bottomEither")
|
||||
end
|
||||
@@ -179,7 +207,7 @@
|
||||
table.insert(xOffsetFrames, "rightRight")
|
||||
end
|
||||
|
||||
if ( ( BenillaPetActionBarFrame and BenillaPetActionBarFrame:IsShown() ) or ( BenillaShapeshiftBarFrame and BenillaShapeshiftBarFrame:IsShown() ) ) then
|
||||
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
|
||||
@@ -280,21 +308,21 @@
|
||||
-- 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
|
||||
local bottomLeftUp = MultiBarBottomLeft and MultiBarBottomLeft:IsShown()
|
||||
if ( SlidingActionBarTexture0 and SlidingActionBarTexture1 ) then
|
||||
if ( bottomLeftUp ) then
|
||||
BenillaSlidingActionBarTexture0:Hide()
|
||||
BenillaSlidingActionBarTexture1:Hide()
|
||||
SlidingActionBarTexture0:Hide()
|
||||
SlidingActionBarTexture1:Hide()
|
||||
else
|
||||
BenillaSlidingActionBarTexture0:Show()
|
||||
BenillaSlidingActionBarTexture1:Show()
|
||||
SlidingActionBarTexture0:Show()
|
||||
SlidingActionBarTexture1:Show()
|
||||
end
|
||||
end
|
||||
if ( BenillaPetActionBarFrame ) then
|
||||
if ( PetActionBarFrame ) 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)
|
||||
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
|
||||
@@ -303,7 +331,7 @@
|
||||
-- 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)
|
||||
PetActionBarFrame:SetPoint("TOPLEFT", "MainMenuBar", "BOTTOMLEFT", PETACTIONBAR_XPOS, PETACTIONBAR_YPOS)
|
||||
end
|
||||
|
||||
-- The under-minimap right stack (ref l.1740-1770): anchorY walks down capture bars →
|
||||
@@ -339,10 +367,10 @@
|
||||
-- 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
|
||||
-- QuestWatchFrame; 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)
|
||||
if ( QuestWatchFrame ) then
|
||||
QuestWatchFrame:SetPoint("TOPRIGHT", "MinimapCluster", "BOTTOMRIGHT", -CONTAINER_OFFSET_X, anchorY)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -429,8 +457,8 @@
|
||||
-- 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.
|
||||
-- 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
|
||||
@@ -473,5 +501,71 @@
|
||||
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
|
||||
|
||||
-- ── MouseIsOver (ref UIParent.lua l.1388-1417), verbatim ──────────────────────────────
|
||||
--
|
||||
-- "is the cursor inside this frame, optionally with per-edge padding" — the idiom every
|
||||
-- hover-driven addon writes, and **9 corpus addons call it**. Missing, each is
|
||||
-- `attempt to call global 'MouseIsOver'`.
|
||||
--
|
||||
-- Transcribed rather than approximated, including the two things that look like bugs and
|
||||
-- are not:
|
||||
-- · the offsets are ADDED to every edge, so a positive `bottomOffset` shrinks the box
|
||||
-- from below while a positive `topOffset` grows it upward. The reference's own caller
|
||||
-- proves the intent — `MouseIsOver(chatFrame, 45, -10, -5, 5)` means 45 up into the
|
||||
-- tab strip, 10 in from the bottom, 5 out each side.
|
||||
-- · a nil `topOffset` zeroes ALL FOUR, so `MouseIsOver(f, nil, 5)` silently ignores the 5.
|
||||
-- That is the reference's `if ( not topOffset ) then` block exactly; an addon written
|
||||
-- against 1.12 may depend on it.
|
||||
-- The reference's `-- Hack to fix a symptom not the real issue` guard on an unresolved rect
|
||||
-- is kept with its comment, because a frame that has never been laid out really does answer
|
||||
-- nil here and returning nil is what callers expect.
|
||||
--
|
||||
-- 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 = frame:GetLeft()
|
||||
local right = frame:GetRight()
|
||||
local top = frame:GetTop()
|
||||
local bottom = frame:GetBottom()
|
||||
if not topOffset then
|
||||
topOffset = 0
|
||||
bottomOffset = 0
|
||||
leftOffset = 0
|
||||
rightOffset = 0
|
||||
end
|
||||
-- Hack to fix a symptom not the real issue
|
||||
if not left then
|
||||
return
|
||||
end
|
||||
left = left + leftOffset
|
||||
right = right + rightOffset
|
||||
top = top + topOffset
|
||||
bottom = bottom + bottomOffset
|
||||
if (x > left and x < right) and (y > bottom and y < top) then
|
||||
return 1
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
]]></Script>
|
||||
</Ui>
|
||||
|
||||
@@ -8,9 +8,11 @@
|
||||
Ground truth: the 1.12 `UIParent.lua` extract (scratchpad/UIParent.lua, patch-2.MPQ), quoted
|
||||
verbatim in the line-numbered comments below — never re-derived. v1 scope (pin §3): the
|
||||
**left + center two-slot logic only**. The real client's `full`/`doublewide` areas, the
|
||||
`CanOpenPanels`/full-screen/native-center gates, `UIChildWindows`, and `UISpecialFrames` are
|
||||
all out of scope — no benilla panel uses them yet — but the registration table keeps their
|
||||
exact shape (`area`, `pushable`, `whileDead`) so a later row drops in unchanged.
|
||||
`CanOpenPanels`/full-screen/native-center gates and `UIChildWindows` are all out of scope —
|
||||
no benilla panel uses them yet — but the registration table keeps their exact shape
|
||||
(`area`, `pushable`, `whileDead`) so a later row drops in unchanged. **`UISpecialFrames`
|
||||
left that list in decision 1206**: it is not ours to use, it is the table an ADDON writes
|
||||
its own window into, and 34 corpus call sites do.
|
||||
|
||||
Frame names here are OUR shipped names (`Benilla*`); UIParent.lua's names (`GossipFrame`,
|
||||
`MerchantFrame`, `LootFrame`) are the semantic reference the pin cites, not what we register.
|
||||
@@ -20,50 +22,52 @@
|
||||
-- rows benilla ships/anticipates today (pin §1): Gossip + Merchant are live; Loot registers
|
||||
-- now (pushable=7) so the coming loot window lands on an already-wired system.
|
||||
UIPanelWindows = {}
|
||||
UIPanelWindows["BenillaGossipFrame"] = { area = "left", pushable = 0 }
|
||||
UIPanelWindows["BenillaMerchantFrame"] = { area = "left", pushable = 0 }
|
||||
UIPanelWindows["GossipFrame"] = { area = "left", pushable = 0 }
|
||||
UIPanelWindows["MerchantFrame"] = { area = "left", pushable = 0 }
|
||||
-- The mail window (decision 0544): the reference UIParent.lua l.37 registers MailFrame
|
||||
-- area="left", pushable=0. The OpenMailFrame is NOT a panel — it is a special-frame toplevel
|
||||
-- (UIParent.lua l.46 UISpecialFrames), closed by ESC in ToggleGameMenu below (this v1 drops
|
||||
-- the UISpecialFrames table, so escapable toplevels are hard-listed there, like WorldMapFrame).
|
||||
UIPanelWindows["BenillaMailFrame"] = { area = "left", pushable = 0 }
|
||||
-- (UIParent.lua l.46 UISpecialFrames), closed by ESC in ToggleGameMenu below. Our own
|
||||
-- escapable toplevels stay hard-listed in that chain rather than pushed into
|
||||
-- UISpecialFrames — the table exists now (1206) but it is the ADDONS' list, and mixing our
|
||||
-- windows into it would make an addon's `tremove` able to break the client's own ESC.
|
||||
UIPanelWindows["MailFrame"] = { area = "left", pushable = 0 }
|
||||
-- The trade window (decision 0592 P1, TradeFrame.xml): the ref's own UIParent.lua l.27
|
||||
-- registration (`UIPanelWindows["TradeFrame"] = { area = "left", pushable = 1 }`). Without
|
||||
-- this row ShowUIPanel takes its unregistered branch — a bare frame:Show() with no left-slot
|
||||
-- placement — so the window flips IsShown() but never lands on screen (the bug: OPEN_WINDOW
|
||||
-- arrived on both clients, yet no window). pushable=1 lets a pushable=0 panel (a merchant)
|
||||
-- shove it to center, the reference's browse-while-trading arbitration.
|
||||
UIPanelWindows["BenillaTradeFrame"] = { area = "left", pushable = 1 }
|
||||
UIPanelWindows["TradeFrame"] = { area = "left", pushable = 1 }
|
||||
-- The trainer window (decision 0237 phase 3, TrainerFrame.xml): an NPC session exactly like
|
||||
-- Gossip/Merchant above (area "left", pushable 0) — competes for/replaces them at the slot.
|
||||
UIPanelWindows["BenillaTrainerFrame"] = { area = "left", pushable = 0 }
|
||||
-- The bank window (decision 0604 phase 4, BankFrame.xml): the ref's own UIParent.lua l.28
|
||||
-- registration (`UIPanelWindows["BankFrame"] = { area = "left", pushable = 6 }`), quoted
|
||||
-- verbatim — the same left-slot competition as the other NPC-session windows above.
|
||||
UIPanelWindows["BenillaBankFrame"] = { area = "left", pushable = 6 }
|
||||
UIPanelWindows["BankFrame"] = { area = "left", pushable = 6 }
|
||||
-- The taxi-map window (decision 0484 phase 2, TaxiFrame.xml): the ref's own UIParent.lua
|
||||
-- registration line (`UIPanelWindows["TaxiFrame"] = { area = "left", pushable = 0 }`),
|
||||
-- quoted verbatim — same left-slot competition as Gossip/Merchant/Trainer above.
|
||||
UIPanelWindows["BenillaTaxiFrame"] = { area = "left", pushable = 0 }
|
||||
UIPanelWindows["TaxiFrame"] = { area = "left", pushable = 0 }
|
||||
-- The dressing room (decision 1060, DressUpFrame.xml): the ref's own UIParent.lua l.41
|
||||
-- registration (`UIPanelWindows["DressUpFrame"] = { area = "left", pushable = 2 }`), quoted
|
||||
-- verbatim — pushable=2 lets the NPC-session windows (pushable=0) shove it aside, which is
|
||||
-- what makes "ctrl-click a merchant's item" show both.
|
||||
UIPanelWindows["BenillaDressUpFrame"] = { area = "left", pushable = 2 }
|
||||
UIPanelWindows["BenillaQuestFrame"] = { area = "left", pushable = 0 }
|
||||
UIPanelWindows["BenillaQuestLogFrame"] = { area = "left", pushable = 0 }
|
||||
UIPanelWindows["BenillaLootFrame"] = { area = "left", pushable = 7 }
|
||||
UIPanelWindows["DressUpFrame"] = { area = "left", pushable = 2 }
|
||||
UIPanelWindows["QuestFrame"] = { area = "left", pushable = 0 }
|
||||
UIPanelWindows["QuestLogFrame"] = { area = "left", pushable = 0 }
|
||||
UIPanelWindows["LootFrame"] = { area = "left", pushable = 7 }
|
||||
-- The character window (decision 0208 phase 1a, CharacterFrame.xml): the ref's own
|
||||
-- CharacterFrame row (area "left", pushable 0) — competes for/replaces the other left-slot
|
||||
-- windows exactly like Gossip/Merchant/Quest/QuestLog above.
|
||||
UIPanelWindows["BenillaCharacterFrame"] = { area = "left", pushable = 0 }
|
||||
UIPanelWindows["CharacterFrame"] = { area = "left", pushable = 0 }
|
||||
-- The inspect window (decision 0631, InspectFrame.xml): the ref's own InspectFrame row
|
||||
-- (`Blizzard_InspectUI.lua:4` — area "left", pushable 0), so inspecting swaps out whichever
|
||||
-- left-slot window was open, exactly as the character window does.
|
||||
UIPanelWindows["BenillaInspectFrame"] = { area = "left", pushable = 0 }
|
||||
-- The spellbook (decision 0216 §8, SpellBookFrame.xml): the ref's own SpellBookFrame row
|
||||
-- (area "left", pushable 0) — same left-slot competition as every window above.
|
||||
UIPanelWindows["BenillaSpellBookFrame"] = { area = "left", pushable = 0 }
|
||||
UIPanelWindows["SpellBookFrame"] = { area = "left", pushable = 0 }
|
||||
-- The talent window (decision 0304 §4, TalentFrame.xml): the ref's own TalentFrame row is
|
||||
-- `{ area = "left", pushable = 6, whileDead = 1 }` — pushable 6 kept (one tier above the
|
||||
-- plain NPC-session/player-state windows' 0, matching the ref exactly); `whileDead` is
|
||||
@@ -74,7 +78,7 @@
|
||||
-- The social window (decision 0668) — the ref's own registration line verbatim
|
||||
-- (`UIPanelWindows["FriendsFrame"] = { area = "left", pushable = 0, whileDead = 1 }`,
|
||||
-- UIParent.lua l.29). whileDead: a corpse run is exactly when you want to whisper someone.
|
||||
UIPanelWindows["BenillaFriendsFrame"] = { area = "left", pushable = 0, whileDead = 1 }
|
||||
UIPanelWindows["FriendsFrame"] = { area = "left", pushable = 0, whileDead = 1 }
|
||||
-- The macro window (decision 0983, MacroFrame.xml) — the ref's own registration line
|
||||
-- verbatim (`UIPanelWindows["MacroFrame"] = { area = "left", pushable = 5, whileDead = 1 }`,
|
||||
-- Blizzard_MacroUI.lua:7). whileDead is the ref's and it earns its keep: writing a macro
|
||||
@@ -375,9 +379,33 @@
|
||||
frame:Hide()
|
||||
end
|
||||
|
||||
-- CloseWindows(ignoreCenter) — UIParent.lua l.928-957, v1 left+center slice (the
|
||||
-- doublewide/full-screen/UISpecialFrames handling, l.932-954, is dropped: none exist yet).
|
||||
-- Implemented per the pin so the function exists; wiring it to ESC is a later slice.
|
||||
-- UISpecialFrames (UIParent.lua l.52-55) — **the addon's own ESC list**, and the reason
|
||||
-- this stopped being "dropped: none exist yet" (decision 1206).
|
||||
--
|
||||
-- `tinsert(UISpecialFrames, "MyFrame")` is THE 1.12 idiom for "ESC should close my
|
||||
-- window", and it is 34 call sites across the corpus. With the table absent that line is
|
||||
-- `bad argument #1 to 'tinsert' (table expected, got nil)` — which killed 11 addons at
|
||||
-- load, the second-largest wall in the survey after the Lua-5.0 generic-for.
|
||||
--
|
||||
-- Seeded with the reference's own two entries — **both** of them now. 1206 seeded only
|
||||
-- `ItemRefTooltip`, and its Rejected section wrote down why, verbatim: *"Listing
|
||||
-- `ColorPickerFrame`. The reference seeds it; we do not ship one, and a name that resolves
|
||||
-- to nothing is skipped harmlessly — but seeding a frame we lack would be writing a claim
|
||||
-- we cannot honour."* ColorPickerFrame.xml ships it, so the claim is honourable now, and
|
||||
-- the entry is the reference's own rather than one of ours.
|
||||
--
|
||||
-- Note it does NOT contradict the paragraph above (our own escapable toplevels stay
|
||||
-- hard-listed in the ESC chain). The concern there is an addon's `tremove` breaking the
|
||||
-- CLIENT's own ESC; here the ESC chain carries the colour picker on its own rung — and has
|
||||
-- to, because ESC on this window must CANCEL (restore the colour), which this walk's plain
|
||||
-- `Hide` cannot do. So the table entry is not what makes ESC work; it is what makes
|
||||
-- `CloseWindows` put the picker away when something ELSE closes the world's windows —
|
||||
-- opening the game menu, a `CloseAllWindows()` — which is exactly the reference's own
|
||||
-- behaviour on that path, cancel-less Hide and all.
|
||||
UISpecialFrames = { "ItemRefTooltip", "ColorPickerFrame" }
|
||||
|
||||
-- CloseWindows(ignoreCenter) — UIParent.lua l.928-957. The doublewide slice stays dropped
|
||||
-- (we have none); the UISpecialFrames loop at l.947-954 is now carried.
|
||||
function CloseWindows(ignoreCenter)
|
||||
local leftFrame = GetLeftFrame()
|
||||
local centerFrame = GetCenterFrame()
|
||||
@@ -393,6 +421,18 @@
|
||||
end
|
||||
end
|
||||
|
||||
-- The special frames, the reference's own loop (l.947-954): a plain Hide, never
|
||||
-- HideUIPanel, because these hold no panel slot. Its `for index, value in
|
||||
-- UISpecialFrames do` is Lua 5.0's iterator-less generic-for, which our VM cannot run
|
||||
-- (decision 1202) — `ipairs` is the same walk in the dialect we have.
|
||||
for _, name in ipairs(UISpecialFrames) do
|
||||
local frame = getglobal(name)
|
||||
if frame and frame:IsVisible() then
|
||||
frame:Hide()
|
||||
found = 1
|
||||
end
|
||||
end
|
||||
|
||||
return found
|
||||
end
|
||||
|
||||
@@ -467,7 +507,7 @@
|
||||
-- The backpack (bag 0) always exists; an equipped-bag window only when a bag is in
|
||||
-- the slot (the same existence test as BagFrame.xml's BenillaOpenAllBags).
|
||||
local exists = bagId == 0
|
||||
or (C_Container and C_Container.GetContainerNumSlots(bagId) > 0)
|
||||
or (GetContainerNumSlots and GetContainerNumSlots(bagId) > 0)
|
||||
local bag = exists and getglobal(BAG_WINDOWS[bagId + 1])
|
||||
if bag then
|
||||
if bag:IsShown() then
|
||||
@@ -557,6 +597,29 @@
|
||||
if StaticPopup_EscapePressed() then
|
||||
return
|
||||
end
|
||||
-- The colour picker next (ColorPickerFrame.xml). The reference hooks ESC on the frame
|
||||
-- itself (`<OnKeyDown>` l.177-184, whose ESCAPE arm is `HideUIPanel(this)` +
|
||||
-- `cancelFunc(previousValues)`); this engine gives plain frames no raw key events, so
|
||||
-- it routes through this chain exactly like the AddOns list on the next rung.
|
||||
-- **Cancel-CLICK, not hide** — the ref's own arm restores the previous colour, and
|
||||
-- clicking the real button is how the options rung below already spells this (the ref's
|
||||
-- `OptionsFrameCancel:Click()`), so there is one cancel path, not two.
|
||||
local picker = getglobal("ColorPickerFrame")
|
||||
if picker and picker:IsVisible() then
|
||||
ColorPickerCancelButton:Click()
|
||||
return
|
||||
end
|
||||
-- The AddOns list next (decision 1197). The reference's glue twin hooks ESC directly
|
||||
-- (`AddonList_OnKeyDown`, whose ESCAPE arm is `AddonList_OnCancel`); this engine gives
|
||||
-- plain frames no raw key events, so it routes through this shared chain like the
|
||||
-- stack-split spinner and the world map do. **Cancel, not hide** — ESC on that window
|
||||
-- means "discard what I just ticked", which is the reference's own arm and the reason
|
||||
-- the panel stages its edits at all.
|
||||
local addons = getglobal("AddonListFrame")
|
||||
if addons and addons:IsVisible() then
|
||||
AddonList_OnCancel()
|
||||
return
|
||||
end
|
||||
-- The options window next (l.1483-1484, live since decision 0950 — this rung sat
|
||||
-- unfilled while GameMenuFrame.xml's disabled buttons were the gap). The ref
|
||||
-- cancel-clicks (`OptionsFrameCancel:Click()`); ours hides: benilla applies changes
|
||||
@@ -585,7 +648,7 @@
|
||||
-- — this engine has no plain-frame keyboard capture (StackSplit.xml's own header comment),
|
||||
-- so it routes through this shared chain instead. Just hides (StackSplitFrame_OnHide
|
||||
-- clears the owner's hasStackSplit flag) — no accept, matching a real Cancel click.
|
||||
local split = getglobal("BenillaStackSplitFrame")
|
||||
local split = getglobal("StackSplitFrame")
|
||||
if split and split:IsVisible() then
|
||||
split:Hide()
|
||||
return
|
||||
@@ -604,7 +667,7 @@
|
||||
-- UISpecialFrames (UIParent.lua l.46), so ESC closes it BEFORE the mail panel itself —
|
||||
-- routed through this chain since this v1 drops the UISpecialFrames table. Its OnHide
|
||||
-- runs the taken-letter auto-delete + close sound (MailFrame.lua OpenMailFrame_OnHide).
|
||||
local openmail = getglobal("BenillaOpenMailFrame")
|
||||
local openmail = getglobal("OpenMailFrame")
|
||||
if openmail and openmail:IsVisible() then
|
||||
HideUIPanel(openmail)
|
||||
return
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
branch on UnitPlayerControlled (which also covers pets); we resolve its player half via
|
||||
UnitIsPlayer, so a player-controlled PET takes the NPC reaction branch until that flag lands.
|
||||
- The classification border swap (elite/rare art, ref-TargetFrame.lua l.205-218) LANDED with
|
||||
decision 0782 — UnitClassification is bound and BenillaTargetFrame_CheckClassification
|
||||
decision 0782 — UnitClassification is bound and TargetFrame_CheckClassification
|
||||
below runs the ref's branch table. Two things worth knowing at this altitude. The rank
|
||||
`UnitClassification` reads is the client's GATED rank (`0x605620`): a pet or charmed unit
|
||||
reports rank 0, so an enslaved elite wears the plain border, and because the tooltip's
|
||||
@@ -163,9 +163,9 @@
|
||||
end
|
||||
end
|
||||
-- The hover seam (1146): who this bar belongs to, and what its click means.
|
||||
local click = BenillaTargetFrame_OnClick
|
||||
local click = TargetFrame_OnClick
|
||||
if unit == "player" then
|
||||
click = BenillaPlayerFrame_OnClick
|
||||
click = PlayerFrame_OnClick
|
||||
end
|
||||
frame.healthBar.owner = frame
|
||||
frame.powerBar.owner = frame
|
||||
@@ -225,7 +225,7 @@
|
||||
-- The status flash (ref PlayerFrame_OnLoad, decision 1082): the resting zzz /
|
||||
-- auto-attack swords under the portrait, the gold/red ring, and the pulse
|
||||
-- counters. Handles cached like pvpIcon above; their presence is what gates
|
||||
-- BenillaPlayerFrame_UpdateStatus in the shared Update.
|
||||
-- PlayerFrame_UpdateStatus in the shared Update.
|
||||
frame.statusTexture = getglobal(tf .. "StatusTexture")
|
||||
frame.attackBackground = getglobal(tf .. "AttackBackground")
|
||||
frame.restIcon = getglobal(tf .. "RestIcon")
|
||||
@@ -249,7 +249,7 @@
|
||||
-- A plain Frame isn't mouse-enabled by construction (only Button/EditBox are); enable it so
|
||||
-- the hover tooltip fires (the real client's unit frames take the mouse to target/tooltip).
|
||||
frame:EnableMouse(true)
|
||||
BenillaUnitFrame_Update(frame)
|
||||
UnitFrame_Update()
|
||||
end
|
||||
|
||||
-- Hover: the engine's SetUnit — the byte-verified unit line law (decision 0276: name/
|
||||
@@ -324,7 +324,7 @@
|
||||
if not UnitExists(unit) then return end
|
||||
GameTooltip_SetDefaultAnchor(GameTooltip, frame)
|
||||
if SHOW_NEWBIE_TIPS == "1" then
|
||||
if frame:GetName() == "BenillaPlayerFrame" then
|
||||
if frame:GetName() == "PlayerFrame" then
|
||||
GameTooltip_AddNewbieTip(frame, PARTY_OPTIONS_LABEL, 1.0, 1.0, 1.0,
|
||||
NEWBIE_TOOLTIP_PARTYOPTIONS)
|
||||
return
|
||||
@@ -356,7 +356,8 @@
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaUnitFrame_OnEvent(frame)
|
||||
function UnitFrame_OnEvent(event)
|
||||
local frame = this
|
||||
-- UNIT_* events name their unit in arg1; ignore other units' updates.
|
||||
if string.sub(event, 1, 5) == "UNIT_" and arg1 ~= frame.unit then
|
||||
return
|
||||
@@ -383,7 +384,7 @@
|
||||
elseif event == "PLAYER_REGEN_ENABLED" then
|
||||
frame.onHateList = nil
|
||||
end
|
||||
BenillaUnitFrame_Update(frame)
|
||||
UnitFrame_Update()
|
||||
-- The player's flag-change sound (decision 0646 §2/§4). The reference plays
|
||||
-- igPVPUpdate inside PlayerFrame_UpdatePvPStatus's two show branches, which it only
|
||||
-- ever reaches from UNIT_FACTION; our repaint runs from the shared Update on every
|
||||
@@ -549,7 +550,8 @@
|
||||
-- sound because all three BLPs are 256x128 (checked, not assumed), so the region's
|
||||
-- XML-declared TexCoords (0.09375..1.0 x 0..0.78125 = the 232x100 crop) frame all three
|
||||
-- identically, and our SetTexture touches only the path (it leaves tex_coords alone).
|
||||
function BenillaTargetFrame_CheckClassification(frame)
|
||||
function TargetFrame_CheckClassification()
|
||||
local frame = this
|
||||
local classification = UnitClassification(frame.unit)
|
||||
if classification == "worldboss" or classification == "rareelite" or classification == "elite" then
|
||||
frame.frameTexture:SetTexture("Interface\\TargetingFrame\\UI-TargetingFrame-Elite")
|
||||
@@ -560,7 +562,8 @@
|
||||
end
|
||||
end
|
||||
|
||||
function BenillaUnitFrame_Update(frame)
|
||||
function UnitFrame_Update()
|
||||
local frame = this
|
||||
local unit = frame.unit
|
||||
if not UnitExists(unit) then
|
||||
frame:Hide()
|
||||
@@ -571,7 +574,7 @@
|
||||
frame.healthBar:SetMinMaxValues(0, UnitHealthMax(unit))
|
||||
frame.healthBar:SetValue(UnitHealth(unit))
|
||||
|
||||
local maxPower = UnitPowerMax(unit)
|
||||
local maxPower = UnitManaMax(unit)
|
||||
if maxPower > 0 then
|
||||
frame.powerBar:Show()
|
||||
local _, token = UnitPowerType(unit)
|
||||
@@ -584,7 +587,7 @@
|
||||
-- mechanism stays — SetTextStatusBarTextPrefix is live and one call brings any
|
||||
-- label back — only the calls are gone.
|
||||
frame.powerBar:SetMinMaxValues(0, maxPower)
|
||||
frame.powerBar:SetValue(UnitPower(unit))
|
||||
frame.powerBar:SetValue(UnitMana(unit))
|
||||
else
|
||||
frame.powerBar:Hide()
|
||||
end
|
||||
@@ -660,7 +663,7 @@
|
||||
-- The classification border (target only) — the ref's TargetFrame_Update calls
|
||||
-- CheckClassification right here, between CheckFaction and CheckDead.
|
||||
if frame.frameTexture then
|
||||
BenillaTargetFrame_CheckClassification(frame)
|
||||
TargetFrame_CheckClassification()
|
||||
end
|
||||
|
||||
-- DEAD text (target only): shown at zero health (ref TargetFrame_CheckDead). Blanked
|
||||
@@ -679,7 +682,7 @@
|
||||
|
||||
-- The status flash, player frame only (handle-gated like pvpIcon; decision 1082).
|
||||
if frame.statusTexture then
|
||||
BenillaPlayerFrame_UpdateStatus(frame)
|
||||
PlayerFrame_UpdateStatus()
|
||||
end
|
||||
|
||||
-- The aura rows (target only) — the ref's TargetFrame_Update →
|
||||
@@ -695,7 +698,7 @@
|
||||
|
||||
-- TargetDebuffButton_Update (ref-TargetFrame.lua l.263-387), transcribed onto the Era API
|
||||
-- (decision 0255: UnitBuff returns name,icon,count,debuffType,… — the 1.12 original returned
|
||||
-- the texture first). Only called with a live target (BenillaUnitFrame_Update guards).
|
||||
-- the texture first). Only called with a live target (UnitFrame_Update guards).
|
||||
-- DebuffTypeColor is defined by BuffFrame.xml, which loads AFTER this file — safe, because
|
||||
-- this function only runs on events, never at load (no target exists at load).
|
||||
function BenillaTargetAuras_Update()
|
||||
@@ -703,7 +706,7 @@
|
||||
local numBuffs = 0
|
||||
for i = 1, MAX_TARGET_BUFFS do
|
||||
local name, icon = UnitBuff("target", i)
|
||||
button = getglobal("BenillaTargetFrameBuff" .. i)
|
||||
button = getglobal("TargetFrameBuff" .. i)
|
||||
if name then
|
||||
getglobal(button:GetName() .. "Icon"):SetTexture(icon or BENILLA_FALLBACK_ICON)
|
||||
button:Show()
|
||||
@@ -716,7 +719,7 @@
|
||||
local numDebuffs = 0
|
||||
for i = 1, MAX_TARGET_DEBUFFS do
|
||||
local name, icon, count, debuffType = UnitDebuff("target", i)
|
||||
button = getglobal("BenillaTargetFrameDebuff" .. i)
|
||||
button = getglobal("TargetFrameDebuff" .. i)
|
||||
if name then
|
||||
getglobal(button:GetName() .. "Icon"):SetTexture(icon or BENILLA_FALLBACK_ICON)
|
||||
local color = DebuffTypeColor[debuffType] or DebuffTypeColor["none"]
|
||||
@@ -740,11 +743,11 @@
|
||||
local debuffWrap = 6
|
||||
|
||||
if UnitIsFriend("player", "target") then
|
||||
BenillaTargetFrameBuff1:SetPoint("TOPLEFT", "BenillaTargetFrame", "BOTTOMLEFT", 5, 32)
|
||||
BenillaTargetFrameDebuff1:SetPoint("TOPLEFT", "BenillaTargetFrameBuff1", "BOTTOMLEFT", 0, -2)
|
||||
TargetFrameBuff1:SetPoint("TOPLEFT", "TargetFrame", "BOTTOMLEFT", 5, 32)
|
||||
TargetFrameDebuff1:SetPoint("TOPLEFT", "TargetFrameBuff1", "BOTTOMLEFT", 0, -2)
|
||||
else
|
||||
BenillaTargetFrameDebuff1:SetPoint("TOPLEFT", "BenillaTargetFrame", "BOTTOMLEFT", 5, 32)
|
||||
BenillaTargetFrameBuff1:SetPoint("TOPLEFT", "BenillaTargetFrameDebuff7", "BOTTOMLEFT", 0, -2)
|
||||
TargetFrameDebuff1:SetPoint("TOPLEFT", "TargetFrame", "BOTTOMLEFT", 5, 32)
|
||||
TargetFrameBuff1:SetPoint("TOPLEFT", "TargetFrameDebuff7", "BOTTOMLEFT", 0, -2)
|
||||
end
|
||||
|
||||
-- Shrink when the debuffs would overlap the frame (ref l.340-347): 21→17 icons,
|
||||
@@ -759,13 +762,13 @@
|
||||
end
|
||||
|
||||
for i = 1, 5 do
|
||||
button = getglobal("BenillaTargetFrameBuff" .. i)
|
||||
button = getglobal("TargetFrameBuff" .. i)
|
||||
button:SetWidth(debuffSize)
|
||||
button:SetHeight(debuffSize)
|
||||
end
|
||||
-- The ref resizes only the FIRST ROW's debuffs (l.358-368) — rows 2+ stay 17px always.
|
||||
for i = 1, 6 do
|
||||
button = getglobal("BenillaTargetFrameDebuff" .. i)
|
||||
button = getglobal("TargetFrameDebuff" .. i)
|
||||
local border = getglobal(button:GetName() .. "Border")
|
||||
border:SetWidth(debuffFrameSize)
|
||||
border:SetHeight(debuffFrameSize)
|
||||
@@ -775,18 +778,18 @@
|
||||
|
||||
-- Re-seat the wrap points (ref l.370-385) — constant with wrap pinned at 6, kept
|
||||
-- verbatim so the ToT wrap-5 leg drops in unchanged.
|
||||
getglobal("BenillaTargetFrameDebuff" .. debuffWrap):ClearAllPoints()
|
||||
getglobal("BenillaTargetFrameDebuff" .. debuffWrap):SetPoint("LEFT", getglobal("BenillaTargetFrameDebuff" .. (debuffWrap - 1)), "RIGHT", 3, 0)
|
||||
getglobal("BenillaTargetFrameDebuff" .. (debuffWrap + 1)):ClearAllPoints()
|
||||
getglobal("BenillaTargetFrameDebuff" .. (debuffWrap + 1)):SetPoint("TOPLEFT", "BenillaTargetFrameDebuff1", "BOTTOMLEFT", 0, -2)
|
||||
getglobal("BenillaTargetFrameDebuff" .. (debuffWrap + 2)):ClearAllPoints()
|
||||
getglobal("BenillaTargetFrameDebuff" .. (debuffWrap + 2)):SetPoint("LEFT", getglobal("BenillaTargetFrameDebuff" .. (debuffWrap + 1)), "RIGHT", 3, 0)
|
||||
getglobal("TargetFrameDebuff" .. debuffWrap):ClearAllPoints()
|
||||
getglobal("TargetFrameDebuff" .. debuffWrap):SetPoint("LEFT", getglobal("TargetFrameDebuff" .. (debuffWrap - 1)), "RIGHT", 3, 0)
|
||||
getglobal("TargetFrameDebuff" .. (debuffWrap + 1)):ClearAllPoints()
|
||||
getglobal("TargetFrameDebuff" .. (debuffWrap + 1)):SetPoint("TOPLEFT", "TargetFrameDebuff1", "BOTTOMLEFT", 0, -2)
|
||||
getglobal("TargetFrameDebuff" .. (debuffWrap + 2)):ClearAllPoints()
|
||||
getglobal("TargetFrameDebuff" .. (debuffWrap + 2)):SetPoint("LEFT", getglobal("TargetFrameDebuff" .. (debuffWrap + 1)), "RIGHT", 3, 0)
|
||||
if debuffWrap == 5 then
|
||||
BenillaTargetFrameDebuff11:ClearAllPoints()
|
||||
BenillaTargetFrameDebuff11:SetPoint("TOPLEFT", "BenillaTargetFrameDebuff6", "BOTTOMLEFT", 0, -2)
|
||||
TargetFrameDebuff11:ClearAllPoints()
|
||||
TargetFrameDebuff11:SetPoint("TOPLEFT", "TargetFrameDebuff6", "BOTTOMLEFT", 0, -2)
|
||||
else
|
||||
BenillaTargetFrameDebuff11:ClearAllPoints()
|
||||
BenillaTargetFrameDebuff11:SetPoint("LEFT", "BenillaTargetFrameDebuff10", "RIGHT", 3, 0)
|
||||
TargetFrameDebuff11:ClearAllPoints()
|
||||
TargetFrameDebuff11:SetPoint("LEFT", "TargetFrameDebuff10", "RIGHT", 3, 0)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -806,7 +809,8 @@
|
||||
-- style; decision 1082). Priority is the ref's: RESTING wins over combat — sitting in an
|
||||
-- inn mid-auto-attack shows the zzz — then the auto-attack state (gold ring/zzz vs red
|
||||
-- ring/swords/red disc), then bare swords for on-someone's-aggro-list, then nothing.
|
||||
function BenillaPlayerFrame_UpdateStatus(frame)
|
||||
function PlayerFrame_UpdateStatus()
|
||||
local frame = this
|
||||
if IsResting() then
|
||||
frame.statusTexture:SetVertexColor(1.0, 0.88, 0.25, 1.0)
|
||||
frame.statusTexture:Show()
|
||||
@@ -843,7 +847,8 @@
|
||||
-- 55/255 and 255/255 on the ADD-blend status ring and the icon glow, run only while the
|
||||
-- ring is visible (resting or auto-attacking); the hit-indicator fade rides the same
|
||||
-- tick, exactly as before.
|
||||
function BenillaPlayerFrame_OnUpdate(frame, elapsed)
|
||||
function PlayerFrame_OnUpdate(elapsed)
|
||||
local frame = this
|
||||
if frame.statusTexture:IsVisible() then
|
||||
local counter = frame.statusCounter + elapsed
|
||||
local sign = frame.statusSign
|
||||
@@ -872,9 +877,9 @@
|
||||
-- with arg1 = the button. The self-target itself is the byte-verified way the client
|
||||
-- selects its own unit; the world-model click is a separate path, deliberately left out
|
||||
-- (director's call).
|
||||
function BenillaPlayerFrame_OnClick(button)
|
||||
function PlayerFrame_OnClick(button)
|
||||
if button == "RightButton" then
|
||||
ToggleDropDownMenu(1, nil, PlayerFrameDropDown, "BenillaPlayerFrame", 106, 27)
|
||||
ToggleDropDownMenu(1, nil, PlayerFrameDropDown, "PlayerFrame", 106, 27)
|
||||
return
|
||||
end
|
||||
if button ~= "LeftButton" then return end
|
||||
@@ -929,9 +934,9 @@
|
||||
-- ref TargetFrame_OnClick: right-click opens the unit menu at (120, 10). The ref's
|
||||
-- LeftButton branch (SpellTargetUnit / DropItemOnUnit) rides the spell-cursor and
|
||||
-- item-drop slices — a bare left-click is a no-op on the ref too.
|
||||
function BenillaTargetFrame_OnClick(button)
|
||||
function TargetFrame_OnClick(button)
|
||||
if button == "RightButton" then
|
||||
ToggleDropDownMenu(1, nil, TargetFrameDropDown, "BenillaTargetFrame", 120, 10)
|
||||
ToggleDropDownMenu(1, nil, TargetFrameDropDown, "TargetFrame", 120, 10)
|
||||
end
|
||||
end
|
||||
]]></Script>
|
||||
@@ -945,7 +950,7 @@
|
||||
BELOW the MEDIUM panel windows on purpose, and the player frame shares the top-left corner
|
||||
with the left-slot panel. See the note on PartyFrame.xml's template (decision 0597) for why
|
||||
the default MEDIUM makes a unit frame's bar children draw through a window's background. -->
|
||||
<Frame name="BenillaPlayerFrame" frameStrata="BACKGROUND">
|
||||
<Frame name="PlayerFrame" frameStrata="BACKGROUND">
|
||||
<Size><AbsDimension x="232" y="100"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="-19" y="-4"/></Offset></Anchor>
|
||||
@@ -1167,17 +1172,17 @@
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad><![CDATA[BenillaUnitFrame_OnLoad(this, "player")]]></OnLoad>
|
||||
<OnEvent><![CDATA[BenillaUnitFrame_OnEvent(this)]]></OnEvent>
|
||||
<OnUpdate><![CDATA[BenillaPlayerFrame_OnUpdate(this, arg1)]]></OnUpdate>
|
||||
<OnEvent><![CDATA[UnitFrame_OnEvent(event)]]></OnEvent>
|
||||
<OnUpdate><![CDATA[PlayerFrame_OnUpdate(arg1)]]></OnUpdate>
|
||||
<OnEnter><![CDATA[BenillaUnitFrame_OnEnter(this)]]></OnEnter>
|
||||
<OnLeave><![CDATA[BenillaUnitFrame_OnLeave(this)]]></OnLeave>
|
||||
<OnClick><![CDATA[BenillaPlayerFrame_OnClick(arg1)]]></OnClick>
|
||||
<OnClick><![CDATA[PlayerFrame_OnClick(arg1)]]></OnClick>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
<!-- The SELF-menu dropdown host (ref $parentDropDown, PlayerFrame.xml): hidden state-holder
|
||||
only — the list anchors to the player frame by name, never to this. -->
|
||||
<Frame name="PlayerFrameDropDown" parent="BenillaPlayerFrame" inherits="UIDropDownMenuTemplate" hidden="true">
|
||||
<Frame name="PlayerFrameDropDown" parent="PlayerFrame" inherits="UIDropDownMenuTemplate" hidden="true">
|
||||
<Size><AbsDimension x="10" y="10"/></Size>
|
||||
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="-20" y="-10"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
@@ -1235,7 +1240,7 @@
|
||||
every login. Steady-state behavior is identical — Update Shows/Hides on target changes. -->
|
||||
<!-- frameStrata LOW is the ref's own (`TargetFrame.xml` l.88) — one above the player frame's
|
||||
BACKGROUND, still below the MEDIUM panels. Same reasoning as PlayerFrame above. -->
|
||||
<Frame name="BenillaTargetFrame" hidden="true" frameStrata="LOW">
|
||||
<Frame name="TargetFrame" hidden="true" frameStrata="LOW">
|
||||
<Size><AbsDimension x="232" y="100"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="250" y="-4"/></Offset></Anchor>
|
||||
@@ -1452,7 +1457,7 @@
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad><![CDATA[BenillaUnitFrame_OnLoad(this, "target")]]></OnLoad>
|
||||
<OnEvent><![CDATA[BenillaUnitFrame_OnEvent(this)]]></OnEvent>
|
||||
<OnEvent><![CDATA[UnitFrame_OnEvent(event)]]></OnEvent>
|
||||
<OnEnter><![CDATA[BenillaUnitFrame_OnEnter(this)]]></OnEnter>
|
||||
<OnLeave><![CDATA[BenillaUnitFrame_OnLeave(this)]]></OnLeave>
|
||||
<!-- The select/deselect pair rides the frame's visibility, exactly the ref
|
||||
@@ -1474,13 +1479,13 @@
|
||||
PlaySound("INTERFACESOUND_LOSTTARGETUNIT")
|
||||
CloseDropDownMenus()
|
||||
]]></OnHide>
|
||||
<OnClick><![CDATA[BenillaTargetFrame_OnClick(arg1)]]></OnClick>
|
||||
<OnClick><![CDATA[TargetFrame_OnClick(arg1)]]></OnClick>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
<!-- The target-menu dropdown host (ref $parentDropDown, TargetFrame.xml): hidden
|
||||
state-holder only, like the player's above. -->
|
||||
<Frame name="TargetFrameDropDown" parent="BenillaTargetFrame" inherits="UIDropDownMenuTemplate" hidden="true">
|
||||
<Frame name="TargetFrameDropDown" parent="TargetFrame" inherits="UIDropDownMenuTemplate" hidden="true">
|
||||
<Size><AbsDimension x="10" y="10"/></Size>
|
||||
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="-20" y="-10"/></Offset></Anchor></Anchors>
|
||||
<Scripts>
|
||||
@@ -1514,30 +1519,31 @@
|
||||
frames' own deferred buff panel, not here.
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
function BenillaPetFrame_OnLoad(frame)
|
||||
frame.portrait = BenillaPetFrameTextureFramePortrait
|
||||
frame.healthBar = BenillaPetFrameHealthBar
|
||||
frame.manaBar = BenillaPetFrameManaBar
|
||||
frame.nameText = BenillaPetFrameTextureFrameName
|
||||
function PetFrame_OnLoad()
|
||||
local frame = this
|
||||
frame.portrait = PetFrameTextureFramePortrait
|
||||
frame.healthBar = PetFrameHealthBar
|
||||
frame.manaBar = PetFrameManaBar
|
||||
frame.nameText = PetFrameTextureFrameName
|
||||
-- The recessed-bar look, the same mechanism the player/target/party frames use
|
||||
-- (decision 0884): push the bars one frame level BELOW the art's TextureFrame, because
|
||||
-- the draw layer is bucket-wide and the frame level is the one key term above it.
|
||||
local level = BenillaPetFrameTextureFrame:GetFrameLevel()
|
||||
local level = PetFrameTextureFrame:GetFrameLevel()
|
||||
frame.healthBar:SetFrameLevel(level - 1)
|
||||
frame.manaBar:SetFrameLevel(level - 1)
|
||||
-- The pet's numerals, textLockable like the ref's (PetFrame.xml l.139/164; 1143).
|
||||
if TextStatusBar_Initialize then -- engine tests load file subsets
|
||||
TextStatusBar_Initialize(frame.healthBar)
|
||||
TextStatusBar_Initialize(frame.manaBar)
|
||||
SetTextStatusBarText(frame.healthBar, BenillaPetFrameTextureFrameHealthBarText)
|
||||
SetTextStatusBarText(frame.manaBar, BenillaPetFrameTextureFrameManaBarText)
|
||||
SetTextStatusBarText(frame.healthBar, PetFrameTextureFrameHealthBarText)
|
||||
SetTextStatusBarText(frame.manaBar, PetFrameTextureFrameManaBarText)
|
||||
frame.healthBar.textLockable = 1
|
||||
frame.manaBar.textLockable = 1
|
||||
end
|
||||
-- The pet's bars hover for their numbers, but hand nothing on: the pet frame has no
|
||||
-- OnEnter of its own, and its click is its own handler (1146).
|
||||
frame.healthBar.ownerClick = BenillaPetFrame_OnClick
|
||||
frame.manaBar.ownerClick = BenillaPetFrame_OnClick
|
||||
frame.healthBar.ownerClick = PetFrame_OnClick
|
||||
frame.manaBar.ownerClick = PetFrame_OnClick
|
||||
frame.attackModeCounter = 0
|
||||
frame.attackModeSign = -1
|
||||
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
@@ -1553,33 +1559,35 @@
|
||||
frame:RegisterEvent("PET_ATTACK_STOP")
|
||||
frame:RegisterEvent("UNIT_HAPPINESS")
|
||||
frame:EnableMouse(true)
|
||||
BenillaPetFrame_Update(frame)
|
||||
PetFrame_Update()
|
||||
end
|
||||
|
||||
-- ref PetFrame_OnEvent, with both of its argument gates kept: UNIT_PET names the OWNER
|
||||
-- ("player" — wow-re §9 carves the arg), every other UNIT_* names the pet itself. Getting
|
||||
-- those backwards is how a pet frame ends up repainting off the player's health.
|
||||
function BenillaPetFrame_OnEvent(frame)
|
||||
function PetFrame_OnEvent(event)
|
||||
local frame = this
|
||||
if event == "PET_ATTACK_START" then
|
||||
BenillaPetFrameAttackMode:SetVertexColor(1.0, 1.0, 1.0, 1.0)
|
||||
BenillaPetFrameAttackMode:Show()
|
||||
PetFrameAttackMode:SetVertexColor(1.0, 1.0, 1.0, 1.0)
|
||||
PetFrameAttackMode:Show()
|
||||
return
|
||||
elseif event == "PET_ATTACK_STOP" then
|
||||
BenillaPetFrameAttackMode:Hide()
|
||||
PetFrameAttackMode:Hide()
|
||||
return
|
||||
elseif event == "UNIT_HAPPINESS" then
|
||||
-- The ref's own last arm: repaint the icon alone, not the whole frame.
|
||||
BenillaPetFrame_SetHappiness()
|
||||
PetFrame_SetHappiness()
|
||||
return
|
||||
elseif event == "UNIT_PET" then
|
||||
if arg1 ~= "player" then return end
|
||||
elseif string.sub(event, 1, 5) == "UNIT_" and arg1 ~= "pet" then
|
||||
return
|
||||
end
|
||||
BenillaPetFrame_Update(frame)
|
||||
PetFrame_Update()
|
||||
end
|
||||
|
||||
function BenillaPetFrame_Update(frame)
|
||||
function PetFrame_Update()
|
||||
local frame = this
|
||||
if not UnitExists("pet") then
|
||||
frame:Hide()
|
||||
return
|
||||
@@ -1593,22 +1601,22 @@
|
||||
|
||||
-- The ref's art swap (PetFrame.lua's Update): a powerless pet wears the -NoMana plate,
|
||||
-- which has no mana rail to leave empty. The bar hides with it, as on every other frame.
|
||||
local maxPower = UnitPowerMax("pet")
|
||||
local maxPower = UnitManaMax("pet")
|
||||
if maxPower > 0 then
|
||||
BenillaPetFrameTexture:SetTexture("Interface\\TargetingFrame\\UI-SmallTargetingFrame")
|
||||
PetFrameTexture:SetTexture("Interface\\TargetingFrame\\UI-SmallTargetingFrame")
|
||||
frame.manaBar:Show()
|
||||
local _, token = UnitPowerType("pet")
|
||||
local c = BENILLA_POWER_COLORS[token] or BENILLA_POWER_COLORS.MANA
|
||||
frame.manaBar:SetStatusBarColor(c.r, c.g, c.b)
|
||||
frame.manaBar:SetMinMaxValues(0, maxPower)
|
||||
frame.manaBar:SetValue(UnitPower("pet"))
|
||||
frame.manaBar:SetValue(UnitMana("pet"))
|
||||
else
|
||||
BenillaPetFrameTexture:SetTexture("Interface\\TargetingFrame\\UI-SmallTargetingFrame-NoMana")
|
||||
PetFrameTexture:SetTexture("Interface\\TargetingFrame\\UI-SmallTargetingFrame-NoMana")
|
||||
frame.manaBar:Hide()
|
||||
end
|
||||
|
||||
BenillaPetFrame_UpdateDebuffs()
|
||||
BenillaPetFrame_SetHappiness()
|
||||
PetFrame_SetHappiness()
|
||||
end
|
||||
|
||||
-- ref PetFrame_SetHappiness (PetFrame.lua l.132-156), quoted (decision 1005).
|
||||
@@ -1622,29 +1630,29 @@
|
||||
-- * the three texcoord cuts run 3/2/1 left-to-right across the strip — happy is at x=0.
|
||||
-- * the loyalty line is chosen by the SIGN of loyaltyRate, which is why that return is
|
||||
-- unscaled and allowed to be negative.
|
||||
function BenillaPetFrame_SetHappiness()
|
||||
function PetFrame_SetHappiness()
|
||||
local happiness, damagePercentage, loyaltyRate = GetPetHappiness()
|
||||
local hasPetUI, isHunterPet = HasPetUI()
|
||||
if not happiness or not isHunterPet then
|
||||
BenillaPetFrameHappiness:Hide()
|
||||
PetFrameHappiness:Hide()
|
||||
return
|
||||
end
|
||||
BenillaPetFrameHappiness:Show()
|
||||
PetFrameHappiness:Show()
|
||||
if happiness == 1 then
|
||||
BenillaPetFrameHappinessTexture:SetTexCoord(0.375, 0.5625, 0, 0.359375)
|
||||
PetFrameHappinessTexture:SetTexCoord(0.375, 0.5625, 0, 0.359375)
|
||||
elseif happiness == 2 then
|
||||
BenillaPetFrameHappinessTexture:SetTexCoord(0.1875, 0.375, 0, 0.359375)
|
||||
PetFrameHappinessTexture:SetTexCoord(0.1875, 0.375, 0, 0.359375)
|
||||
elseif happiness == 3 then
|
||||
BenillaPetFrameHappinessTexture:SetTexCoord(0, 0.1875, 0, 0.359375)
|
||||
PetFrameHappinessTexture:SetTexCoord(0, 0.1875, 0, 0.359375)
|
||||
end
|
||||
BenillaPetFrameHappiness.tooltip = getglobal("PET_HAPPINESS" .. happiness)
|
||||
BenillaPetFrameHappiness.tooltipDamage = format(PET_DAMAGE_PERCENTAGE, damagePercentage)
|
||||
PetFrameHappiness.tooltip = getglobal("PET_HAPPINESS" .. happiness)
|
||||
PetFrameHappiness.tooltipDamage = format(PET_DAMAGE_PERCENTAGE, damagePercentage)
|
||||
if loyaltyRate < 0 then
|
||||
BenillaPetFrameHappiness.tooltipLoyalty = getglobal("LOSING_LOYALTY")
|
||||
PetFrameHappiness.tooltipLoyalty = getglobal("LOSING_LOYALTY")
|
||||
elseif loyaltyRate > 0 then
|
||||
BenillaPetFrameHappiness.tooltipLoyalty = getglobal("GAINING_LOYALTY")
|
||||
PetFrameHappiness.tooltipLoyalty = getglobal("GAINING_LOYALTY")
|
||||
else
|
||||
BenillaPetFrameHappiness.tooltipLoyalty = nil
|
||||
PetFrameHappiness.tooltipLoyalty = nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1653,7 +1661,7 @@
|
||||
-- so these are icon + border + stack count, the party rows' shape exactly.
|
||||
function BenillaPetFrame_UpdateDebuffs()
|
||||
for i = 1, 4 do
|
||||
local button = getglobal("BenillaPetFrameDebuff" .. i)
|
||||
local button = getglobal("PetFrameDebuff" .. i)
|
||||
local _, icon, count = UnitDebuff("pet", i)
|
||||
if icon then
|
||||
getglobal(button:GetName() .. "Icon"):SetTexture(icon)
|
||||
@@ -1673,15 +1681,16 @@
|
||||
|
||||
-- ref PetFrame_OnUpdate: the attack overlay's alpha ramp — a 0.5 s-per-leg triangle between
|
||||
-- 55/255 and 1, sign-flipped at each end (the ref's own counter shape, transcribed).
|
||||
function BenillaPetFrame_OnUpdate(frame, elapsed)
|
||||
if not BenillaPetFrameAttackMode:IsVisible() then return end
|
||||
function PetFrame_OnUpdate(elapsed)
|
||||
local frame = this
|
||||
if not PetFrameAttackMode:IsVisible() then return end
|
||||
local counter = frame.attackModeCounter + elapsed
|
||||
local sign = frame.attackModeSign
|
||||
if counter > 0.5 then
|
||||
sign = -sign
|
||||
frame.attackModeSign = sign
|
||||
end
|
||||
counter = math.fmod(counter, 0.5)
|
||||
counter = mod(counter, 0.5)
|
||||
frame.attackModeCounter = counter
|
||||
local alpha
|
||||
if sign == 1 then
|
||||
@@ -1689,13 +1698,13 @@
|
||||
else
|
||||
alpha = (255 - (counter * 400)) / 255
|
||||
end
|
||||
BenillaPetFrameAttackMode:SetVertexColor(1.0, 1.0, 1.0, alpha)
|
||||
PetFrameAttackMode:SetVertexColor(1.0, 1.0, 1.0, alpha)
|
||||
end
|
||||
|
||||
-- ref PetFrame_OnClick, now whole (decision 1066 built the right branch). Its FIRST leg is
|
||||
-- the one that is easy to drop: with a spell armed, a RIGHT click disarms rather than
|
||||
-- opening the menu, and it precedes both branches.
|
||||
function BenillaPetFrame_OnClick(button)
|
||||
function PetFrame_OnClick(button)
|
||||
if SpellIsTargeting() and button == "RightButton" then
|
||||
SpellStopTargeting()
|
||||
return
|
||||
@@ -1780,7 +1789,7 @@
|
||||
|
||||
<!-- frameStrata LOW matches the party frames' and the target's — above the player frame's
|
||||
BACKGROUND (which it overlaps), still below the MEDIUM panels. -->
|
||||
<Frame name="BenillaPetFrame" parent="BenillaPlayerFrame" hidden="true" frameStrata="LOW">
|
||||
<Frame name="PetFrame" parent="PlayerFrame" hidden="true" frameStrata="LOW">
|
||||
<Size><AbsDimension x="128" y="53"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="80" y="-60"/></Offset></Anchor>
|
||||
@@ -1840,7 +1849,7 @@
|
||||
<Layer level="BORDER">
|
||||
<!-- 128×64 art at (0,-2) — it hangs 13 px below the frame rect, as
|
||||
authored. The file is swapped at update (see the header). -->
|
||||
<Texture name="BenillaPetFrameTexture" file="Interface\TargetingFrame\UI-SmallTargetingFrame">
|
||||
<Texture name="PetFrameTexture" file="Interface\TargetingFrame\UI-SmallTargetingFrame">
|
||||
<Size><AbsDimension x="128" y="64"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
@@ -1848,7 +1857,7 @@
|
||||
<Layer level="ARTWORK">
|
||||
<!-- The attacking overlay (ref PetAttackModeTexture, l.104-118): the right
|
||||
29.7% of UI-Player-AttackStatus, ADD-blended, pulsed by OnUpdate. -->
|
||||
<Texture name="BenillaPetFrameAttackMode" file="Interface\TargetingFrame\UI-Player-AttackStatus" alphaMode="ADD" hidden="true">
|
||||
<Texture name="PetFrameAttackMode" file="Interface\TargetingFrame\UI-Player-AttackStatus" alphaMode="ADD" hidden="true">
|
||||
<Size><AbsDimension x="76" y="64"/></Size>
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="6" y="-9"/></Offset></Anchor></Anchors>
|
||||
<TexCoords left="0.703125" right="1.0" top="0" bottom="1.0"/>
|
||||
@@ -1919,22 +1928,22 @@
|
||||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="48" y="-42"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="$parentDebuff2" inherits="BenillaPetDebuffTemplate" id="2">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaPetFrameDebuff1" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="PetFrameDebuff1" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="$parentDebuff3" inherits="BenillaPetDebuffTemplate" id="3">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaPetFrameDebuff2" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="PetFrameDebuff2" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
<Button name="$parentDebuff4" inherits="BenillaPetDebuffTemplate" id="4">
|
||||
<Anchors><Anchor point="LEFT" relativeTo="BenillaPetFrameDebuff3" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor></Anchors>
|
||||
<Anchors><Anchor point="LEFT" relativeTo="PetFrameDebuff3" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor></Anchors>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>BenillaPetFrame_OnLoad(this)</OnLoad>
|
||||
<OnEvent>BenillaPetFrame_OnEvent(this)</OnEvent>
|
||||
<OnUpdate>BenillaPetFrame_OnUpdate(this, arg1)</OnUpdate>
|
||||
<OnLoad>PetFrame_OnLoad()</OnLoad>
|
||||
<OnEvent>PetFrame_OnEvent(event)</OnEvent>
|
||||
<OnUpdate>PetFrame_OnUpdate(arg1)</OnUpdate>
|
||||
<OnEnter>BenillaPetFrame_OnEnter(this)</OnEnter>
|
||||
<OnLeave>if SpellIsTargeting() then SetCursor("CAST_ERROR_CURSOR") end GameTooltip:FadeOut()</OnLeave>
|
||||
<OnClick>BenillaPetFrame_OnClick(arg1)</OnClick>
|
||||
<OnClick>PetFrame_OnClick(arg1)</OnClick>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
|
||||
@@ -624,7 +624,7 @@
|
||||
elseif ( button == "PET_ABANDON" ) then
|
||||
StaticPopup_Show("ABANDON_PET");
|
||||
elseif ( button == "PET_PAPERDOLL" ) then
|
||||
ToggleCharacter("BenillaPetPaperDollFrame");
|
||||
ToggleCharacter("PetPaperDollFrame");
|
||||
elseif ( button == "PET_RENAME" ) then
|
||||
StaticPopup_Show("RENAME_PET");
|
||||
end
|
||||
|
||||
@@ -157,8 +157,8 @@
|
||||
end
|
||||
|
||||
-- ── The continent/zone dropdowns (ref verbatim, except the vararg walk: the reference's
|
||||
-- `arg.n` needs LUA_COMPAT_VARARG, which stock 5.1 — and ours — doesn't ship; select()
|
||||
-- is the 5.1-native spelling). ─────────────────────────────────────────────────────────
|
||||
-- `arg.n` is written as select("#", ...), the 5.1-native spelling and this tree's house
|
||||
-- style for it). ──────────────────────────────────────────────────────────────────────
|
||||
function WorldMapContinentsDropDown_OnLoad()
|
||||
UIDropDownMenu_Initialize(this, WorldMapContinentsDropDown_Initialize);
|
||||
UIDropDownMenu_SetWidth(130);
|
||||
|
||||
@@ -170,8 +170,11 @@ PvP info shows only on NEW_AREA (SetZoneText(1)).
|
||||
end
|
||||
]]></Script>
|
||||
<!-- These frames are shown when the current zone or subzone changes.
|
||||
(ref carries toplevel="true" on both; dropped here — SetToplevel is a v1 object-model
|
||||
gap and click-raise is meaningless on mouse-less splash text.) -->
|
||||
(ref carries toplevel="true" on both; still dropped here, but the reason has changed.
|
||||
`SetToplevel` is no longer a gap — the raise law is built, `script::object::toplevel` —
|
||||
and its real trigger is Show, not a click, so "click-raise is meaningless on mouse-less
|
||||
splash text" was the wrong argument. What holds now: these are LOW-strata splash frames
|
||||
that overlap nothing in LOW, so the occlusion gate declines. One word to re-add.) -->
|
||||
<Frame name="ZoneTextFrame" parent="UIParent" frameStrata="LOW">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM">
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
# The named virtual Font objects (decision 0084) — must load first so any FontString's
|
||||
# `inherits="GameFontNormal"` resolves against a registered object.
|
||||
Fonts.xml
|
||||
# The reference's own second file, in its own position: TEXT/message/_ERRORMESSAGE, the
|
||||
# ScriptErrors dialog, and the DialogBoxFrame + DialogButton*Texture kit it is built from.
|
||||
# Straight after Fonts.xml because those are the only things it needs (GameFontRedLarge,
|
||||
# DialogButtonNormalText/HighlightText) and everything above it in FrameXML.toc is engine.
|
||||
BasicControls.xml
|
||||
# The UIPanel slot manager (decision 0084 §2): script-only, no frames of its own — must load
|
||||
# before any panel frame (Gossip/Merchant today) so ShowUIPanel/HideUIPanel already exist
|
||||
# when those frames' OnLoad/OnEvent reference them.
|
||||
@@ -32,7 +37,10 @@ UiPanels.xml
|
||||
UIParent.xml
|
||||
# The tooltip: a hover window the bag/merchant/loot/unit frames drive. Loaded before them so
|
||||
# `GameTooltip` + its verbs exist when their OnEnter handlers fire (runtime, so order
|
||||
# is not strictly required — but it keeps the dependency legible).
|
||||
# is not strictly required — but it keeps the dependency legible). It also declares the two
|
||||
# virtual templates (GameTooltipTemplate, ShoppingTooltipTemplate), which IS an order
|
||||
# requirement: ItemRef.xml below expands one, and so do the 27 corpus addons that name
|
||||
# GameTooltipTemplate in an XML inherits=.
|
||||
GameTooltip.xml
|
||||
# The dropdown kit (0203 phase 2's closing sub-slice, widened for 0434 phase 5):
|
||||
# UIDropDownMenuTemplate + DropDownList1/2 + the UIDropDownMenu_* API — before its
|
||||
@@ -43,9 +51,11 @@ UIDropDownMenu.xml
|
||||
# children's OnLoad initialize into it.
|
||||
UnitPopup.xml
|
||||
# The chat-link click router (SetItemRef): item links → ItemRefTooltip:SetHyperlink; a
|
||||
# player name → left-click whisper / right-click FRIEND dropdown. After GameTooltip.xml
|
||||
# (GameTooltip_OnLoad) AND the dropdown kit + UnitPopup — its FriendsDropDown inherits
|
||||
# UIDropDownMenuTemplate and opens a UnitPopup FRIEND menu.
|
||||
# player name → left-click whisper / right-click FRIEND dropdown. After GameTooltip.xml —
|
||||
# ItemRefTooltip inherits=GameTooltipTemplate, so this order is now REQUIRED, not just legible
|
||||
# (GameTooltip_OnLoad was the old, runtime-only reason) — AND after the dropdown kit +
|
||||
# UnitPopup, since its FriendsDropDown inherits UIDropDownMenuTemplate and opens a UnitPopup
|
||||
# FRIEND menu.
|
||||
ItemRef.xml
|
||||
# The shared status-bar numerals machinery (decision 1082): TextStatusBar_* + the opt-in
|
||||
# template — before its customers (the XP bar in ActionBar.xml today; the unit-frame
|
||||
@@ -53,7 +63,7 @@ ItemRef.xml
|
||||
# loads TextStatusBar right before its unit frames, same seat as here.
|
||||
TextStatusBar.xml
|
||||
UnitFrames.xml
|
||||
# The combo-point dots (decision 0869) — anchored to BenillaTargetFrame, so after
|
||||
# The combo-point dots (decision 0869) — anchored to TargetFrame, so after
|
||||
# UnitFrames.xml; its fade chain needs UiPanels.xml's UIFrameFade above.
|
||||
ComboFrame.xml
|
||||
# The center-screen scrolling combat text (decision 0578) — the Blizzard_CombatText
|
||||
@@ -79,26 +89,26 @@ Cooldown.xml
|
||||
ActionBar.xml
|
||||
# The two always-on bottom multibars (actions 61-72 / 49-60): pure XML/Lua over
|
||||
# ActionBar.xml's shared button template + handler set — loads right after it
|
||||
# (inherits BenillaActionButtonTemplate, anchors to BenillaActionButton1, and its
|
||||
# (inherits BenillaActionButtonTemplate, anchors to ActionButton1, and its
|
||||
# buttons join the BENILLA_ACTION_BUTTONS roster ActionBar.xml declares).
|
||||
MultiBars.xml
|
||||
# The stance/shapeshift bar: hidden until `crate::ui_shapeshift`'s feed pushes a
|
||||
# non-empty form list (a formless class never shows it). After Cooldown.xml
|
||||
# (CooldownFrame_SetTimer) + ActionBar.xml (BENILLA_FALLBACK_ICON, the BenillaActionBar
|
||||
# (CooldownFrame_SetTimer) + ActionBar.xml (BENILLA_FALLBACK_ICON, the MainMenuBar
|
||||
# anchor target).
|
||||
StanceBar.xml
|
||||
# The pet action bar (decision 0982): hidden until `crate::ui_pet`'s feed reports a bar,
|
||||
# i.e. until the server sends `SMSG_PET_SPELLS` for a live pet or charm — so a class that
|
||||
# never has one never sees it. Right after the stance bar, whose row it shares (either
|
||||
# one showing raises UIParent.xml's managed "pet" delta), and after Cooldown.xml
|
||||
# (CooldownFrame_SetTimer), ActionBar.xml (the `BenillaActionBar` anchor target) and
|
||||
# (CooldownFrame_SetTimer), ActionBar.xml (the `MainMenuBar` anchor target) and
|
||||
# GameTooltip.xml (its hover). It is also the pet ARC's file, so decision 1066's three
|
||||
# right-click-menu dialogs (ABANDON_PET / RENAME_PET / PETRENAMECONFIRM) register here —
|
||||
# which adds UiPanels.xml to its dependency set, already far above.
|
||||
PetActionBar.xml
|
||||
# The micro-button row in the bar's right-hand recess (the ref's own file split, its TOC
|
||||
# loads MainMenuBarMicroButtons right after MainMenuBar). After ActionBar.xml: it anchors
|
||||
# into BenillaActionBarArtFrame by name and seats itself with that file's
|
||||
# into MainMenuBarArtFrame by name and seats itself with that file's
|
||||
# BenillaActionBarArt_SeatAbove. It must come BEFORE the panels it toggles, because each of
|
||||
# their OnShow/OnHide calls the `UpdateMicroButtons` this file defines (the panel frames
|
||||
# themselves are read back through getglobal, so the reverse dependency is call-time).
|
||||
@@ -117,9 +127,47 @@ GameTime.xml
|
||||
# other consumer of the zone-text host globals.
|
||||
ZoneText.xml
|
||||
# The faux-scroll kit (decisions 0247/0250/0251): BenillaScrollBarTemplate (the draggable
|
||||
# Slider scroll bar) + BenillaFauxScrollFrameTemplate + the BenillaFauxScrollFrame_* API —
|
||||
# Slider scroll bar) + FauxScrollFrameTemplate + the reference's own FauxScrollFrame_*
|
||||
# API (Update/GetOffset/SetOffset/OnVerticalScroll + ScrollFrame_OnLoad) —
|
||||
# before its first customer (the trainer list) below.
|
||||
ScrollTemplates.xml
|
||||
# The reference's SHARED widget kit (decision 1203's queue): UIPanelButtonTemplate,
|
||||
# UICheckButtonTemplate, UIPanelCloseButton, InputBoxTemplate, the UIPanelScroll* family and
|
||||
# their siblings, under the reference's own names — the templates a third-party addon names in
|
||||
# `CreateFrame(kind, name, parent, "Template")`, which nothing in benilla's own XML consumes
|
||||
# yet (each of our windows still carries its private copy; see the file's header). After
|
||||
# ScrollTemplates.xml because UIPanelScrollFrameTemplate's OnLoad is that file's
|
||||
# ScrollFrame_OnLoad, and this file supplies the two remaining handlers of that same kit
|
||||
# (ScrollFrameTemplate_OnMouseWheel, ScrollFrame_OnScrollRangeChanged).
|
||||
UIPanelTemplates.xml
|
||||
# The reference's shared OPTIONS widgets: OptionsCheckButtonTemplate /
|
||||
# UIOptionsCheckButtonTemplate / OptionsSliderTemplate / OptionsButtonTemplate /
|
||||
# OptionFrameBoxTemplate. Straight after UIPanelTemplates.xml — two of them inherit its
|
||||
# UICheckButtonTemplate and UIPanelButtonTemplate. Not benilla's own options window
|
||||
# (OptionsFrame.xml, far below): these are the shared names addons build panels out of.
|
||||
OptionsFrameTemplates.xml
|
||||
# The reference's money-frame kit: MoneyFrameTemplate/SmallMoneyFrameTemplate + MoneyTypeInfo
|
||||
# + the MoneyFrame_* API. Needs only Fonts (NumberFontNormal/NumberFontNormalLarge), and
|
||||
# nothing benilla ships consumes it yet — our own eight coin displays run on MerchantFrame.xml's
|
||||
# BenillaMoney_* slot kit, and MoneyFrame.xml's header is where the two are reconciled. Seated
|
||||
# beside the scroll kit (the other shared widget kit) and ahead of every window that shows
|
||||
# coins, which is where the reference's own TOC puts it too.
|
||||
MoneyFrame.xml
|
||||
# The shared colour dialog + its opacity slider (`ColorPickerFrame`, `OpacitySliderFrame`,
|
||||
# `ColorPickerOkayButton`, `ColorPickerCancelButton`). Last of the "shared FrameXML surface with no
|
||||
# benilla consumer yet" cluster that runs from UIPanelTemplates.xml down — the same posture as
|
||||
# MoneyFrame.xml above, and for the same reason: 86 of the 218 corpus addons reach this window (64
|
||||
# of those files are `Dewdrop-2.0.lua`, which drives THIS one rather than rolling its own), and
|
||||
# nothing we ship opens it. The seat is a REQUIREMENT, not tidiness: its Okay/Cancel buttons
|
||||
# `inherits="GameMenuButtonTemplate"`, which is resolved at LOAD, and that template lives in
|
||||
# UIPanelTemplates.xml six lines up (it moved there from GameMenuFrame.xml — the reference's own
|
||||
# file for it — precisely because GameMenuFrame.xml is deliberately LAST). Everything else it needs
|
||||
# is far above (Fonts' GameFontNormal/…Small trio; UiPanels' ShowUIPanel/HideUIPanel, its
|
||||
# UISpecialFrames entry and the ESC rung that cancel-clicks it). UIDropDownMenu.xml, which owns the
|
||||
# colour-swatch row that OPENS this window, sits above too — but that direction is call-time only
|
||||
# (`UIDropDownMenuButton_OpenColorPicker` reaches `ColorPickerFrame` by name when the swatch is
|
||||
# clicked), so the two could sit either way round.
|
||||
ColorPickerFrame.xml
|
||||
# The fullscreen world map (decision 0203 phase 2), over the worldmap host API
|
||||
# (`crate::ui_world_map` feeds it); the 'M' binding (ui_script/input.rs) calls its
|
||||
# ToggleWorldMap().
|
||||
@@ -160,8 +208,8 @@ PetPaperDollFrame.xml
|
||||
# The Skills tab (decision 0437 phase 4): a CharacterFrame child page, but a separate
|
||||
# top-level file (SkillFrame.xml's own header comment — this engine has no cross-file
|
||||
# `parent=` attachment, so it's positioned by name via `<Anchors relativeTo=
|
||||
# "BenillaCharacterFrame">` instead). Loaded immediately after CharacterFrame.xml so it
|
||||
# paints ON TOP of it (later file = higher z) and so `BenillaCharacterFrame` already exists
|
||||
# "CharacterFrame">` instead). Loaded immediately after CharacterFrame.xml so it
|
||||
# paints ON TOP of it (later file = higher z) and so `CharacterFrame` already exists
|
||||
# for that anchor to resolve against.
|
||||
SkillFrame.xml
|
||||
# The inspect window (decision 0631): another player's paper doll. Beside the character
|
||||
@@ -218,7 +266,7 @@ ItemTextFrame.xml
|
||||
# session windows below.
|
||||
TrainerFrame.xml
|
||||
# The bank window (decision 0604 phase 4): the vault, purchase ladder, and the 6 bank-bag
|
||||
# popouts, over the C_Container feed already carrying container -1/5..10 (ui_items.rs) +
|
||||
# popouts, over the container feed already carrying container -1/5..10 (ui_items.rs) +
|
||||
# the bank Lua surface (benilla-ui/src/script/bank.rs) + crate::ui_bank's feed/drain.
|
||||
# Loaded AFTER MerchantFrame.xml (reuses its BenillaMoney_* coin helpers for the purchase
|
||||
# row + purse) and AFTER BagFrame.xml, already above (its BenillaBagSlotTemplate/
|
||||
@@ -289,6 +337,10 @@ KeyBindingsPage.xml
|
||||
# (all far above); sits right before GameMenuFrame so the menu — which reaches it by
|
||||
# name at click time — stays the last, outermost layer.
|
||||
OptionsFrame.xml
|
||||
# The in-game AddOns list (decision 1197) — the ESC menu's AddOns entry. Before GameMenuFrame,
|
||||
# which reaches AddonList_Toggle by name at click time; it depends only on the AddOn API verbs
|
||||
# (1191) and defines its own templates, so it can sit anywhere after the fonts.
|
||||
AddonList.xml
|
||||
# The game menu (decision 0674): the frame ESC opens, plus the CAMP/QUIT dialogs and their
|
||||
# event driver. LAST on purpose — it is the outermost layer of the shell, it depends on
|
||||
# everything it touches being loaded (UiPanels' popup engine + panel manager, MicroMenu's
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
//! `addon_harness` — load a folder of addons, one per VM, and print what happened.
|
||||
//!
|
||||
//! ```text
|
||||
//! cargo run -q -p benilla-app --example addon_harness -- <folder of addons> [--verbose] [--why <substr>]
|
||||
//! ```
|
||||
//!
|
||||
//! The instrument decision 1188 phase 6 asks for: *"which addons work" is a number that can be
|
||||
//! re-read on any day*. The mechanics, and what the numbers are and are not worth, are in
|
||||
//! [`benilla_app::addon_harness`]'s module doc — read it before quoting anything from here.
|
||||
//!
|
||||
//! **Expect a long tail and do not chase 100 %** (1188's own instruction). The report is a
|
||||
//! distribution, not a pass/fail: a handful of addons will always want features we have not built.
|
||||
use benilla_app::addon_harness;
|
||||
|
||||
fn main() {
|
||||
let mut args = std::env::args().skip(1);
|
||||
let Some(root) = args.next() else {
|
||||
eprintln!(
|
||||
"usage: addon_harness <folder of addons> [--verbose] [--why <blocker substring>]"
|
||||
);
|
||||
std::process::exit(2);
|
||||
};
|
||||
let rest: Vec<String> = args.collect();
|
||||
let verbose = rest.iter().any(|a| a == "--verbose");
|
||||
// `--why <substring>` — the addons behind one blocker row, with their verbatim first errors.
|
||||
// The ranked table collapses quoted names by design (1193); this is the read-back, and two of
|
||||
// this arc's decisions came from doing it by hand (1206, 1210).
|
||||
let why = rest
|
||||
.iter()
|
||||
.position(|a| a == "--why")
|
||||
.and_then(|i| rest.get(i + 1))
|
||||
.cloned();
|
||||
let root = std::path::PathBuf::from(root);
|
||||
|
||||
let reports = addon_harness::survey(&root);
|
||||
if reports.is_empty() {
|
||||
eprintln!(
|
||||
"no addons under {} — is that an AddOns folder?",
|
||||
root.display()
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let loaded = reports.iter().filter(|r| r.loaded).count();
|
||||
let clean = reports
|
||||
.iter()
|
||||
.filter(|r| r.loaded && r.missing_globals.is_empty())
|
||||
.count();
|
||||
let blocked = reports
|
||||
.iter()
|
||||
.filter(|r| !r.missing_deps.is_empty())
|
||||
.count();
|
||||
|
||||
println!("\n{} addon(s) under {}", reports.len(), root.display());
|
||||
// Which VM the survey ran against. Without an install there is no GlobalStrings.lua, ~5,000
|
||||
// globals are missing, and every number below is worse for a reason that has nothing to do
|
||||
// with the client — say so rather than letting two machines' numbers be compared in silence.
|
||||
println!(
|
||||
" VM: our whole FrameXML + a seated session{}\n",
|
||||
if addon_harness::seated_with_global_strings() {
|
||||
" + the real GlobalStrings.lua"
|
||||
} else {
|
||||
" ** no install found: GlobalStrings absent, these numbers are NOT comparable **"
|
||||
}
|
||||
);
|
||||
// The tree these numbers came from. Two runs are comparable only if this matches: in a dev
|
||||
// build `assets/ui` is read from the SOURCE TREE, so anything else editing the checkout moves
|
||||
// the headline with no rebuild. Quoting a delta across two different digests is how a wrong
|
||||
// attribution got into a decision record (1209).
|
||||
println!(
|
||||
" FrameXML digest : {}",
|
||||
addon_harness::framexml_digest()
|
||||
);
|
||||
println!(
|
||||
" loaded without a single load error : {loaded}/{}",
|
||||
reports.len()
|
||||
);
|
||||
println!(
|
||||
" ...and calling nothing we lack : {clean}/{}",
|
||||
reports.len()
|
||||
);
|
||||
println!(" with a dependency not installed : {blocked}");
|
||||
// The stricter column, and the one that answers what the survey is really asking. Every other
|
||||
// number here is LOAD-time; this one drives the client's own session start
|
||||
// (ADDON_LOADED -> VARIABLES_LOADED -> PLAYER_LOGIN -> PLAYER_ENTERING_WORLD, then a second of
|
||||
// ticks) and reports what the addon's HANDLERS raised. Four decision records in this arc end
|
||||
// with "the headline cannot see this"; this is the number that can.
|
||||
let survived = reports
|
||||
.iter()
|
||||
.filter(|r| r.loaded && r.session_errors.is_empty())
|
||||
.count();
|
||||
println!(
|
||||
" ...and survived a session start : {survived}/{}",
|
||||
reports.len()
|
||||
);
|
||||
|
||||
// What the session start broke, ranked the way `blockers` ranks load failures.
|
||||
let mut session: std::collections::BTreeMap<String, usize> = Default::default();
|
||||
for r in reports.iter().filter(|r| r.loaded) {
|
||||
if let Some(e) = r.session_errors.first() {
|
||||
*session.entry(addon_harness::normalise(e)).or_default() += 1;
|
||||
}
|
||||
}
|
||||
if !session.is_empty() {
|
||||
let mut rows: Vec<(String, usize)> = session.into_iter().collect();
|
||||
rows.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
|
||||
println!("\n what broke at SESSION START (addons that loaded clean, first error each):");
|
||||
for (err, count) in rows.into_iter().take(12) {
|
||||
println!(" {count:>4} {err}");
|
||||
}
|
||||
}
|
||||
|
||||
// The distribution, because a mean would hide the shape.
|
||||
let mut buckets = [0usize; 5];
|
||||
for r in &reports {
|
||||
let n = r.missing_globals.len();
|
||||
buckets[match n {
|
||||
0 => 0,
|
||||
1..=2 => 1,
|
||||
3..=5 => 2,
|
||||
6..=15 => 3,
|
||||
_ => 4,
|
||||
}] += 1;
|
||||
}
|
||||
println!("\n missing-global count per addon:");
|
||||
for (label, n) in ["0", "1-2", "3-5", "6-15", "16+"].iter().zip(buckets) {
|
||||
println!(" {label:>5} {n:>4} {}", "#".repeat(n.min(60)));
|
||||
}
|
||||
|
||||
// What actually STOPPED them — the ranked first error. Read this before the demand list: a
|
||||
// wall 60 addons hit is worth more than a verb 60 addons would like (decision 1193).
|
||||
println!("\n what stopped them (addons whose FIRST load error was each):");
|
||||
for (err, count) in addon_harness::blockers(&reports).into_iter().take(12) {
|
||||
println!(" {count:>4} {err}");
|
||||
}
|
||||
|
||||
// Templates an addon names in `CreateFrame(..., "Template")` that we have never declared
|
||||
// (decision 1203). Its own list because it is invisible to every other number here: an
|
||||
// unresolved template raises no load error, so the addon scores as a pass and paints nothing.
|
||||
let templates = addon_harness::template_demand(&reports);
|
||||
if !templates.is_empty() {
|
||||
println!("\n most-wanted missing TEMPLATES (addons naming each in CreateFrame):");
|
||||
for (name, count) in templates.into_iter().take(12) {
|
||||
println!(" {count:>4} {name}");
|
||||
}
|
||||
}
|
||||
|
||||
// The same question over the OTHER axis, and the one that actually moves the headline: a
|
||||
// template named in an addon's own XML `inherits=`. Unlike the list above this failure is
|
||||
// usually LOUD — the element's `<OnLoad>` fires at load and its first line is normally
|
||||
// `getglobal(this:GetName().."Text")` — which is why transcribing the reference's shared kit
|
||||
// was worth twelve addons while the CreateFrame list predicted none of them. Printed second
|
||||
// and separately because merging the two would hide exactly that difference.
|
||||
let inherits = addon_harness::inherits_demand(&reports);
|
||||
if !inherits.is_empty() {
|
||||
println!("\n most-wanted missing TEMPLATES (addons naming each in an XML inherits=):");
|
||||
for (name, count) in inherits.into_iter().take(12) {
|
||||
println!(" {count:>4} {name}");
|
||||
}
|
||||
}
|
||||
|
||||
// Frames and tables, ranked separately: a missing function is a Rust verb to write, a missing
|
||||
// frame is FrameXML to transcribe, and the two queues go to different people. The scan was
|
||||
// blind to this whole shape until 2026-08-11 — a window 86 addons reach scored 0.
|
||||
let tables = addon_harness::table_demand(&reports);
|
||||
if !tables.is_empty() {
|
||||
println!("\n most-wanted missing FRAMES/TABLES (addons indexing each):");
|
||||
for (name, count) in tables.into_iter().take(16) {
|
||||
println!(" {count:>4} {name}");
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n most-wanted missing globals (addons wanting each):");
|
||||
for (name, count) in addon_harness::demand(&reports).into_iter().take(30) {
|
||||
println!(" {count:>4} {name}");
|
||||
}
|
||||
|
||||
if let Some(pattern) = &why {
|
||||
let hits = addon_harness::blocked_by(&reports, pattern);
|
||||
println!(
|
||||
"\n addons whose first error matches {pattern:?} ({}):",
|
||||
hits.len()
|
||||
);
|
||||
for (name, err) in &hits {
|
||||
println!(" {name}");
|
||||
// Two lines, not one. The first is the message; the SECOND is mlua's first traceback
|
||||
// frame, and for the row this instrument is most often pointed at that frame is the
|
||||
// whole answer — `in local '(for generator)'` is what tells a generic-for
|
||||
// (decision 1202) apart from any other call of a table value.
|
||||
for line in err.lines().take(2) {
|
||||
println!(" {}", line.trim());
|
||||
}
|
||||
}
|
||||
if hits.is_empty() {
|
||||
println!(" (none — the pattern is matched against the NORMALISED row as printed)");
|
||||
}
|
||||
}
|
||||
|
||||
if verbose {
|
||||
println!("\n per addon:");
|
||||
for r in &reports {
|
||||
let iface = r
|
||||
.interface
|
||||
.iter()
|
||||
.map(u32::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
println!(
|
||||
" {:<28} iface={:<12} {:<8} missing={:<4} {}",
|
||||
r.name,
|
||||
if iface.is_empty() { "-".into() } else { iface },
|
||||
if r.loaded { "loaded" } else { "ERRORS" },
|
||||
r.missing_globals.len(),
|
||||
r.errors.first().map(String::as_str).unwrap_or("")
|
||||
);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,13 @@
|
||||
//! - [`Kind::Host`] lands in [`BindingsState::fired`] for engine consumers (chat open, TAB
|
||||
//! targeting, nameplates, autorun, camera zoom, …).
|
||||
//!
|
||||
//! **An addon's `Bindings.xml` rows dispatch here too** (decision 1188 phase 4). They are not in
|
||||
//! [`SPECS`] — they are read off disk at addon load ([`benilla_ui::bindings_xml`]) — so a resolved
|
||||
//! chord names a [`Bound`], which is either a registry [`Cmd`] or an index into this frame's
|
||||
//! addon table. That enum is the whole design: an addon row is a *runtime* `String` body run with
|
||||
//! the reference's `keystate` global, and every `SPECS[cmd]` in this file would be a latent panic
|
||||
//! if it were instead an index past the end of the static table.
|
||||
//!
|
||||
//! While the Keybindings page (the Options window's category since 1008) has a capsule
|
||||
//! selected it arms the **capture seam** (`BenillaBindCapture`): raw input is swallowed here,
|
||||
//! canonicalized (`ALT-CTRL-SHIFT-<TOKEN>`), and handed back to the page's own capture handler
|
||||
@@ -36,7 +43,7 @@ use bevy::input::mouse::AccumulatedMouseScroll;
|
||||
use bevy::input::ButtonState;
|
||||
use bevy::prelude::*;
|
||||
|
||||
use benilla_ui::script::keybind::{KeybindCommand, KeybindRequest};
|
||||
use benilla_ui::script::keybind::{AddonBindingBody, KeybindCommand, KeybindRequest};
|
||||
use benilla_ui::script::UiScript;
|
||||
|
||||
use crate::char_select::ClientState;
|
||||
@@ -50,11 +57,32 @@ use chord::{BindKey, Chord};
|
||||
pub(crate) use commands::cmd;
|
||||
use commands::{Cmd, Kind, SPECS};
|
||||
|
||||
/// The derived chord→command map — rebuilt whenever the engine table's generation moves. Probed
|
||||
/// What a bound chord names — a registry command, or an addon's `Bindings.xml` body
|
||||
/// (decision 1188 phase 4).
|
||||
///
|
||||
/// **An enum rather than one index space with a sentinel in it.** The tempting shape is
|
||||
/// "`Cmd(u16)`, and anything `>= SPECS.len()` is an addon" — which would turn every one of this
|
||||
/// file's eleven `SPECS[cmd.0 as usize]` reads into a panic waiting for the first addon binding to
|
||||
/// be pressed. The compiler is what should be enforcing that split, so it does: `state.fired` /
|
||||
/// `state.just` / `state.amounts` stay [`Cmd`]-typed (a host command is always a built-in and
|
||||
/// there is nothing for an addon to fire into), and only the map and the latches carry a `Bound`.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
enum Bound {
|
||||
/// A registry command — indexes [`SPECS`].
|
||||
Spec(Cmd),
|
||||
/// An addon-declared binding — indexes [`BindingDispatch::addons`].
|
||||
Addon(u16),
|
||||
}
|
||||
|
||||
/// The derived chord→binding map — rebuilt whenever the engine table's generation moves. Probed
|
||||
/// through [`BindingDispatch::resolve`], never directly: the lookup is two probes, not one.
|
||||
#[derive(Resource, Default)]
|
||||
struct BindingDispatch {
|
||||
map: std::collections::HashMap<Chord, Cmd>,
|
||||
map: std::collections::HashMap<Chord, Bound>,
|
||||
/// The addon-declared bindings [`Bound::Addon`] indexes, in the engine table's registration
|
||||
/// order — re-snapshotted with the map, so the two can never disagree about what an index
|
||||
/// means. Empty in every session with no addon bindings, which is every session today.
|
||||
addons: Vec<AddonBindingBody>,
|
||||
seen_generation: Option<u64>,
|
||||
}
|
||||
|
||||
@@ -76,9 +104,9 @@ impl BindingDispatch {
|
||||
/// there are untouched. Only the plane's fallback shadow is ours. Mouse and wheel are not
|
||||
/// suppressed — every dev instrument is a letter, so a modified click is nobody's but the
|
||||
/// game's.
|
||||
fn resolve(&self, chord: Chord, dev_plane: bool) -> Option<Cmd> {
|
||||
if let Some(&cmd) = self.map.get(&chord) {
|
||||
return Some(cmd);
|
||||
fn resolve(&self, chord: Chord, dev_plane: bool) -> Option<Bound> {
|
||||
if let Some(&bound) = self.map.get(&chord) {
|
||||
return Some(bound);
|
||||
}
|
||||
if dev_plane {
|
||||
return None;
|
||||
@@ -90,9 +118,9 @@ impl BindingDispatch {
|
||||
/// This frame's binding activity — what the engine-side consumers read instead of raw keys.
|
||||
#[derive(Resource, Default)]
|
||||
pub(crate) struct BindingsState {
|
||||
/// Live latches: (base key, command) — a [`Kind::Held`] or [`Kind::EdgeUpDown`] press that
|
||||
/// has not released yet.
|
||||
latched: Vec<(BindKey, Cmd)>,
|
||||
/// Live latches: (base key, binding) — a [`Kind::Held`], [`Kind::EdgeUpDown`] or `runOnUp`
|
||||
/// addon press that has not released yet.
|
||||
latched: Vec<(BindKey, Bound)>,
|
||||
/// Commands whose first latch began this frame (the press edge).
|
||||
just: Vec<Cmd>,
|
||||
/// Host-edge commands fired this frame.
|
||||
@@ -105,9 +133,11 @@ pub(crate) struct BindingsState {
|
||||
}
|
||||
|
||||
impl BindingsState {
|
||||
/// Is a held command latched right now? (The reference's held movement bit.)
|
||||
/// Is a held command latched right now? (The reference's held movement bit.) Registry
|
||||
/// commands only — an addon's latch exists to deliver its release half, and no engine system
|
||||
/// reads it.
|
||||
pub(crate) fn pressed(&self, c: Cmd) -> bool {
|
||||
self.latched.iter().any(|&(_, l)| l == c)
|
||||
self.latched.iter().any(|&(_, l)| l == Bound::Spec(c))
|
||||
}
|
||||
/// Did this command's latch begin this frame? (The key-DOWN edge — the autorun cancel set.)
|
||||
pub(crate) fn just_pressed(&self, c: Cmd) -> bool {
|
||||
@@ -289,24 +319,37 @@ fn sync_dispatch(script: Option<NonSendMut<UiScript>>, mut dispatch: ResMut<Bind
|
||||
}
|
||||
dispatch.seen_generation = Some(generation);
|
||||
dispatch.map.clear();
|
||||
let by_name: std::collections::HashMap<&str, Cmd> = SPECS
|
||||
// The addon table first: it is what the names the registry does not know resolve into. Before
|
||||
// 1188 phase 4 an unknown name hit the `continue` below and the binding silently never fired
|
||||
// — it registered, listed in the window, saved and loaded, and did nothing.
|
||||
let addons = script.addon_binding_bodies();
|
||||
let mut by_name: std::collections::HashMap<&str, Bound> = SPECS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, s)| (s.name, Cmd(i as u16)))
|
||||
.map(|(i, s)| (s.name, Bound::Spec(Cmd(i as u16))))
|
||||
.collect();
|
||||
for (i, a) in addons.iter().enumerate() {
|
||||
// A registry name is never overwritten — the engine table already refuses to register an
|
||||
// addon row over one, so this only ever adds. The `try_from` is the honest form of the
|
||||
// cast: 65k addon bindings is not a real case, and a wrapped index would dispatch the
|
||||
// wrong body rather than none.
|
||||
let Ok(i) = u16::try_from(i) else { break };
|
||||
by_name.entry(a.name.as_str()).or_insert(Bound::Addon(i));
|
||||
}
|
||||
for (name, keys) in script.keybind_snapshot() {
|
||||
let Some(&cmd) = by_name.get(name.as_str()) else {
|
||||
let Some(&bound) = by_name.get(name.as_str()) else {
|
||||
continue;
|
||||
};
|
||||
for key in keys {
|
||||
match Chord::parse(&key) {
|
||||
Some(ch) => {
|
||||
dispatch.map.insert(ch, cmd);
|
||||
dispatch.map.insert(ch, bound);
|
||||
}
|
||||
None => warn!("bindings: {name}: unpressable chord '{key}' (unknown token)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatch.addons = addons;
|
||||
script.fire_event("UPDATE_BINDINGS", vec![]);
|
||||
}
|
||||
|
||||
@@ -400,9 +443,12 @@ fn latch_and_dispatch(
|
||||
// the up of a pressed binding regardless).
|
||||
let typing = capture.0;
|
||||
if typing && !state.was_typing {
|
||||
state
|
||||
.latched
|
||||
.retain(|&(_, c)| !matches!(SPECS[c.0 as usize].kind, Kind::Held));
|
||||
state.latched.retain(|&(_, b)| match b {
|
||||
Bound::Spec(c) => !matches!(SPECS[c.0 as usize].kind, Kind::Held),
|
||||
// An addon's `runOnUp` latch is an EdgeUpDown pair by another name — its release half
|
||||
// is a Lua body that must still run, so it stays armed for the same reason.
|
||||
Bound::Addon(_) => true,
|
||||
});
|
||||
}
|
||||
state.was_typing = typing;
|
||||
|
||||
@@ -424,12 +470,25 @@ fn latch_and_dispatch(
|
||||
shift,
|
||||
key: BindKey::Key(key),
|
||||
};
|
||||
if let Some(cmd) = dispatch.resolve(chord, dev_plane) {
|
||||
press(&mut state, &mut script, run_lua, cmd, BindKey::Key(key));
|
||||
if let Some(bound) = dispatch.resolve(chord, dev_plane) {
|
||||
press(
|
||||
&mut state,
|
||||
&mut script,
|
||||
run_lua,
|
||||
&dispatch,
|
||||
bound,
|
||||
BindKey::Key(key),
|
||||
);
|
||||
}
|
||||
}
|
||||
ButtonState::Released => {
|
||||
release(&mut state, &mut script, run_lua, BindKey::Key(key));
|
||||
release(
|
||||
&mut state,
|
||||
&mut script,
|
||||
run_lua,
|
||||
&dispatch,
|
||||
BindKey::Key(key),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -455,12 +514,25 @@ fn latch_and_dispatch(
|
||||
shift,
|
||||
key: BindKey::Mouse(b),
|
||||
};
|
||||
if let Some(cmd) = dispatch.resolve(chord, false) {
|
||||
press(&mut state, &mut script, run_lua, cmd, BindKey::Mouse(b));
|
||||
if let Some(bound) = dispatch.resolve(chord, false) {
|
||||
press(
|
||||
&mut state,
|
||||
&mut script,
|
||||
run_lua,
|
||||
&dispatch,
|
||||
bound,
|
||||
BindKey::Mouse(b),
|
||||
);
|
||||
}
|
||||
}
|
||||
if buttons.just_released(b) {
|
||||
release(&mut state, &mut script, run_lua, BindKey::Mouse(b));
|
||||
release(
|
||||
&mut state,
|
||||
&mut script,
|
||||
run_lua,
|
||||
&dispatch,
|
||||
BindKey::Mouse(b),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,8 +558,8 @@ fn latch_and_dispatch(
|
||||
shift,
|
||||
key,
|
||||
};
|
||||
if let Some(cmd) = dispatch.resolve(chord, false) {
|
||||
match &SPECS[cmd.0 as usize].kind {
|
||||
match dispatch.resolve(chord, false) {
|
||||
Some(Bound::Spec(cmd)) => match &SPECS[cmd.0 as usize].kind {
|
||||
Kind::Edge(lua) => run_lua(&mut script, lua, SPECS[cmd.0 as usize].name),
|
||||
Kind::Host => {
|
||||
state.fired.push(cmd);
|
||||
@@ -496,7 +568,18 @@ fn latch_and_dispatch(
|
||||
// Unreachable by construction (SetBinding refuses these); harmless if a
|
||||
// hand-edited file smuggles one in.
|
||||
Kind::Held | Kind::EdgeUpDown(..) => {}
|
||||
},
|
||||
// An addon binding on the wheel is a one-shot press, the `Kind::Edge` case. A
|
||||
// `runOnUp` one cannot be here at all — its entry carries the same press+release flag
|
||||
// a Held/EdgeUpDown command does, and the table's wheel refusal reads exactly that.
|
||||
Some(Bound::Addon(i)) => {
|
||||
if let Some(a) = dispatch.addons.get(i as usize) {
|
||||
if !a.run_on_up {
|
||||
run_addon(&mut script, a, "down");
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,22 +600,28 @@ fn latch_and_dispatch(
|
||||
}
|
||||
}
|
||||
for k in stuck {
|
||||
release(&mut state, &mut script, run_lua, k);
|
||||
release(&mut state, &mut script, run_lua, &dispatch, k);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl BindingDispatch {
|
||||
/// A dispatch seeded straight from the registry defaults — the no-VM test seam.
|
||||
/// A dispatch seeded straight from the registry defaults — the no-VM test seam. No addon
|
||||
/// bindings: an addon body is Lua, and a harness with no VM could only assert it vacuously
|
||||
/// (the with-VM harness below is where those live).
|
||||
fn test_defaults() -> Self {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
for (i, s) in SPECS.iter().enumerate() {
|
||||
for d in [s.d1, s.d2].into_iter().flatten() {
|
||||
map.insert(Chord::parse(d).expect("default parses"), Cmd(i as u16));
|
||||
map.insert(
|
||||
Chord::parse(d).expect("default parses"),
|
||||
Bound::Spec(Cmd(i as u16)),
|
||||
);
|
||||
}
|
||||
}
|
||||
Self {
|
||||
map,
|
||||
addons: Vec::new(),
|
||||
seen_generation: None,
|
||||
}
|
||||
}
|
||||
@@ -543,21 +632,37 @@ fn press(
|
||||
state: &mut BindingsState,
|
||||
script: &mut Option<NonSendMut<UiScript>>,
|
||||
run_lua: impl Fn(&mut Option<NonSendMut<UiScript>>, &str, &str),
|
||||
cmd: Cmd,
|
||||
dispatch: &BindingDispatch,
|
||||
bound: Bound,
|
||||
key: BindKey,
|
||||
) {
|
||||
let cmd = match bound {
|
||||
Bound::Spec(cmd) => cmd,
|
||||
// An addon's press: run its one body with `keystate = "down"`, and latch it only if it
|
||||
// asked for the release half — the `Kind::Edge` / `Kind::EdgeUpDown` fork, decided by the
|
||||
// file's `runOnUp` instead of by our own registry.
|
||||
Bound::Addon(i) => {
|
||||
if let Some(a) = dispatch.addons.get(i as usize) {
|
||||
run_addon(script, a, "down");
|
||||
if a.run_on_up {
|
||||
state.latched.push((key, bound));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
let spec = &SPECS[cmd.0 as usize];
|
||||
match &spec.kind {
|
||||
Kind::Held => {
|
||||
if !state.pressed(cmd) {
|
||||
state.just.push(cmd);
|
||||
}
|
||||
state.latched.push((key, cmd));
|
||||
state.latched.push((key, bound));
|
||||
}
|
||||
Kind::Edge(lua) => run_lua(script, lua, spec.name),
|
||||
Kind::EdgeUpDown(down, _) => {
|
||||
run_lua(script, down, spec.name);
|
||||
state.latched.push((key, cmd));
|
||||
state.latched.push((key, bound));
|
||||
}
|
||||
Kind::Host => {
|
||||
state.fired.push(cmd);
|
||||
@@ -575,14 +680,25 @@ fn release(
|
||||
state: &mut BindingsState,
|
||||
script: &mut Option<NonSendMut<UiScript>>,
|
||||
run_lua: impl Fn(&mut Option<NonSendMut<UiScript>>, &str, &str),
|
||||
dispatch: &BindingDispatch,
|
||||
key: BindKey,
|
||||
) {
|
||||
let mut i = 0;
|
||||
while i < state.latched.len() {
|
||||
if state.latched[i].0 == key {
|
||||
let (_, cmd) = state.latched.remove(i);
|
||||
if let Kind::EdgeUpDown(_, up) = &SPECS[cmd.0 as usize].kind {
|
||||
run_lua(script, up, SPECS[cmd.0 as usize].name);
|
||||
match state.latched.remove(i).1 {
|
||||
Bound::Spec(cmd) => {
|
||||
if let Kind::EdgeUpDown(_, up) = &SPECS[cmd.0 as usize].kind {
|
||||
run_lua(script, up, SPECS[cmd.0 as usize].name);
|
||||
}
|
||||
}
|
||||
// The same body again, this time with `keystate = "up"` — the one law that makes
|
||||
// an addon's `runOnUp` binding one chunk instead of two.
|
||||
Bound::Addon(a) => {
|
||||
if let Some(a) = dispatch.addons.get(a as usize) {
|
||||
run_addon(script, a, "up");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
i += 1;
|
||||
@@ -590,6 +706,42 @@ fn release(
|
||||
}
|
||||
}
|
||||
|
||||
/// Run one addon binding's body with the reference's `keystate` global set **for the duration of
|
||||
/// the call, and restored after**.
|
||||
///
|
||||
/// A `Bindings.xml` body is **one Lua chunk read twice**: `runOnUp="true"` means it runs on the
|
||||
/// press and again on the release, and every shipped body forks on the bare global `keystate`
|
||||
/// (`if ( keystate == "down" ) then MoveForwardStart(); else MoveForwardStop(); end`). So the
|
||||
/// global is set in `_G` where the chunk will look for it — not prepended to the source, which
|
||||
/// would shift every line number in the addon's own error messages.
|
||||
///
|
||||
/// **Restoring it is not tidiness.** `keystate` is absent from the 1.12.1 client's in-world `_G`
|
||||
/// (`reference/1.12-globals.tsv`) even though its own `Bindings.xml` bodies read it as a bare
|
||||
/// global — which is only possible if the reference sets it transiently around the call, exactly
|
||||
/// as it does `this`/`event`/`arg1` (`invoke_with_globals`, RF-0025). Leaving it set would hand
|
||||
/// every addon a global the reference does not have, and an addon that feature-detects it would
|
||||
/// take a path we cannot honour — decision 1189's "a superset is not free", one call deeper.
|
||||
///
|
||||
/// Save-and-restore rather than set-and-delete, because these bodies nest: a binding whose Lua
|
||||
/// fires another binding must not clear the outer one's `keystate` on the way out.
|
||||
fn run_addon(script: &mut Option<NonSendMut<UiScript>>, bind: &AddonBindingBody, keystate: &str) {
|
||||
let Some(s) = script.as_mut() else { return };
|
||||
let globals = s.lua().globals();
|
||||
// `Option<String>` rather than a raw Lua value: benilla-app does not depend on mlua, and
|
||||
// `keystate` is a string or nothing — `None` converts back to nil on the way out.
|
||||
let prior: Option<String> = globals.get("keystate").unwrap_or(None);
|
||||
if let Err(e) = globals.set("keystate", keystate) {
|
||||
warn!("bindings({}): setting keystate: {e}", bind.name);
|
||||
return;
|
||||
}
|
||||
if let Err(e) = s.run(&bind.body) {
|
||||
warn!("bindings({}): {e}", bind.name);
|
||||
}
|
||||
if let Err(e) = s.lua().globals().set("keystate", prior) {
|
||||
warn!("bindings({}): restoring keystate: {e}", bind.name);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy::input::keyboard::{Key, KeyboardInput};
|
||||
@@ -633,6 +785,190 @@ mod tests {
|
||||
app.world().resource::<BindingsState>()
|
||||
}
|
||||
|
||||
/// One addon's `Bindings.xml`, in the reference's own shape: a `runOnUp` binding whose single
|
||||
/// body forks on `keystate`, and a one-shot beside it. Each half counts itself in a global, so
|
||||
/// the assertions below read what the VM actually ran rather than what we told it to run.
|
||||
const PROBE_BINDINGS: &str = r#"<Bindings>
|
||||
<Binding name="PROBEHOLD" runOnUp="true" header="PROBE">
|
||||
if ( keystate == "down" ) then
|
||||
PROBE_DOWN = (PROBE_DOWN or 0) + 1;
|
||||
else
|
||||
PROBE_UP = (PROBE_UP or 0) + 1;
|
||||
end
|
||||
PROBE_LAST = keystate;
|
||||
</Binding>
|
||||
<Binding name="PROBEEDGE">
|
||||
PROBE_EDGE = (PROBE_EDGE or 0) + 1;
|
||||
</Binding>
|
||||
</Bindings>"#;
|
||||
|
||||
/// The **with-VM** harness: a real engine table (the whole registry plus one addon's parsed
|
||||
/// `Bindings.xml`) with the real [`sync_dispatch`] chained in front of [`latch_and_dispatch`].
|
||||
///
|
||||
/// Deliberately not [`BindingDispatch::test_defaults`]: the bug 1188 phase 4 removes lived in
|
||||
/// the *derivation* — `sync_dispatch` dropped every name it could not find in `SPECS`, so a
|
||||
/// hand-built map would assert past exactly the code that was wrong.
|
||||
fn vm_harness(script: UiScript) -> App {
|
||||
let mut app = App::new();
|
||||
app.add_plugins((MinimalPlugins, bevy::input::InputPlugin))
|
||||
.init_resource::<UiKeyboardCapture>()
|
||||
.init_resource::<PlayerUiHover>()
|
||||
.init_resource::<PointerOverUi>()
|
||||
.init_resource::<BindingsState>()
|
||||
.init_resource::<BindingDispatch>()
|
||||
.add_systems(Update, (sync_dispatch, latch_and_dispatch).chain());
|
||||
app.insert_non_send_resource(script);
|
||||
app
|
||||
}
|
||||
|
||||
/// A counter a binding body left in the VM — `0` when the body never ran.
|
||||
fn lua_count(app: &App, global: &str) -> i64 {
|
||||
app.world()
|
||||
.non_send_resource::<UiScript>()
|
||||
.eval::<i64>(&format!("return {global} or 0"))
|
||||
.expect("eval")
|
||||
}
|
||||
|
||||
/// A string a binding body left in the VM — `""` when it never ran.
|
||||
fn lua_str(app: &App, global: &str) -> String {
|
||||
app.world()
|
||||
.non_send_resource::<UiScript>()
|
||||
.eval::<String>(&format!(r#"return {global} or """#))
|
||||
.expect("eval")
|
||||
}
|
||||
|
||||
/// **An addon's binding actually fires** (decision 1188 phase 4) — the assertion the whole
|
||||
/// phase exists for. Before it, an addon's `Bindings.xml` registered, listed in the Key
|
||||
/// Bindings window, saved and loaded, and then did *nothing*: `sync_dispatch` silently dropped
|
||||
/// every name that was not in [`SPECS`], so no chord ever reached the body.
|
||||
///
|
||||
/// The `runOnUp` half is the part that cannot be guessed from the built-in path, because it is
|
||||
/// shaped differently: our own registry holds two Lua strings ([`Kind::EdgeUpDown`]), an
|
||||
/// addon holds **one chunk run twice** with the global `keystate` set to `"down"` then
|
||||
/// `"up"`. A dispatch that ran the body once, or twice with the same `keystate`, leaves the
|
||||
/// player's key held down forever — which is what every shipped `runOnUp` body's `else` branch
|
||||
/// is there to prevent.
|
||||
#[test]
|
||||
fn an_addon_binding_fires_its_lua_and_runs_again_on_release_when_it_asked_to() {
|
||||
let mut script = UiScript::new().expect("VM");
|
||||
script.register_bindings(®istry_commands());
|
||||
script.register_addon_bindings(
|
||||
"ProbeAddon",
|
||||
&benilla_ui::bindings_xml::parse(PROBE_BINDINGS).expect("well-formed"),
|
||||
);
|
||||
// A `<Binding>` ships no default chord in 1.12 — the shipped defaults live in the engine's
|
||||
// own table — so an addon binding starts unbound and the player binds it, which is what
|
||||
// the Key Bindings window does through exactly this call.
|
||||
script
|
||||
.run(r#"SetBinding("J", "PROBEHOLD"); SetBinding("G", "PROBEEDGE")"#)
|
||||
.expect("bind");
|
||||
let mut app = vm_harness(script);
|
||||
|
||||
// Press: one run, `keystate == "down"`.
|
||||
press_key(&mut app, KeyCode::KeyJ);
|
||||
app.update();
|
||||
assert_eq!(
|
||||
lua_count(&app, "PROBE_DOWN"),
|
||||
1,
|
||||
"the press must reach the addon's body — this is the phase-4 bug"
|
||||
);
|
||||
assert_eq!(lua_str(&app, "PROBE_LAST"), "down");
|
||||
assert_eq!(
|
||||
lua_count(&app, "PROBE_UP"),
|
||||
0,
|
||||
"no release has happened yet"
|
||||
);
|
||||
|
||||
// Release: the SAME body again, with `keystate == "up"`.
|
||||
release_key(&mut app, KeyCode::KeyJ);
|
||||
app.update();
|
||||
assert_eq!(lua_count(&app, "PROBE_UP"), 1);
|
||||
assert_eq!(lua_str(&app, "PROBE_LAST"), "up");
|
||||
assert_eq!(
|
||||
lua_count(&app, "PROBE_DOWN"),
|
||||
1,
|
||||
"the release runs the chunk with keystate=up, not the down half a second time"
|
||||
);
|
||||
|
||||
// The one-shot binding: press runs it, release does not — the `Kind::Edge` behaviour,
|
||||
// decided here by the file's missing `runOnUp` rather than by our registry.
|
||||
press_key(&mut app, KeyCode::KeyG);
|
||||
app.update();
|
||||
assert_eq!(lua_count(&app, "PROBE_EDGE"), 1);
|
||||
release_key(&mut app, KeyCode::KeyG);
|
||||
app.update();
|
||||
assert_eq!(
|
||||
lua_count(&app, "PROBE_EDGE"),
|
||||
1,
|
||||
"no runOnUp, no second run — an addon that toggled here would toggle back"
|
||||
);
|
||||
|
||||
// And the registry dispatches unchanged beside them: the enum routes, it does not divert.
|
||||
press_key(&mut app, KeyCode::KeyW);
|
||||
app.update();
|
||||
assert!(state(&app).pressed(cmd::MOVE_FORWARD));
|
||||
assert_eq!(
|
||||
lua_count(&app, "PROBE_DOWN"),
|
||||
1,
|
||||
"a built-in press is nobody else's"
|
||||
);
|
||||
|
||||
// **`keystate` does not outlive the call.** It is absent from the 1.12.1 client's in-world
|
||||
// `_G` (`reference/1.12-globals.tsv`) even though its own binding bodies read it, so the
|
||||
// reference sets it transiently — as it does `this`/`arg1`. Leaving it set would hand
|
||||
// every addon a global the reference lacks, and one that feature-detects `if keystate`
|
||||
// would take a branch we cannot honour (decision 1189's "a superset is not free").
|
||||
assert_eq!(
|
||||
lua_str(&app, "tostring(keystate)"),
|
||||
"nil",
|
||||
"keystate must be restored after a binding body runs, not left standing in _G"
|
||||
);
|
||||
}
|
||||
|
||||
/// A `runOnUp` addon latch survives a chat box taking focus, and its release half still runs.
|
||||
///
|
||||
/// The typing edge clears [`Kind::Held`] latches (the reference's focus handler releasing the
|
||||
/// direction bits) and deliberately leaves [`Kind::EdgeUpDown`] armed, because the up half of
|
||||
/// a *pressed* binding is delivered regardless of focus. An addon's `runOnUp` binding is that
|
||||
/// same pair wearing one chunk, so it must follow the same rule — dropped on the focus edge,
|
||||
/// it would leave whatever its down half started running forever, with no key left to press
|
||||
/// to stop it.
|
||||
#[test]
|
||||
fn a_run_on_up_addon_latch_survives_the_typing_edge_and_still_releases() {
|
||||
let mut script = UiScript::new().expect("VM");
|
||||
script.register_bindings(®istry_commands());
|
||||
script.register_addon_bindings(
|
||||
"ProbeAddon",
|
||||
&benilla_ui::bindings_xml::parse(PROBE_BINDINGS).expect("well-formed"),
|
||||
);
|
||||
script.run(r#"SetBinding("J", "PROBEHOLD")"#).expect("bind");
|
||||
let mut app = vm_harness(script);
|
||||
|
||||
press_key(&mut app, KeyCode::KeyW);
|
||||
press_key(&mut app, KeyCode::KeyJ);
|
||||
app.update();
|
||||
assert!(state(&app).pressed(cmd::MOVE_FORWARD));
|
||||
assert_eq!(lua_count(&app, "PROBE_DOWN"), 1);
|
||||
|
||||
// A box takes focus: movement stops, the addon's latch stays.
|
||||
app.world_mut().resource_mut::<UiKeyboardCapture>().0 = true;
|
||||
app.update();
|
||||
assert!(!state(&app).pressed(cmd::MOVE_FORWARD));
|
||||
assert_eq!(
|
||||
lua_count(&app, "PROBE_UP"),
|
||||
0,
|
||||
"the focus edge is not a release — nothing has run the up half yet"
|
||||
);
|
||||
|
||||
release_key(&mut app, KeyCode::KeyJ);
|
||||
app.update();
|
||||
assert_eq!(
|
||||
lua_count(&app, "PROBE_UP"),
|
||||
1,
|
||||
"the up half is delivered even while typing, like every other pressed binding's"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn held_commands_latch_across_frames_and_release_per_base_key() {
|
||||
let mut app = harness();
|
||||
@@ -755,7 +1091,7 @@ mod tests {
|
||||
// is 0585's original "one key did two things" bug, reborn one strip further along...
|
||||
assert_eq!(
|
||||
dispatch.resolve(plane_p, false),
|
||||
Some(pet_paper_doll),
|
||||
Some(Bound::Spec(pet_paper_doll)),
|
||||
"without the plane rule the retry does reach SHIFT-P — this is what is being blocked"
|
||||
);
|
||||
// ...so on the plane, the retry is what it costs.
|
||||
@@ -764,15 +1100,18 @@ mod tests {
|
||||
// shipped default proves it (1.12's own two, CTRL-SHIFT-TAB and CTRL-SHIFT-PAGEDOWN,
|
||||
// belong to commands the honest tree doesn't carry yet), so the entry is made here: this
|
||||
// is the case a player creates the moment they bind anything on the plane.
|
||||
dispatch.map.insert(plane_p, pet_paper_doll);
|
||||
dispatch.map.insert(plane_p, Bound::Spec(pet_paper_doll));
|
||||
assert_eq!(
|
||||
dispatch.resolve(plane_p, true),
|
||||
Some(pet_paper_doll),
|
||||
Some(Bound::Spec(pet_paper_doll)),
|
||||
"the plane spends the retry, never the exact probe"
|
||||
);
|
||||
// And SHIFT-P itself is still the pet paper doll: the plane costs nothing outside itself.
|
||||
let shift_p = Chord::parse("SHIFT-P").expect("parses");
|
||||
assert_eq!(dispatch.resolve(shift_p, false), Some(pet_paper_doll));
|
||||
assert_eq!(
|
||||
dispatch.resolve(shift_p, false),
|
||||
Some(Bound::Spec(pet_paper_doll))
|
||||
);
|
||||
}
|
||||
|
||||
/// **The pet lane routes on the CTRL digits, and the number row is untouched** (B218,
|
||||
@@ -793,7 +1132,7 @@ mod tests {
|
||||
"the modifier decides: CTRL-1 is not the number row's"
|
||||
);
|
||||
// The runOnUp half: the latch drops on the BASE key's release (which is what runs
|
||||
// BenillaPetActionButtonUp → CastPetAction in the app's VM), Ctrl still held.
|
||||
// PetActionButtonUp → CastPetAction in the app's VM), Ctrl still held.
|
||||
release_key(&mut app, KeyCode::Digit1);
|
||||
app.update();
|
||||
assert!(!state(&app).pressed(by_name("BONUSACTIONBUTTON1")));
|
||||
|
||||
@@ -165,84 +165,84 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
spec!(
|
||||
"ACTIONBUTTON1",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown("BenillaActionButtonDown(1)", "BenillaActionButtonUp(1)"),
|
||||
Kind::EdgeUpDown("ActionButtonDown(1)", "ActionButtonUp(1)"),
|
||||
Some("1"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"ACTIONBUTTON2",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown("BenillaActionButtonDown(2)", "BenillaActionButtonUp(2)"),
|
||||
Kind::EdgeUpDown("ActionButtonDown(2)", "ActionButtonUp(2)"),
|
||||
Some("2"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"ACTIONBUTTON3",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown("BenillaActionButtonDown(3)", "BenillaActionButtonUp(3)"),
|
||||
Kind::EdgeUpDown("ActionButtonDown(3)", "ActionButtonUp(3)"),
|
||||
Some("3"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"ACTIONBUTTON4",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown("BenillaActionButtonDown(4)", "BenillaActionButtonUp(4)"),
|
||||
Kind::EdgeUpDown("ActionButtonDown(4)", "ActionButtonUp(4)"),
|
||||
Some("4"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"ACTIONBUTTON5",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown("BenillaActionButtonDown(5)", "BenillaActionButtonUp(5)"),
|
||||
Kind::EdgeUpDown("ActionButtonDown(5)", "ActionButtonUp(5)"),
|
||||
Some("5"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"ACTIONBUTTON6",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown("BenillaActionButtonDown(6)", "BenillaActionButtonUp(6)"),
|
||||
Kind::EdgeUpDown("ActionButtonDown(6)", "ActionButtonUp(6)"),
|
||||
Some("6"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"ACTIONBUTTON7",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown("BenillaActionButtonDown(7)", "BenillaActionButtonUp(7)"),
|
||||
Kind::EdgeUpDown("ActionButtonDown(7)", "ActionButtonUp(7)"),
|
||||
Some("7"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"ACTIONBUTTON8",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown("BenillaActionButtonDown(8)", "BenillaActionButtonUp(8)"),
|
||||
Kind::EdgeUpDown("ActionButtonDown(8)", "ActionButtonUp(8)"),
|
||||
Some("8"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"ACTIONBUTTON9",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown("BenillaActionButtonDown(9)", "BenillaActionButtonUp(9)"),
|
||||
Kind::EdgeUpDown("ActionButtonDown(9)", "ActionButtonUp(9)"),
|
||||
Some("9"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"ACTIONBUTTON10",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown("BenillaActionButtonDown(10)", "BenillaActionButtonUp(10)"),
|
||||
Kind::EdgeUpDown("ActionButtonDown(10)", "ActionButtonUp(10)"),
|
||||
Some("0"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"ACTIONBUTTON11",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown("BenillaActionButtonDown(11)", "BenillaActionButtonUp(11)"),
|
||||
Kind::EdgeUpDown("ActionButtonDown(11)", "ActionButtonUp(11)"),
|
||||
Some("-"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"ACTIONBUTTON12",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown("BenillaActionButtonDown(12)", "BenillaActionButtonUp(12)"),
|
||||
Kind::EdgeUpDown("ActionButtonDown(12)", "ActionButtonUp(12)"),
|
||||
Some("="),
|
||||
None
|
||||
),
|
||||
@@ -252,7 +252,7 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"SHAPESHIFTBUTTON1",
|
||||
ACTIONBAR,
|
||||
Kind::Edge(
|
||||
r#"local b = getglobal("BenillaShapeshiftButton1"); if b and b:IsVisible() then b:Click() end"#
|
||||
r#"local b = getglobal("ShapeshiftButton1"); if b and b:IsVisible() then b:Click() end"#
|
||||
),
|
||||
Some("CTRL-F1"),
|
||||
None
|
||||
@@ -261,7 +261,7 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"SHAPESHIFTBUTTON2",
|
||||
ACTIONBAR,
|
||||
Kind::Edge(
|
||||
r#"local b = getglobal("BenillaShapeshiftButton2"); if b and b:IsVisible() then b:Click() end"#
|
||||
r#"local b = getglobal("ShapeshiftButton2"); if b and b:IsVisible() then b:Click() end"#
|
||||
),
|
||||
Some("CTRL-F2"),
|
||||
None
|
||||
@@ -270,7 +270,7 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"SHAPESHIFTBUTTON3",
|
||||
ACTIONBAR,
|
||||
Kind::Edge(
|
||||
r#"local b = getglobal("BenillaShapeshiftButton3"); if b and b:IsVisible() then b:Click() end"#
|
||||
r#"local b = getglobal("ShapeshiftButton3"); if b and b:IsVisible() then b:Click() end"#
|
||||
),
|
||||
Some("CTRL-F3"),
|
||||
None
|
||||
@@ -279,7 +279,7 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"SHAPESHIFTBUTTON4",
|
||||
ACTIONBAR,
|
||||
Kind::Edge(
|
||||
r#"local b = getglobal("BenillaShapeshiftButton4"); if b and b:IsVisible() then b:Click() end"#
|
||||
r#"local b = getglobal("ShapeshiftButton4"); if b and b:IsVisible() then b:Click() end"#
|
||||
),
|
||||
Some("CTRL-F4"),
|
||||
None
|
||||
@@ -288,7 +288,7 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"SHAPESHIFTBUTTON5",
|
||||
ACTIONBAR,
|
||||
Kind::Edge(
|
||||
r#"local b = getglobal("BenillaShapeshiftButton5"); if b and b:IsVisible() then b:Click() end"#
|
||||
r#"local b = getglobal("ShapeshiftButton5"); if b and b:IsVisible() then b:Click() end"#
|
||||
),
|
||||
Some("CTRL-F5"),
|
||||
None
|
||||
@@ -297,7 +297,7 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"SHAPESHIFTBUTTON6",
|
||||
ACTIONBAR,
|
||||
Kind::Edge(
|
||||
r#"local b = getglobal("BenillaShapeshiftButton6"); if b and b:IsVisible() then b:Click() end"#
|
||||
r#"local b = getglobal("ShapeshiftButton6"); if b and b:IsVisible() then b:Click() end"#
|
||||
),
|
||||
Some("CTRL-F6"),
|
||||
None
|
||||
@@ -306,7 +306,7 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"SHAPESHIFTBUTTON7",
|
||||
ACTIONBAR,
|
||||
Kind::Edge(
|
||||
r#"local b = getglobal("BenillaShapeshiftButton7"); if b and b:IsVisible() then b:Click() end"#
|
||||
r#"local b = getglobal("ShapeshiftButton7"); if b and b:IsVisible() then b:Click() end"#
|
||||
),
|
||||
Some("CTRL-F7"),
|
||||
None
|
||||
@@ -315,7 +315,7 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"SHAPESHIFTBUTTON8",
|
||||
ACTIONBAR,
|
||||
Kind::Edge(
|
||||
r#"local b = getglobal("BenillaShapeshiftButton8"); if b and b:IsVisible() then b:Click() end"#
|
||||
r#"local b = getglobal("ShapeshiftButton8"); if b and b:IsVisible() then b:Click() end"#
|
||||
),
|
||||
Some("CTRL-F8"),
|
||||
None
|
||||
@@ -324,7 +324,7 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"SHAPESHIFTBUTTON9",
|
||||
ACTIONBAR,
|
||||
Kind::Edge(
|
||||
r#"local b = getglobal("BenillaShapeshiftButton9"); if b and b:IsVisible() then b:Click() end"#
|
||||
r#"local b = getglobal("ShapeshiftButton9"); if b and b:IsVisible() then b:Click() end"#
|
||||
),
|
||||
Some("CTRL-F9"),
|
||||
None
|
||||
@@ -333,7 +333,7 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"SHAPESHIFTBUTTON10",
|
||||
ACTIONBAR,
|
||||
Kind::Edge(
|
||||
r#"local b = getglobal("BenillaShapeshiftButton10"); if b and b:IsVisible() then b:Click() end"#
|
||||
r#"local b = getglobal("ShapeshiftButton10"); if b and b:IsVisible() then b:Click() end"#
|
||||
),
|
||||
Some("CTRL-F10"),
|
||||
None
|
||||
@@ -349,100 +349,70 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
spec!(
|
||||
"BONUSACTIONBUTTON1",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
"BenillaPetActionButtonDown(1)",
|
||||
"BenillaPetActionButtonUp(1)"
|
||||
),
|
||||
Kind::EdgeUpDown("PetActionButtonDown(1)", "PetActionButtonUp(1)"),
|
||||
Some("CTRL-1"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"BONUSACTIONBUTTON2",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
"BenillaPetActionButtonDown(2)",
|
||||
"BenillaPetActionButtonUp(2)"
|
||||
),
|
||||
Kind::EdgeUpDown("PetActionButtonDown(2)", "PetActionButtonUp(2)"),
|
||||
Some("CTRL-2"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"BONUSACTIONBUTTON3",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
"BenillaPetActionButtonDown(3)",
|
||||
"BenillaPetActionButtonUp(3)"
|
||||
),
|
||||
Kind::EdgeUpDown("PetActionButtonDown(3)", "PetActionButtonUp(3)"),
|
||||
Some("CTRL-3"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"BONUSACTIONBUTTON4",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
"BenillaPetActionButtonDown(4)",
|
||||
"BenillaPetActionButtonUp(4)"
|
||||
),
|
||||
Kind::EdgeUpDown("PetActionButtonDown(4)", "PetActionButtonUp(4)"),
|
||||
Some("CTRL-4"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"BONUSACTIONBUTTON5",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
"BenillaPetActionButtonDown(5)",
|
||||
"BenillaPetActionButtonUp(5)"
|
||||
),
|
||||
Kind::EdgeUpDown("PetActionButtonDown(5)", "PetActionButtonUp(5)"),
|
||||
Some("CTRL-5"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"BONUSACTIONBUTTON6",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
"BenillaPetActionButtonDown(6)",
|
||||
"BenillaPetActionButtonUp(6)"
|
||||
),
|
||||
Kind::EdgeUpDown("PetActionButtonDown(6)", "PetActionButtonUp(6)"),
|
||||
Some("CTRL-6"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"BONUSACTIONBUTTON7",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
"BenillaPetActionButtonDown(7)",
|
||||
"BenillaPetActionButtonUp(7)"
|
||||
),
|
||||
Kind::EdgeUpDown("PetActionButtonDown(7)", "PetActionButtonUp(7)"),
|
||||
Some("CTRL-7"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"BONUSACTIONBUTTON8",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
"BenillaPetActionButtonDown(8)",
|
||||
"BenillaPetActionButtonUp(8)"
|
||||
),
|
||||
Kind::EdgeUpDown("PetActionButtonDown(8)", "PetActionButtonUp(8)"),
|
||||
Some("CTRL-8"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"BONUSACTIONBUTTON9",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
"BenillaPetActionButtonDown(9)",
|
||||
"BenillaPetActionButtonUp(9)"
|
||||
),
|
||||
Kind::EdgeUpDown("PetActionButtonDown(9)", "PetActionButtonUp(9)"),
|
||||
Some("CTRL-9"),
|
||||
None
|
||||
),
|
||||
spec!(
|
||||
"BONUSACTIONBUTTON10",
|
||||
ACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
"BenillaPetActionButtonDown(10)",
|
||||
"BenillaPetActionButtonUp(10)"
|
||||
),
|
||||
Kind::EdgeUpDown("PetActionButtonDown(10)", "PetActionButtonUp(10)"),
|
||||
Some("CTRL-0"),
|
||||
None
|
||||
),
|
||||
@@ -580,7 +550,7 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
spec!(
|
||||
"TOGGLECHARACTER0",
|
||||
INTERFACE,
|
||||
Kind::Edge(r#"ToggleCharacter("BenillaPaperDollFrame")"#),
|
||||
Kind::Edge(r#"ToggleCharacter("PaperDollFrame")"#),
|
||||
Some("C"),
|
||||
None
|
||||
),
|
||||
@@ -597,7 +567,7 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
spec!(
|
||||
"TOGGLECHARACTER1",
|
||||
INTERFACE,
|
||||
Kind::Edge(r#"ToggleCharacter("BenillaSkillFrame")"#),
|
||||
Kind::Edge(r#"ToggleCharacter("SkillFrame")"#),
|
||||
Some("K"),
|
||||
None
|
||||
),
|
||||
@@ -610,7 +580,7 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
spec!(
|
||||
"TOGGLECHARACTER3",
|
||||
INTERFACE,
|
||||
Kind::Edge(r#"ToggleCharacter("BenillaPetPaperDollFrame")"#),
|
||||
Kind::Edge(r#"ToggleCharacter("PetPaperDollFrame")"#),
|
||||
Some("SHIFT-P"),
|
||||
None
|
||||
),
|
||||
@@ -769,8 +739,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR1BUTTON1",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomLeft", 1)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomLeft", 1)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomLeft", 1)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomLeft", 1)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -779,8 +749,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR1BUTTON2",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomLeft", 2)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomLeft", 2)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomLeft", 2)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomLeft", 2)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -789,8 +759,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR1BUTTON3",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomLeft", 3)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomLeft", 3)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomLeft", 3)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomLeft", 3)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -799,8 +769,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR1BUTTON4",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomLeft", 4)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomLeft", 4)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomLeft", 4)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomLeft", 4)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -809,8 +779,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR1BUTTON5",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomLeft", 5)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomLeft", 5)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomLeft", 5)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomLeft", 5)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -819,8 +789,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR1BUTTON6",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomLeft", 6)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomLeft", 6)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomLeft", 6)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomLeft", 6)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -829,8 +799,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR1BUTTON7",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomLeft", 7)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomLeft", 7)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomLeft", 7)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomLeft", 7)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -839,8 +809,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR1BUTTON8",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomLeft", 8)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomLeft", 8)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomLeft", 8)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomLeft", 8)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -849,8 +819,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR1BUTTON9",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomLeft", 9)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomLeft", 9)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomLeft", 9)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomLeft", 9)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -859,8 +829,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR1BUTTON10",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomLeft", 10)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomLeft", 10)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomLeft", 10)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomLeft", 10)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -869,8 +839,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR1BUTTON11",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomLeft", 11)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomLeft", 11)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomLeft", 11)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomLeft", 11)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -879,8 +849,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR1BUTTON12",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomLeft", 12)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomLeft", 12)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomLeft", 12)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomLeft", 12)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -889,8 +859,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR2BUTTON1",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomRight", 1)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomRight", 1)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomRight", 1)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomRight", 1)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -899,8 +869,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR2BUTTON2",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomRight", 2)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomRight", 2)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomRight", 2)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomRight", 2)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -909,8 +879,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR2BUTTON3",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomRight", 3)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomRight", 3)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomRight", 3)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomRight", 3)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -919,8 +889,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR2BUTTON4",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomRight", 4)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomRight", 4)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomRight", 4)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomRight", 4)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -929,8 +899,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR2BUTTON5",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomRight", 5)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomRight", 5)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomRight", 5)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomRight", 5)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -939,8 +909,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR2BUTTON6",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomRight", 6)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomRight", 6)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomRight", 6)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomRight", 6)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -949,8 +919,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR2BUTTON7",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomRight", 7)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomRight", 7)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomRight", 7)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomRight", 7)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -959,8 +929,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR2BUTTON8",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomRight", 8)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomRight", 8)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomRight", 8)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomRight", 8)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -969,8 +939,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR2BUTTON9",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomRight", 9)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomRight", 9)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomRight", 9)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomRight", 9)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -979,8 +949,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR2BUTTON10",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomRight", 10)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomRight", 10)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomRight", 10)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomRight", 10)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -989,8 +959,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR2BUTTON11",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomRight", 11)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomRight", 11)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomRight", 11)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomRight", 11)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
@@ -999,8 +969,8 @@ pub(crate) static SPECS: &[Spec] = &[
|
||||
"MULTIACTIONBAR2BUTTON12",
|
||||
MULTIACTIONBAR,
|
||||
Kind::EdgeUpDown(
|
||||
r#"BenillaMultiActionButtonDown("BottomRight", 12)"#,
|
||||
r#"BenillaMultiActionButtonUp("BottomRight", 12)"#
|
||||
r#"MultiActionButtonDown("MultiBarBottomRight", 12)"#,
|
||||
r#"MultiActionButtonUp("MultiBarBottomRight", 12)"#
|
||||
),
|
||||
None,
|
||||
None
|
||||
|
||||
@@ -96,8 +96,15 @@ pub(crate) fn resolve(diff: &[(String, Vec<String>)]) -> Vec<(String, Vec<String
|
||||
held.retain(|h| h != k);
|
||||
}
|
||||
}
|
||||
if let Some((_, slot)) = table.iter_mut().find(|(n, _)| n == name) {
|
||||
*slot = keys.clone();
|
||||
match table.iter_mut().find(|(n, _)| n == name) {
|
||||
Some((_, slot)) => *slot = keys.clone(),
|
||||
// **A name that is not in SPECS is an ADDON's command, and it is kept** (decision
|
||||
// 1201). Dropping it here is what made an addon binding forget its key every restart:
|
||||
// the row was written by `to_diff`, read back by `from_diff`, had its chord stolen
|
||||
// from whoever else held it by the pass above — and was then thrown away, so the
|
||||
// binding registered at world entry with nothing to restore. `Bindings.txt` is a flat
|
||||
// list in the reference too; an addon's row is a row like any other.
|
||||
None => table.push((name.clone(), keys.clone())),
|
||||
}
|
||||
}
|
||||
table
|
||||
@@ -180,11 +187,16 @@ mod tests {
|
||||
("BOGUSCMD".to_string(), vec!["Q".to_string()]),
|
||||
]
|
||||
);
|
||||
// Unknown commands pass through parse (kept for forward compat); resolve just drops
|
||||
// what the registry doesn't know — after stealing its keys, so the file's intent
|
||||
// ("Q belongs to BOGUSCMD, not STRAFELEFT") still holds for the commands we do have.
|
||||
// **A command the registry does not know is KEPT** (decision 1201). It used to be
|
||||
// dropped after its keys were stolen, which was right for a genuinely bogus line and
|
||||
// catastrophic for the case that actually occurs: an ADDON's command, whose
|
||||
// `Bindings.xml` registers at world entry, hours after this file was read. Dropping it
|
||||
// meant the binding registered with nothing to restore and forgot the player's chord
|
||||
// every restart (1192 §4). The steal still happens either way, so the file's intent
|
||||
// ("Q belongs to that command, not STRAFELEFT") holds for the commands we do have.
|
||||
let resolved = resolve(&parsed);
|
||||
assert!(!resolved.iter().any(|(n, _)| n == "BOGUSCMD"));
|
||||
let bogus = &resolved.iter().find(|(n, _)| n == "BOGUSCMD").unwrap().1;
|
||||
assert_eq!(bogus, &vec!["Q".to_string()]);
|
||||
let strafe = &resolved.iter().find(|(n, _)| n == "STRAFELEFT").unwrap().1;
|
||||
assert!(
|
||||
strafe.is_empty(),
|
||||
|
||||
@@ -346,10 +346,10 @@ pub(super) fn seed_ui_fixture(
|
||||
// frames — the window's OnUpdate polls, flag-guarded, exactly once).
|
||||
if let Some(s) = script.as_mut() {
|
||||
if let Err(e) = s.run(
|
||||
"BenillaBankFrame:SetScript(\"OnUpdate\", function()\n\
|
||||
if not benillaBankPopoutDone and C_Container.GetContainerNumSlots(5) > 0 then\n\
|
||||
"BankFrame:SetScript(\"OnUpdate\", function()\n\
|
||||
if not benillaBankPopoutDone and GetContainerNumSlots(5) > 0 then\n\
|
||||
benillaBankPopoutDone = 1\n\
|
||||
BenillaBankBagButton_OnClick(getglobal(\"BenillaBankBagButton1\"))\n\
|
||||
BenillaBankBagButton_OnClick(getglobal(\"BankBagButton1\"))\n\
|
||||
end\n\
|
||||
end)",
|
||||
) {
|
||||
@@ -866,7 +866,7 @@ pub(super) fn seed_ui_fixture(
|
||||
// Open the window (the C binding's path). The feed pushes on the following frames of
|
||||
// the settle window; OnShow + the UNIT_* events repaint exactly as live.
|
||||
if let Some(s) = script.as_mut() {
|
||||
if let Err(e) = s.run("ToggleCharacter(\"BenillaPaperDollFrame\")") {
|
||||
if let Err(e) = s.run("ToggleCharacter(\"PaperDollFrame\")") {
|
||||
warn!("capture: ui-char seed failed to open the window: {e}");
|
||||
}
|
||||
}
|
||||
@@ -1138,7 +1138,7 @@ pub(super) fn seed_ui_fixture(
|
||||
// opaque-gray selection highlight (ctor 0xFF606060) under the glyphs AND the white
|
||||
// caret at the selection's end — the whole text-UI overlay stack in one golden.
|
||||
// …then pin the hover-revealed chrome: in-game the tab + the black window textures
|
||||
// follow the OS cursor (BenillaFCF_OnUpdate), which a headless capture can't hover —
|
||||
// follow the OS cursor (FCF_OnUpdate), which a headless capture can't hover —
|
||||
// replace the OnUpdate with a fixed-reveal one so the golden also locks the window
|
||||
// tint (chat-cache COLOR 0 0 0) and the text-sized tab (BenillaFCF_TabResize, which
|
||||
// must keep retrying until the label's measure lands).
|
||||
@@ -1261,7 +1261,7 @@ pub(super) fn seed_ui_fixture(
|
||||
}
|
||||
|
||||
/// Seed + open the backpack window with a fixed item set — shared by the `Bag` and `Tooltip`
|
||||
/// fixtures. The bag is a standalone addon (no `ShowUIPanel` path): drive the `C_Container` snapshot
|
||||
/// fixtures. The bag is a standalone addon (no `ShowUIPanel` path): drive the container snapshot
|
||||
/// and purse directly, then open and paint. The feed (`crate::ui_items`) leaves bag 0 alone when
|
||||
/// there is no `SelfPlayer` (net is disabled in capture), so this manual snapshot is not clobbered.
|
||||
fn seed_bag_window(
|
||||
|
||||
@@ -86,10 +86,10 @@ struct ProbeCastCancel {
|
||||
/// probe's target) and use it. `false` = not resolved yet — a fresh character's first login
|
||||
/// still has the item-query round trip in flight, so the caller retries per frame.
|
||||
const USE_HEARTH_CHUNK: &str = "return (function()\n\
|
||||
for s = 1, C_Container.GetContainerNumSlots(0) do\n\
|
||||
local link = C_Container.GetContainerItemLink(0, s)\n\
|
||||
for s = 1, GetContainerNumSlots(0) do\n\
|
||||
local link = GetContainerItemLink(0, s)\n\
|
||||
if link and string.find(link, \"item:6948\", 1, true) then\n\
|
||||
C_Container.UseContainerItem(0, s)\n\
|
||||
UseContainerItem(0, s)\n\
|
||||
return true\n\
|
||||
end\n\
|
||||
end\n\
|
||||
|
||||
@@ -474,17 +474,15 @@ fn mail_probe(
|
||||
// itself uses; encoded bag*100+slot (-1 = not there yet).
|
||||
let found = script
|
||||
.eval::<i64>(
|
||||
"for bag=0,4 do local n=C_Container.GetContainerNumSlots(bag) or 0 \
|
||||
for slot=1,n do local link=C_Container.GetContainerItemLink(bag,slot) \
|
||||
"for bag=0,4 do local n=GetContainerNumSlots(bag) or 0 \
|
||||
for slot=1,n do local link=GetContainerItemLink(bag,slot) \
|
||||
if link and string.find(link,'item:8383',1,true) then return bag*100+slot end end end \
|
||||
return -1",
|
||||
)
|
||||
.unwrap_or(-1);
|
||||
if found >= 0 {
|
||||
let (bag, lslot) = (found / 100, found % 100);
|
||||
if let Err(e) =
|
||||
script.run(&format!("C_Container.UseContainerItem({bag}, {lslot})"))
|
||||
{
|
||||
if let Err(e) = script.run(&format!("UseContainerItem({bag}, {lslot})")) {
|
||||
error!("PROBE_MAIL: FAIL (b3) — UseContainerItem({bag}, {lslot}) errored: {e}");
|
||||
probe.fails += 1;
|
||||
probe.phase = Phase::TakeMoney {
|
||||
@@ -510,23 +508,18 @@ fn mail_probe(
|
||||
Some((bag, lslot)) => {
|
||||
let painted = script
|
||||
.eval::<bool>(
|
||||
"return BenillaItemTextFrame:IsShown() \
|
||||
and string.find(BenillaItemTextPageText:GetText() or '', 'hello', 1, true) ~= nil",
|
||||
"return ItemTextFrame:IsShown() \
|
||||
and string.find(ItemTextPageText:GetText() or '', 'hello', 1, true) ~= nil",
|
||||
)
|
||||
.unwrap_or(false);
|
||||
if painted {
|
||||
info!("PROBE_MAIL: PASS (b3) — the reader painted the letter body (title/creator/text via ITEM_TEXT_BEGIN→READY)");
|
||||
probe.passes += 1;
|
||||
// Cleanup: close the reader, destroy the copy (else bags gain one per run).
|
||||
crate::ui_script::run_or_warn(&script, "ItemTextCloseButton:Click()");
|
||||
crate::ui_script::run_or_warn(
|
||||
&script,
|
||||
"BenillaItemTextCloseButton:Click()",
|
||||
);
|
||||
crate::ui_script::run_or_warn(
|
||||
&script,
|
||||
&format!(
|
||||
"C_Container.PickupContainerItem({bag}, {lslot}) DeleteCursorItem()"
|
||||
),
|
||||
&format!("PickupContainerItem({bag}, {lslot}) DeleteCursorItem()"),
|
||||
);
|
||||
probe.phase = Phase::TakeMoney {
|
||||
since: now,
|
||||
|
||||
@@ -39,7 +39,7 @@ pub(super) enum UiFixture {
|
||||
/// render): vault slots with icons, bag buttons (owned icon / bought-empty / red unpurchased),
|
||||
/// and the purchase row's DBC cost.
|
||||
Bank,
|
||||
/// The multi-quest greeting panel (`QUEST_GREETING`, `BenillaQuestGreetingPanel`): a greeting
|
||||
/// The multi-quest greeting panel (`QUEST_GREETING`, `QuestGreetingPanel`): a greeting
|
||||
/// line plus an "Available Quests" list of `UI-Quest-BulletPoint` title rows — the frame the
|
||||
/// gossip-vs-greeting confusion turned on, and previously uncaptured (bullet/title seating had
|
||||
/// no regression baseline).
|
||||
|
||||
@@ -0,0 +1,730 @@
|
||||
//! The **character-select AddOns screen** (decision 1197) — the glue half of the director's ask,
|
||||
//! and the button 1191 §5 recorded as *"not hidden; never built"*.
|
||||
//!
|
||||
//! The reference's is `Interface\GlueXML\AddonList.xml/.lua`, opened by
|
||||
//! `CharacterSelectAddonsButton`. We have that Lua (read off the patch chain) and it is the
|
||||
//! authority for **behaviour**; we do not have its XML, so the geometry here is ours, derived from
|
||||
//! the two constants the Lua does carry: `ADDON_BUTTON_HEIGHT = 16` and
|
||||
//! `MAX_ADDONS_DISPLAYED = 19` — a 19-row list at 16 px pitch, 304 px of list. Everything else is
|
||||
//! sized around that and stated as ours rather than passed off as transcribed.
|
||||
//!
|
||||
//! ## What the reference does, and what we do
|
||||
//!
|
||||
//! `AddonList_Update` per row: a tri-state checkbox, the title (`## Title` else the folder name)
|
||||
//! coloured **gold when loadable, red when enabled-but-broken, grey when disabled**, the status
|
||||
//! token (`getglobal("ADDON_"..reason)`), a URL/update button, and a security icon. Hovering a row
|
||||
//! raises `AddonTooltip` with title + notes + `ADDON_DEPENDENCIES`.
|
||||
//!
|
||||
//! Carried: the list, the checkbox, the three-colour title rule, the status column, the
|
||||
//! hover tooltip with notes and dependencies, scrolling, Enable All / Disable All, and
|
||||
//! Okay/Cancel with Cancel meaning *discard* (the reference's `ResetAddOns`).
|
||||
//!
|
||||
//! **Not carried, each for a stated reason:**
|
||||
//! - the **security icon** — `## Secure: 1` is Blizzard's own signature marker and nothing a
|
||||
//! player installs carries it honestly (1191 §6 already refused to treat it as granting
|
||||
//! anything). An icon that is always "insecure" tells nobody anything.
|
||||
//! - the **URL / update buttons** — they launch a browser. That is an outward-facing action from a
|
||||
//! game client, and it is the director's call to make, not this screen's. `## URL` is shown in
|
||||
//! the tooltip instead, where it is information rather than a trigger.
|
||||
//! - the **character dropdown** (`AddonCharacterDropDown`, the "All" vs per-character switch).
|
||||
//! Ours edits the **selected** character's enable file, because that is the file the world-entry
|
||||
//! walk will read (1191 §7). "All characters" needs a glue dropdown widget we do not have and a
|
||||
//! fan-out write across every roster file; it is queued, not forgotten.
|
||||
//! - `SetScriptMemory` / `GetScriptMemory` — a 1.12 Lua-heap dial with no equivalent here.
|
||||
//!
|
||||
//! ## Why it reads the folder rather than the VM
|
||||
//!
|
||||
//! At character select no addon has been *loaded* — `load_third_party` is a world-entry step
|
||||
//! (1191 §2) — so `GetNumAddOns()` would answer 0. The screen asks
|
||||
//! [`crate::ui_script::addons::installed`] instead: the same discovery, the same manifest parser,
|
||||
//! the same enable file. Two views of one folder, never two folders.
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::glue::art::{GlueArt, DIM, GOLD};
|
||||
use crate::glue::backdrop::{backdrop_border, tiled_bg_node};
|
||||
use crate::glue::widgets::{glue_button, outlined_text, overlay, GlueBtnKind, GlueText, Hilight};
|
||||
use crate::glue_strings::GlueStrings;
|
||||
use crate::ui_script::addons::{self, InstalledAddOn};
|
||||
|
||||
use super::wow_font;
|
||||
|
||||
/// `MAX_ADDONS_DISPLAYED` (AddonList.lua l.2) — the reference's own row count.
|
||||
const MAX_ROWS: usize = 19;
|
||||
/// `ADDON_BUTTON_HEIGHT` (AddonList.lua l.1).
|
||||
const ROW_H: f32 = 16.0;
|
||||
/// Ours: the list is 19×16 = 304 tall, and the panel is that plus a title and a button row.
|
||||
const PANEL_W: f32 = 560.0;
|
||||
const PANEL_H: f32 = 420.0;
|
||||
const LIST_TOP: f32 = 56.0;
|
||||
const LIST_LEFT: f32 = 24.0;
|
||||
const CHECK: f32 = 14.0;
|
||||
|
||||
/// A red that reads as "this is enabled and will not load" — `SetTextColor(1.0, 0.1, 0.1)`,
|
||||
/// AddonList.lua's own value.
|
||||
const BROKEN: Color = Color::srgb(1.0, 0.1, 0.1);
|
||||
|
||||
/// The screen's state. Opened by the select screen's AddOns button, driven by
|
||||
/// [`drive_addons_panel`], cleared on leaving character select.
|
||||
///
|
||||
/// `staged` is the whole point of Okay/Cancel: edits go here, and only Okay writes the file. The
|
||||
/// reference does the same with `SaveAddOns`/`ResetAddOns` over the client's in-memory records.
|
||||
#[derive(Resource, Default)]
|
||||
pub(super) struct AddonsPanel {
|
||||
pub(super) open: bool,
|
||||
/// The installed list as read at open — never re-read while up, so a row's index is stable.
|
||||
list: Vec<InstalledAddOn>,
|
||||
/// The edited enable state, parallel to `list`.
|
||||
staged: Vec<bool>,
|
||||
/// First visible row (`AddonList.offset`).
|
||||
offset: usize,
|
||||
/// The row the cursor is over, for the tooltip.
|
||||
hovered: Option<usize>,
|
||||
root: Option<Entity>,
|
||||
spawned_s: f32,
|
||||
/// Set while the spawned tree no longer matches `staged`/`offset`/`hovered` — one flag rather
|
||||
/// than a diff, because a 19-row list is cheaper to respawn than to reconcile.
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl AddonsPanel {
|
||||
/// Open for `identity`, reading the folder fresh. The reference's `AddonList_OnShow` also
|
||||
/// re-reads (`AddonList_Update`), so a folder that changed under us is picked up.
|
||||
pub(super) fn open_for(&mut self, identity: Option<&(String, String)>) {
|
||||
self.list = addons::installed(identity);
|
||||
self.staged = self.list.iter().map(|a| a.enabled).collect();
|
||||
self.offset = 0;
|
||||
self.hovered = None;
|
||||
self.open = true;
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
pub(super) fn close(&mut self) {
|
||||
self.open = false;
|
||||
self.list.clear();
|
||||
self.staged.clear();
|
||||
self.hovered = None;
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
/// Are there any installed addons at all? The reference hides its button when not
|
||||
/// (`UpdateAddonButton`), and so do we — a button that opens an empty list is worse than none.
|
||||
///
|
||||
/// **Answered once per process, not per call.** The screen asks this while it is spawning its
|
||||
/// tree, and the tree respawns on every glue-scale change — which during a window drag-resize
|
||||
/// is *every frame*. Reading the folder there means a `read_dir` plus a `.toc` parse per addon
|
||||
/// per frame for as long as the director holds the mouse down. Installing or removing an addon
|
||||
/// mid-session is not a thing that happens, so the cache never needs invalidating; the panel
|
||||
/// itself re-reads the folder on every open ([`Self::open_for`]), which is where freshness
|
||||
/// actually matters.
|
||||
pub(super) fn any_installed() -> bool {
|
||||
static ANY: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
|
||||
*ANY.get_or_init(|| !addons::installed(None).is_empty())
|
||||
}
|
||||
|
||||
/// The rows currently visible, as `(index, addon, enabled)`.
|
||||
fn visible(&self) -> impl Iterator<Item = (usize, &InstalledAddOn, bool)> {
|
||||
self.list
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(self.offset)
|
||||
.take(MAX_ROWS)
|
||||
.map(|(i, a)| (i, a, self.staged.get(i).copied().unwrap_or(a.enabled)))
|
||||
}
|
||||
|
||||
/// The highest first-row index that still fills the list.
|
||||
fn max_offset(&self) -> usize {
|
||||
self.list.len().saturating_sub(MAX_ROWS)
|
||||
}
|
||||
|
||||
/// **Why a row will not load, as the reference's `reason` token** — the string the status
|
||||
/// column shows and the colour rule keys on.
|
||||
///
|
||||
/// `DEP_DISABLED`/`DEP_MISSING` come from the dependency walk; `DISABLED` from the checkbox;
|
||||
/// `INCOMPATIBLE` from `## Interface`. Reported and never enforced for the last one (1191 §6),
|
||||
/// which is why an out-of-date addon still shows gold-if-enabled: the status column says so
|
||||
/// and the client still loads it, and those two facts must not contradict each other.
|
||||
fn reason(&self, i: usize) -> Option<&'static str> {
|
||||
let addon = self.list.get(i)?;
|
||||
if !self.staged.get(i).copied().unwrap_or(addon.enabled) {
|
||||
return Some("Disabled");
|
||||
}
|
||||
for dep in &addon.dependencies {
|
||||
match self
|
||||
.list
|
||||
.iter()
|
||||
.position(|a| a.name.eq_ignore_ascii_case(dep))
|
||||
{
|
||||
None => return Some("Dependency missing"),
|
||||
Some(j) if !self.staged.get(j).copied().unwrap_or(true) => {
|
||||
return Some("Dependency disabled")
|
||||
}
|
||||
Some(_) => {}
|
||||
}
|
||||
}
|
||||
addon.out_of_date().then_some("Out of date")
|
||||
}
|
||||
}
|
||||
|
||||
/// The panel's clickable parts.
|
||||
#[derive(Component, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum AddonsAction {
|
||||
/// A list row — toggles its checkbox (`AddonList_Enable`).
|
||||
Row(usize),
|
||||
EnableAll,
|
||||
DisableAll,
|
||||
Okay,
|
||||
Cancel,
|
||||
ScrollUp,
|
||||
ScrollDown,
|
||||
}
|
||||
|
||||
/// The panel root (despawned whole on close).
|
||||
#[derive(Component)]
|
||||
struct AddonsUi;
|
||||
|
||||
/// Spawn/despawn the panel, run its flows, and repaint when anything it shows has changed.
|
||||
///
|
||||
/// Ordered before the select screen's own click handling so a click that lands on the panel is
|
||||
/// never also read as a click on the screen behind it.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn drive_addons_panel(
|
||||
mut commands: Commands,
|
||||
mut panel: ResMut<AddonsPanel>,
|
||||
art: Res<GlueArt>,
|
||||
assets: Res<AssetServer>,
|
||||
strings: Option<Res<GlueStrings>>,
|
||||
identity: Res<crate::ui_script::AddOnIdentity>,
|
||||
roster: Res<super::Roster>,
|
||||
keys: Res<ButtonInput<KeyCode>>,
|
||||
mut wheel: MessageReader<bevy::input::mouse::MouseWheel>,
|
||||
presses: Query<(&AddonsAction, &Interaction), Changed<Interaction>>,
|
||||
hovers: Query<(&AddonsAction, &Interaction)>,
|
||||
window: Query<&Window, With<bevy::window::PrimaryWindow>>,
|
||||
) {
|
||||
if !panel.open {
|
||||
if let Some(root) = panel.root.take() {
|
||||
commands.entity(root).despawn();
|
||||
}
|
||||
wheel.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// ── the flows ─────────────────────────────────────────────────────────────────────────────
|
||||
let mut close_and_save = false;
|
||||
let mut close_and_discard = false;
|
||||
for (action, interaction) in &presses {
|
||||
if *interaction != Interaction::Pressed {
|
||||
continue;
|
||||
}
|
||||
match *action {
|
||||
AddonsAction::Row(i) => {
|
||||
if let Some(slot) = panel.staged.get_mut(i) {
|
||||
*slot = !*slot;
|
||||
panel.dirty = true;
|
||||
}
|
||||
}
|
||||
AddonsAction::EnableAll => {
|
||||
panel.staged.iter_mut().for_each(|s| *s = true);
|
||||
panel.dirty = true;
|
||||
}
|
||||
AddonsAction::DisableAll => {
|
||||
panel.staged.iter_mut().for_each(|s| *s = false);
|
||||
panel.dirty = true;
|
||||
}
|
||||
AddonsAction::Okay => close_and_save = true,
|
||||
AddonsAction::Cancel => close_and_discard = true,
|
||||
AddonsAction::ScrollUp => scroll(&mut panel, -1),
|
||||
AddonsAction::ScrollDown => scroll(&mut panel, 1),
|
||||
}
|
||||
}
|
||||
// `AddonList_OnKeyDown`: ESCAPE cancels, ENTER accepts.
|
||||
if keys.just_pressed(KeyCode::Escape) {
|
||||
close_and_discard = true;
|
||||
}
|
||||
if keys.just_pressed(KeyCode::Enter) || keys.just_pressed(KeyCode::NumpadEnter) {
|
||||
close_and_save = true;
|
||||
}
|
||||
for ev in wheel.read() {
|
||||
if ev.y != 0.0 {
|
||||
scroll(&mut panel, if ev.y > 0.0 { -1 } else { 1 });
|
||||
}
|
||||
}
|
||||
|
||||
// The hovered row, for the tooltip — read every frame rather than on change, because a row
|
||||
// that scrolls out from under a stationary cursor changes what is hovered without an event.
|
||||
let hovered = hovers.iter().find_map(|(a, i)| match (a, i) {
|
||||
(AddonsAction::Row(r), Interaction::Hovered | Interaction::Pressed) => Some(*r),
|
||||
_ => None,
|
||||
});
|
||||
if panel.hovered != hovered {
|
||||
panel.hovered = hovered;
|
||||
panel.dirty = true;
|
||||
}
|
||||
|
||||
if close_and_save {
|
||||
let states: Vec<(String, bool)> = panel
|
||||
.list
|
||||
.iter()
|
||||
.zip(panel.staged.iter())
|
||||
.map(|(a, &on)| (a.name.clone(), on))
|
||||
.collect();
|
||||
// The identity the world-entry walk will read. `AddOnIdentity` is only filled once a
|
||||
// character has entered the world, so at first launch the roster's own selection is the
|
||||
// one that matters — the same resolution `load_ingame_ui_on_world_entry` performs.
|
||||
let who = identity
|
||||
.0
|
||||
.clone()
|
||||
.or_else(|| crate::ui_macro::identity(&roster));
|
||||
addons::write_enable_state(who.as_ref(), &states);
|
||||
panel.close();
|
||||
return;
|
||||
}
|
||||
if close_and_discard {
|
||||
panel.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// ── the tree ──────────────────────────────────────────────────────────────────────────────
|
||||
let s = crate::glue::screen_scale(window.single().ok());
|
||||
let stale = panel.root.is_some() && (panel.spawned_s != s || panel.dirty);
|
||||
if stale {
|
||||
if let Some(root) = panel.root.take() {
|
||||
commands.entity(root).despawn();
|
||||
}
|
||||
}
|
||||
if panel.root.is_none() {
|
||||
let empty = GlueStrings::default();
|
||||
let strings = strings.as_deref().unwrap_or(&empty);
|
||||
panel.root = Some(spawn_panel(
|
||||
&mut commands,
|
||||
&art,
|
||||
&assets,
|
||||
strings,
|
||||
&panel,
|
||||
s,
|
||||
));
|
||||
panel.spawned_s = s;
|
||||
panel.dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Move the first visible row by `delta`, clamped — the reference's `AddonList.offset`.
|
||||
fn scroll(panel: &mut AddonsPanel, delta: i32) {
|
||||
let max = panel.max_offset();
|
||||
let next = (panel.offset as i32 + delta).clamp(0, max as i32) as usize;
|
||||
if next != panel.offset {
|
||||
panel.offset = next;
|
||||
panel.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_panel(
|
||||
commands: &mut Commands,
|
||||
art: &GlueArt,
|
||||
assets: &AssetServer,
|
||||
strings: &GlueStrings,
|
||||
panel: &AddonsPanel,
|
||||
s: f32,
|
||||
) -> Entity {
|
||||
let px = |v: f32| Val::Px(v * s);
|
||||
let font = wow_font(assets);
|
||||
let title = strings.text("ADDONS", "AddOns");
|
||||
|
||||
commands
|
||||
.spawn((
|
||||
AddonsUi,
|
||||
GlobalZIndex(1200), // over the select screen's 1100, like the delete dialog
|
||||
Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
))
|
||||
.with_children(|overlay_ui| {
|
||||
let mut boxed = overlay_ui.spawn(Node {
|
||||
width: px(PANEL_W),
|
||||
height: px(PANEL_H),
|
||||
..default()
|
||||
});
|
||||
boxed.with_children(|b| {
|
||||
// The dialog backdrop, exactly as the delete dialog builds it.
|
||||
if let (Some(bg), Some(border)) = (&art.dialog_bg, &art.dialog_border) {
|
||||
b.spawn((
|
||||
tiled_bg_node(bg.clone(), 32.0, s, Color::WHITE),
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
left: px(11.0),
|
||||
right: px(12.0),
|
||||
top: px(12.0),
|
||||
bottom: px(11.0),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
backdrop_border(b, border, 32.0, Color::WHITE);
|
||||
} else {
|
||||
b.spawn((
|
||||
BackgroundColor(Color::srgba(0.05, 0.05, 0.08, 0.95)),
|
||||
overlay(),
|
||||
));
|
||||
}
|
||||
|
||||
// Title.
|
||||
outlined_text(
|
||||
b,
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
left: px(0.0),
|
||||
right: px(0.0),
|
||||
top: px(22.0),
|
||||
justify_content: JustifyContent::Center,
|
||||
..default()
|
||||
},
|
||||
(),
|
||||
(),
|
||||
GlueText {
|
||||
text: title,
|
||||
size: 18.0,
|
||||
color: GOLD,
|
||||
wrap: false,
|
||||
},
|
||||
&font,
|
||||
s,
|
||||
);
|
||||
|
||||
// The rows.
|
||||
for (slot, (index, addon, enabled)) in panel.visible().enumerate() {
|
||||
let top = LIST_TOP + slot as f32 * ROW_H;
|
||||
let reason = panel.reason(index);
|
||||
// AddonList.lua's three-colour rule: gold when it will load, red when the
|
||||
// player has it ON and it still will not, grey when they turned it off.
|
||||
let colour = match (enabled, reason) {
|
||||
(true, None) => GOLD,
|
||||
(true, Some("Out of date")) => GOLD,
|
||||
(true, _) => BROKEN,
|
||||
(false, _) => DIM,
|
||||
};
|
||||
let mut row = b.spawn((
|
||||
AddonsAction::Row(index),
|
||||
Button,
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
left: px(LIST_LEFT),
|
||||
top: px(top),
|
||||
width: px(PANEL_W - LIST_LEFT * 2.0),
|
||||
height: px(ROW_H),
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
row.with_children(|r| {
|
||||
// The checkbox. The glue art's own `UI-CheckBox-*` when we have it, a
|
||||
// drawn box when we do not — the same fallback every glue control takes.
|
||||
let check = &art.checkbox;
|
||||
let face = match (check.as_ref(), enabled) {
|
||||
(Some(c), true) => Some(c.checked.clone()),
|
||||
(Some(c), false) => Some(c.up.clone()),
|
||||
(None, _) => None,
|
||||
};
|
||||
let mut cb = r.spawn(Node {
|
||||
position_type: PositionType::Absolute,
|
||||
left: px(0.0),
|
||||
top: px((ROW_H - CHECK) / 2.0),
|
||||
width: px(CHECK),
|
||||
height: px(CHECK),
|
||||
..default()
|
||||
});
|
||||
match face {
|
||||
Some(image) => {
|
||||
cb.insert(ImageNode::new(image));
|
||||
}
|
||||
None => {
|
||||
cb.insert(BackgroundColor(if enabled { GOLD } else { DIM }));
|
||||
}
|
||||
}
|
||||
// Title.
|
||||
outlined_text(
|
||||
r,
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
left: px(CHECK + 8.0),
|
||||
top: px(0.0),
|
||||
..default()
|
||||
},
|
||||
(),
|
||||
(),
|
||||
GlueText {
|
||||
text: addon.display_title(),
|
||||
size: 12.0,
|
||||
color: colour,
|
||||
wrap: false,
|
||||
},
|
||||
&font,
|
||||
s,
|
||||
);
|
||||
// Status — the reference's `getglobal("ADDON_"..reason)` column.
|
||||
if let Some(reason) = reason {
|
||||
outlined_text(
|
||||
r,
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
right: px(0.0),
|
||||
top: px(0.0),
|
||||
..default()
|
||||
},
|
||||
(),
|
||||
(),
|
||||
GlueText {
|
||||
text: reason,
|
||||
size: 11.0,
|
||||
color: DIM,
|
||||
wrap: false,
|
||||
},
|
||||
&font,
|
||||
s,
|
||||
);
|
||||
}
|
||||
if let Some(hi) = &art.hilight {
|
||||
r.spawn((
|
||||
Hilight,
|
||||
Visibility::Hidden,
|
||||
MaterialNode(hi.clone()),
|
||||
overlay(),
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// The scroll pair, shown only when the list is longer than the window — the
|
||||
// reference's `GlueScrollFrame_Update` hides its bar on the same condition.
|
||||
if panel.max_offset() > 0 {
|
||||
for (action, caption, top) in [
|
||||
(AddonsAction::ScrollUp, "-", LIST_TOP),
|
||||
(
|
||||
AddonsAction::ScrollDown,
|
||||
"+",
|
||||
LIST_TOP + (MAX_ROWS as f32 - 1.0) * ROW_H,
|
||||
),
|
||||
] {
|
||||
b.spawn((
|
||||
action,
|
||||
Button,
|
||||
BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.10)),
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
right: px(6.0),
|
||||
top: px(top),
|
||||
width: px(14.0),
|
||||
height: px(14.0),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
))
|
||||
.with_children(|sb| {
|
||||
outlined_text(
|
||||
sb,
|
||||
Node::default(),
|
||||
(),
|
||||
(),
|
||||
GlueText {
|
||||
text: caption,
|
||||
size: 11.0,
|
||||
color: GOLD,
|
||||
wrap: false,
|
||||
},
|
||||
&font,
|
||||
s,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// The hover tooltip: title, notes, dependencies — `AddonTooltip_Update`'s three
|
||||
// lines, in its order.
|
||||
if let Some(i) = panel.hovered.and_then(|i| panel.list.get(i)) {
|
||||
let deps = if i.dependencies.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("Dependencies: {}", i.dependencies.join(", "))
|
||||
};
|
||||
let body = [
|
||||
i.notes.clone().unwrap_or_default(),
|
||||
deps,
|
||||
i.url.clone().unwrap_or_default(),
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|l| !l.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
if !body.is_empty() {
|
||||
outlined_text(
|
||||
b,
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
left: px(LIST_LEFT),
|
||||
right: px(LIST_LEFT),
|
||||
bottom: px(58.0),
|
||||
..default()
|
||||
},
|
||||
(),
|
||||
(),
|
||||
GlueText {
|
||||
text: &body,
|
||||
size: 11.0,
|
||||
color: Color::WHITE,
|
||||
wrap: true,
|
||||
},
|
||||
&font,
|
||||
s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The button row.
|
||||
b.spawn(Node {
|
||||
position_type: PositionType::Absolute,
|
||||
left: px(24.0),
|
||||
right: px(24.0),
|
||||
bottom: px(20.0),
|
||||
height: px(28.0),
|
||||
justify_content: JustifyContent::SpaceBetween,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
})
|
||||
.with_children(|row| {
|
||||
for (action, caption) in [
|
||||
(AddonsAction::EnableAll, "Enable All"),
|
||||
(AddonsAction::DisableAll, "Disable All"),
|
||||
(AddonsAction::Cancel, "Cancel"),
|
||||
(AddonsAction::Okay, "Okay"),
|
||||
] {
|
||||
glue_button(
|
||||
row,
|
||||
art,
|
||||
&font,
|
||||
action,
|
||||
caption,
|
||||
118.0,
|
||||
28.0,
|
||||
GlueBtnKind::Small,
|
||||
s,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.id()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn addon(name: &str, deps: &[&str], iface: &[u32]) -> InstalledAddOn {
|
||||
InstalledAddOn {
|
||||
name: name.into(),
|
||||
title: None,
|
||||
notes: None,
|
||||
url: None,
|
||||
dependencies: deps.iter().map(|d| (*d).to_string()).collect(),
|
||||
interface: iface.to_vec(),
|
||||
load_on_demand: false,
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn panel(list: Vec<InstalledAddOn>) -> AddonsPanel {
|
||||
let staged = list.iter().map(|a| a.enabled).collect();
|
||||
AddonsPanel {
|
||||
open: true,
|
||||
list,
|
||||
staged,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// The status column's rule, which is also the title-colour rule — the reference's own
|
||||
/// `reason` token set, in its own precedence.
|
||||
///
|
||||
/// The ordering is the part worth pinning: a row the player turned OFF reads `Disabled` and
|
||||
/// nothing else, even when its dependency is also missing. The reference does the same
|
||||
/// (`GetAddOnInfo` reports `DISABLED` before the dependency walk runs), and the alternative
|
||||
/// tells a player to go fix a dependency for an addon they deliberately switched off.
|
||||
#[test]
|
||||
fn the_status_column_reports_the_references_reason_in_its_own_precedence() {
|
||||
let mut p = panel(vec![
|
||||
addon("Solo", &[], &[11200]),
|
||||
addon("Lib", &[], &[11200]),
|
||||
addon("Needs", &["Lib"], &[11200]),
|
||||
addon("Orphan", &["Nowhere"], &[11200]),
|
||||
addon("Old", &[], &[11100]),
|
||||
addon("Silent", &[], &[]),
|
||||
]);
|
||||
|
||||
assert_eq!(p.reason(0), None, "nothing wrong: no status, gold title");
|
||||
assert_eq!(p.reason(2), None, "its dependency is installed and enabled");
|
||||
assert_eq!(p.reason(3), Some("Dependency missing"));
|
||||
assert_eq!(
|
||||
p.reason(4),
|
||||
Some("Out of date"),
|
||||
"reported — 1191 §6 does not ENFORCE it, and the two must not contradict"
|
||||
);
|
||||
assert_eq!(
|
||||
p.reason(5),
|
||||
None,
|
||||
"a manifest with no `## Interface` is silent, not out of date"
|
||||
);
|
||||
|
||||
// Turning the library off makes its dependent unloadable, and the dependent says so.
|
||||
p.staged[1] = false;
|
||||
assert_eq!(p.reason(1), Some("Disabled"));
|
||||
assert_eq!(p.reason(2), Some("Dependency disabled"));
|
||||
|
||||
// ...and a row the player turned off reports only that, even with a broken dependency.
|
||||
p.staged[3] = false;
|
||||
assert_eq!(p.reason(3), Some("Disabled"));
|
||||
}
|
||||
|
||||
/// Okay/Cancel is a **staged** edit: nothing the panel does touches the enable file until
|
||||
/// Okay. `Cancel` is the reference's `ResetAddOns`, and `close` is what implements it.
|
||||
#[test]
|
||||
fn edits_are_staged_and_cancel_discards_them() {
|
||||
let mut p = panel(vec![addon("A", &[], &[11200]), addon("B", &[], &[11200])]);
|
||||
assert_eq!(p.staged, vec![true, true]);
|
||||
p.staged[0] = false;
|
||||
assert!(
|
||||
p.list[0].enabled,
|
||||
"the read-in list is never mutated — only `staged` is, which is what makes Cancel free"
|
||||
);
|
||||
p.close();
|
||||
assert!(!p.open);
|
||||
assert!(
|
||||
p.staged.is_empty(),
|
||||
"a cancelled edit leaves nothing behind"
|
||||
);
|
||||
}
|
||||
|
||||
/// Scrolling clamps at both ends and never moves a list that fits.
|
||||
#[test]
|
||||
fn the_offset_clamps_to_the_list() {
|
||||
let mut short = panel((0..5).map(|i| addon(&format!("A{i}"), &[], &[])).collect());
|
||||
assert_eq!(short.max_offset(), 0);
|
||||
scroll(&mut short, 1);
|
||||
assert_eq!(short.offset, 0, "a list that fits does not scroll");
|
||||
|
||||
let mut long = panel(
|
||||
(0..MAX_ROWS + 4)
|
||||
.map(|i| addon(&format!("A{i}"), &[], &[]))
|
||||
.collect(),
|
||||
);
|
||||
assert_eq!(long.max_offset(), 4);
|
||||
scroll(&mut long, 10);
|
||||
assert_eq!(long.offset, 4, "clamped at the bottom, not past it");
|
||||
assert_eq!(long.visible().count(), MAX_ROWS);
|
||||
scroll(&mut long, -100);
|
||||
assert_eq!(long.offset, 0);
|
||||
}
|
||||
}
|
||||
@@ -34,13 +34,15 @@ pub(super) fn select_input(
|
||||
mut roster: ResMut<Roster>,
|
||||
pick: Res<CharPick>,
|
||||
mut dialog: ResMut<DeleteDialog>,
|
||||
mut panel: ResMut<super::addons::AddonsPanel>,
|
||||
mut next: ResMut<NextState<ClientState>>,
|
||||
mut sounds: MessageWriter<GlueSound>,
|
||||
mut intent: ResMut<crate::login::LoginIntent>,
|
||||
time: Res<Time>,
|
||||
mut last_click: Local<Option<(usize, f32)>>,
|
||||
) {
|
||||
if dialog.open {
|
||||
// A modal owns the input while it is up — the delete confirm, or the AddOns list.
|
||||
if dialog.open || panel.open {
|
||||
return;
|
||||
}
|
||||
let now = time.elapsed_secs();
|
||||
@@ -76,6 +78,12 @@ pub(super) fn select_input(
|
||||
sounds.write(GlueSound("gsCharacterSelectionCreateNew"));
|
||||
next.set(ClientState::CharCreate);
|
||||
}
|
||||
SelectAction::Addons => {
|
||||
// The reference plays `gsCharacterSelectionOpen`-family click here; ours reuses
|
||||
// the create-screen open sound rather than inventing a name the client lacks.
|
||||
sounds.write(GlueSound("gsCharacterSelectionCreateNew"));
|
||||
panel.open_for(crate::ui_macro::identity(&roster).as_ref());
|
||||
}
|
||||
SelectAction::Back => back_to_login = true,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
//! [`screen`] (the authored layout), [`refresh`] (list/banner/booth-feed refresh), [`input`]
|
||||
//! (clicks, keys, rotation, the flows), [`dialog`] (the delete confirm).
|
||||
|
||||
mod addons;
|
||||
mod dialog;
|
||||
mod input;
|
||||
mod refresh;
|
||||
@@ -93,6 +94,7 @@ impl Plugin for CharSelectPlugin {
|
||||
publish_world_live.before(benilla_world::schedule::WorldStage::Net),
|
||||
)
|
||||
.init_resource::<dialog::DeleteDialog>()
|
||||
.init_resource::<addons::AddonsPanel>()
|
||||
.add_systems(OnEnter(ClientState::CharSelect), screen::enter_select)
|
||||
.add_systems(OnExit(ClientState::CharSelect), screen::exit_select)
|
||||
.add_systems(Update, (debug_glue_roundtrip, debug_logout_smoke))
|
||||
@@ -108,6 +110,9 @@ impl Plugin for CharSelectPlugin {
|
||||
input::rotate_model,
|
||||
debug_select_dialog,
|
||||
dialog::drive_delete_dialog,
|
||||
// Before the list refresh, and before `select_input` reads a click that
|
||||
// landed on the panel rather than the screen (decision 1196).
|
||||
addons::drive_addons_panel,
|
||||
refresh::refresh_list,
|
||||
refresh::refresh_banner_and_buttons,
|
||||
refresh::feed_glue_preview,
|
||||
|
||||
@@ -43,6 +43,8 @@ pub(super) enum SelectAction {
|
||||
CreateChar,
|
||||
/// Rendered disabled — realm choice is out of scope (decision 0465 §6).
|
||||
ChangeRealm,
|
||||
/// Open the AddOns list (decision 1197) — the reference's `CharacterSelectAddonsButton`.
|
||||
Addons,
|
||||
RotateLeft,
|
||||
RotateRight,
|
||||
}
|
||||
@@ -254,6 +256,34 @@ fn spawn_screen(
|
||||
|
||||
rotate_cluster(ui, art, &font, s);
|
||||
|
||||
// **AddOns** (decision 1197) — the reference's `CharacterSelectAddonsButton`, which 1191
|
||||
// §5 recorded as never built because there was no list behind it. There is now. Bottom
|
||||
// LEFT, out of the way of the Enter World / Back cluster; the reference sits it in the
|
||||
// same lower band. Shown only when something is installed, exactly as the reference's
|
||||
// `UpdateAddonButton` hides it on `GetNumAddOns() == 0`.
|
||||
if super::addons::AddonsPanel::any_installed() {
|
||||
ui.spawn((Node {
|
||||
position_type: PositionType::Absolute,
|
||||
left: px(30.0),
|
||||
bottom: px(25.0),
|
||||
flex_direction: FlexDirection::Row,
|
||||
..default()
|
||||
},))
|
||||
.with_children(|actions| {
|
||||
glue_button(
|
||||
actions,
|
||||
art,
|
||||
&font,
|
||||
SelectAction::Addons,
|
||||
strings.text("ADDONS", "AddOns"),
|
||||
140.0,
|
||||
35.0,
|
||||
GlueBtnKind::Small,
|
||||
s,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Back (100×35 at BOTTOMRIGHT (−30,25)) and Delete Character (165×35 at its LEFT).
|
||||
ui.spawn((Node {
|
||||
position_type: PositionType::Absolute,
|
||||
|
||||
@@ -198,6 +198,24 @@ impl Items {
|
||||
(!left.is_zero()).then_some(left.as_millis() as u64)
|
||||
}
|
||||
|
||||
/// The same deadline read **without** the tooltip's expired-is-absent collapse: `Some(0)` for a
|
||||
/// timer that has run out, `None` only when the slot never had one.
|
||||
///
|
||||
/// `GetWeaponEnchantInfo` needs the two apart where the tooltip does not. Its expiration return
|
||||
/// is `max(0, deadline − now)` from the client-local deadline array (`0x5d9d00`, subtract on
|
||||
/// read — VERIFIED wow-re `system/ui/scratch/weapon-enchant-info.md`), so an enchant whose
|
||||
/// timer has elapsed answers the NUMBER 0, and `BuffFrame_Enchant_OnUpdate` then draws "0 s"
|
||||
/// and pulses the icon. Collapsing that to nil would silently hide a expiring enchant's last
|
||||
/// state. The wire carries **seconds** and this returns **milliseconds**, which is the
|
||||
/// conversion `set_enchant_deadline` already performs.
|
||||
pub(crate) fn enchant_deadline_ms(&self, guid: u64, slot: u32) -> Option<u64> {
|
||||
let at = self.enchant_deadlines.get(&(guid, slot))?;
|
||||
Some(
|
||||
at.saturating_duration_since(std::time::Instant::now())
|
||||
.as_millis() as u64,
|
||||
)
|
||||
}
|
||||
|
||||
/// A tracked item object's merged descriptor fields.
|
||||
pub(crate) fn object(&self, guid: u64) -> Option<&ObjectFields> {
|
||||
self.objects.get(&guid)
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
// spent its whole diff clearing it out of. Dev builds still warn normally.
|
||||
#![cfg_attr(not(feature = "dev"), allow(dead_code))]
|
||||
|
||||
pub mod addon_harness;
|
||||
mod area;
|
||||
mod area_trigger;
|
||||
#[cfg(feature = "dev")]
|
||||
|
||||
@@ -157,6 +157,39 @@ pub(crate) fn saved_variables_path() -> Option<PathBuf> {
|
||||
home().map(|h| h.join("saved-variables.lua"))
|
||||
}
|
||||
|
||||
/// `benilla-config/addons/<Realm>-<Character>.txt` — the AddOn **enable state** (1188 phase 2).
|
||||
///
|
||||
/// Per character, like the reference: it writes `WTF/Account/<ACC>/<Realm>/<Char>/AddOns.txt` as
|
||||
/// the last step of its UI shutdown (`0x490bd0`'s tail, after the saved-variables files). The
|
||||
/// file's own format is the reference's too — one `<AddOnName>: enabled|disabled` per line,
|
||||
/// confirmed against a real 1.12 install rather than remembered. An addon absent from the file is
|
||||
/// enabled, which is what makes a freshly-dropped-in folder just work.
|
||||
pub(crate) fn addons_state_path(realm: &str, character: &str) -> Option<PathBuf> {
|
||||
let key = format!("{}-{}", file_token(realm), file_token(character));
|
||||
home().map(|h| h.join("addons").join(format!("{key}.txt")))
|
||||
}
|
||||
|
||||
/// `benilla-config/saved/` — **per-addon** saved variables, account scope (1188 phase 3): one
|
||||
/// `<Addon>.lua` per addon that declares `## SavedVariables`.
|
||||
///
|
||||
/// The reference's shape, one level flatter: it writes
|
||||
/// `WTF/Account/<ACC>/SavedVariables/<Addon>.lua`, and our whole state folder is already
|
||||
/// per-install, so the folder IS the account scope (0954, and 1128 reserved this exact path —
|
||||
/// spelled `benilla/saved/` before 1180 renamed the folder).
|
||||
///
|
||||
/// Distinct from [`saved_variables_path`], which is the *flat* channel our own FrameXML uses
|
||||
/// through `RegisterForSave`. The reference has both too, and they are not the same mechanism.
|
||||
pub(crate) fn addon_saved_account_dir() -> Option<PathBuf> {
|
||||
home().map(|h| h.join("saved"))
|
||||
}
|
||||
|
||||
/// `benilla-config/saved/<Realm>-<Character>/` — per-addon saved variables, character scope
|
||||
/// (`## SavedVariablesPerCharacter`). Loaded **after** the account file, so it wins.
|
||||
pub(crate) fn addon_saved_character_dir(realm: &str, character: &str) -> Option<PathBuf> {
|
||||
let key = format!("{}-{}", file_token(realm), file_token(character));
|
||||
home().map(|h| h.join("saved").join(key))
|
||||
}
|
||||
|
||||
/// `benilla-config/camera/<realm>-<character>.txt` — the third-person camera pose (decision 1131; the
|
||||
/// `<Char>/camera-settings.txt` analog, and character-scoped for the same reason it is there: a
|
||||
/// gnome rogue and a tauren warrior want different zooms). Two lines, the reference's own keys and
|
||||
|
||||
@@ -106,6 +106,20 @@ pub(crate) use swim::swim_enter_depth;
|
||||
/// [`crate::creature_anim::MovementState::stunned`] (decision 0880).
|
||||
pub(crate) const UNIT_FLAG_STUNNED: u32 = 0x0004_0000;
|
||||
|
||||
/// `UNIT_FLAG_IN_COMBAT` — the same `UNIT_FIELD_FLAGS` word, **bit 19** (vmangos
|
||||
/// `UnitDefines.h`; the client reads it as `shr reg,0x13; test rl,1`).
|
||||
///
|
||||
/// Its readers are deliberately unrelated to each other — the spell-usability walk's leg 8, the
|
||||
/// probe preflight banner, and `UnitAffectingCombat`'s snapshot field — which is exactly why the
|
||||
/// constant lives here beside its neighbour rather than being declared a fourth time: three
|
||||
/// private copies of one bit is how a mask silently drifts.
|
||||
///
|
||||
/// **There is no player-specific combat latch to pair it with.** wow-re censused the
|
||||
/// `shr reg,0x13` + `test rl,1` idiom image-wide (7 hits, 6 on this field+bit) and found the two
|
||||
/// hardcoded *local-player* readers going through this same flag; `UnitAffectingCombat("player")`
|
||||
/// takes a GUID fast path and lands on the identical word.
|
||||
pub(crate) const UNIT_FLAG_IN_COMBAT: u32 = 0x0008_0000;
|
||||
|
||||
/// Ask for a **stand state** — the client's `SetStandState(newState)` (`0x5ed430`: send
|
||||
/// `CMSG_STANDSTATECHANGE` + apply locally through `0x6127b0`), as a message so every path that
|
||||
/// wants a posture funnels through the ONE setter in [`control`] (the [`crate::creature_anim::
|
||||
|
||||
@@ -29,6 +29,13 @@ use benilla_world::view::CAM_NEAR;
|
||||
/// || (elapsed < 800ms && yaw_travel < 2.25° && pitch_travel < 2.0°)
|
||||
/// ```
|
||||
///
|
||||
/// **The `ms` on those two numbers is now VERIFIED, where 1122 could only infer it** (wow-re
|
||||
/// `ui/scratch/button-doubleclick-law.md`, 2026-08-11, a side effect of the `OnDoubleClick` §5):
|
||||
/// the clock both constants are compared against is `0x42c010` → `0x42b790`, whose counter is the
|
||||
/// `KERNEL32!GetTickCount` import at `[0x7ff310]` and whose scale is stored as `1.0/freq × 1000.0`
|
||||
/// — milliseconds in either counter mode. Nothing here changes; the units simply stopped being a
|
||||
/// guess.
|
||||
///
|
||||
/// **A press under 200 world selects however far the mouse swept.** That arm is the entire bug report
|
||||
/// (ledger B226): flick the cursor across a mob and click on arrival with the hand still moving, and
|
||||
/// the reference targets it — the camera has been orbiting since the first motion sample and selects
|
||||
|
||||
@@ -74,9 +74,10 @@ const MOVE_BLOCKERS: &[(u32, &str)] = &[
|
||||
(0x0100_0000, "POSSESSED (another unit holds the reins)"),
|
||||
];
|
||||
|
||||
/// `UNIT_FLAG_IN_COMBAT` — worth naming on entry: an unattended probe that logs in already fighting
|
||||
/// is the exact shape of the accident the unattended-combat ban exists for (method.md).
|
||||
const UNIT_FLAG_IN_COMBAT: u32 = 0x0008_0000;
|
||||
/// Worth naming on entry: an unattended probe that logs in already fighting is the exact shape of
|
||||
/// the accident the unattended-combat ban exists for (method.md). The bit itself is declared once
|
||||
/// ([`crate::player`]).
|
||||
use crate::player::UNIT_FLAG_IN_COMBAT;
|
||||
|
||||
/// `UNIT_FLAG_SILENCED` — casts silently refuse.
|
||||
const UNIT_FLAG_SILENCED: u32 = 0x0000_2000;
|
||||
|
||||
@@ -420,6 +420,48 @@ pub(super) fn target_by_name_requests(
|
||||
}
|
||||
}
|
||||
|
||||
/// Drain the **Lua** `TargetByName(name, exactMatch)` asks — the binding half of the same
|
||||
/// resolver [`target_by_name_requests`] runs for `/target`.
|
||||
///
|
||||
/// A separate system rather than a second writer of [`TargetByNameRequest`] because the binding
|
||||
/// carries a second argument the slash command has no way to supply: `0x489d8e` fetches Lua arg #2
|
||||
/// with default 0 and hands it to `0x493aa0` as the **exact-only** flag (`ctx+0x0c`, consumed at
|
||||
/// `0x493cab`). Shipped FrameXML never passes it — its own usage string documents one argument —
|
||||
/// so a stock `/target` and a stock addon call both run with prefix matching live; an addon that
|
||||
/// already knows the exact name can turn tier 2 off, exactly as `UnitPopup.lua` does for
|
||||
/// `FollowByName(name, 1)`.
|
||||
///
|
||||
/// Everything else is identical to the slash path, deliberately: typemask 8 (creatures *and*
|
||||
/// players), filter mode 0 (accept unconditionally — no range, cone, dead, reaction or
|
||||
/// self-exclusion test), commit through [`SelectCommit`], and a miss that leaves the current
|
||||
/// target untouched and says nothing (the reference's two game-message ids are `0x127`/`0xb8`,
|
||||
/// whose strings are runtime-populated BSS and are not statically recoverable — the known
|
||||
/// deviation this path already carries).
|
||||
pub(super) fn script_target_by_name_requests(
|
||||
script: Option<NonSendMut<benilla_ui::script::UiScript>>,
|
||||
scan_params: ByNameScan,
|
||||
mut commit: SelectCommit,
|
||||
) {
|
||||
let Some(mut script) = script else {
|
||||
return;
|
||||
};
|
||||
for (name, exact) in script.take_target_by_name_requests() {
|
||||
let Some((entity, guid, _)) = scan_params.resolve(
|
||||
&name,
|
||||
NameSearch::AnyUnit,
|
||||
Filter::AcceptAll,
|
||||
if exact {
|
||||
Match::ExactOnly
|
||||
} else {
|
||||
Match::PrefixOk
|
||||
},
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
commit.commit(entity, guid);
|
||||
}
|
||||
}
|
||||
|
||||
/// Drain `/assist [name]`: find the **basis** unit, read its `UNIT_FIELD_TARGET` (+0x28 off the unit
|
||||
/// block — wow-re proved the offset arithmetic against HEALTH and DYNAMIC_FLAGS on the same line),
|
||||
/// and select whatever it is pointing at.
|
||||
|
||||
@@ -335,6 +335,10 @@ impl Plugin for TargetPlugin {
|
||||
// touching `Selection` at all.
|
||||
(
|
||||
by_name::target_by_name_requests,
|
||||
// The Lua binding's own by-name asks (11 corpus addons call
|
||||
// `TargetByName`), beside the chat layer's — same resolver, same commit,
|
||||
// one extra argument the slash command cannot supply.
|
||||
by_name::script_target_by_name_requests,
|
||||
by_name::assist_requests,
|
||||
by_name::follow_requests,
|
||||
)
|
||||
|
||||
@@ -32,8 +32,8 @@ use crate::target::{can_attack, ring_reaction, Factions};
|
||||
|
||||
use super::Spells;
|
||||
|
||||
/// `UNIT_FLAG_IN_COMBAT` (vmangos `UnitDefines.h`, bit 19) — leg 8's caster unit-flag test.
|
||||
const UNIT_FLAG_IN_COMBAT: u32 = 0x0008_0000;
|
||||
/// Leg 8's caster unit-flag test — the shared bit, declared once ([`crate::player`]).
|
||||
use crate::player::UNIT_FLAG_IN_COMBAT;
|
||||
|
||||
/// The implicit-target enums leg 10b forks on (`0x6e3f8a`/`0x6e3fa2`): 6 = single enemy →
|
||||
/// `CanAttack 0x606980`, 21 = single friend → `CanAssist 0x6066f0`.
|
||||
|
||||
@@ -333,6 +333,13 @@ fn other_unit_auras(
|
||||
expiration_time: 0.0,
|
||||
helpful: a.slot < UNIT_AURA_POSITIVE_SLOTS,
|
||||
cancelable: a.flags & AURA_FLAG_CANCELABLE != 0,
|
||||
// `untilCancelled` is a field of the PLAYER cache record (`0xbc6040`+0xc) and
|
||||
// `GetPlayerBuff` is the only reader; no other unit has a cache, and no binding can
|
||||
// reach this flag through a non-player token. A self-target does not come through
|
||||
// here at all — it mirrors the player list (decision 0257 §2).
|
||||
until_cancelled: false,
|
||||
channeled: display
|
||||
.is_some_and(|d| d.attributes_ex & SPELL_ATTR_EX_IS_CHANNELED != 0),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
@@ -382,6 +389,59 @@ fn buffs_visible_on(
|
||||
/// `UNIT_FLAG_AURAS_VISIBLE` — see [`buffs_visible_on`].
|
||||
const UNIT_FLAG_AURAS_VISIBLE: u32 = 0x0800_0000;
|
||||
|
||||
/// `Spell.dbc` `AttributesEx & 0x4` — `SPELL_ATTR_EX_IS_CHANNELED` (vmangos `SpellDefines.h`), the
|
||||
/// DBC arm of the cancel gate (`benilla_ui::script::aura`'s `cancel_authorized`, `0x4e4a10`).
|
||||
const SPELL_ATTR_EX_IS_CHANNELED: u32 = 0x4;
|
||||
|
||||
/// The cache record's **`untilCancelled`** flag — `GetPlayerBuff`'s second return, `+0xc` of a
|
||||
/// `0xbc6040` record. "This aura has no finite duration to display."
|
||||
///
|
||||
/// Byte-verified against `BuildBuffRecord 0x4e44b0`'s tail (`0x4e452e`-`0x4e45c5`), which is two
|
||||
/// clauses ORed in sequence:
|
||||
///
|
||||
/// 1. **The DBC duration row.** `SpellRec+0x78` (`DurationIndex`) resolves a `SpellDuration.dbc` row;
|
||||
/// the flag is set when the row says *infinite* — `Duration < 0 && DurationPerLevel <= 0`
|
||||
/// (`0x4e456e`-`0x4e457a`) — or when the index resolves to no row at all (`jl`/`jg`/NULL all fall
|
||||
/// into the same `mov ecx,1` at `0x4e4580`).
|
||||
/// 2. **The area-aura override.** Only for a *non*-cancelable aura (`test [edi+0xa],0x1; jne` at
|
||||
/// `0x4e4588` returns early otherwise): if any of the spell's three effects is one of
|
||||
/// `{0x23, 0x77, 0x80, 0x81}` — the persistent/area-aura effects, which have no per-application
|
||||
/// timer because they last while you stand in them — the flag is **forced** to 1
|
||||
/// (`0x4e458e`-`0x4e45be`).
|
||||
///
|
||||
/// This closes the gap this module's header named ("the reference avoids this via a DBC 'until
|
||||
/// cancelled' flag we don't parse"). It matters because it is answerable from the *spell*, on the
|
||||
/// frame the aura appears: `ref-BuffFrame.lua:124` returns before ever calling
|
||||
/// `GetPlayerBuffTimeLeft` when it reads 1, so a permanent aura never flashes a `0 s` countdown
|
||||
/// while waiting for a duration packet that is never coming.
|
||||
///
|
||||
/// **The one inference.** When the catalog cannot resolve the spell at all, the reference leaves
|
||||
/// `+0xc` holding whatever the record's previous occupant left there (its early-outs at `0x4e4533`/
|
||||
/// `0x4e4550` skip the write) — stale memory, not a mechanism. We answer from the only evidence we
|
||||
/// still have: no expiry stamp joined ⇒ nothing finite to display. A catalog miss already costs the
|
||||
/// aura its icon and name, so this only decides whether a nameless icon also shows `0 s`.
|
||||
fn until_cancelled(
|
||||
display: Option<&benilla_formats::SpellDisplay>,
|
||||
duration_row: Option<&benilla_formats::SpellDuration>,
|
||||
cancelable: bool,
|
||||
expiration_time: f64,
|
||||
) -> bool {
|
||||
let Some(d) = display else {
|
||||
return expiration_time == 0.0;
|
||||
};
|
||||
let permanent = match duration_row {
|
||||
Some(row) => row.base_ms < 0 && row.per_level_ms <= 0,
|
||||
// No row — a `DurationIndex` that is negative, out of range, or resolves to nothing. The
|
||||
// reference funnels all three into the same `mov ecx,1` at `0x4e4580`.
|
||||
None => true,
|
||||
};
|
||||
permanent
|
||||
|| (!cancelable
|
||||
&& d.effects
|
||||
.iter()
|
||||
.any(|e| matches!(e, 0x23 | 0x77 | 0x80 | 0x81)))
|
||||
}
|
||||
|
||||
/// The reference's aura display filter (decisions 0268 + 0417): an aura is shown iff its spell is
|
||||
/// *not* flagged never-display (`SPELL_ATTR_DO_NOT_DISPLAY` / `SPELL_ATTR_EX_NO_AURA_ICON`, via
|
||||
/// `SpellDisplay::hidden_from_aura_bar`) **and** is not a tracking spell
|
||||
@@ -480,6 +540,9 @@ fn feed_auras(
|
||||
|
||||
let bevy_now = time.elapsed_secs_f64();
|
||||
let catalog = spells.as_ref().map(|s| &s.catalog);
|
||||
// `SpellDuration.dbc`, for the cache record's `untilCancelled` flag ([`until_cancelled`]) — the
|
||||
// same catalog the `$d` tooltip token resolves against, so nothing new is loaded for this.
|
||||
let spell_durations = spells.as_ref().map(|s| &s.durations);
|
||||
let occupied: Vec<UnitAuraSlot> = store.0.unit_auras().collect();
|
||||
// The reference's DISPLAY FILTER (byte-verified sites `0x4e42b6`–`0x4e42c8`; decisions
|
||||
// 0268 + 0385, and 0417 for the target frame): a slot whose spell carries
|
||||
@@ -531,6 +594,14 @@ fn feed_auras(
|
||||
expiration_time,
|
||||
helpful: c.slot < UNIT_AURA_POSITIVE_SLOTS,
|
||||
cancelable: c.flags & AURA_FLAG_CANCELABLE != 0,
|
||||
until_cancelled: until_cancelled(
|
||||
display,
|
||||
display.and_then(|d| spell_durations.and_then(|c| c.get(d.duration_index))),
|
||||
c.flags & AURA_FLAG_CANCELABLE != 0,
|
||||
expiration_time,
|
||||
),
|
||||
channeled: display
|
||||
.is_some_and(|d| d.attributes_ex & SPELL_ATTR_EX_IS_CHANNELED != 0),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@@ -678,8 +749,9 @@ fn feed_auras(
|
||||
if changed {
|
||||
script.fire_event("UNIT_AURA", vec![ScriptValue::Str("player".into())]);
|
||||
// The reference's own event for the same rebuild — PLAYER_AURAS_CHANGED, no args — which
|
||||
// the verbatim-transcribed 1.12 frames register (MiniMapTrackingFrame). Fired beside the
|
||||
// Era-shaped UNIT_AURA the adapted BuffFrame listens on: one rebuild, both dialects.
|
||||
// the verbatim-transcribed 1.12 frames register: MiniMapTrackingFrame, the buff bar since
|
||||
// it came onto the `GetPlayerBuff` family, and every corpus aura addon. Fired beside the
|
||||
// Era-shaped UNIT_AURA the unit frames listen on: one rebuild, both dialects.
|
||||
script.fire_event("PLAYER_AURAS_CHANGED", vec![]);
|
||||
}
|
||||
if pet_changed {
|
||||
@@ -1166,4 +1238,74 @@ mod tests {
|
||||
"activePlayer.CHARMEDBY != 0 → no buffs on anyone"
|
||||
);
|
||||
}
|
||||
|
||||
/// [`until_cancelled`] — `GetPlayerBuff`'s second return, against the two clauses the reference
|
||||
/// derives it from (`0x4e452e`-`0x4e45c5`). Every case here is one the corpus reads as a
|
||||
/// **number** compared to `1` (`CT_BuffMod/CT_BuffFrame.lua:175,232` and three more), so a wrong
|
||||
/// answer shows or hides a countdown rather than erroring.
|
||||
#[test]
|
||||
fn until_cancelled_follows_the_duration_row_and_the_area_aura_override() {
|
||||
use benilla_formats::{SpellDisplay, SpellDuration};
|
||||
|
||||
let row = |base_ms: i32, per_level_ms: i32| SpellDuration {
|
||||
base_ms,
|
||||
per_level_ms,
|
||||
max_ms: base_ms,
|
||||
};
|
||||
// The shapes the shipped file actually holds (asserted on the real DBC in
|
||||
// `benilla_formats::spells::duration`'s tests): row 21 `{-1, 0}` = permanent, row 30
|
||||
// `{1_800_000, 0}` = Frost Armor's 30 minutes, row 427 `{-600_000, 60_000}` = the one that
|
||||
// makes this a TWO-field test, since a negative base alone is not permanence.
|
||||
let permanent_row = row(-1, 0);
|
||||
let timed_row = row(1_800_000, 0);
|
||||
let scaling_row = row(-600_000, 60_000);
|
||||
|
||||
let spell = |effects: [u32; 3]| SpellDisplay {
|
||||
effects,
|
||||
..SpellDisplay::default()
|
||||
};
|
||||
let ordinary = spell([6, 0, 0]);
|
||||
// 0x23 = the persistent-area-aura effect, one of the four in the override set.
|
||||
let area = spell([0x23, 0, 0]);
|
||||
|
||||
// Clause 1 — the DBC duration row decides, cancelable or not.
|
||||
assert!(!until_cancelled(
|
||||
Some(&ordinary),
|
||||
Some(&timed_row),
|
||||
true,
|
||||
0.0
|
||||
));
|
||||
assert!(!until_cancelled(
|
||||
Some(&ordinary),
|
||||
Some(&timed_row),
|
||||
false,
|
||||
0.0
|
||||
));
|
||||
assert!(until_cancelled(
|
||||
Some(&ordinary),
|
||||
Some(&permanent_row),
|
||||
true,
|
||||
0.0
|
||||
));
|
||||
assert!(
|
||||
!until_cancelled(Some(&ordinary), Some(&scaling_row), true, 0.0),
|
||||
"a negative base with a positive per-level term is a SCALING duration, not permanence"
|
||||
);
|
||||
// No row at all is the reference's `mov ecx,1` branch — permanent.
|
||||
assert!(until_cancelled(Some(&ordinary), None, true, 0.0));
|
||||
|
||||
// Clause 2 — the area-aura override applies ONLY to a non-cancelable aura (`test
|
||||
// [edi+0xa],0x1; jne` returns early at 0x4e4588). This asymmetry is the whole clause: the
|
||||
// same spell answers differently depending on the wire's cancelable nibble.
|
||||
assert!(until_cancelled(Some(&area), Some(&timed_row), false, 0.0));
|
||||
assert!(
|
||||
!until_cancelled(Some(&area), Some(&timed_row), true, 0.0),
|
||||
"a CANCELABLE aura skips the effect scan entirely"
|
||||
);
|
||||
|
||||
// The stated inference: the catalog cannot resolve the spell, so answer from the joined
|
||||
// expiry — the only evidence left.
|
||||
assert!(until_cancelled(None, None, false, 0.0));
|
||||
assert!(!until_cancelled(None, None, false, 1234.0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -838,4 +838,49 @@ fn feed_char(
|
||||
);
|
||||
feed.last_inv = Some(inv);
|
||||
}
|
||||
|
||||
// `GetWeaponEnchantInfo`'s whole data path (the buff bar's TemporaryEnchantFrame row, plus 8
|
||||
// corpus addons). Pushed EVERY frame and change-gated by nothing: the remaining time is a live
|
||||
// countdown, so riding the snapshot above would fire `UNIT_INVENTORY_CHANGED` on every tick.
|
||||
// The reference recomputes it per call for exactly the same reason (`0x5d9d00`).
|
||||
script.set_weapon_enchants(
|
||||
weapon_enchant(store, &items, EQUIPMENT_SLOT_MAINHAND),
|
||||
weapon_enchant(store, &items, EQUIPMENT_SLOT_OFFHAND),
|
||||
);
|
||||
}
|
||||
|
||||
/// The client's own 0-based `EQUIPMENT_SLOT_*` ids for the two weapon slots — the numbers
|
||||
/// `GetWeaponEnchantInfo 0x4c9790` hands its container lookup (`push 0xf` at `0x4c97c3`,
|
||||
/// `push 0x10` at `0x4c988b`). The live-API `GetInventorySlotInfo` ids the *frames* use are one
|
||||
/// higher (16 `MainHandSlot`, 17 `SecondaryHandSlot`).
|
||||
const EQUIPMENT_SLOT_MAINHAND: u8 = 15;
|
||||
const EQUIPMENT_SLOT_OFFHAND: u8 = 16;
|
||||
|
||||
/// `ITEM_FIELD_ENCHANTMENT`'s temporary slot (0 = permanent, 1 = temporary, 2..6 random property)
|
||||
/// — the slot index the reference passes to its remaining-time reader (`push 0x1`, `0x4c9828`).
|
||||
const TEMP_ENCHANTMENT_SLOT: u8 = 1;
|
||||
|
||||
/// One weapon's temporary enchantment for [`benilla_ui::script::WeaponEnchant`], read off the
|
||||
/// equipped item object.
|
||||
///
|
||||
/// **The RAW enchantment triple, not [`InvSlotView::enchants`]**: that view is tooltip-shaped and
|
||||
/// drops both an id the `SpellItemEnchantment` catalog cannot name and the whole `Flags & 0x2`
|
||||
/// print-no-line family (decision 0928 — the totem weapon imbues), which is exactly the set the
|
||||
/// enchant row exists to show. The binary's gate is `[descriptor+0x4c] != 0`, the id and nothing
|
||||
/// else, and `item_enchant` already answers `None` for a zero id.
|
||||
fn weapon_enchant(
|
||||
store: &ObjectStore,
|
||||
items: &Items,
|
||||
slot0: u8,
|
||||
) -> Option<benilla_ui::script::WeaponEnchant> {
|
||||
let guid = store.0.player_inv_slot(slot0)?;
|
||||
// Before the object borrow — the deadlines and the objects both live on `Items`. The
|
||||
// *deadline* read, not the tooltip's: an elapsed timer must answer 0, not "no timer".
|
||||
let remaining_ms = items.enchant_deadline_ms(guid, u32::from(TEMP_ENCHANTMENT_SLOT));
|
||||
let obj = items.object(guid)?;
|
||||
obj.item_enchant(TEMP_ENCHANTMENT_SLOT)?;
|
||||
Some(benilla_ui::script::WeaponEnchant {
|
||||
remaining_ms,
|
||||
charges: obj.item_enchant_charges(TEMP_ENCHANTMENT_SLOT),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ use bevy::prelude::*;
|
||||
|
||||
use crate::ui_script::UiInput;
|
||||
|
||||
#[cfg(test)]
|
||||
mod ace_gate_tests;
|
||||
mod channels;
|
||||
pub(crate) mod commands;
|
||||
mod edit;
|
||||
mod event;
|
||||
@@ -21,6 +24,11 @@ mod input;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Test-only: `ui_script::chat_tests` checks every name we fire against the live `ChatTypeInfo`
|
||||
/// table, which lives on that side of the tree. The app itself calls it through `event::` — the
|
||||
/// router is the only production caller and it is inside this module.
|
||||
#[cfg(test)]
|
||||
pub(crate) use event::event_name;
|
||||
pub(crate) use event::{default_color, ChatEvent, ChatEventKind};
|
||||
pub(crate) use feed::ChatLog;
|
||||
|
||||
@@ -32,6 +40,17 @@ impl Plugin for UiChatPlugin {
|
||||
.init_resource::<frames::ChatWindows>()
|
||||
.init_resource::<edit::ChatEditState>()
|
||||
.init_resource::<edit::ChannelState>()
|
||||
.init_resource::<channels::ZoneChannelWalk>()
|
||||
// `ChatChannels.dbc` — six rows, read once; the auto-join walk and every chat event's
|
||||
// arg7 both come out of it. **`.after(AssetSet::Open)` is load-bearing**: without it
|
||||
// this runs before the patch chain exists, takes its `assets: Option<Res<_>>` `None`
|
||||
// arm, and silently loads nothing — no zone channels, ever, with no error. A live
|
||||
// probe is what caught that; no unit test could, because the tests hand the catalog in.
|
||||
// `crate::area`'s `AreaTable.dbc` load carries the same ordering for the same reason.
|
||||
.add_systems(
|
||||
Startup,
|
||||
channels::load_chat_channels.after(benilla_assets::AssetSet::Open),
|
||||
)
|
||||
// The slash-command table (decision 0881), built from the reference's own alias strings
|
||||
// once the VM's globals and the emote catalog exist — both are `Startup`, so this runs
|
||||
// at the next schedule rather than chasing an ordering constraint into two modules.
|
||||
@@ -51,10 +70,21 @@ impl Plugin for UiChatPlugin {
|
||||
edit::chat_edit_live,
|
||||
edit::chat_tab_cycle,
|
||||
input::drain_chat_input,
|
||||
// An addon's own line into the wire (decision 1199). AFTER the box's drain
|
||||
// and in the same chain, so a `SendChatMessage` fired from a slash handler
|
||||
// that the box's drain just ran goes out on the same frame.
|
||||
input::drain_addon_chat_sends,
|
||||
)
|
||||
.chain()
|
||||
.after(UiInput)
|
||||
.run_if(in_state(crate::char_select::ClientState::InWorld)),
|
||||
)
|
||||
// The zone-channel auto-join (0288 P6): the client half of a handshake vmangos
|
||||
// deliberately leaves to us. In-world only, and it early-outs on an unchanged zone.
|
||||
.add_systems(
|
||||
Update,
|
||||
channels::auto_join_zone_channels
|
||||
.run_if(in_state(crate::char_select::ClientState::InWorld)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
//! **Ace2's initialisation gate, opened end to end** — the reason 0288 §1's chat-event fire and
|
||||
//! 0288 phase 6's zone auto-join were worth doing together.
|
||||
//!
|
||||
//! ## The failure this closes
|
||||
//!
|
||||
//! `AceEvent:IsFullyInitialized()` is called by 85 of the 218 addons in the vanilla corpus, and
|
||||
//! under benilla it answered **false forever** — with no Lua error anywhere, which is why it went
|
||||
//! unnoticed. The consequences are all silent: RosterLib never scans, BigWigs and oRA2 are
|
||||
//! complete no-ops, Jostle never repositions, AceComm refuses to join its channels, and FuBar's
|
||||
//! bar stays permanently blank across ~75 `FuBar_*` plugins.
|
||||
//!
|
||||
//! The gate is `AceEvent-2.0.lua:913-947`. On a fresh load `self.postInit` is false, and exactly
|
||||
//! three things can set it:
|
||||
//!
|
||||
//! | armed by | what it schedules |
|
||||
//! |---|---|
|
||||
//! | `CHAT_MSG_CHANNEL_NOTICE` | `func` after 0.05 s |
|
||||
//! | `MEETINGSTONE_CHANGED` | `func` after 1 s |
|
||||
//! | `LANGUAGE_LIST_CHANGED` → `MINIMAP_ZONE_CHANGED` | `func` after 1 s |
|
||||
//!
|
||||
//! `func` sets `postInit = true` and fires `AceEvent_FullyInitialized`. Of the three arming
|
||||
//! events, `CHAT_MSG_CHANNEL_NOTICE` is the one that can honestly fire at login on a 1.12 server —
|
||||
//! it is the server's answer to joining a channel. benilla fired **no** `CHAT_MSG_*` at all, and
|
||||
//! joined **no** channels, so none of the three ever arrived.
|
||||
//!
|
||||
//! ## What this file proves, and how
|
||||
//!
|
||||
//! Against the corpus's own Ace2 chain, loaded in FuBar's `.toc` order into a VM with our whole
|
||||
//! FrameXML under it — not a stub, because the thing under test is third-party library code and a
|
||||
//! stub would only ever prove that our stub works.
|
||||
//!
|
||||
//! **Nothing from the corpus is committed**, and the tests skip cleanly on a machine without it
|
||||
//! (see [`corpus`]) — a checkout with no addon folder must never go red here.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use benilla_ui::script::UiScript;
|
||||
|
||||
use super::event::{ChatEvent, ChatEventKind as K};
|
||||
use super::frames::{route, ChatWindows};
|
||||
|
||||
/// Where the vanilla addon corpus might be, in order. `$BENILLA_ADDON_CORPUS` first so a machine
|
||||
/// that keeps it elsewhere needs no patch; then the sibling checkout, resolved from this crate's
|
||||
/// manifest rather than the cwd (a pool worktree's cwd is not stable across tool calls, and
|
||||
/// `CARGO_MANIFEST_DIR` is).
|
||||
fn corpus_candidates() -> Vec<PathBuf> {
|
||||
let mut out = Vec::new();
|
||||
if let Some(over) = std::env::var_os("BENILLA_ADDON_CORPUS") {
|
||||
out.push(PathBuf::from(over));
|
||||
}
|
||||
// …/<checkout>/crates/benilla-app → up to the checkout, then to its parent(s). A pool slot
|
||||
// lives one level deeper than the primary checkout, so both hops are tried.
|
||||
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
for up in [2usize, 3, 4] {
|
||||
if let Some(root) = manifest.ancestors().nth(up) {
|
||||
out.push(root.join("wow-addons-vanilla"));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The corpus root, or `None`. **`None` is a skip, never a failure**: the corpus is third-party
|
||||
/// content that is deliberately not in this repo, so a machine without it must still be green.
|
||||
fn corpus() -> Option<PathBuf> {
|
||||
corpus_candidates().into_iter().find(|c| c.is_dir())
|
||||
}
|
||||
|
||||
macro_rules! corpus_or_skip {
|
||||
() => {
|
||||
match corpus() {
|
||||
Some(root) => root,
|
||||
None => {
|
||||
eprintln!(
|
||||
"skipping: no vanilla addon corpus — looked in {:?} (set $BENILLA_ADDON_CORPUS)",
|
||||
corpus_candidates()
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// FuBar's `.toc` load order for the four files the gate lives in (`FuBar/FuBar.toc:16-19`).
|
||||
/// AceEvent is last because it needs all three under it — AceLibrary to register into, Compost for
|
||||
/// its table recycling, AceOO for the class it derives from.
|
||||
const ACE_CHAIN: &[&str] = &[
|
||||
"FuBar/libs/AceLibrary/AceLibrary.lua",
|
||||
"FuBar/libs/Compost-2.0/Compost-2.0.lua",
|
||||
"FuBar/libs/AceOO-2.0/AceOO-2.0.lua",
|
||||
"FuBar/libs/AceEvent-2.0/AceEvent-2.0.lua",
|
||||
];
|
||||
|
||||
/// A VM with our whole FrameXML and the corpus's Ace2 chain in it, at the point a real session
|
||||
/// reaches `PLAYER_LOGIN` — which is where `AceEvent` starts waiting.
|
||||
fn ace_vm(root: &Path) -> UiScript {
|
||||
let mut script = UiScript::new().expect("VM");
|
||||
script.set_screen_size(1024.0, 768.0);
|
||||
// Our interface, exactly as the app loads it — Ace calls `CreateFrame`, `GetTime` and
|
||||
// `ChatTypeInfo` as readily as it calls anything of its own.
|
||||
let failures = crate::ui_script::load_default_ui(&script);
|
||||
assert!(
|
||||
failures.is_empty(),
|
||||
"our own FrameXML failed to load: {failures:#?}"
|
||||
);
|
||||
for rel in ACE_CHAIN {
|
||||
let path = root.join(rel);
|
||||
let src = benilla_ui::source::decode(&std::fs::read(&path).unwrap_or_else(|e| {
|
||||
panic!("{}: {e}", path.display());
|
||||
}))
|
||||
.into_owned();
|
||||
script.run(&src).unwrap_or_else(|e| panic!("{rel}: {e}"))
|
||||
}
|
||||
// The UI-init tail the app runs once every file has loaded (`ui_script::finish_ui_load`'s
|
||||
// second half): AceEvent registers PLAYER_LOGIN in `activate` and flips `playerLogin` on it.
|
||||
script.fire_event("PLAYER_LOGIN", vec![]);
|
||||
script
|
||||
}
|
||||
|
||||
/// Whether `AceEvent:IsFullyInitialized()` answers true.
|
||||
fn gate_open(s: &UiScript) -> bool {
|
||||
s.eval::<bool>("return AceLibrary('AceEvent-2.0'):IsFullyInitialized() and true or false")
|
||||
.expect("AceEvent-2.0 is registered")
|
||||
}
|
||||
|
||||
/// **The headline.** A zone-channel join's `YOU_JOINED` notice opens Ace2's gate.
|
||||
///
|
||||
/// The event goes in through the real router, so what is being tested is benilla's own chat path
|
||||
/// end to end — not a hand-rolled `fire_event`.
|
||||
#[test]
|
||||
fn a_you_joined_notice_opens_ace2s_initialisation_gate() {
|
||||
let root = corpus_or_skip!();
|
||||
let mut s = ace_vm(&root);
|
||||
let mut windows = ChatWindows::default();
|
||||
|
||||
// An addon's-eye view of the gate opening: AceEvent fires `AceEvent_FullyInitialized` at its
|
||||
// own registry, which is how every FuBar plugin learns it may start.
|
||||
// The library-level registration AceEvent documents for a bare function (`:87-91` — called on
|
||||
// AceEvent itself, `self` becomes the handler). `AceEvent_FullyInitialized` is in its
|
||||
// `eventsWhichHappenOnce` table (`:79-84`), so it is a once-only registration by construction:
|
||||
// the count below can only ever be 0 or 1, which is what makes 1 mean something.
|
||||
s.run(
|
||||
r#"
|
||||
AceGateFired = 0
|
||||
AceLibrary("AceEvent-2.0"):RegisterEvent("AceEvent_FullyInitialized", function()
|
||||
AceGateFired = AceGateFired + 1
|
||||
end)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
!gate_open(&s),
|
||||
"before any notice the gate is shut — that IS the bug this closes"
|
||||
);
|
||||
|
||||
// The notice our zone auto-join provokes: the server's YOU_JOINED for "General - <zone>".
|
||||
let mut joined = ChatEvent::text_only(K::ChannelNotice, String::new());
|
||||
joined.notice = "2".into(); // channel_notice::YOU_JOINED
|
||||
joined.channel = "General - Elwynn Forest".into();
|
||||
joined.channel_base = "General - Elwynn Forest".into();
|
||||
joined.channel_number = 1;
|
||||
joined.zone_channel_id = 1; // ChatChannels.dbc General
|
||||
route(&mut s, &mut windows, &joined);
|
||||
|
||||
// AceEvent does not open on the event itself — it schedules `func` 0.05 s out and lets its own
|
||||
// OnUpdate drive it (`AceEvent-2.0.lua:938`, `:471`). So the clock has to move.
|
||||
assert!(!gate_open(&s), "still shut inside the 0.05 s delay");
|
||||
s.tick(0.02);
|
||||
assert!(!gate_open(&s), "0.02 s is not 0.05 s");
|
||||
s.tick(0.05);
|
||||
|
||||
assert!(
|
||||
gate_open(&s),
|
||||
"AceEvent:IsFullyInitialized() must be true once the notice's delay elapses"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return AceGateFired").unwrap(),
|
||||
1,
|
||||
"AceEvent_FullyInitialized fired exactly once"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "Lua errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The control, and the reason the headline is not circular: **without the notice the gate stays
|
||||
/// shut**, no matter how much time passes. If this ever goes green, something else is arming the
|
||||
/// gate and the headline is measuring the wrong thing.
|
||||
#[test]
|
||||
fn without_the_notice_the_gate_stays_shut() {
|
||||
let root = corpus_or_skip!();
|
||||
let mut s = ace_vm(&root);
|
||||
for _ in 0..40 {
|
||||
s.tick(0.1); // four seconds — well past both the 0.05 s and the 1 s schedules
|
||||
}
|
||||
assert!(
|
||||
!gate_open(&s),
|
||||
"nothing but CHAT_MSG_CHANNEL_NOTICE can honestly arm this gate on a 1.12 server"
|
||||
);
|
||||
}
|
||||
|
||||
/// The other half: the walk that makes the notice happen at all. Composed against the corpus's
|
||||
/// own expectations is not possible here (the corpus does not know our zone), so this asserts the
|
||||
/// names the auto-join sends for a real zone and a real capital — the strings the server matches
|
||||
/// to `ChatChannels.dbc` rows, and therefore the strings that decide whether we are in the same
|
||||
/// channel as everyone else.
|
||||
#[test]
|
||||
fn the_auto_join_walk_names_the_channels_the_server_resolves() {
|
||||
let data = benilla_formats::wow_data_or_skip!();
|
||||
let mut chain = benilla_formats::open_chain(&data).expect("open chain");
|
||||
let cat = benilla_formats::load_chat_channels_catalog(&mut chain).expect("ChatChannels.dbc");
|
||||
|
||||
// The city word is DBC data, so read it the way the walk does rather than spelling it here —
|
||||
// that this resolves to "City" at all is the finding (`AreaTable.dbc` row 3459,
|
||||
// `Flags & 0x200`; wow-re `zone-chat-channel-autojoin.md` §3).
|
||||
let areas = benilla_formats::load_area_table_catalog(&mut chain).expect("AreaTable.dbc");
|
||||
let city = super::channels::city_word(&areas);
|
||||
assert_eq!(
|
||||
city,
|
||||
Some("City"),
|
||||
"the 0x200 sentinel row names the channel"
|
||||
);
|
||||
|
||||
let out_in_the_world = super::channels::wanted_channels(&cat, "Elwynn Forest", false, city);
|
||||
assert_eq!(
|
||||
out_in_the_world,
|
||||
vec!["General - Elwynn Forest", "LocalDefense - Elwynn Forest"]
|
||||
);
|
||||
let in_a_capital = super::channels::wanted_channels(&cat, "Stormwind City", true, city);
|
||||
assert_eq!(
|
||||
in_a_capital,
|
||||
vec![
|
||||
"General - Stormwind City",
|
||||
"Trade - City",
|
||||
"LocalDefense - Stormwind City",
|
||||
]
|
||||
);
|
||||
// Every one of them resolves back to a built-in row — i.e. vmangos will treat them as the
|
||||
// constant channels, not as custom ones (`GetChannelEntryFor`, DBCStores.cpp:531).
|
||||
for name in out_in_the_world.iter().chain(in_a_capital.iter()) {
|
||||
assert_ne!(
|
||||
cat.zone_channel_id(name),
|
||||
0,
|
||||
"{name} must resolve to a ChatChannels.dbc row"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
//! The zone-channel auto-join (decision 0288 phase 6's remainder) — the client half of a
|
||||
//! handshake the server deliberately does not perform.
|
||||
//!
|
||||
//! **Nobody joins these for you.** vmangos's `Player::UpdateLocalChannels`
|
||||
//! (`src/game/Objects/Player.cpp:5121`) is an empty function whose entire body is the comment
|
||||
//! `// Updated client-side`, so a client that never sends `CMSG_JOIN_CHANNEL` sits in no channel
|
||||
//! at all — silently, because nothing on the wire complains. That is what benilla did until now:
|
||||
//! `/join` worked, and General/Trade/LocalDefense simply never existed.
|
||||
//!
|
||||
//! The walk is [`ChatChannels.dbc`](benilla_formats::ChatChannelsCatalog)'s `INITIAL` rows
|
||||
//! composed against the player's current **zone** and sent as ordinary joins. It re-runs whenever
|
||||
//! the zone (or the in-a-capital answer) changes: the zone-dependent rows carry the zone name
|
||||
//! *inside* the channel name, so crossing a border is genuinely leaving one channel and joining
|
||||
//! another — LEAVE(old) then JOIN(new), interleaved per DBC row.
|
||||
//!
|
||||
//! The mechanism is byte-verified in wow-re's `system/ui/scratch/zone-chat-channel-autojoin.md`
|
||||
//! (the whole thing is one function, `ZoneChannelRefresh 0x49a210`); each part below cites the
|
||||
//! section it comes from.
|
||||
//!
|
||||
//! ## Why this is worth more than three channel names
|
||||
//!
|
||||
//! The server answers each join with `SMSG_CHANNEL_NOTIFY`/`YOU_JOINED`, which becomes a
|
||||
//! `CHAT_MSG_CHANNEL_NOTICE` at the Lua VM (0288 §1's addon-API phase) — and that one event is the
|
||||
//! condition for **Ace2's initialisation gate**. `AceEvent`'s `activate` registers
|
||||
//! `CHAT_MSG_CHANNEL_NOTICE` and, on the first one, schedules the function that sets
|
||||
//! `self.postInit = true` and fires `AceEvent_FullyInitialized`
|
||||
//! (`AceEvent-2.0.lua:913-947`). Until that happens `AceEvent:IsFullyInitialized()` answers false
|
||||
//! forever, with no error anywhere — and ~75 FuBar plugins, BigWigs, oRA2, RosterLib, Jostle and
|
||||
//! AceComm all wait on it. `ui_chat::ace_gate_tests` is that claim, proved against the corpus's
|
||||
//! own Ace2 chain.
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use benilla_assets::LockRecover;
|
||||
use benilla_formats::ChatChannelsCatalog;
|
||||
|
||||
use crate::area::AreaTableRes;
|
||||
use crate::net::{ClientCommand, NetCommands};
|
||||
|
||||
use super::edit::ChannelState;
|
||||
|
||||
/// `AreaTable.dbc` flag `0x08` — vmangos calls it `AREA_FLAG_SLAVE_CAPITAL`, with the comment
|
||||
/// *"Allow trade channel"* (`src/game/Database/DBCEnums.h:58`). In the shipped 5875 table exactly
|
||||
/// six rows carry it, all top-level zones and all with flags `0x138`: Undercity, Stormwind City,
|
||||
/// Ironforge, Orgrimmar, Thunder Bluff and Darnassus. (`0x100` = `AREA_FLAG_CAPITAL` selects the
|
||||
/// identical six in this build; `0x08` is the bit named for this job, so it is the one read.)
|
||||
const AREA_FLAG_TRADE_CHANNEL: u32 = 0x08;
|
||||
|
||||
/// `AreaTable.dbc` flag `0x200` — the sentinel marking the row whose name is the **shared city
|
||||
/// word** the Trade channel is named after.
|
||||
///
|
||||
/// Not a geographic area at all: the client scans the table once at load for this bit and keeps
|
||||
/// the row's name at `0xb4e4f0` (single writer `0x4985fd`), then splices it into `Trade - %s`
|
||||
/// (wow-re `system/ui/scratch/zone-chat-channel-autojoin.md` §3, VERIFIED). Exactly one row in the
|
||||
/// shipped 5875 table carries it — **id 3459, `AreaName[enUS] = "City"`** — which is the whole
|
||||
/// reason the word appears nowhere in `WoW.exe`. Reading it here rather than hardcoding `"City"`
|
||||
/// is what keeps a localized install localized.
|
||||
const AREA_FLAG_CITY_NAME_ROW: u32 = 0x200;
|
||||
|
||||
/// The shared city word, scanned out of `AreaTable.dbc` — the client's own load-time scan
|
||||
/// ([`AREA_FLAG_CITY_NAME_ROW`]). `None` when no row carries the sentinel, in which case the
|
||||
/// city-named rows are skipped rather than joined under a half-formed name.
|
||||
pub(crate) fn city_word(areas: &benilla_formats::AreaTableCatalog) -> Option<&str> {
|
||||
areas
|
||||
.rows()
|
||||
.find(|r| r.flags & AREA_FLAG_CITY_NAME_ROW != 0)
|
||||
.map(|r| r.name.as_str())
|
||||
.filter(|n| !n.is_empty())
|
||||
}
|
||||
|
||||
/// What the walk currently believes it holds, and the state it composed that from.
|
||||
///
|
||||
/// Deliberately **not** [`ChannelState::joined`], which is server truth fed by YOU_JOINED /
|
||||
/// YOU_LEFT notices. This is the *request* side: keying the diff off what we asked for is what
|
||||
/// stops a join the server refuses (wrong faction, banned) from being re-sent every frame.
|
||||
#[derive(Resource, Default)]
|
||||
pub(crate) struct ZoneChannelWalk {
|
||||
/// The composed names last requested, in `ChatChannels.dbc` row order.
|
||||
held: Vec<String>,
|
||||
/// The `(zone name, in a capital)` the held set was composed for. `None` = never walked, or
|
||||
/// the session ended — either way the next in-world frame re-walks from scratch.
|
||||
at: Option<(String, bool)>,
|
||||
}
|
||||
|
||||
impl ZoneChannelWalk {
|
||||
/// Forget everything: a dead socket took our channel membership with it, so the next login
|
||||
/// must re-join rather than assume.
|
||||
fn clear_session(&mut self) {
|
||||
self.held.clear();
|
||||
self.at = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Startup: read `ChatChannels.dbc` into [`ChannelState`], which owns it because the two things
|
||||
/// that need it — composing the auto-join names and answering a chat event's arg7 — are both its
|
||||
/// business. Absent install ⇒ an empty catalog: no auto-join, arg7 stays 0, nothing errors.
|
||||
pub(super) fn load_chat_channels(
|
||||
mut channels: ResMut<ChannelState>,
|
||||
assets: Option<Res<benilla_assets::WorldAssets>>,
|
||||
) {
|
||||
let Some(assets) = assets else { return };
|
||||
let loaded = {
|
||||
let mut chain = assets.chain.lock_recover();
|
||||
benilla_formats::load_chat_channels_catalog(&mut chain)
|
||||
};
|
||||
match loaded {
|
||||
Ok(cat) => {
|
||||
info!("chat: {} ChatChannels rows", cat.rows().len());
|
||||
channels.channels = cat;
|
||||
}
|
||||
Err(e) => warn!("chat: ChatChannels.dbc failed to load — no zone channels: {e:#}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// The names the auto-join rows compose to for a player standing in `zone_name`, in table order.
|
||||
///
|
||||
/// `in_city` gates the city-**only** rows (the `0x10` bit); `city_word` is what a city-**named**
|
||||
/// row (`0x20`) puts in its `%s`, and an absent one skips those rows rather than composing
|
||||
/// `"Trade - "`. The client has no such guard — its capital arm null-checks the DBC row pointer
|
||||
/// but not the string, so a locale with that row's name blank would compose exactly that
|
||||
/// (wow-re `zone-chat-channel-autojoin.md`, branch caveat). We decline instead: joining the wrong
|
||||
/// channel name is invisible and permanent, and joining none is neither.
|
||||
pub(crate) fn wanted_channels(
|
||||
catalog: &ChatChannelsCatalog,
|
||||
zone_name: &str,
|
||||
in_city: bool,
|
||||
city_word: Option<&str>,
|
||||
) -> Vec<String> {
|
||||
catalog
|
||||
.auto_join_rows()
|
||||
.filter(|r| in_city || !r.is_city_only())
|
||||
.filter(|r| !r.takes_city_name() || city_word.is_some())
|
||||
// A zone-dependent name with no zone to put in it would join a channel called
|
||||
// "General - " — join nothing rather than something wrong.
|
||||
.filter(|r| !r.is_zone_dependent() || !zone_name.is_empty())
|
||||
.map(|r| r.joinable_name(zone_name, city_word.unwrap_or_default()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The walk: re-join the zone channels whenever the zone changes, and drop the ones that no
|
||||
/// longer apply.
|
||||
///
|
||||
/// Reads the zone the same way [`crate::area`]'s splash does — the leaf `AreaTable` id under the
|
||||
/// player, walked to its top-level parent — but takes the **real** zone name rather than the
|
||||
/// display text. That is not a preference: the client composes from `GetRealZoneText`'s own cache
|
||||
/// (`0xb4b404`, written from the parent zone's `AreaName_lang`), and the indoor/WMO name override
|
||||
/// `0x67e670` rewrites only the slot feeding `GetZoneText`, never the one that becomes a channel
|
||||
/// name (wow-re `zone-chat-channel-autojoin.md` §3). So a building never renames your channel.
|
||||
///
|
||||
/// **Timing:** the client re-walks inside `UpdateZoneText 0x494780` on every zone change (`§1`,
|
||||
/// callsite `0x494931`), immediately *before* it fires `ZONE_CHANGED_NEW_AREA`, plus once at world
|
||||
/// entry. This polls the same two inputs each frame and early-outs when they have not moved, which
|
||||
/// reaches the same states; it is a poll rather than a hook because the zone is already derived
|
||||
/// here, not published as an event payload.
|
||||
///
|
||||
/// **Selection:** the client's live predicate is the *saved* `ZONECHANNELS` mask, not `flags & 1`
|
||||
/// read fresh — but that mask is **seeded from `flags & 1` when there is no usable chat-cache
|
||||
/// file** (`0x4997fc`, gated `0x4997e8`). We persist no chat cache, so every session is that
|
||||
/// fresh-character path and reading the DBC bit directly is exactly right. It stops being right
|
||||
/// the day per-window channel settings are persisted.
|
||||
pub(super) fn auto_join_zone_channels(
|
||||
commands: Res<NetCommands>,
|
||||
channels: Res<ChannelState>,
|
||||
areas: Option<Res<AreaTableRes>>,
|
||||
world: benilla_world::world_point::WorldPoint,
|
||||
mut walk: ResMut<ZoneChannelWalk>,
|
||||
mut disconnects: MessageReader<crate::net::DisconnectedMessage>,
|
||||
) {
|
||||
if disconnects.read().next().is_some() {
|
||||
walk.clear_session();
|
||||
}
|
||||
let (Some(areas), false) = (areas, channels.channels.is_empty()) else {
|
||||
return;
|
||||
};
|
||||
// The zone under the player, or nothing to do yet (tiles not streamed, no body).
|
||||
let Some(zone_row) = world
|
||||
.area()
|
||||
.and_then(|leaf| areas.0.top_zone(leaf))
|
||||
.and_then(|zone| areas.0.get(zone))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let at = (
|
||||
zone_row.name.clone(),
|
||||
zone_row.flags & AREA_FLAG_TRADE_CHANNEL != 0,
|
||||
);
|
||||
if walk.at.as_ref() == Some(&at) {
|
||||
return; // same zone as last frame — the common case, and free
|
||||
}
|
||||
|
||||
let wanted = wanted_channels(&channels.channels, &at.0, at.1, city_word(&areas.0));
|
||||
|
||||
// The diff is **per DBC row**, not per name — because a zone change does not add and remove
|
||||
// channels, it *renames* one: `General - Felwood` and `General - Winterspring` are the same
|
||||
// row (ChannelID 1) with a different `%s`. Keying on the row is what makes that a leave/join
|
||||
// pair instead of an unrelated drop and add.
|
||||
let row_of = |name: &str| channels.channels.zone_channel_id(name);
|
||||
let leave = |name: &str| {
|
||||
debug!("chat: leaving zone channel {name:?}");
|
||||
let _ = commands.0.send(ClientCommand::LeaveChannel {
|
||||
name: name.to_string(),
|
||||
});
|
||||
};
|
||||
let join = |name: &str| {
|
||||
debug!("chat: joining zone channel {name:?}");
|
||||
let _ = commands.0.send(ClientCommand::JoinChannel {
|
||||
name: name.to_string(),
|
||||
password: String::new(),
|
||||
});
|
||||
};
|
||||
|
||||
// LEAVE(old) immediately followed by JOIN(new), per row, interleaved — the client does both in
|
||||
// one iteration of its own walk (`0x49a367` sits earlier in the loop body than the join), and a
|
||||
// retail sniff shows the pairs adjacent on the wire in exactly that order: LEAVE
|
||||
// `General - Felwood` → JOIN `General - Winterspring` → LEAVE `LocalDefense - Felwood` → …
|
||||
// (wow-re `zone-chat-channel-autojoin.md` §5).
|
||||
for name in &wanted {
|
||||
match walk.held.iter().find(|h| row_of(h) == row_of(name)) {
|
||||
Some(old) if old == name => continue, // this row's name did not move
|
||||
Some(old) => leave(old),
|
||||
None => {}
|
||||
}
|
||||
join(name);
|
||||
}
|
||||
// Rows that stopped applying entirely — walking out of a capital drops Trade with nothing to
|
||||
// replace it.
|
||||
for old in walk
|
||||
.held
|
||||
.iter()
|
||||
.filter(|h| !wanted.iter().any(|w| row_of(w) == row_of(h)))
|
||||
{
|
||||
leave(old);
|
||||
}
|
||||
|
||||
walk.held = wanted;
|
||||
walk.at = Some(at);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use benilla_formats::ChatChannelRow;
|
||||
|
||||
/// The shipped table, hand-built so this runs without an install (the real rows are asserted
|
||||
/// against the DBC in `benilla_formats::chat_channels`).
|
||||
fn catalog() -> ChatChannelsCatalog {
|
||||
ChatChannelsCatalog::from_rows(
|
||||
[
|
||||
(1, 0x00003, "General - %s", "General"),
|
||||
(2, 0x0003B, "Trade - %s", "Trade"),
|
||||
(22, 0x10003, "LocalDefense - %s", "LocalDefense"),
|
||||
(23, 0x10004, "WorldDefense", "WorldDefense"),
|
||||
(24, 0x00000, "LookingForGroup", "LookingForGroup"),
|
||||
(25, 0x20032, "GuildRecruitment - %s", "GuildRecruitment"),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(id, flags, pattern, shortcut)| ChatChannelRow {
|
||||
id,
|
||||
flags,
|
||||
pattern: pattern.into(),
|
||||
shortcut: shortcut.into(),
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// The city word as the shipped `AreaTable.dbc` supplies it (row 3459, the `Flags & 0x200`
|
||||
/// sentinel — see [`city_word`]).
|
||||
const CITY: Option<&str> = Some("City");
|
||||
|
||||
/// Out in the world: General and LocalDefense, both zone-named. Trade is a city channel and
|
||||
/// stays out; WorldDefense, LookingForGroup and GuildRecruitment carry no INITIAL bit.
|
||||
#[test]
|
||||
fn an_ordinary_zone_joins_general_and_local_defense() {
|
||||
assert_eq!(
|
||||
wanted_channels(&catalog(), "Elwynn Forest", false, CITY),
|
||||
vec!["General - Elwynn Forest", "LocalDefense - Elwynn Forest"]
|
||||
);
|
||||
}
|
||||
|
||||
/// Inside a capital, Trade joins too — under the one shared name, not the city's own.
|
||||
#[test]
|
||||
fn a_capital_adds_the_shared_trade_channel() {
|
||||
assert_eq!(
|
||||
wanted_channels(&catalog(), "Stormwind City", true, CITY),
|
||||
vec![
|
||||
"General - Stormwind City",
|
||||
"Trade - City",
|
||||
"LocalDefense - Stormwind City",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// No zone name ⇒ no zone-dependent join. "General - " is a real channel on the server, and
|
||||
/// joining it would be a bug nothing else would ever report.
|
||||
#[test]
|
||||
fn an_unknown_zone_joins_nothing_zone_dependent() {
|
||||
assert!(wanted_channels(&catalog(), "", false, CITY).is_empty());
|
||||
assert!(wanted_channels(&catalog(), "", true, CITY).is_empty());
|
||||
}
|
||||
|
||||
/// No city word (a locale whose sentinel row ships blank) ⇒ the city-named rows are skipped,
|
||||
/// not composed as `"Trade - "`. The client has no such guard; declining is ours.
|
||||
#[test]
|
||||
fn a_missing_city_word_skips_the_trade_channel_rather_than_half_naming_it() {
|
||||
assert_eq!(
|
||||
wanted_channels(&catalog(), "Stormwind City", true, None),
|
||||
vec!["General - Stormwind City", "LocalDefense - Stormwind City"]
|
||||
);
|
||||
}
|
||||
|
||||
/// A zone change **renames** a row rather than adding one: both zones' General lines carry
|
||||
/// ChannelID 1, which is what makes the walk's diff a leave/join pair on one row instead of an
|
||||
/// unrelated drop and add.
|
||||
#[test]
|
||||
fn crossing_a_zone_border_renames_the_same_rows() {
|
||||
let cat = catalog();
|
||||
let felwood = wanted_channels(&cat, "Felwood", false, CITY);
|
||||
let winterspring = wanted_channels(&cat, "Winterspring", false, CITY);
|
||||
assert_ne!(felwood, winterspring, "the names differ");
|
||||
let ids = |v: &[String]| -> Vec<u32> { v.iter().map(|n| cat.zone_channel_id(n)).collect() };
|
||||
assert_eq!(
|
||||
ids(&felwood),
|
||||
ids(&winterspring),
|
||||
"…but they are the same DBC rows: [1, 22] either side of the border"
|
||||
);
|
||||
assert_eq!(ids(&felwood), vec![1, 22]);
|
||||
}
|
||||
|
||||
/// arg7: the composed names resolve back to their `ChatChannels.dbc` id, custom ones to 0 —
|
||||
/// matched by the server's own substring rule.
|
||||
#[test]
|
||||
fn composed_names_carry_their_channel_id_back() {
|
||||
let cat = catalog();
|
||||
assert_eq!(cat.zone_channel_id("General - Elwynn Forest"), 1);
|
||||
assert_eq!(cat.zone_channel_id("Trade - City"), 2);
|
||||
assert_eq!(cat.zone_channel_id("LocalDefense - Durotar"), 22);
|
||||
assert_eq!(cat.zone_channel_id("World"), 0);
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,32 @@ impl SendType {
|
||||
)
|
||||
}
|
||||
|
||||
/// The chat-type TOKEN an addon passes to `SendChatMessage` (decision 1199) — the reference's
|
||||
/// own `ChatTypeInfo` keys, uppercase.
|
||||
///
|
||||
/// `None` for a token we do not send. That is the honest answer for `"AFK"`/`"DND"` (which
|
||||
/// set a flag rather than sending a line) and for anything an addon simply made up; the
|
||||
/// caller reports it rather than guessing SAY, because a raid warning silently going to /say
|
||||
/// is worse than one that does not go.
|
||||
pub(crate) fn from_token(token: &str) -> Option<SendType> {
|
||||
Some(match token {
|
||||
"SAY" => SendType::Say,
|
||||
"YELL" => SendType::Yell,
|
||||
"EMOTE" => SendType::Emote,
|
||||
"WHISPER" => SendType::Whisper,
|
||||
"PARTY" => SendType::Party,
|
||||
"RAID" => SendType::Raid,
|
||||
"RAID_LEADER" => SendType::RaidLeader,
|
||||
"RAID_WARNING" => SendType::RaidWarning,
|
||||
"GUILD" => SendType::Guild,
|
||||
"OFFICER" => SendType::Officer,
|
||||
"BATTLEGROUND" => SendType::Battleground,
|
||||
"BATTLEGROUND_LEADER" => SendType::BattlegroundLeader,
|
||||
"CHANNEL" => SendType::Channel,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// The wire kind this type sends as.
|
||||
pub(crate) fn wire(self) -> ChatKind {
|
||||
match self {
|
||||
@@ -127,11 +153,19 @@ impl SendType {
|
||||
/// The channels this session has joined, in join order — the CLIENT-side number law
|
||||
/// (`GetChannelName(n)`): `/1` is the first joined, `/2` the second; the numbered display form
|
||||
/// ("1. General - Elwynn Forest") and the `[N. Name]` prefixes all derive from this order.
|
||||
/// Fed by YOU_JOINED / YOU_LEFT notices ([`super::feed`]); the zone AUTO-join walk is the P6
|
||||
/// remainder (flagged in 0288).
|
||||
/// Fed by YOU_JOINED / YOU_LEFT notices ([`super::feed`]); the zone AUTO-join walk that fills it
|
||||
/// at login is [`super::channels`].
|
||||
#[derive(Resource, Default)]
|
||||
pub(crate) struct ChannelState {
|
||||
pub joined: Vec<String>,
|
||||
/// `ChatChannels.dbc`, loaded once at Startup ([`super::channels::load_chat_channels`]).
|
||||
///
|
||||
/// It lives here because both of its consumers are this type's own business: composing the
|
||||
/// auto-join names, and answering a chat event's **arg7** — the built-in ChannelID behind a
|
||||
/// name, which is a pure function of the name (the server resolves it the same way) and so
|
||||
/// needs no extra bookkeeping at join time. Empty without an install, which degrades to
|
||||
/// "no zone channels, arg7 always 0" rather than to an error.
|
||||
pub channels: benilla_formats::ChatChannelsCatalog,
|
||||
}
|
||||
|
||||
impl ChannelState {
|
||||
@@ -142,6 +176,40 @@ impl ChannelState {
|
||||
.position(|c| c.eq_ignore_ascii_case(name))
|
||||
.map(|i| i as u32 + 1)
|
||||
}
|
||||
|
||||
/// Fill an event's four channel slots (arg4, arg7, arg8, arg9) in place.
|
||||
///
|
||||
/// **They are one record, not four fields.** In the reference all four are read off the
|
||||
/// client's local channel record — `slot+0x00`, `+0x04`, `+0x94`, `+0x98` — so a name that is
|
||||
/// *not* in the local list has no record to read and every one of them is empty: arg4 falls
|
||||
/// back to the bare incoming name and arg7/arg8/arg9/arg10 are `0/0/""/0` together. They are
|
||||
/// never independently populated. (wow-re `system/ui/scratch/chat-msg-event-args.md` §§4, 7-10,
|
||||
/// VERIFIED; the `"%d. %s"` prefix at `0x8445c8` is applied on the hit leg `0x49aa48`, and
|
||||
/// `0x49aa86` is the bare-name miss leg.)
|
||||
///
|
||||
/// So: on entry `event.channel` holds the name as the wire gave it ("General - Elwynn Forest").
|
||||
/// If we are in that channel, on exit arg4 is the numbered display form, arg9 the stored name
|
||||
/// **with its " - Zone" tail intact** (§9: the DBC name column *is* the format string the
|
||||
/// client built the stored name with), arg8 the 1-based local slot and arg7 the
|
||||
/// `ChatChannels.dbc` ChannelID — 0 for a custom channel. If we are not, nothing is stamped.
|
||||
///
|
||||
/// arg7 is resolved from the name against `ChatChannels.dbc` rather than remembered per join.
|
||||
/// That is safe *because* it only ever runs on the hit leg: the id the client stores in
|
||||
/// `slot+0x94` came from the same DBC row at join time, and vmangos resolves the name the same
|
||||
/// way (`GetChannelEntryFor`), so no two of the three can disagree.
|
||||
pub(crate) fn stamp_channel(&self, event: &mut super::event::ChatEvent) {
|
||||
// A miss leaves all four alone — see the "one record" note above.
|
||||
let Some(n) = self
|
||||
.number_of(&event.channel)
|
||||
.filter(|_| !event.channel.is_empty())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
event.channel_base = event.channel.clone();
|
||||
event.zone_channel_id = self.channels.zone_channel_id(&event.channel_base);
|
||||
event.channel_number = n;
|
||||
event.channel = format!("{n}. {}", event.channel_base);
|
||||
}
|
||||
}
|
||||
|
||||
/// The chat edit box's cross-open state (the fields `ChatEdit_OnLoad` seeds on the box).
|
||||
|
||||
@@ -11,6 +11,16 @@
|
||||
//! the group tables are `ChatTypeGroup` transcribed; the colors are the complete shipped default
|
||||
//! table (the ref client's own `chat-cache.txt` COLORS block ≡ wow-re's byte-verified
|
||||
//! `.rdata 0x804710` table, double-sourced in 0288's pin).
|
||||
//!
|
||||
//! This module also carries the **Lua face** of that currency: [`event_name`]
|
||||
//! (kind → the reference's `CHAT_MSG_*` event name) and [`ChatEvent::script_args`] (the ten
|
||||
//! positional args the client's own fire helper passes). 0288 §1 left that door open in its own
|
||||
//! words — *"a future 0068 addon-API phase can move it into the VM (fire CHAT_MSG_* events at Lua)
|
||||
//! without touching sources or sinks"* — and this is that phase: the router
|
||||
//! ([`super::frames::route`]) now fires the real event beside the Rust render, so an addon sees the
|
||||
//! same chat the window does.
|
||||
|
||||
use benilla_ui::script::ScriptValue;
|
||||
|
||||
/// The renderable chat-event kinds — `ChatTypeInfo`'s keys, minus the combat-log block.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
@@ -55,13 +65,120 @@ pub(crate) enum ChatEventKind {
|
||||
BgSystemHorde,
|
||||
}
|
||||
|
||||
/// One chat event — the reference's `CHAT_MSG_*` fire, typed. Field ↔ arg mapping (the 1.12
|
||||
/// handler's positional args, constrained by `ChatFrame_OnEvent`'s reads):
|
||||
/// `text`=arg1 · `sender`=arg2 · `language`=arg3 (already a *name*, empty = no header) ·
|
||||
/// `channel`=arg4 (the display form, "N. Name - Zone" when numbered) · `target`=arg5 (the second
|
||||
/// name of a two-name channel notice) · `flag`=arg6 ("AFK"/"DND"/"GM", empty none) ·
|
||||
/// `channel_number`=arg8 · `channel_base`=arg9 (the number-less name). `notice`=arg1 of the
|
||||
/// CHANNEL_NOTICE family (the token selecting the `CHAT_<X>_NOTICE` string).
|
||||
impl ChatEventKind {
|
||||
/// Every kind, for the sweeps that must be exhaustive to be worth anything — chiefly
|
||||
/// `ui_script::chat_tests::fired_event_names_are_all_chat_type_info_keys`, which checks each
|
||||
/// name we fire against the live `ChatTypeInfo` table rather than against a second copy of the
|
||||
/// same list. Adding a variant makes [`event_name`]'s match fail to compile; the length
|
||||
/// assertion in `tests::every_kind_is_in_all` is what makes you add it here too.
|
||||
///
|
||||
/// Test-only: the app itself never sweeps the kinds — it always has one in hand.
|
||||
#[cfg(test)]
|
||||
pub(crate) const ALL: &'static [ChatEventKind] = {
|
||||
use ChatEventKind as K;
|
||||
&[
|
||||
K::Say,
|
||||
K::Party,
|
||||
K::Raid,
|
||||
K::Guild,
|
||||
K::Officer,
|
||||
K::Yell,
|
||||
K::Whisper,
|
||||
K::WhisperInform,
|
||||
K::Emote,
|
||||
K::TextEmote,
|
||||
K::System,
|
||||
K::MonsterSay,
|
||||
K::MonsterYell,
|
||||
K::MonsterEmote,
|
||||
K::MonsterWhisper,
|
||||
K::Channel,
|
||||
K::ChannelJoin,
|
||||
K::ChannelLeave,
|
||||
K::ChannelNotice,
|
||||
K::ChannelNoticeUser,
|
||||
K::ChannelList,
|
||||
K::Afk,
|
||||
K::Dnd,
|
||||
K::Ignored,
|
||||
K::Skill,
|
||||
K::Loot,
|
||||
K::Money,
|
||||
K::CombatXpGain,
|
||||
K::RaidLeader,
|
||||
K::RaidWarning,
|
||||
K::RaidBossEmote,
|
||||
K::Battleground,
|
||||
K::BattlegroundLeader,
|
||||
K::BgSystemNeutral,
|
||||
K::BgSystemAlliance,
|
||||
K::BgSystemHorde,
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/// One chat event — the reference's `CHAT_MSG_*` fire, typed.
|
||||
///
|
||||
/// **The arg list is TEN wide, and its shape is byte-pinned.** The client's per-type fire helper
|
||||
/// `0x49b0b0` (reached from the chat chokepoint `0x49a870` at `0x49ac9a`) calls
|
||||
/// `FrameScript_SignalEvent 0x703f50` with the format string `"%s%s%s%s%s%s%d%d%s%d"`
|
||||
/// (`.rdata 0x844608`) — wow-re `system/ui/scratch/rested-xp-bindings.md` §9, VERIFIED there in
|
||||
/// the course of the rest-state RE. So **arg1..arg6 are strings, arg7 and arg8 are numbers, arg9
|
||||
/// is a string, arg10 is a number**, and not one of them is ever `nil`: `ChatFrame_OnEvent`
|
||||
/// compares `arg7 > 0` and `arg10 > 0` bare, which under Lua 5.0 errors on a nil. Every slot is
|
||||
/// always passed — zero or empty when unused.
|
||||
///
|
||||
/// Field ↔ arg mapping, each slot named by the consumer that reads it in the shipped
|
||||
/// `ChatFrame.lua` (0288's pin; line numbers are that file's):
|
||||
///
|
||||
/// | arg | field | what it is |
|
||||
/// |---|---|---|
|
||||
/// | 1 | `text` / `notice` | the message body; for the CHANNEL_NOTICE family instead the **notice token** selecting `CHAT_<token>_NOTICE` (l.1416/1424) |
|
||||
/// | 2 | `sender` | the speaker, or the notice's affected player (l.1404, l.1416) |
|
||||
/// | 3 | `language` | already a *name* ("Orcish"); empty = no header (l.1442) |
|
||||
//
|
||||
// (arg3's empty case, checked rather than assumed: l.1442 also guards `arg3 ~= "Universal"`, which
|
||||
// reads like the client passes that word for language 0. It does not — "Universal" is in neither
|
||||
// `Languages.dbc` (13 rows, ids 1-33, no 0) nor `WoW.exe` nor `GlobalStrings.lua`, so that arm is
|
||||
// vestigial in 1.12 and `strlen(arg3) > 0` is what actually suppresses the header. Our
|
||||
// [`language_name`] answering "" for 0 is therefore the right shape, not a shortcut.)
|
||||
/// | 4 | `channel` | the display form, "N. Name - Zone" when numbered (l.1373, l.1463) |
|
||||
/// | 5 | `target` | the second name of a two-name notice — "X kicked by Y" (l.1414-1416) |
|
||||
/// | 6 | `flag` | "AFK"/"DND"/"GM", empty none; read as `CHAT_FLAG_<flag>` (l.1431) |
|
||||
/// | 7 | `zone_channel_id` | the **`ChatChannels.dbc` ChannelID** behind a zone channel, 0 for a custom one — matched against `ChatFrame.zoneChannelList` (l.1379) |
|
||||
/// | 8 | `channel_number` | the client-local joined-channel slot; `ChatTypeInfo["CHANNEL"..arg8]` (l.1381) |
|
||||
/// | 9 | `channel_base` | the channel name **without** the leading number (l.1378's own comment) |
|
||||
/// | 10 | — (always 0) | the channel's **split/instance index**, appended as `arg4.." "..arg10` when `> 0` (l.1421-1423) |
|
||||
///
|
||||
/// **arg10 is deliberately not a field.** It is `slot+0x98`, the same value `GetChannelName`
|
||||
/// returns third, and its wire source is the **second** `u32` of `SMSG_CHANNEL_NOTIFY`'s YOU_JOINED
|
||||
/// tail — the one `Channel::MakeYouJoined` (`Chat/Channel.cpp:823-827`) hardcodes to 0 with the
|
||||
/// comment *"the non-zero number will be appended to the channel name"*, which is `ChatFrame.lua`
|
||||
/// l.1421-1423 exactly. (The *first* u32 of that tail is the channel flags, which the client reads
|
||||
/// and discards.) Our decode already drops the second for that reason
|
||||
/// (`ChannelNoticeTail::YouJoined`), so [`ChatEvent::script_args`] passes the literal 0 rather than
|
||||
/// carrying a field that can only ever hold it.
|
||||
///
|
||||
/// **arg4/arg7/arg8/arg9 are one record**, all `""`/`0`/`0`/`""` together when the channel is not
|
||||
/// in the local list — see [`super::edit::ChannelState::stamp_channel`], which is the only place
|
||||
/// they are written.
|
||||
///
|
||||
/// **KNOWN GAP — arg1 should be GARBLED, and ours is not.** The reference garbles the body
|
||||
/// client-side (`0x49b560` from `0x49aa7c`) and passes the wire text through only when the language
|
||||
/// is Universal, there is no local player, or the player's skill in that language is ≥ 300
|
||||
/// (`0x49b599 cmp esi,0x12c`). benilla speaks Common and does not model language skill at all
|
||||
/// (0288 §3 keeps garbling out with the language picker), so arg1 is always the ungarbled wire
|
||||
/// text. An addon reading a foreign-language line therefore sees more than the reference would give
|
||||
/// it. Also unmodeled: the profanity filter `0x4a1ca0` (`0x49ab23`), which in the reference can
|
||||
/// suppress the whole event for non-whisper types.
|
||||
///
|
||||
/// **Corrections on record.** This comment previously listed args 1-6, 8 and 9 only — arg7 and
|
||||
/// arg10 were absent, and arg7 is a real slot the reference's own channel routing turns on. The
|
||||
/// omission was ours. The whole map is now byte-verified end to end in wow-re
|
||||
/// `system/ui/scratch/chat-msg-event-args.md` (T2), which also **refutes** 0288's standing lead
|
||||
/// that rf77 had traced this marshaller: rf77's trace was opcode `0x92` = SMSG_GUILD_EVENT,
|
||||
/// misfiled. Real `SMSG_MESSAGECHAT` is `0x96` → `0x49d560` and matches vmangos branch for branch,
|
||||
/// which is what benilla's decode already did.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub(crate) struct ChatEvent {
|
||||
pub kind: Option<ChatEventKind>,
|
||||
@@ -71,6 +188,9 @@ pub(crate) struct ChatEvent {
|
||||
pub channel: String,
|
||||
pub target: String,
|
||||
pub flag: String,
|
||||
/// arg7 — the `ChatChannels.dbc` ChannelID behind a zone channel (1 General, 2 Trade,
|
||||
/// 22 LocalDefense); 0 for a custom channel. Filled from [`super::edit::ChannelState`].
|
||||
pub zone_channel_id: u32,
|
||||
pub channel_number: u32,
|
||||
pub channel_base: String,
|
||||
pub notice: String,
|
||||
@@ -85,6 +205,151 @@ impl ChatEvent {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// The notice byte behind a CHANNEL_NOTICE(_USER) event, if this is one.
|
||||
///
|
||||
/// `notice` carries the `SMSG_CHANNEL_NOTIFY` byte in decimal because the composer and the
|
||||
/// event bridge both need it and the field predates both; this is the single parse.
|
||||
pub(crate) fn notice_byte(&self) -> Option<u8> {
|
||||
self.notice.parse().ok()
|
||||
}
|
||||
|
||||
/// This event's `arg1..arg10`, in the reference's own order and types (see the struct doc's
|
||||
/// table). Ten values, always — `nil` is not a legal value in any slot.
|
||||
pub(crate) fn script_args(&self) -> Vec<ScriptValue> {
|
||||
// arg1 is the notice TOKEN for the notice family and the body for everything else — the
|
||||
// one slot whose meaning is type-dependent (`ChatFrame_OnEvent` l.1416/1424 vs l.1396).
|
||||
let arg1 = match (self.kind, self.notice_byte()) {
|
||||
(Some(ChatEventKind::ChannelNotice | ChatEventKind::ChannelNoticeUser), Some(byte)) => {
|
||||
notice_token(byte).unwrap_or_default().to_string()
|
||||
}
|
||||
_ => self.text.clone(),
|
||||
};
|
||||
vec![
|
||||
ScriptValue::Str(arg1),
|
||||
ScriptValue::Str(self.sender.clone()),
|
||||
ScriptValue::Str(self.language.clone()),
|
||||
ScriptValue::Str(self.channel.clone()),
|
||||
ScriptValue::Str(self.target.clone()),
|
||||
ScriptValue::Str(self.flag.clone()),
|
||||
ScriptValue::Int(i64::from(self.zone_channel_id)),
|
||||
ScriptValue::Int(i64::from(self.channel_number)),
|
||||
ScriptValue::Str(self.channel_base.clone()),
|
||||
// arg10 — see the struct doc: always 0 off this server.
|
||||
ScriptValue::Int(0),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// The reference's event NAME for a kind — `"CHAT_MSG_" ++ <the `ChatTypeInfo` key>`.
|
||||
///
|
||||
/// The key spellings are the reference's own (they are what `ChatFrame_OnEvent` recovers with
|
||||
/// `strsub(event, 10)` and looks up in `ChatTypeInfo`), so this table and the `ChatTypeInfo` the
|
||||
/// engine seeds are the same set by construction — asserted in
|
||||
/// `ui_script::chat_tests::every_fired_event_name_is_a_chat_type_info_key`.
|
||||
pub(crate) fn event_name(kind: ChatEventKind) -> &'static str {
|
||||
use ChatEventKind as K;
|
||||
match kind {
|
||||
K::Say => "CHAT_MSG_SAY",
|
||||
K::Party => "CHAT_MSG_PARTY",
|
||||
K::Raid => "CHAT_MSG_RAID",
|
||||
K::Guild => "CHAT_MSG_GUILD",
|
||||
K::Officer => "CHAT_MSG_OFFICER",
|
||||
K::Yell => "CHAT_MSG_YELL",
|
||||
K::Whisper => "CHAT_MSG_WHISPER",
|
||||
K::WhisperInform => "CHAT_MSG_WHISPER_INFORM",
|
||||
K::Emote => "CHAT_MSG_EMOTE",
|
||||
K::TextEmote => "CHAT_MSG_TEXT_EMOTE",
|
||||
K::System => "CHAT_MSG_SYSTEM",
|
||||
K::MonsterSay => "CHAT_MSG_MONSTER_SAY",
|
||||
K::MonsterYell => "CHAT_MSG_MONSTER_YELL",
|
||||
K::MonsterEmote => "CHAT_MSG_MONSTER_EMOTE",
|
||||
K::MonsterWhisper => "CHAT_MSG_MONSTER_WHISPER",
|
||||
K::Channel => "CHAT_MSG_CHANNEL",
|
||||
K::ChannelJoin => "CHAT_MSG_CHANNEL_JOIN",
|
||||
K::ChannelLeave => "CHAT_MSG_CHANNEL_LEAVE",
|
||||
K::ChannelNotice => "CHAT_MSG_CHANNEL_NOTICE",
|
||||
K::ChannelNoticeUser => "CHAT_MSG_CHANNEL_NOTICE_USER",
|
||||
K::ChannelList => "CHAT_MSG_CHANNEL_LIST",
|
||||
K::Afk => "CHAT_MSG_AFK",
|
||||
K::Dnd => "CHAT_MSG_DND",
|
||||
K::Ignored => "CHAT_MSG_IGNORED",
|
||||
K::Skill => "CHAT_MSG_SKILL",
|
||||
K::Loot => "CHAT_MSG_LOOT",
|
||||
K::Money => "CHAT_MSG_MONEY",
|
||||
K::CombatXpGain => "CHAT_MSG_COMBAT_XP_GAIN",
|
||||
K::RaidLeader => "CHAT_MSG_RAID_LEADER",
|
||||
K::RaidWarning => "CHAT_MSG_RAID_WARNING",
|
||||
K::RaidBossEmote => "CHAT_MSG_RAID_BOSS_EMOTE",
|
||||
K::Battleground => "CHAT_MSG_BATTLEGROUND",
|
||||
K::BattlegroundLeader => "CHAT_MSG_BATTLEGROUND_LEADER",
|
||||
K::BgSystemNeutral => "CHAT_MSG_BG_SYSTEM_NEUTRAL",
|
||||
K::BgSystemAlliance => "CHAT_MSG_BG_SYSTEM_ALLIANCE",
|
||||
K::BgSystemHorde => "CHAT_MSG_BG_SYSTEM_HORDE",
|
||||
}
|
||||
}
|
||||
|
||||
/// The notice TOKEN a `SMSG_CHANNEL_NOTIFY` byte becomes in `arg1` — the token
|
||||
/// `ChatFrame_OnEvent` splices into `getglobal("CHAT_"..arg1.."_NOTICE")` (l.1416/1424).
|
||||
///
|
||||
/// The token set is read off the shipped `GlobalStrings.lua`'s own `CHAT_<X>_NOTICE` keys
|
||||
/// (l.494-745 of the extracted file), paired to the vmangos notice byte that produces each line —
|
||||
/// the same pairing [`super::frames::compose_notice`] already renders, which is why the two tables
|
||||
/// are asserted against each other rather than left to drift
|
||||
/// (`ui_chat::tests::every_rendered_notice_has_a_token`).
|
||||
///
|
||||
/// Byte-for-byte identical to the client's own jump table (`0x49c60c`, 32 direct arms), verified
|
||||
/// arm by arm in wow-re `chat-msg-event-args.md` §9 — checked against that table after the fact,
|
||||
/// not derived from it.
|
||||
///
|
||||
/// `None` = a byte the reference passes no token for: `0x00`/`0x01` are the CHANNEL_JOIN /
|
||||
/// CHANNEL_LEAVE member lines (their arg1 is the empty string, not a token), `0x0C` MODE_CHANGE
|
||||
/// fires **no chat event at all** (`0x49c24d` calls `0x49e910` and returns — which is why
|
||||
/// [`super::feed::ChatLog::push_channel_notice`] drops it before it becomes an event), and anything
|
||||
/// past `0x1F` is outside vmangos's range.
|
||||
///
|
||||
/// **Two state-dependent tokens we do not model:** the client answers `"YOU_CHANGED"` for `0x02`
|
||||
/// and `"SUSPENDED"` for `0x03` when its own channel record is in the matching state
|
||||
/// (`rec+0x9c == 2` / `== 3`) — a per-channel state benilla keeps nothing equivalent to, so we
|
||||
/// always send the plain `YOU_JOINED` / `YOU_LEFT`. Both alternates are `CHAT_<X>_NOTICE` strings
|
||||
/// that exist in GlobalStrings (`CHAT_YOU_CHANGED_NOTICE`, `CHAT_SUSPENDED_NOTICE`).
|
||||
pub(crate) fn notice_token(byte: u8) -> Option<&'static str> {
|
||||
use benilla_protocol::messages::channel_notice as n;
|
||||
Some(match byte {
|
||||
n::YOU_JOINED => "YOU_JOINED",
|
||||
n::YOU_LEFT => "YOU_LEFT",
|
||||
n::WRONG_PASSWORD => "WRONG_PASSWORD",
|
||||
n::NOT_MEMBER => "NOT_MEMBER",
|
||||
n::NOT_MODERATOR => "NOT_MODERATOR",
|
||||
n::PASSWORD_CHANGED => "PASSWORD_CHANGED",
|
||||
n::OWNER_CHANGED => "OWNER_CHANGED",
|
||||
n::PLAYER_NOT_FOUND => "PLAYER_NOT_FOUND",
|
||||
n::NOT_OWNER => "NOT_OWNER",
|
||||
n::CHANNEL_OWNER => "CHANNEL_OWNER",
|
||||
n::ANNOUNCEMENTS_ON => "ANNOUNCEMENTS_ON",
|
||||
n::ANNOUNCEMENTS_OFF => "ANNOUNCEMENTS_OFF",
|
||||
n::MODERATION_ON => "MODERATION_ON",
|
||||
n::MODERATION_OFF => "MODERATION_OFF",
|
||||
n::MUTED => "MUTED",
|
||||
n::PLAYER_KICKED => "PLAYER_KICKED",
|
||||
n::BANNED => "BANNED",
|
||||
n::PLAYER_BANNED => "PLAYER_BANNED",
|
||||
n::PLAYER_UNBANNED => "PLAYER_UNBANNED",
|
||||
n::PLAYER_NOT_BANNED => "PLAYER_NOT_BANNED",
|
||||
n::PLAYER_ALREADY_MEMBER => "PLAYER_ALREADY_MEMBER",
|
||||
n::INVITE => "INVITE",
|
||||
n::INVITE_WRONG_FACTION => "INVITE_WRONG_FACTION",
|
||||
n::WRONG_FACTION => "WRONG_FACTION",
|
||||
n::INVALID_NAME => "INVALID_NAME",
|
||||
n::NOT_MODERATED => "NOT_MODERATED",
|
||||
n::PLAYER_INVITED => "PLAYER_INVITED",
|
||||
n::PLAYER_INVITE_BANNED => "PLAYER_INVITE_BANNED",
|
||||
n::THROTTLED => "THROTTLED",
|
||||
// MODE_CHANGE (0x0C) has no CHAT_*_NOTICE string in 1.12 — the reference renders nothing,
|
||||
// so there is no token. JOINED/LEFT (0x00/0x01) are not notices at all: they are the
|
||||
// CHANNEL_JOIN/CHANNEL_LEAVE member-line events.
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// The message groups a window registers — `ChatTypeGroup`'s keys (transcribed; the chat-cache
|
||||
|
||||
@@ -301,12 +301,24 @@ pub(super) fn feed_chat(
|
||||
Pending::Event(mut event) => {
|
||||
// The joined-list upkeep (the client-side number law): YOU_JOINED appends,
|
||||
// YOU_LEFT (and the suspend form) removes.
|
||||
//
|
||||
// Logged, because this edge is where "we asked to join" becomes "the server says
|
||||
// we are in": [`super::channels`]'s walk only ever proves the request went out, and
|
||||
// the round trip is what actually arms an addon (it is the `CHAT_MSG_CHANNEL_NOTICE`
|
||||
// Ace2's whole init gate waits on). A join the server refuses is otherwise
|
||||
// completely silent on this side.
|
||||
if event.kind == Some(ChatEventKind::ChannelNotice) {
|
||||
match event.notice.as_str() {
|
||||
"2" if channels.number_of(&event.channel).is_none() => {
|
||||
channels.joined.push(event.channel.clone());
|
||||
debug!(
|
||||
"chat: server confirms channel {:?} joined (slot {})",
|
||||
event.channel,
|
||||
channels.joined.len()
|
||||
);
|
||||
}
|
||||
"3" => {
|
||||
debug!("chat: server confirms channel {:?} left", event.channel);
|
||||
channels
|
||||
.joined
|
||||
.retain(|c| !c.eq_ignore_ascii_case(&event.channel));
|
||||
@@ -315,9 +327,7 @@ pub(super) fn feed_chat(
|
||||
}
|
||||
}
|
||||
// A member-line / notice channel renders numbered when we know its slot.
|
||||
if let Some(n) = channels.number_of(&event.channel) {
|
||||
event.channel = format!("{n}. {}", event.channel);
|
||||
}
|
||||
channels.stamp_channel(&mut event);
|
||||
route(&mut script, &mut windows, &event);
|
||||
}
|
||||
Pending::Wire { msg, tries } => {
|
||||
@@ -348,16 +358,9 @@ pub(super) fn feed_chat(
|
||||
});
|
||||
continue;
|
||||
}
|
||||
// The numbered display form ("1. General - Elwynn Forest") when the channel is
|
||||
// one of ours — the client-built arg4 the composer strips/brackets.
|
||||
let channel_disp = msg
|
||||
.channel
|
||||
.clone()
|
||||
.map(|c| match channels.number_of(&c) {
|
||||
Some(n) => format!("{n}. {c}"),
|
||||
None => c,
|
||||
})
|
||||
.unwrap_or_default();
|
||||
// The channel's base name; the numbered display form, its slot number and its
|
||||
// zone id are stamped on below ([`ChannelState::stamp_channel`]) — arg4/arg7-arg9.
|
||||
let channel_base = msg.channel.clone().unwrap_or_default();
|
||||
// `$`-macro expansion (decision 0754, corrected by 0759): the reference runs its one
|
||||
// server-text expander over the monster/boss + BG-system types and nothing else,
|
||||
// against the guid the line is ADDRESSED to. Every other type reaches the frame
|
||||
@@ -416,7 +419,7 @@ pub(super) fn feed_chat(
|
||||
None
|
||||
};
|
||||
let text = expanded.unwrap_or_else(|| msg.text.clone());
|
||||
let event = ChatEvent {
|
||||
let mut event = ChatEvent {
|
||||
kind,
|
||||
text: text.clone(),
|
||||
sender: name.unwrap_or_else(|| {
|
||||
@@ -427,10 +430,11 @@ pub(super) fn feed_chat(
|
||||
}
|
||||
}),
|
||||
language: language_name(msg.language).to_string(),
|
||||
channel: channel_disp,
|
||||
channel: channel_base,
|
||||
flag: flag_of_tag(msg.chat_tag).to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
channels.stamp_channel(&mut event);
|
||||
// A received whisper remembers its sender (`ChatEdit_SetLastTellTarget`,
|
||||
// ChatFrame_OnEvent l.1471) — the `/r` + Tab-cycle ring.
|
||||
if event.kind == Some(ChatEventKind::Whisper) && !event.sender.is_empty() {
|
||||
@@ -469,6 +473,17 @@ pub(super) fn feed_chat(
|
||||
});
|
||||
continue;
|
||||
}
|
||||
// NOT stamped, deliberately — and this arm is inconsistent with the other two
|
||||
// because of it. A guid-tail notice (a join/leave member line, a kick, a
|
||||
// moderation change) reaches the composer with its channel name UNNUMBERED, so it
|
||||
// renders "[World] Ann joined channel." where the same channel's speech renders
|
||||
// "[1. World]". The reference numbers both: `ChatFrame_OnEvent` l.1463 strips only
|
||||
// the " - Zone" tail from arg4, never the number.
|
||||
//
|
||||
// Adding `channels.stamp_channel(&mut event)` here fixes it in one line — and
|
||||
// changes what the player sees, which this pass is not allowed to do. Left for the
|
||||
// director's call; the cost of leaving it is that arg7/arg8/arg9 are 0/0/empty on
|
||||
// these events alone.
|
||||
let event = notice_event(
|
||||
notice,
|
||||
channel,
|
||||
|
||||
@@ -11,7 +11,7 @@ use bevy::prelude::*;
|
||||
|
||||
use benilla_protocol::messages::channel_notice as notice;
|
||||
|
||||
use super::event::{default_color, group_kinds, ChatEvent, ChatEventKind, ChatGroup};
|
||||
use super::event::{default_color, event_name, group_kinds, ChatEvent, ChatEventKind, ChatGroup};
|
||||
|
||||
/// How long after a received whisper the `TellMessage` alert stays silent
|
||||
/// (`CHAT_TELL_ALERT_TIME = 300` — ref ChatFrame.lua l.4: only a tell arriving ≥5 min after the
|
||||
@@ -72,10 +72,34 @@ impl ChatWindows {
|
||||
}
|
||||
}
|
||||
|
||||
/// Route one event: compose it once, then AddMessage into every subscribed window. Whisper
|
||||
/// receipt side-effects (the throttled `TellMessage` chime + the unselected-tab flash — ref
|
||||
/// ChatFrame_OnEvent l.1470-1477) fire through the script seam. A kind-less event (an
|
||||
/// unmodeled wire type) drops with a warn — never silently.
|
||||
/// Route one event: compose it once and AddMessage it into every subscribed window, then fire the
|
||||
/// real `CHAT_MSG_*` at the VM. Whisper receipt side-effects (the throttled `TellMessage` chime +
|
||||
/// the unselected-tab flash — ref ChatFrame_OnEvent l.1470-1477) ride the render half. A kind-less
|
||||
/// event (an unmodeled wire type) drops with a warn — never silently.
|
||||
///
|
||||
/// **Two consumers, one event, no double-print.** In the reference, C fires `CHAT_MSG_<TYPE>` and
|
||||
/// *Lua* — `ChatFrame_OnEvent` — is what turns it into a line; here the composer below IS that
|
||||
/// handler, transcribed into Rust (0288 §1). So the fire is additive: it exists for **addons**,
|
||||
/// and our windows keep rendering exactly as they did. Nothing prints twice because our shipped
|
||||
/// `ChatFrame.xml` handles exactly one event, `EXECUTE_CHAT_LINE` (assets/ui/ChatFrame.xml, its
|
||||
/// `<OnEvent>`) — an addon may `ChatFrame1:RegisterEvent("CHAT_MSG_SAY")` for its own reasons and
|
||||
/// our frame's handler will simply not match it.
|
||||
/// `ui_chat::tests::an_addon_registering_our_own_chat_frame_does_not_double_print` is the guard.
|
||||
///
|
||||
/// **Render first, then fire — that ordering is the reference's, not a convenience.** The client
|
||||
/// dispatches an event to its listeners in registration order (FIFO, wow-re
|
||||
/// `event-dispatch-order.md`, and [`benilla_ui::script::UiScript::fire_event`] transcribes it), and
|
||||
/// ChatFrame1 registers at FrameXML load — before any addon exists. So in the real client the line
|
||||
/// is already in the window by the time an addon's handler runs, and an addon that reads
|
||||
/// `GetNumMessages()` or re-reads the last line from its own `CHAT_MSG_*` handler depends on that.
|
||||
/// Our Rust composer stands in for ChatFrame1's handler, so it has to go first for the same reason.
|
||||
///
|
||||
/// **The fire is unconditional; the render is not.** The client's `SignalEvent` does not consult
|
||||
/// any window's message-group registration — that is `ChatFrame_OnEvent`'s job, per frame — so an
|
||||
/// event reaches Lua even when neither of our windows wants it. What does *not* reach here is a
|
||||
/// notice the reference declines to make an event out of at all: MODE_CHANGE is dropped upstream,
|
||||
/// at the feed, exactly as the client's `0x0C` arm returns without firing
|
||||
/// (`ui_chat::tests::a_mode_change_notice_never_becomes_an_event`).
|
||||
pub(crate) fn route(
|
||||
script: &mut benilla_ui::script::UiScript,
|
||||
windows: &mut ChatWindows,
|
||||
@@ -85,27 +109,30 @@ pub(crate) fn route(
|
||||
warn!("chat: unroutable event (no kind): {:?}", event.text);
|
||||
return;
|
||||
};
|
||||
let Some(line) = compose(event, kind) else {
|
||||
return; // composed to nothing (a silent notice)
|
||||
};
|
||||
let color = default_color(kind);
|
||||
for idx in 0..2 {
|
||||
if windows.wants(idx, kind) {
|
||||
add(script, &format!("ChatFrame{}", idx + 1), &line, color);
|
||||
// ── our own window: the transcribed ChatFrame_OnEvent, i.e. the first-registered listener ──
|
||||
if let Some(line) = compose(event, kind) {
|
||||
let color = default_color(kind);
|
||||
for idx in 0..2 {
|
||||
if windows.wants(idx, kind) {
|
||||
add(script, &format!("ChatFrame{}", idx + 1), &line, color);
|
||||
}
|
||||
}
|
||||
if kind == ChatEventKind::Whisper {
|
||||
// The tell chime, throttled to one per 5 minutes (CHAT_TELL_ALERT_TIME), and the tab
|
||||
// flash when the receiving window (1) isn't the selected dock tab. Inside this half
|
||||
// because the reference does them inside ChatFrame_OnEvent too (l.1470-1477).
|
||||
if windows.tell_alert_left <= 0.0 {
|
||||
crate::ui_script::run_or_warn(script, "PlaySound(\"TellMessage\")");
|
||||
}
|
||||
windows.tell_alert_left = TELL_ALERT_SECS;
|
||||
crate::ui_script::run_or_warn(
|
||||
script,
|
||||
"if BenillaFCF.selected ~= 1 then BenillaFCF_FlashTab(1) end",
|
||||
);
|
||||
}
|
||||
}
|
||||
if kind == ChatEventKind::Whisper {
|
||||
// The tell chime, throttled to one per 5 minutes (CHAT_TELL_ALERT_TIME), and the tab
|
||||
// flash when the receiving window (1) isn't the selected dock tab.
|
||||
if windows.tell_alert_left <= 0.0 {
|
||||
crate::ui_script::run_or_warn(script, "PlaySound(\"TellMessage\")");
|
||||
}
|
||||
windows.tell_alert_left = TELL_ALERT_SECS;
|
||||
crate::ui_script::run_or_warn(
|
||||
script,
|
||||
"if BenillaFCF.selected ~= 1 then BenillaFCF_FlashTab(1) end",
|
||||
);
|
||||
}
|
||||
// ── everyone else: the addons, after the default UI, exactly as registration order says ──
|
||||
script.fire_event(event_name(kind), event.script_args());
|
||||
}
|
||||
|
||||
/// Add one composed line, converting the `0..255` table color to the seam's `0..1` floats.
|
||||
@@ -241,7 +268,7 @@ pub(crate) fn compose_notice(event: &ChatEvent) -> Option<String> {
|
||||
let chan = strip_zone(&event.channel);
|
||||
let a = &event.sender; // the notice's first name (actor / affected)
|
||||
let b = &event.target; // the second name (kicked-by style)
|
||||
let n: u8 = event.notice.parse().unwrap_or(0xFF);
|
||||
let n: u8 = event.notice_byte().unwrap_or(0xFF);
|
||||
Some(match n {
|
||||
notice::YOU_JOINED => format!("Joined Channel: [{chan}]"),
|
||||
notice::YOU_LEFT => format!("Left Channel: [{chan}]"),
|
||||
|
||||
@@ -739,6 +739,17 @@ pub(super) fn drain_chat_input(
|
||||
}
|
||||
}
|
||||
ParsedChat::Unknown => {
|
||||
// Before the help line: an ADDON may claim it (decision 1195). The reference
|
||||
// resolves `SlashCmdList` in the same pass as its own commands; ours runs after
|
||||
// the boot table misses, which gives the same precedence — a shipped command can
|
||||
// never be shadowed — without moving our handlers into Lua.
|
||||
if let Some(rest) = msg.strip_prefix('/') {
|
||||
let rest = rest.trim();
|
||||
let (cmd, args) = rest.split_once(char::is_whitespace).unwrap_or((rest, ""));
|
||||
if script.run_slash_command(cmd, args.trim()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// HELP_TEXT_SIMPLE (the ref's unknown-command reply, ChatEdit_ParseText l.2203).
|
||||
chat_log.push_event(super::event::ChatEvent::text_only(
|
||||
super::event::ChatEventKind::System,
|
||||
@@ -840,3 +851,42 @@ pub(super) fn emote_send_eligible(emote_flags: u32, stand_state: u8, swimming: b
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Turn an addon's `SendChatMessage` calls into sends (decision 1199).
|
||||
///
|
||||
/// Its own system rather than a branch inside [`drain_chat_input`], for the reason
|
||||
/// `benilla_ui::script::chat_send`'s module doc gives: the box's drain runs the **slash grammar**
|
||||
/// and this path must not. An addon announcing `"/dance"` is saying six characters.
|
||||
///
|
||||
/// An unknown chat-type token is **reported, not guessed**. `SendChatMessage(msg, "RAID_WARNING")`
|
||||
/// silently going to /say is worse than not going: the addon believes it warned the raid.
|
||||
pub(super) fn drain_addon_chat_sends(
|
||||
script: Option<NonSendMut<benilla_ui::script::UiScript>>,
|
||||
commands: Res<NetCommands>,
|
||||
mut chat_log: ResMut<super::feed::ChatLog>,
|
||||
) {
|
||||
let Some(mut script) = script else {
|
||||
return;
|
||||
};
|
||||
for send in script.take_chat_sends() {
|
||||
let Some(kind) = super::edit::SendType::from_token(&send.chat_type) else {
|
||||
warn!(
|
||||
"chat: SendChatMessage with unknown type {:?}",
|
||||
send.chat_type
|
||||
);
|
||||
chat_log.push_event(super::event::ChatEvent::text_only(
|
||||
super::event::ChatEventKind::System,
|
||||
format!("Unknown chat type \"{}\".", send.chat_type),
|
||||
));
|
||||
continue;
|
||||
};
|
||||
let cmd = ClientCommand::Chat {
|
||||
kind: kind.wire(),
|
||||
target: send.target,
|
||||
text: send.text,
|
||||
};
|
||||
if commands.0.send(cmd).is_err() {
|
||||
warn!("chat: not connected; addon line dropped");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,321 @@ fn channel_notices_compose_by_the_notice_law() {
|
||||
);
|
||||
}
|
||||
|
||||
// ── the Lua face: the real CHAT_MSG_* fire (0288 §1's addon-API phase) ────────────────────────
|
||||
//
|
||||
// These drive the REAL router into a REAL VM with our shipped ChatFrame.xml under it, because the
|
||||
// question they exist to answer — "does anything print twice now?" — cannot be answered by
|
||||
// reasoning about the composer in isolation. `route` both renders and fires; only a VM holding
|
||||
// our actual window can show that the two do not add up to two lines.
|
||||
|
||||
/// A fresh VM with the shipped chat stack under it — the same files the app loads, so `ChatFrame1`
|
||||
/// here is the real window carrying its real `<OnEvent>`.
|
||||
fn chat_vm() -> benilla_ui::script::UiScript {
|
||||
let mut s = benilla_ui::script::UiScript::new().unwrap();
|
||||
for file in ["Fonts.xml", "ChatFrame.xml"] {
|
||||
let text = std::fs::read_to_string(
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("assets/ui")
|
||||
.join(file),
|
||||
)
|
||||
.unwrap();
|
||||
let doc = benilla_ui::framexml::parse(&text).unwrap();
|
||||
let report = benilla_ui::loader::load(&s, &doc, &|_| None);
|
||||
assert!(report.errors.is_empty(), "{file}: {:?}", report.errors);
|
||||
}
|
||||
s.set_screen_size(1600.0, 900.0);
|
||||
s.resolve();
|
||||
s
|
||||
}
|
||||
|
||||
/// An "addon" that records what a `CHAT_MSG_*` fire actually delivered — the count, the event
|
||||
/// name, and `arg1..arg10` joined with `|`. The concatenation is the point: a `nil` in any slot
|
||||
/// raises in Lua, so a passing read is itself the proof that all ten args arrived.
|
||||
const SPY: &str = r#"
|
||||
SpyN, SpyEvent, SpyLine = 0, "", ""
|
||||
Spy = CreateFrame("Frame", "BenillaChatSpy")
|
||||
Spy:SetScript("OnEvent", function()
|
||||
SpyN = SpyN + 1
|
||||
SpyEvent = event
|
||||
SpyLine = arg1.."|"..arg2.."|"..arg3.."|"..arg4.."|"..arg5.."|"..arg6..
|
||||
"|"..arg7.."|"..arg8.."|"..arg9.."|"..arg10
|
||||
end)
|
||||
"#;
|
||||
|
||||
/// How many lines `ChatFrame1` is holding (`GetNumMessages`).
|
||||
fn lines_in_window(s: &benilla_ui::script::UiScript) -> i64 {
|
||||
s.eval::<i64>("return ChatFrame1:GetNumMessages()").unwrap()
|
||||
}
|
||||
|
||||
/// **The double-print answer, proved rather than argued.**
|
||||
///
|
||||
/// In the reference, `CHAT_MSG_SAY` is what MAKES the line: C fires it, `ChatFrame_OnEvent` calls
|
||||
/// `AddMessage`. benilla composes and adds in Rust instead (0288 §1) and now fires the event as
|
||||
/// well — so the honest worry is that an addon registering the event on *our own* ChatFrame1 makes
|
||||
/// the line land twice. It does not, and the mechanism is that our shipped `ChatFrame.xml`
|
||||
/// `<OnEvent>` handles exactly one event (`EXECUTE_CHAT_LINE`) and ignores everything else.
|
||||
///
|
||||
/// The spy is a control, not decoration: without it a broken fire would pass this test.
|
||||
#[test]
|
||||
fn an_addon_registering_our_own_chat_frame_does_not_double_print() {
|
||||
let mut s = chat_vm();
|
||||
let mut windows = super::frames::ChatWindows::default();
|
||||
s.run(SPY).unwrap();
|
||||
s.run(r#"BenillaChatSpy:RegisterEvent("CHAT_MSG_SAY")"#)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(lines_in_window(&s), 0, "the window starts empty");
|
||||
super::frames::route(&mut s, &mut windows, &ev(K::Say, "hi there", "Bob"));
|
||||
assert_eq!(lines_in_window(&s), 1, "our window prints exactly once");
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return SpyN").unwrap(),
|
||||
1,
|
||||
"the addon saw the fire — otherwise the count above proves nothing"
|
||||
);
|
||||
|
||||
// Now the addon registers OUR window for the event, exactly as the reference's own
|
||||
// FloatingChatFrame does. This is the double-print case if there is one.
|
||||
s.run(r#"ChatFrame1:RegisterEvent("CHAT_MSG_SAY")"#)
|
||||
.unwrap();
|
||||
super::frames::route(&mut s, &mut windows, &ev(K::Say, "hi again", "Bob"));
|
||||
assert_eq!(
|
||||
lines_in_window(&s),
|
||||
2,
|
||||
"one more line, not two — ChatFrame1's own OnEvent does not render CHAT_MSG_*"
|
||||
);
|
||||
assert_eq!(s.eval::<i64>("return SpyN").unwrap(), 2);
|
||||
assert!(s.errors().is_empty(), "handler errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// **The line is already in the window when an addon's handler runs.** The reference dispatches to
|
||||
/// listeners in registration order and ChatFrame1 registers at FrameXML load, before any addon
|
||||
/// exists — so an addon that reads `GetNumMessages()` (or re-reads the last line to recolour it)
|
||||
/// from its own `CHAT_MSG_*` handler sees the line, not the gap before it. Our Rust composer stands
|
||||
/// in for ChatFrame1's handler, so it has to run first for the same reason.
|
||||
#[test]
|
||||
fn an_addons_handler_sees_the_line_already_in_the_window() {
|
||||
let mut s = chat_vm();
|
||||
let mut windows = super::frames::ChatWindows::default();
|
||||
s.run(
|
||||
r#"
|
||||
SeenAtFireTime = -1
|
||||
Spy = CreateFrame("Frame", "BenillaChatSpy")
|
||||
Spy:SetScript("OnEvent", function()
|
||||
SeenAtFireTime = ChatFrame1:GetNumMessages()
|
||||
end)
|
||||
BenillaChatSpy:RegisterEvent("CHAT_MSG_SAY")
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
super::frames::route(&mut s, &mut windows, &ev(K::Say, "hi there", "Bob"));
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return SeenAtFireTime").unwrap(),
|
||||
1,
|
||||
"the handler ran AFTER our window took the line, as registration order requires"
|
||||
);
|
||||
}
|
||||
|
||||
/// A player line fires `CHAT_MSG_SAY` with the reference's own arg positions — including the two
|
||||
/// slots our doc comment used to omit (arg7, arg10), both numbers, both zero for a non-channel
|
||||
/// line.
|
||||
#[test]
|
||||
fn a_say_line_fires_chat_msg_say_in_the_references_arg_positions() {
|
||||
let mut s = chat_vm();
|
||||
let mut windows = super::frames::ChatWindows::default();
|
||||
s.run(SPY).unwrap();
|
||||
s.run(r#"BenillaChatSpy:RegisterEvent("CHAT_MSG_SAY")"#)
|
||||
.unwrap();
|
||||
|
||||
let mut e = ev(K::Say, "throm-ka", "Grunk");
|
||||
e.language = "Orcish".into();
|
||||
e.flag = "GM".into();
|
||||
super::frames::route(&mut s, &mut windows, &e);
|
||||
|
||||
assert_eq!(s.eval::<String>("return SpyEvent").unwrap(), "CHAT_MSG_SAY");
|
||||
// arg1 is the RAW body, not the composed line — the reference's Lua is what adds
|
||||
// "%s says: " and the |Hplayer link, so an addon must see what the wire sent.
|
||||
assert_eq!(
|
||||
s.eval::<String>("return SpyLine").unwrap(),
|
||||
"throm-ka|Grunk|Orcish|||GM|0|0||0"
|
||||
);
|
||||
}
|
||||
|
||||
/// A channel notice fires the **token** in arg1 (not the rendered line), the numbered display form
|
||||
/// in arg4, and the three numeric slots the reference reads bare.
|
||||
///
|
||||
/// The last assertion runs `ChatFrame_OnEvent`'s own two comparisons — `arg7 > 0` and
|
||||
/// `arg10 > 0` — inside the handler. Under Lua 5.0 a `nil` there raises, so this is the test that
|
||||
/// would have caught passing nine args instead of ten.
|
||||
#[test]
|
||||
fn a_channel_notice_fires_its_token_and_the_reference_reads_arg7_and_arg10_bare() {
|
||||
let mut s = chat_vm();
|
||||
let mut windows = super::frames::ChatWindows::default();
|
||||
s.run(SPY).unwrap();
|
||||
s.run(
|
||||
r#"
|
||||
SpyZone, SpySuffix = nil, nil
|
||||
BenillaChatSpy:SetScript("OnEvent", function()
|
||||
SpyN = SpyN + 1
|
||||
SpyEvent = event
|
||||
SpyLine = arg1.."|"..arg2.."|"..arg3.."|"..arg4.."|"..arg5.."|"..arg6..
|
||||
"|"..arg7.."|"..arg8.."|"..arg9.."|"..arg10
|
||||
-- ChatFrame_OnEvent l.1379 and l.1421, verbatim shape.
|
||||
if arg7 > 0 then SpyZone = arg7 end
|
||||
if arg10 > 0 then SpySuffix = arg10 end
|
||||
end)
|
||||
BenillaChatSpy:RegisterEvent("CHAT_MSG_CHANNEL_NOTICE")
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut e = ChatEvent::text_only(K::ChannelNotice, String::new());
|
||||
e.notice = "2".into(); // YOU_JOINED
|
||||
e.channel = "1. General - Elwynn Forest".into();
|
||||
e.channel_base = "General - Elwynn Forest".into();
|
||||
e.channel_number = 1;
|
||||
e.zone_channel_id = 1; // ChatChannels.dbc General
|
||||
super::frames::route(&mut s, &mut windows, &e);
|
||||
|
||||
assert_eq!(s.eval::<i64>("return SpyN").unwrap(), 1);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return SpyEvent").unwrap(),
|
||||
"CHAT_MSG_CHANNEL_NOTICE"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return SpyLine").unwrap(),
|
||||
"YOU_JOINED|||1. General - Elwynn Forest|||1|1|General - Elwynn Forest|0"
|
||||
);
|
||||
assert_eq!(s.eval::<i64>("return SpyZone").unwrap(), 1);
|
||||
assert!(s.errors().is_empty(), "handler errors: {:?}", s.errors());
|
||||
// ...and the window still shows the one composed line it always did.
|
||||
assert_eq!(lines_in_window(&s), 1);
|
||||
}
|
||||
|
||||
/// **MODE_CHANGE produces no chat event at all** — not a silent one.
|
||||
///
|
||||
/// This test replaced an earlier one that asserted the opposite (that a notice the UI renders
|
||||
/// silently still reaches Lua, using MODE_CHANGE as the example). The byte-level carve settled it
|
||||
/// the other way: `0x49c24d`, the `0x0C` arm of the notice jump table, calls `0x49e910` and
|
||||
/// **returns** — it never reaches the fire (wow-re `chat-msg-event-args.md` §9). So the right
|
||||
/// behaviour is what our feed already does: drop it before it becomes an event, which is what this
|
||||
/// now asserts.
|
||||
#[test]
|
||||
fn a_mode_change_notice_never_becomes_an_event() {
|
||||
use benilla_protocol::messages::{channel_notice, ChannelNoticeTail};
|
||||
|
||||
let mut log = super::feed::ChatLog::default();
|
||||
log.push_channel_notice(
|
||||
channel_notice::MODE_CHANGE,
|
||||
"World".into(),
|
||||
&ChannelNoticeTail::ModeChange {
|
||||
guid: 42,
|
||||
old_flags: 0,
|
||||
new_flags: 1,
|
||||
},
|
||||
);
|
||||
assert_eq!(
|
||||
log.pending_len(),
|
||||
0,
|
||||
"MODE_CHANGE is dropped at the feed — the reference's 0x0C arm fires nothing"
|
||||
);
|
||||
|
||||
// The control: a notice that DOES fire still gets queued, so the assertion above is about
|
||||
// MODE_CHANGE and not about `push_channel_notice` being broken.
|
||||
log.push_channel_notice(
|
||||
channel_notice::YOU_JOINED,
|
||||
"World".into(),
|
||||
&ChannelNoticeTail::YouJoined { flags: 0 },
|
||||
);
|
||||
assert_eq!(log.pending_len(), 1);
|
||||
}
|
||||
|
||||
/// A channel line whose channel we are **not** in leaves all four channel slots empty — arg4 falls
|
||||
/// back to the bare name and arg7/arg8/arg9 stay `0/0/""`. They are one record in the reference
|
||||
/// (`slot+0x00/+0x04/+0x94/+0x98`), so they are one record here (`chat-msg-event-args.md` §§4, 7-10).
|
||||
#[test]
|
||||
fn a_channel_we_are_not_in_fires_the_bare_name_and_zeroes() {
|
||||
let mut s = chat_vm();
|
||||
let mut windows = super::frames::ChatWindows::default();
|
||||
s.run(SPY).unwrap();
|
||||
s.run(r#"BenillaChatSpy:RegisterEvent("CHAT_MSG_CHANNEL")"#)
|
||||
.unwrap();
|
||||
|
||||
// An empty joined list: nothing is in the local channel record array.
|
||||
let channels = super::edit::ChannelState::default();
|
||||
let mut e = ev(K::Channel, "wts boar livers", "Bob");
|
||||
e.channel = "SomeoneElsesChannel".into();
|
||||
channels.stamp_channel(&mut e);
|
||||
super::frames::route(&mut s, &mut windows, &e);
|
||||
|
||||
assert_eq!(
|
||||
s.eval::<String>("return SpyLine").unwrap(),
|
||||
"wts boar livers|Bob||SomeoneElsesChannel|||0|0||0",
|
||||
"arg4 keeps the bare INCOMING name (the miss leg still has one); arg7/8/9/10 are the \
|
||||
record we do not have, so 0/0/\"\"/0"
|
||||
);
|
||||
}
|
||||
|
||||
/// `stamp_channel` splits the wire's bare name into the reference's arg4/arg8/arg9 trio: the
|
||||
/// display form gets the number prefix, arg9 never does.
|
||||
#[test]
|
||||
fn stamping_a_channel_splits_the_display_form_from_the_base_name() {
|
||||
let mut channels = super::edit::ChannelState::default();
|
||||
channels.joined.push("World".into());
|
||||
channels.joined.push("General - Elwynn Forest".into());
|
||||
|
||||
let mut e = ev(K::Channel, "wts boar livers", "Bob");
|
||||
e.channel = "General - Elwynn Forest".into();
|
||||
channels.stamp_channel(&mut e);
|
||||
assert_eq!(e.channel, "2. General - Elwynn Forest"); // arg4
|
||||
assert_eq!(e.channel_number, 2); // arg8
|
||||
assert_eq!(e.channel_base, "General - Elwynn Forest"); // arg9, " - Zone" tail intact
|
||||
|
||||
// A channel we are not in keeps its bare name in arg4 and leaves the whole rest of the record
|
||||
// empty — the reference's miss leg `0x49aa86`, where there is no local record to read
|
||||
// `slot+0x04/+0x94/+0x98` out of at all.
|
||||
let mut other = ev(K::Channel, "hi", "Bob");
|
||||
other.channel = "SomeoneElsesChannel".into();
|
||||
channels.stamp_channel(&mut other);
|
||||
assert_eq!(other.channel, "SomeoneElsesChannel"); // arg4: the bare incoming name
|
||||
assert_eq!(other.channel_number, 0); // arg8
|
||||
assert_eq!(other.channel_base, ""); // arg9 — NOT the name
|
||||
assert_eq!(other.zone_channel_id, 0); // arg7
|
||||
}
|
||||
|
||||
/// Every notice byte the composer renders has a token to fire, and vice versa — the two tables are
|
||||
/// the same set by assertion rather than by good intentions (they are read off the same
|
||||
/// `CHAT_<X>_NOTICE` GlobalStrings keys).
|
||||
#[test]
|
||||
fn every_rendered_notice_has_a_token() {
|
||||
for byte in 0x00u8..=0x1F {
|
||||
let mut e = ChatEvent::text_only(K::ChannelNotice, String::new());
|
||||
e.channel = "World".into();
|
||||
e.notice = byte.to_string();
|
||||
let rendered = super::frames::compose_notice(&e).is_some();
|
||||
let token = super::event::notice_token(byte).is_some();
|
||||
assert_eq!(
|
||||
rendered, token,
|
||||
"notice {byte:#04x}: renders={rendered} but token={token}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The `ALL` sweep list really is every variant. A new kind fails [`super::event::event_name`]'s
|
||||
/// exhaustive match at compile time; this is what makes you add it to `ALL` as well.
|
||||
#[test]
|
||||
fn every_kind_is_in_all() {
|
||||
let mut seen: Vec<&str> = K::ALL
|
||||
.iter()
|
||||
.map(|&k| super::event::event_name(k))
|
||||
.collect();
|
||||
let before = seen.len();
|
||||
seen.sort_unstable();
|
||||
seen.dedup();
|
||||
assert_eq!(seen.len(), before, "a kind is listed twice in ALL");
|
||||
assert_eq!(before, 36, "36 kinds — update this when the kind set grows");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn colors_match_the_shipped_table() {
|
||||
assert_eq!(default_color(K::Say), [255, 255, 255]);
|
||||
|
||||
@@ -106,6 +106,7 @@ fn standing_label(rank: u32) -> &'static str {
|
||||
/// `0x506f70`; its bare name otherwise), the skill requirement's name off `SkillLine.dbc`, the
|
||||
/// reputation requirement off `Faction.dbc` names (the red check is the engine's, against the
|
||||
/// player's rank map).
|
||||
#[allow(clippy::too_many_arguments)] // one app-resolved catalog per argument, by design
|
||||
fn template_view(
|
||||
t: &ItemInfo,
|
||||
spells: Option<&crate::ui_action::Spells>,
|
||||
@@ -113,6 +114,8 @@ fn template_view(
|
||||
home_area: Option<&str>,
|
||||
factions: Option<&benilla_formats::FactionCatalog>,
|
||||
sub_classes: Option<&benilla_formats::ItemSubClassCatalog>,
|
||||
classes: Option<&benilla_formats::ItemClassCatalog>,
|
||||
icons: Option<&ItemDisplays>,
|
||||
) -> benilla_ui::script::ItemTemplateView {
|
||||
let spell_name = |id: u32| -> Option<String> {
|
||||
spells
|
||||
@@ -128,9 +131,25 @@ fn template_view(
|
||||
inventory_type: t.inventory_type,
|
||||
proficiency_alt: sub_classes.and_then(|c| c.proficiency_alt(t.class, t.subclass)),
|
||||
hide_subclass: sub_classes.is_some_and(|c| c.hides_name(t.class, t.subclass)),
|
||||
// `GetItemInfo`'s type pair. The subclass spelling is `name()` — VerboseName first —
|
||||
// because that is the binding's own two-step (`0x48e311`), and it is deliberately NOT the
|
||||
// `display_name()` the tooltip's type cell prints: a one-handed sword is "One-Handed
|
||||
// Swords" to an addon and "Sword" on the tooltip. Both spellings are in the same row.
|
||||
item_type: classes.and_then(|c| c.name(t.class)).map(str::to_string),
|
||||
item_sub_type: sub_classes
|
||||
.and_then(|c| c.name(t.class, t.subclass))
|
||||
.map(str::to_string),
|
||||
flags: t.flags,
|
||||
bonding: t.bonding,
|
||||
max_count: t.max_count,
|
||||
// The stack size — a DIFFERENT wire field from `max_count` (the account-wide unique cap).
|
||||
// Linen Cloth is `max_count 0, stackable 20`; `GetItemInfo`'s `itemStackCount` is this one.
|
||||
stackable: t.stackable,
|
||||
// The same `ItemDisplayInfo.dbc` icon the bag slots resolve, as a ready
|
||||
// `Interface\Icons\…` path — `GetItemInfo`'s `itemTexture`.
|
||||
icon: icons
|
||||
.and_then(|i| i.catalog.get(t.display_info_id))
|
||||
.and_then(|d| d.icon.clone()),
|
||||
start_quest: t.start_quest,
|
||||
container_slots: t.container_slots,
|
||||
stats: t.stats.clone(),
|
||||
@@ -281,6 +300,10 @@ pub(super) fn feed_item_stats(
|
||||
area_names: Option<Res<crate::ui_quest_log::QuestHeaderNamesRes>>,
|
||||
factions: Option<Res<crate::target::Factions>>,
|
||||
sub_classes: Option<Res<super::ItemSubClasses>>,
|
||||
// `GetItemInfo`'s `itemType` and `itemTexture`: the class-name table, and the same
|
||||
// ItemDisplayInfo icons the bag slots already resolve through.
|
||||
classes: Option<Res<super::ItemClasses>>,
|
||||
icons: Option<Res<ItemDisplays>>,
|
||||
mut pending: Local<std::collections::HashSet<u32>>,
|
||||
mut last_home: Local<Option<String>>,
|
||||
) {
|
||||
@@ -322,6 +345,8 @@ pub(super) fn feed_item_stats(
|
||||
home_area.as_deref(),
|
||||
factions.as_deref().map(|f| f.catalog()),
|
||||
sub_classes.as_deref().map(|s| &s.0),
|
||||
classes.as_deref().map(|c| &c.0),
|
||||
icons.as_deref(),
|
||||
);
|
||||
script.set_item_template(id, view);
|
||||
}
|
||||
|
||||
@@ -926,6 +926,11 @@ pub(crate) struct ItemSubClasses(pub(crate) benilla_formats::ItemSubClassCatalog
|
||||
#[derive(Resource)]
|
||||
pub(crate) struct ItemBagFamilies(pub(crate) benilla_formats::ItemBagFamilyCatalog);
|
||||
|
||||
/// The ItemClass.dbc catalog — what an item's class is *called* ("Weapon", "Container"),
|
||||
/// i.e. `GetItemInfo`'s `itemType`. See [`feed`]'s template resolve.
|
||||
#[derive(Resource)]
|
||||
pub(crate) struct ItemClasses(pub(crate) benilla_formats::ItemClassCatalog);
|
||||
|
||||
/// Startup (after the MPQ chain opens): the item-tooltip DBCs. On failure a resource is simply
|
||||
/// absent — set items render without their SET block, subclass gates read as absent.
|
||||
fn load_item_dbcs(mut commands: Commands, world_assets: Option<Res<benilla_assets::WorldAssets>>) {
|
||||
@@ -948,6 +953,13 @@ fn load_item_dbcs(mut commands: Commands, world_assets: Option<Res<benilla_asset
|
||||
}
|
||||
Err(e) => warn!("ui_items: ItemSubClass.dbc failed to load: {e:#}"),
|
||||
}
|
||||
match benilla_formats::load_item_classes(&mut chain) {
|
||||
Ok(cat) => {
|
||||
info!("ui_items: ItemClass.dbc loaded ({} classes)", cat.len());
|
||||
commands.insert_resource(ItemClasses(cat));
|
||||
}
|
||||
Err(e) => warn!("ui_items: ItemClass.dbc failed to load: {e:#}"),
|
||||
}
|
||||
match benilla_formats::load_item_bag_families(&mut chain) {
|
||||
Ok(cat) => {
|
||||
info!(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! The connection-telemetry feed — the app side of `benilla_ui::script::net_stats`' seam.
|
||||
//!
|
||||
//! One number: the latency `GetNetStats()` reports, which is the main bar's performance ("ping")
|
||||
//! meter's whole input (`ActionBar.xml`'s `BenillaPerformanceBarFrame` polls it every 10 s and
|
||||
//! meter's whole input (`ActionBar.xml`'s `MainMenuBarPerformanceBarFrame` polls it every 10 s and
|
||||
//! colors the bar green/yellow/red). The measurement itself belongs to the net drain — every
|
||||
//! `SMSG_PONG` lands a round trip in [`NetStatus::rtt_ring`], the reference's 16-deep RTT history —
|
||||
//! and this feed only carries its average across the engine boundary (decision 0068 §3: the engine
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
//! composer live in the parent module; this file owns only the per-frame bridge.
|
||||
|
||||
use benilla_protocol::messages::{
|
||||
member_status, GroupLootInfo, GroupMemberEntry, PartyMemberStatsInfo,
|
||||
member_status, GroupLootInfo, GroupMemberEntry, PartyMemberStatsInfo, GROUP_MEMBER_ASSISTANT,
|
||||
};
|
||||
use benilla_ui::script::{
|
||||
PartyMemberInfo, PartyRequest, PartyState, ScriptValue, UiScript, UnitState,
|
||||
PartyMemberInfo, PartyRequest, PartyState, RaidMemberInfo, ScriptValue, UiScript, UnitState,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
|
||||
@@ -31,10 +31,19 @@ pub(super) struct FedParty {
|
||||
|
||||
const PARTY_TOKENS: [&str; 4] = ["party1", "party2", "party3", "party4"];
|
||||
|
||||
/// `GROUPTYPE_RAID` — `SMSG_GROUP_LIST`'s first byte (`0` party, `1` raid; vmangos `Group.h:116`).
|
||||
const GROUPTYPE_RAID: u8 = 1;
|
||||
|
||||
/// The subgroup index in a member's flags byte — bits 0-2 (`GroupMemberEntry::flags`'s own doc;
|
||||
/// the assistant bit `0x80` is its neighbour, and the `0x7f` mask `party_slots` uses is a
|
||||
/// different question, "same subgroup AND same assistant state").
|
||||
const GROUP_MEMBER_SUBGROUP: u8 = 0x07;
|
||||
|
||||
/// Push the roster snapshot + the `party1..party4` unit snapshots into the VM and fire the party
|
||||
/// events on their edges. The per-member unit state is the 0434 §2 **merged view**: a streamed
|
||||
/// member's live descriptor wins; the `PARTY_MEMBER_STATS` snapshot covers the rest — and the
|
||||
/// roster status byte overlays both (the descriptor never carries connected/AFK/DND).
|
||||
#[allow(clippy::too_many_arguments)] // a Bevy system's param list IS its dependency set
|
||||
pub(super) fn feed_party(
|
||||
script: Option<NonSendMut<UiScript>>,
|
||||
group: Res<GroupState>,
|
||||
@@ -42,6 +51,13 @@ pub(super) fn feed_party(
|
||||
stores: Query<&ObjectStore>,
|
||||
self_q: Query<(&Guid, &ObjectStore), With<SelfPlayer>>,
|
||||
factions: Option<Res<crate::target::Factions>>,
|
||||
names: Res<NameCache>,
|
||||
areas: Option<Res<crate::area::AreaTableRes>>,
|
||||
// The leaf area under us, through the SAME accessor `crate::area`'s zone-text resolver uses.
|
||||
// Deliberately not `terrain_stream::CurrentArea` directly: that item is named today only by
|
||||
// the instruments, and naming it from a game module would push it across the world-API wall
|
||||
// (`tests/world_api_wall.rs`, decision 1164) for a value this already answers.
|
||||
here: benilla_world::world_point::WorldPoint,
|
||||
mut fed: Local<FedParty>,
|
||||
) {
|
||||
let Some(mut script) = script else {
|
||||
@@ -59,9 +75,10 @@ pub(super) fn feed_party(
|
||||
// the 0440 byte law) — in a plain party this is simply the roster.
|
||||
let slots: Vec<&GroupMemberEntry> = group.party_slots().collect();
|
||||
|
||||
// The roster-level snapshot (leader/master indices on the Lua scale: 0 = the player). A
|
||||
// raid's other subgroups feed only the count (the engine contract's v1 gap; a leader in
|
||||
// another subgroup reads as index 0 until the raid UI slice).
|
||||
// The roster-level snapshot (leader/master indices on the Lua scale: 0 = the player). These
|
||||
// indices are the PARTY view — our own subgroup's four slots — so in a raid a leader in
|
||||
// another subgroup still reads as index 0 here; the whole-raid answer is `raid` below, which
|
||||
// `GetRaidRosterInfo` reads and which does carry every subgroup.
|
||||
let members: Vec<PartyMemberInfo> = slots
|
||||
.iter()
|
||||
.map(|m| PartyMemberInfo {
|
||||
@@ -100,14 +117,31 @@ pub(super) fn feed_party(
|
||||
}
|
||||
None => ("group".to_string(), None, 0),
|
||||
};
|
||||
// The raid roster — `GetNumRaidMembers`/`GetRaidRosterInfo`/`UnitInRaid` all read this one
|
||||
// list, so the count can never disagree with the array the way it would if we kept both.
|
||||
let me = self_pair.map(|(g, store)| RaidSelf {
|
||||
guid: g.0,
|
||||
flags: group.own_flags,
|
||||
level: store.0.unit_level().unwrap_or(0),
|
||||
area: here.area(),
|
||||
dead: store.0.unit_is_dead(),
|
||||
});
|
||||
let zone_name = |area: u32| {
|
||||
let areas = areas.as_ref()?;
|
||||
// The wire's per-member `zone` is already a top-level zone id; our own `CurrentArea` is
|
||||
// the finest MCNK leaf, so it needs the parent walk first. `top_zone` is idempotent on a
|
||||
// row that is already a zone, which is what lets one resolver serve both.
|
||||
areas
|
||||
.0
|
||||
.name(areas.0.top_zone(area).unwrap_or(area))
|
||||
.map(str::to_string)
|
||||
};
|
||||
let raid = raid_roster(&group, me.as_ref(), &names, &zone_name);
|
||||
|
||||
script.set_party(PartyState {
|
||||
members,
|
||||
leader_index,
|
||||
raid_members: if group.group_type == 1 {
|
||||
group.members.len() as u32 + 1
|
||||
} else {
|
||||
0
|
||||
},
|
||||
raid,
|
||||
loot_method,
|
||||
master_looter,
|
||||
loot_threshold,
|
||||
@@ -160,6 +194,116 @@ pub(super) fn feed_party(
|
||||
}
|
||||
}
|
||||
|
||||
/// The player's own raid row — the one entry `GroupState` cannot supply, because
|
||||
/// `SMSG_GROUP_LIST` never lists the recipient.
|
||||
pub(super) struct RaidSelf {
|
||||
pub(super) guid: u64,
|
||||
/// Our own subgroup bits + the assistant flag, `SMSG_GROUP_LIST`'s second byte.
|
||||
pub(super) flags: u8,
|
||||
pub(super) level: u32,
|
||||
/// The finest area under us (`WorldPoint::area()`), walked to a zone by the caller's resolver
|
||||
/// — the reference reads a zone-id global here (`0xb4e314`) on the live-object arm.
|
||||
pub(super) area: Option<u32>,
|
||||
/// `UNIT_FIELD_HEALTH <= 0` — the reference's live-object arm for return 9, and *only* that:
|
||||
/// deliberately `unit_is_dead` rather than `unit_reads_dead`, so a feigning hunter does not
|
||||
/// take the arm a health test would not.
|
||||
pub(super) dead: bool,
|
||||
}
|
||||
|
||||
/// Build `GetRaidRosterInfo`'s array (decision 0434 §6's roster, wow-re
|
||||
/// `ui/scratch/raid-roster-bindings.md` §2). Empty outside a raid — `GroupState::group_type` is
|
||||
/// `1` only for one — which is what makes `GetNumRaidMembers()` answer `0` in a party.
|
||||
///
|
||||
/// **The player is row 1.** The reference's array contains the local player (it is why
|
||||
/// `UnitInRaid("player")` answers `1`), and the wire's list does not, so the recipient is spliced
|
||||
/// back in here. *Where* the real client puts itself is **not derived** — the note carves the
|
||||
/// binding, not the array's fill order — and nothing observed depends on it: every corpus consumer
|
||||
/// sweeps `1..GetNumRaidMembers()` (or `1..MAX_RAID_MEMBERS`) and keys the result by name.
|
||||
///
|
||||
/// A pure function over plain data so the shape is testable without a second account in a raid:
|
||||
/// the tuple this fills is the part a live raid could confirm and a unit test cannot, but the
|
||||
/// *mapping* — rank, subgroup, the online/dead bit pair — is the part that can be, and is below.
|
||||
fn raid_roster(
|
||||
group: &GroupState,
|
||||
me: Option<&RaidSelf>,
|
||||
names: &NameCache,
|
||||
zone_name: &dyn Fn(u32) -> Option<String>,
|
||||
) -> Vec<RaidMemberInfo> {
|
||||
if group.group_type != GROUPTYPE_RAID {
|
||||
return Vec::new();
|
||||
}
|
||||
// rank: 2 leader · 1 assistant · 0 member. The binding exposes `[edi+0xc]` unadjusted and
|
||||
// wow-re could not derive the convention from its bytes; the corpus can and does —
|
||||
// `ChatLog.lua:351-353` prints `@` for 2 and `*` for 1.
|
||||
let rank_of = |guid: u64, flags: u8| {
|
||||
if guid == group.leader {
|
||||
2
|
||||
} else if flags & GROUP_MEMBER_ASSISTANT != 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
let row = |guid: u64,
|
||||
name: String,
|
||||
flags: u8,
|
||||
level: u32,
|
||||
zone: Option<String>,
|
||||
online: bool,
|
||||
ninth: bool| {
|
||||
let class = names
|
||||
.player_traits(guid)
|
||||
.and_then(|(_, class, _)| crate::ui_unit::class_names(class));
|
||||
RaidMemberInfo {
|
||||
name,
|
||||
guid,
|
||||
rank: rank_of(guid, flags),
|
||||
// Stored 0-based; the binding adds the `0x4bb61a inc`.
|
||||
subgroup: u32::from(flags & GROUP_MEMBER_SUBGROUP),
|
||||
level,
|
||||
class: class.map(|(n, _)| n.to_string()),
|
||||
class_file: class.map(|(_, f)| f.to_string()),
|
||||
zone,
|
||||
online,
|
||||
ninth,
|
||||
}
|
||||
};
|
||||
let mut roster = Vec::with_capacity(group.members.len() + 1);
|
||||
if let Some(me) = me {
|
||||
roster.push(row(
|
||||
me.guid,
|
||||
// An unresolved name takes the reference's name-cache-miss arm at the binding: the
|
||||
// whole 9-tuple becomes the fixed miss tuple, not a half-filled row.
|
||||
names.peek(me.guid).unwrap_or_default().to_string(),
|
||||
me.flags,
|
||||
me.level,
|
||||
me.area.and_then(zone_name),
|
||||
true,
|
||||
me.dead,
|
||||
));
|
||||
}
|
||||
for m in &group.members {
|
||||
let stats = group.stats.get(&m.guid);
|
||||
let online = m.status & member_status::ONLINE != 0;
|
||||
roster.push(row(
|
||||
m.guid,
|
||||
m.name.clone(),
|
||||
m.flags,
|
||||
stats.and_then(|s| s.level).map_or(0, u32::from),
|
||||
stats
|
||||
.and_then(|s| s.zone)
|
||||
.and_then(|z| zone_name(u32::from(z))),
|
||||
online,
|
||||
// The reference's cached arm: `[edi+0x18]` must carry BOTH `0x4` and `0x1`. Our wire
|
||||
// spells those `member_status::DEAD` and `ONLINE` (vmangos `Group.cpp:45-63`), which
|
||||
// is the corroboration recorded on `RaidMemberInfo::ninth` — an offline dead member
|
||||
// answers nil there, and reproducing the conjunction is the point.
|
||||
online && m.status & member_status::DEAD != 0,
|
||||
));
|
||||
}
|
||||
roster
|
||||
}
|
||||
|
||||
/// One member's merged-view unit snapshot (see [`feed_party`]).
|
||||
fn member_unit_state(
|
||||
m: &GroupMemberEntry,
|
||||
@@ -617,4 +761,88 @@ mod tests {
|
||||
group.apply_list(0, 0, vec![], 0x123, None);
|
||||
assert!(!group.test, "the real wire always wins");
|
||||
}
|
||||
|
||||
/// The raid roster mapping — the half a unit test can actually settle. The nine-value *shape*
|
||||
/// is pinned in `benilla-ui`'s own tests; what is pinned here is which wire fact lands in
|
||||
/// which slot, because that is what a live raid would otherwise be the only witness to.
|
||||
#[test]
|
||||
fn the_raid_roster_maps_the_wire_to_get_raid_roster_info() {
|
||||
let mut names = NameCache::default();
|
||||
names.insert_player(0x5E1F, "Me".into(), Some((4, 11, 0))); // Night Elf DRUID
|
||||
names.insert_player(0xA11CE, "Alice".into(), Some((1, 1, 1))); // Human WARRIOR
|
||||
names.insert_player(0xB0B, "Bob".into(), None); // no traits yet
|
||||
|
||||
let member = |guid: u64, name: &str, status: u8, flags: u8| GroupMemberEntry {
|
||||
name: name.into(),
|
||||
guid,
|
||||
status,
|
||||
flags,
|
||||
};
|
||||
let mut group = GroupState {
|
||||
group_type: GROUPTYPE_RAID,
|
||||
own_flags: 0,
|
||||
leader: 0x5E1F,
|
||||
members: vec![
|
||||
// Alice: subgroup 2 (stored), an assistant, online and DEAD.
|
||||
member(
|
||||
0xA11CE,
|
||||
"Alice",
|
||||
member_status::ONLINE | member_status::DEAD,
|
||||
2 | GROUP_MEMBER_ASSISTANT,
|
||||
),
|
||||
// Bob: subgroup 0, offline — and offline-AND-dead, which must NOT set return 9.
|
||||
member(0xB0B, "Bob", member_status::DEAD, 0),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
group.stats.insert(
|
||||
0xA11CE,
|
||||
PartyMemberStatsInfo {
|
||||
level: Some(58),
|
||||
zone: Some(1537),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let zone = |id: u32| (id == 1537).then(|| "Ironforge".to_string());
|
||||
let me = RaidSelf {
|
||||
guid: 0x5E1F,
|
||||
flags: 0,
|
||||
level: 60,
|
||||
area: Some(1537),
|
||||
dead: false,
|
||||
};
|
||||
|
||||
let roster = raid_roster(&group, Some(&me), &names, &zone);
|
||||
assert_eq!(roster.len(), 3, "the player is spliced back in");
|
||||
|
||||
// Row 1 — us. Leader (rank 2), subgroup stored 0, class from our own name-query traits.
|
||||
assert_eq!(roster[0].name, "Me");
|
||||
assert_eq!(roster[0].rank, 2);
|
||||
assert_eq!(
|
||||
roster[0].subgroup, 0,
|
||||
"stored 0-based; the BINDING adds one"
|
||||
);
|
||||
assert_eq!(roster[0].level, 60);
|
||||
assert_eq!(roster[0].class_file.as_deref(), Some("DRUID"));
|
||||
assert_eq!(roster[0].zone.as_deref(), Some("Ironforge"));
|
||||
assert!(roster[0].online && !roster[0].ninth);
|
||||
|
||||
// Row 2 — an assistant, and the online+dead pair the reference's cached arm tests.
|
||||
assert_eq!((roster[1].rank, roster[1].subgroup), (1, 2));
|
||||
assert_eq!(roster[1].level, 58);
|
||||
assert_eq!(roster[1].class_file.as_deref(), Some("WARRIOR"));
|
||||
assert!(roster[1].online && roster[1].ninth, "online AND dead");
|
||||
|
||||
// Row 3 — offline. Return 9 needs BOTH bits, so a dead-but-offline member is nil there,
|
||||
// and an unresolved class is nil rather than a guess.
|
||||
assert!(!roster[2].online, "offline");
|
||||
assert!(!roster[2].ninth, "0x4 without 0x1 is not the arm");
|
||||
assert_eq!(roster[2].class, None);
|
||||
assert_eq!(roster[2].zone, None, "no stats packet, no zone");
|
||||
|
||||
// A PARTY is not a raid: the whole list is empty, so `GetNumRaidMembers()` answers 0
|
||||
// while `IsRaidLeader()` still answers 1 — the pair that surprises people.
|
||||
group.group_type = 0;
|
||||
assert!(raid_roster(&group, Some(&me), &names, &zone).is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,13 +73,13 @@ pub(crate) struct PetFamilyTables {
|
||||
/// the codebase's own "push before firing" rule (`crate::ui_unit`), which held *inside* every
|
||||
/// feed but not *between* them.
|
||||
///
|
||||
/// It cost the Pet tab. `BenillaPetTab_Update` is edge-driven off `UNIT_PET` / `PET_BAR_UPDATE`
|
||||
/// It cost the Pet tab. `PetTab_Update` is edge-driven off `UNIT_PET` / `PET_BAR_UPDATE`
|
||||
/// and its whole predicate is `HasPetUI()`, which is this snapshot. With the three pet feeds
|
||||
/// merely unordered inside [`UnitFeed`], a cold first summon fired both edges while `HasPetUI()`
|
||||
/// still answered "no pet"; the push landed after, and since neither edge repeats, the tab stayed
|
||||
/// down for the rest of the session. Measured live, frame by frame: both events on frame 440 with
|
||||
/// `HasPetUI()=(nil,nil)`, the flip to `(1,nil)` on 441, and the tab still hidden 48 s later —
|
||||
/// while calling `BenillaPetTab_Update()` by hand raised it instantly.
|
||||
/// while calling `PetTab_Update()` by hand raised it instantly.
|
||||
///
|
||||
/// A set rather than a pair of `.before()` calls because the constraint belongs to the *snapshot*,
|
||||
/// not to today's two consumers: anything that later fires a pet event inherits it by ordering
|
||||
@@ -679,7 +679,7 @@ mod tests {
|
||||
/// ordering does. Driven through **one** `app.update()` on a cold pet — the exact live shape,
|
||||
/// where `feed_pet_unit` sees the guid appear and fires while `feed_pet_stats` has or has not
|
||||
/// yet pushed. A handler that reads `HasPetUI()` at that instant is what the shipped
|
||||
/// `BenillaPetTab_Update` is, minus the frames.
|
||||
/// `PetTab_Update` is, minus the frames.
|
||||
#[test]
|
||||
fn unit_pet_reaches_lua_with_has_pet_ui_already_true() {
|
||||
use crate::char_select::ClientState;
|
||||
|
||||
@@ -28,14 +28,16 @@ use bevy::prelude::*;
|
||||
|
||||
use benilla_ui::script::UiScript;
|
||||
|
||||
use crate::char_select::ClientState;
|
||||
|
||||
pub(crate) struct UiSavedPlugin;
|
||||
|
||||
impl Plugin for UiSavedPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(OnExit(ClientState::InWorld), save_on_session_end)
|
||||
.add_systems(Update, save_on_exit);
|
||||
fn build(&self, _app: &mut App) {
|
||||
// **The write is no longer this plugin's own edge.** It is one step of the ordered
|
||||
// shutdown tail in [`crate::ui_script::shutdown_ui_state`], because the reference has
|
||||
// exactly one shutdown (`0x490bd0`) reached from five roots and its steps are ordered
|
||||
// against each other: `PLAYER_LOGOUT` fires before any write, the flat file is written
|
||||
// before the per-addon files, and `AddOns.txt` is last. Three independent systems on the
|
||||
// same edge cannot express that.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,11 +72,12 @@ pub(crate) fn load_saved_variables(script: &mut UiScript) {
|
||||
script.fire_event("VARIABLES_LOADED", vec![]);
|
||||
}
|
||||
|
||||
/// Write the file from the live globals. No-op when nothing is registered — the UI never loaded
|
||||
/// Write the flat file from the live globals — step 3 of the shutdown tail
|
||||
/// ([`crate::ui_script::shutdown_ui_state`]). No-op when nothing is registered — the UI never loaded
|
||||
/// (a glue-only run, a capture), and an empty write would be a wipe rather than a save. The
|
||||
/// reference *does* delete the file when an addon declares nothing, which is a different case:
|
||||
/// there, the addon loaded and its declaration list is genuinely empty.
|
||||
fn save(script: &mut UiScript) {
|
||||
pub(crate) fn save(script: &mut UiScript) {
|
||||
let names = script.saved_variable_names();
|
||||
if names.is_empty() {
|
||||
return;
|
||||
@@ -103,25 +106,6 @@ const HEADER: &str = "\
|
||||
-- Written at logout/exit from the live values; executed as a Lua chunk at UI load.
|
||||
";
|
||||
|
||||
/// `OnExit(InWorld)`: a `/logout` back to the glue, or a disconnect — the reference's
|
||||
/// logout-to-character-select and disconnect roots.
|
||||
fn save_on_session_end(script: Option<NonSendMut<UiScript>>) {
|
||||
if let Some(mut script) = script {
|
||||
save(&mut script);
|
||||
}
|
||||
}
|
||||
|
||||
/// `AppExit`: quitting the client — the reference's quit / application-exit roots. Reads the
|
||||
/// message rather than a state edge because a quit from in-world never leaves `InWorld`.
|
||||
fn save_on_exit(script: Option<NonSendMut<UiScript>>, mut exits: MessageReader<AppExit>) {
|
||||
if exits.read().next().is_none() {
|
||||
return;
|
||||
}
|
||||
if let Some(mut script) = script {
|
||||
save(&mut script);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -94,11 +94,11 @@ fn shipped_action_bar_drives_end_to_end() {
|
||||
s.mouse_button(26.0, 22.0, "LeftButton", false);
|
||||
assert_eq!(s.take_action_uses(), vec![73]);
|
||||
|
||||
// The keybinding entry (the app's key feed runs `BenillaActionButtonDown/Up(i)` on the two
|
||||
// The keybinding entry (the app's key feed runs `ActionButtonDown/Up(i)` on the two
|
||||
// key edges — the ref's ACTIONBUTTONn binding, ActionButton.lua:15-45): UP fires UseAction
|
||||
// directly with no checkCursor (a keybind never places, decision 0216 §7) — but ONLY from
|
||||
// the PUSHED state a DOWN set, so a stray release with no press is the ref's own no-op.
|
||||
s.run("BenillaActionButtonUp(2)").unwrap();
|
||||
s.run("ActionButtonUp(2)").unwrap();
|
||||
assert!(
|
||||
s.take_action_uses().is_empty(),
|
||||
"an Up without a Down is a no-op (the PUSHED gate)"
|
||||
@@ -112,14 +112,14 @@ fn shipped_action_bar_drives_end_to_end() {
|
||||
})
|
||||
.count()
|
||||
};
|
||||
s.run("BenillaActionButtonDown(2)").unwrap();
|
||||
s.run("ActionButtonDown(2)").unwrap();
|
||||
assert_eq!(depressed(&s), 1, "key DOWN shows the pushed texture");
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BenillaActionButton2:GetButtonState()")
|
||||
s.eval::<String>("return ActionButton2:GetButtonState()")
|
||||
.unwrap(),
|
||||
"PUSHED"
|
||||
);
|
||||
s.run("BenillaActionButtonUp(2)").unwrap();
|
||||
s.run("ActionButtonUp(2)").unwrap();
|
||||
assert_eq!(s.take_action_uses(), vec![74], "key '2' fires action 74");
|
||||
assert_eq!(depressed(&s), 0, "key UP restores the normal state");
|
||||
|
||||
@@ -212,9 +212,7 @@ fn state_feedback_drives_cooldown_checked_and_usable_through_the_xml() {
|
||||
}),
|
||||
);
|
||||
s.fire_event("ACTIONBAR_UPDATE_STATE", vec![]);
|
||||
assert!(s
|
||||
.eval::<bool>("return BenillaActionButton1:GetChecked()")
|
||||
.unwrap());
|
||||
assert!(s.eval::<bool>("return ActionButton1:GetChecked()").unwrap());
|
||||
|
||||
// The OOM blue tint (the transcribed UpdateUsable): usable=false + notEnoughMana=true.
|
||||
s.set_action_state(
|
||||
@@ -442,7 +440,7 @@ fn the_action_bar_lock_stops_the_drag_and_leaves_shift_click_alone() {
|
||||
s.resolve();
|
||||
|
||||
s.run(r#"LOCK_ACTIONBAR = "1""#).unwrap();
|
||||
s.run("BenillaActionButton_OnDragStart(BenillaActionButton1)")
|
||||
s.run("BenillaActionButton_OnDragStart(ActionButton1)")
|
||||
.unwrap();
|
||||
assert!(
|
||||
s.cursor_payload().is_none(),
|
||||
@@ -468,7 +466,7 @@ fn the_action_bar_lock_stops_the_drag_and_leaves_shift_click_alone() {
|
||||
);
|
||||
|
||||
// The receiving end is guarded too: the held action cannot be dropped back by a drag…
|
||||
s.run("BenillaActionButton_OnReceiveDrag(BenillaActionButton1)")
|
||||
s.run("BenillaActionButton_OnReceiveDrag(ActionButton1)")
|
||||
.unwrap();
|
||||
assert!(
|
||||
s.cursor_payload().is_some(),
|
||||
@@ -476,7 +474,7 @@ fn the_action_bar_lock_stops_the_drag_and_leaves_shift_click_alone() {
|
||||
);
|
||||
// …and unlocking makes both ends live again.
|
||||
s.run(r#"LOCK_ACTIONBAR = "0""#).unwrap();
|
||||
s.run("BenillaActionButton_OnReceiveDrag(BenillaActionButton1)")
|
||||
s.run("BenillaActionButton_OnReceiveDrag(ActionButton1)")
|
||||
.unwrap();
|
||||
assert!(s.cursor_payload().is_none(), "unlocked, the drop lands");
|
||||
assert_eq!(
|
||||
@@ -614,10 +612,8 @@ fn count_fontstring_follows_is_consumable_action_not_the_bag_count() {
|
||||
// Read the fontstrings by name, not by scanning painted text: a single-character count
|
||||
// ("0") is indistinguishable from a neighbouring button's static HotKey label by content.
|
||||
let count_of = |s: &UiScript, n: u32| {
|
||||
s.eval::<String>(&format!(
|
||||
"return BenillaActionButton{n}Count:GetText() or \"\""
|
||||
))
|
||||
.unwrap()
|
||||
s.eval::<String>(&format!("return ActionButton{n}Count:GetText() or \"\""))
|
||||
.unwrap()
|
||||
};
|
||||
assert_eq!(
|
||||
count_of(&s, 1),
|
||||
@@ -670,7 +666,7 @@ fn shipped_bag_frame_drives_end_to_end() {
|
||||
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
// The bag toggle now seats on the main action bar (BenillaActionBarArtFrame, ActionBar.xml);
|
||||
// The bag toggle now seats on the main action bar (MainMenuBarArtFrame, ActionBar.xml);
|
||||
// load the bar FIRST so the toggle's cross-file relativeTo resolves to the real art frame at
|
||||
// SetPoint time. The app loads ActionBar before BagFrame (load_default_ui order); without it the
|
||||
// anchor would silently fall back to the screen root (it only lands right here by the 1024-wide
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
//! The shipped `assets/ui/AddonList.xml` — the in-game AddOns list (decision 1197).
|
||||
//!
|
||||
//! **The window is the test of the API as much as the API is the test of the window.** This panel
|
||||
//! is written entirely against the eleven AddOn verbs 1191 landed — `GetNumAddOns`,
|
||||
//! `GetAddOnInfo`, `GetAddOnDependencies`, `EnableAddOn`, `DisableAddOn`, `EnableAllAddOns`,
|
||||
//! `DisableAllAddOns` — with no host support of its own. So driving it drives them, through the
|
||||
//! same Lua an addon author would write, which is worth more than a Rust test of each verb.
|
||||
//!
|
||||
//! What these guard: the row painter's three-colour rule and its status column against a registry
|
||||
//! with a real dependency graph; the checkbox round-trip through `EnableAddOn`/`DisableAddOn`;
|
||||
//! Cancel restoring what the panel found (`ResetAddOns` written out, since that verb is
|
||||
//! glue-namespace and absent in-world); the scroll clamp; and the Enable All / Disable All pair.
|
||||
|
||||
use benilla_ui::script::{AddOnInfo, UiScript};
|
||||
|
||||
/// Fonts + the panel, and a registry with a shape worth painting: one plain addon, a library, a
|
||||
/// dependent on that library, and one whose dependency is not installed.
|
||||
fn harness() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"UiPanels.xml",
|
||||
"GameTooltip.xml",
|
||||
"AddonList.xml",
|
||||
] {
|
||||
let text = std::fs::read_to_string(
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("assets/ui")
|
||||
.join(file),
|
||||
)
|
||||
.unwrap();
|
||||
let doc = benilla_ui::framexml::parse(&text).unwrap();
|
||||
let report = benilla_ui::loader::load(&s, &doc, &|_| None);
|
||||
assert!(
|
||||
report.errors.is_empty(),
|
||||
"{file}: loader errors: {:?}",
|
||||
report.errors
|
||||
);
|
||||
}
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
let info = |name: &str, title: &str, deps: &[&str]| AddOnInfo {
|
||||
name: name.into(),
|
||||
title: Some(title.into()),
|
||||
notes: Some(format!("{name}'s notes")),
|
||||
url: None,
|
||||
secure: false,
|
||||
load_on_demand: false,
|
||||
dependencies: deps.iter().map(|d| (*d).to_string()).collect(),
|
||||
directives: Default::default(),
|
||||
files: Vec::new(),
|
||||
saved_variables: Vec::new(),
|
||||
saved_variables_per_character: Vec::new(),
|
||||
enabled: true,
|
||||
loaded: false,
|
||||
};
|
||||
s.register_addons(
|
||||
vec![
|
||||
info("Plain", "Plain Addon", &[]),
|
||||
info("Lib", "The Library", &[]),
|
||||
info("Needs", "Needs The Library", &["Lib"]),
|
||||
info("Orphan", "Orphaned", &["Nowhere"]),
|
||||
],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
s
|
||||
}
|
||||
|
||||
/// The text of row `i`'s title and status, as the panel painted them.
|
||||
fn row(s: &UiScript, i: usize) -> (String, String) {
|
||||
let title = s
|
||||
.eval::<String>(&format!("return AddonListEntry{i}Title:GetText() or ''"))
|
||||
.unwrap();
|
||||
let status = s
|
||||
.eval::<String>(&format!("return AddonListEntry{i}Status:GetText() or ''"))
|
||||
.unwrap();
|
||||
(title, status)
|
||||
}
|
||||
|
||||
/// The panel paints the registry: title (`## Title` else the folder name), the reference's status
|
||||
/// token, and a checkbox per row — all through `GetAddOnInfo`.
|
||||
#[test]
|
||||
fn the_panel_paints_the_registry_through_the_addon_api() {
|
||||
let s = harness();
|
||||
s.run("AddonList_Show()").unwrap();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
assert!(s.eval::<bool>("return AddonListFrame:IsVisible()").unwrap());
|
||||
assert_eq!(
|
||||
row(&s, 1).0,
|
||||
"Plain Addon",
|
||||
"`## Title` wins over the folder"
|
||||
);
|
||||
assert_eq!(row(&s, 3).0, "Needs The Library");
|
||||
|
||||
// Rows past the registry are hidden, not left showing a stale addon.
|
||||
assert!(
|
||||
!s.eval::<bool>("return AddonListEntry5:IsVisible()")
|
||||
.unwrap(),
|
||||
"row 5 has no addon behind it"
|
||||
);
|
||||
// Four addons is well under MAX_ADDONS_DISPLAYED, so the scroll pair stays hidden — the
|
||||
// reference's own GlueScrollFrame_Update condition.
|
||||
assert!(!s
|
||||
.eval::<bool>("return AddonListScrollUp:IsVisible()")
|
||||
.unwrap());
|
||||
|
||||
// A dependency that is not installed is a status, not a checkbox state.
|
||||
assert_eq!(
|
||||
row(&s, 4).1,
|
||||
"DEP_MISSING",
|
||||
"the reference's own reason token, shown when GlobalStrings has no ADDON_ string for it"
|
||||
);
|
||||
}
|
||||
|
||||
/// The checkbox round-trips through `EnableAddOn`/`DisableAddOn`, and the effect on a *dependent*
|
||||
/// shows up in its status column — which is the whole reason the panel re-reads rather than
|
||||
/// caching.
|
||||
#[test]
|
||||
fn a_checkbox_click_disables_through_the_api_and_the_dependent_says_so() {
|
||||
let s = harness();
|
||||
s.run("AddonList_Show()").unwrap();
|
||||
|
||||
// Row 2 is the library. Click its checkbox the way the XML does.
|
||||
s.run("AddonList_ToggleEntry(2)").unwrap();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
assert_eq!(row(&s, 2).1, "DISABLED", "the player turned it off");
|
||||
assert_eq!(
|
||||
row(&s, 3).1,
|
||||
"DEP_DISABLED",
|
||||
"its dependent reports the reference's dependency token, not a stale clean row"
|
||||
);
|
||||
assert!(
|
||||
!s.eval::<bool>("return AddonListEntry2Check:GetChecked()")
|
||||
.unwrap(),
|
||||
"and the checkbox follows the registry rather than its own memory"
|
||||
);
|
||||
|
||||
// Clicking again re-enables, and the dependent recovers.
|
||||
s.run("AddonList_ToggleEntry(2)").unwrap();
|
||||
assert_eq!(row(&s, 2).1, "");
|
||||
assert_eq!(row(&s, 3).1, "");
|
||||
}
|
||||
|
||||
/// **Cancel restores what the panel found** — the reference's `ResetAddOns`, written out in Lua
|
||||
/// because that verb is glue-namespace and deliberately absent in-world (1191 §6).
|
||||
#[test]
|
||||
fn cancel_restores_the_state_the_panel_opened_with() {
|
||||
let s = harness();
|
||||
s.run("AddonList_Show()").unwrap();
|
||||
s.run("AddonList_ToggleEntry(1) AddonList_ToggleEntry(2)")
|
||||
.unwrap();
|
||||
assert_eq!(row(&s, 1).1, "DISABLED");
|
||||
|
||||
s.run("AddonList_OnCancel()").unwrap();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert!(!s.eval::<bool>("return AddonListFrame:IsVisible()").unwrap());
|
||||
|
||||
// Re-open: nothing the cancelled session did survived.
|
||||
s.run("AddonList_Show()").unwrap();
|
||||
assert_eq!(row(&s, 1).1, "");
|
||||
assert_eq!(row(&s, 2).1, "");
|
||||
}
|
||||
|
||||
/// Okay keeps the edit — the other half of the pair, and the one that would silently pass if
|
||||
/// `AddonList_OnOk` accidentally called the restore loop.
|
||||
#[test]
|
||||
fn okay_keeps_the_edit() {
|
||||
let s = harness();
|
||||
s.run("AddonList_Show()").unwrap();
|
||||
s.run("AddonList_ToggleEntry(1)").unwrap();
|
||||
s.run("AddonList_OnOk()").unwrap();
|
||||
assert!(!s.eval::<bool>("return AddonListFrame:IsVisible()").unwrap());
|
||||
|
||||
s.run("AddonList_Show()").unwrap();
|
||||
assert_eq!(row(&s, 1).1, "DISABLED", "Okay is not a disguised Cancel");
|
||||
}
|
||||
|
||||
/// Enable All / Disable All go through the API's own pair, and the panel repaints.
|
||||
#[test]
|
||||
fn enable_all_and_disable_all_move_every_row() {
|
||||
let s = harness();
|
||||
s.run("AddonList_Show()").unwrap();
|
||||
|
||||
s.run("AddonList_SetAll(nil)").unwrap();
|
||||
for i in 1..=4 {
|
||||
assert_eq!(row(&s, i).1, "DISABLED", "row {i} after Disable All");
|
||||
}
|
||||
|
||||
s.run("AddonList_SetAll(1)").unwrap();
|
||||
assert_eq!(row(&s, 1).1, "");
|
||||
assert_eq!(
|
||||
row(&s, 4).1,
|
||||
"DEP_MISSING",
|
||||
"Enable All cannot fix a dependency that is not installed — the status is not a checkbox"
|
||||
);
|
||||
}
|
||||
|
||||
/// The scroll offset clamps at both ends, and a list that fits never scrolls.
|
||||
#[test]
|
||||
fn the_scroll_offset_clamps() {
|
||||
let s = harness();
|
||||
s.run("AddonList_Show()").unwrap();
|
||||
s.run("AddonList_Scroll(5)").unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return AddonList.offset").unwrap(),
|
||||
0,
|
||||
"four addons fit in nineteen rows — there is nothing to scroll"
|
||||
);
|
||||
s.run("AddonList_Scroll(-5)").unwrap();
|
||||
assert_eq!(s.eval::<i64>("return AddonList.offset").unwrap(), 0);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// **The ESC-menu path, end to end** — the director's ask stated as a test: open the game menu,
|
||||
/// click AddOns, and the list is up with the menu gone.
|
||||
///
|
||||
/// Loaded in the shipped manifest's own order (`AddonList.xml` before `GameMenuFrame.xml`), which
|
||||
/// is what makes the button's `AddonList_Toggle()` resolve at click time. A test that loaded them
|
||||
/// the other way round would pass on the button's `IsEnabled()` and fail on the click, which is
|
||||
/// exactly the seam worth covering.
|
||||
#[test]
|
||||
fn the_esc_menu_addons_entry_opens_the_list() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"UiPanels.xml",
|
||||
"GameTooltip.xml",
|
||||
"AddonList.xml",
|
||||
"GameMenuFrame.xml",
|
||||
] {
|
||||
let text = std::fs::read_to_string(
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("assets/ui")
|
||||
.join(file),
|
||||
)
|
||||
.unwrap();
|
||||
let doc = benilla_ui::framexml::parse(&text).unwrap();
|
||||
let report = benilla_ui::loader::load(&s, &doc, &|_| None);
|
||||
assert!(report.errors.is_empty(), "{file}: {:?}", report.errors);
|
||||
}
|
||||
|
||||
s.run("ShowUIPanel(GameMenuFrame)").unwrap();
|
||||
assert!(
|
||||
s.eval::<bool>("return GameMenuButtonAddOns:IsEnabled()")
|
||||
.unwrap(),
|
||||
"the entry is live — it stopped being GameMenuButton_Pending in 1197"
|
||||
);
|
||||
|
||||
s.run("GameMenuButtonAddOns:Click()").unwrap();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert!(
|
||||
s.eval::<bool>("return AddonListFrame:IsVisible()").unwrap(),
|
||||
"the list came up"
|
||||
);
|
||||
assert!(
|
||||
!s.eval::<bool>("return GameMenuFrame:IsVisible()").unwrap(),
|
||||
"and the menu went away — the Macros entry's own body shape"
|
||||
);
|
||||
}
|
||||
|
||||
/// **ESC cancels the list**, the reference's own `AddonList_OnKeyDown` arm — and it discards.
|
||||
///
|
||||
/// This engine gives plain frames no raw key events, so the window joins `ToggleGameMenu`'s
|
||||
/// shared ESC chain like the stack-split spinner and the world map. The rung sits above the
|
||||
/// options window and below the popup engine, so a confirm dialog still wins the key.
|
||||
#[test]
|
||||
fn escape_cancels_the_list_and_discards_the_edit() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
for file in [
|
||||
"Fonts.xml",
|
||||
"UiPanels.xml",
|
||||
"GameTooltip.xml",
|
||||
"AddonList.xml",
|
||||
] {
|
||||
let text = std::fs::read_to_string(
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("assets/ui")
|
||||
.join(file),
|
||||
)
|
||||
.unwrap();
|
||||
let doc = benilla_ui::framexml::parse(&text).unwrap();
|
||||
let report = benilla_ui::loader::load(&s, &doc, &|_| None);
|
||||
assert!(report.errors.is_empty(), "{file}: {:?}", report.errors);
|
||||
}
|
||||
s.register_addons(
|
||||
vec![benilla_ui::script::AddOnInfo {
|
||||
name: "Solo".into(),
|
||||
title: Some("Solo".into()),
|
||||
notes: None,
|
||||
url: None,
|
||||
secure: false,
|
||||
load_on_demand: false,
|
||||
dependencies: Vec::new(),
|
||||
directives: Default::default(),
|
||||
files: Vec::new(),
|
||||
saved_variables: Vec::new(),
|
||||
saved_variables_per_character: Vec::new(),
|
||||
enabled: true,
|
||||
loaded: false,
|
||||
}],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
s.run("AddonList_Show()").unwrap();
|
||||
s.run("AddonList_ToggleEntry(1)").unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>("return AddonListEntry1Status:GetText() or ''")
|
||||
.unwrap(),
|
||||
"DISABLED"
|
||||
);
|
||||
|
||||
s.run("ToggleGameMenu()").unwrap();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert!(
|
||||
!s.eval::<bool>("return AddonListFrame:IsVisible()").unwrap(),
|
||||
"ESC put the list away"
|
||||
);
|
||||
s.run("AddonList_Show()").unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>("return AddonListEntry1Status:GetText() or ''")
|
||||
.unwrap(),
|
||||
"",
|
||||
"and discarded the edit — ESC is Cancel, not Okay"
|
||||
);
|
||||
}
|
||||
|
||||
/// The hover tooltip runs — `AddonTooltip_Update`'s three lines (title, notes, dependencies) on
|
||||
/// the shared `GameTooltip` rather than a private frame.
|
||||
///
|
||||
/// Worth its own test because it is the one path the other seven never touch: a load error would
|
||||
/// have shown up in the harness, but a *runtime* error in a hover handler goes to
|
||||
/// `UiScript::errors` and nothing else, which is exactly the silent kind.
|
||||
#[test]
|
||||
fn hovering_a_row_raises_the_tooltip_with_notes_and_dependencies() {
|
||||
let s = harness();
|
||||
s.run("AddonList_Show()").unwrap();
|
||||
|
||||
// Row 3 is `Needs`, which depends on `Lib` — so it exercises both optional lines.
|
||||
s.run("AddonList_RowEnter(AddonListEntry3)").unwrap();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert!(s
|
||||
.eval::<bool>("return GameTooltip:IsOwned(AddonListEntry3)")
|
||||
.unwrap());
|
||||
|
||||
s.run("AddonList_RowLeave()").unwrap();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
// And a row with no dependencies takes the other branch without erroring.
|
||||
s.run("AddonList_RowEnter(AddonListEntry1)").unwrap();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user