diff --git a/crates/benilla-app/assets/ui/BagFrame.xml b/crates/benilla-app/assets/ui/BagFrame.xml
index e94aa7fc..967a9c38 100644
--- a/crates/benilla-app/assets/ui/BagFrame.xml
+++ b/crates/benilla-app/assets/ui/BagFrame.xml
@@ -1174,7 +1174,8 @@
SetDesaturation(b.icon, nil)
end
-- The four slots' own paint decides their restored colour: an item locked on the cursor
- -- wants 0.4 grey, not the flat white SetDesaturation(nil) just left.
+ -- wants its 0.4 grey back, which the un-desaturate above does not restore (it clears
+ -- the greyscale flag, not the tint).
for i = 1, 4 do
BenillaBagBarSlot_Update(getglobal("CharacterBag" .. (i - 1) .. "Slot"))
end
diff --git a/crates/benilla-app/assets/ui/BinderConfirm.xml b/crates/benilla-app/assets/ui/BinderConfirm.xml
new file mode 100644
index 00000000..f9f8dc9f
--- /dev/null
+++ b/crates/benilla-app/assets/ui/BinderConfirm.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+ self:RegisterEvent("CONFIRM_BINDER")
+
+ BenillaBinderConfirmDriver_OnEvent()
+
+
+
diff --git a/crates/benilla-app/assets/ui/FriendsFrame.xml b/crates/benilla-app/assets/ui/FriendsFrame.xml
index d484f437..5ed4afb5 100644
--- a/crates/benilla-app/assets/ui/FriendsFrame.xml
+++ b/crates/benilla-app/assets/ui/FriendsFrame.xml
@@ -15,15 +15,36 @@
row's *button* is 31 tall while the scroll step is 34; both are the ref's).
SCOPE (stated, not hidden):
- · Tab 3 GUILD is present but DISABLED — which is exactly what a character in no guild
- sees in the reference (`InGuildCheck` → PanelTemplates_DisableTab). benilla has no
- guild arc yet (decision 0434 §1 keeps it separate), so it is permanently that.
+ · Tab 3 GUILD is LIVE (decision 1257's guild arc). It is enabled or disabled by
+ `InGuildCheck` off `IsInGuild()` — greyed and un-clickable for a guildless character,
+ exactly as the reference greys it, and live the moment the server says we are in one.
+ The four guild windows below (GuildFrame, GuildControlPopupFrame, GuildInfoFrame,
+ GuildMemberDetailFrame) answer to the ref's FriendsFrame.xml l.1719-3438 as their
+ SPECIFICATION — every global, frame, template, event, texture, sound and number below
+ is the contract addons bind to — but the code is ours. It did not start that way: the
+ guild landing transcribed those windows and said so here, which is the thing the hard
+ rule forbids outright, so they were rewritten in our own terms (decision 1334).
· Tab 4 RAID is NOT built. The ref's RaidFrame is the party/raid management pane, and
decision 0434 §6 defers the raid UI wholesale; an empty fourth panel would read as a
- bug, so the tab is absent until that arc lands.
+ bug, so the tab is absent until that arc lands. That is why `PanelTemplates_SetNumTabs`
+ says 3 where the ref says 4 — guild is tab 3 of 3 here, tab 3 of 4 there — and why
+ `FriendsFrame_Update`/`FRIENDSFRAME_SUBFRAMES` have no `selectedTab == 4` arm.
+ · FOUNDING a guild is out of scope and deliberately so (1257's own boundary): no
+ GuildRegistrar / PetitionFrame / TabardFrame, no charter or emblem path. Nothing in it
+ is reachable until you are IN a guild, which is what this file now covers.
· The row right-click menu IS the shared UnitPopup "FRIEND" menu, addressed by name
- (see `BenillaFriendsFrame_ShowDropdown`). Its TARGET/GUILD_* rows stay hidden by
- UnitPopup's own deferred set, so what opens is Whisper / Invite / Cancel.
+ (see `BenillaFriendsFrame_ShowDropdown`). Its TARGET row stays hidden by UnitPopup's
+ own deferred set; its GUILD_PROMOTE/GUILD_LEAVE rows are live but gate themselves on
+ `GuildFrame:IsVisible()`, so a friends-list or /who row still opens
+ Whisper / Invite / Cancel and a guild roster row opens more.
+
+ THE ENGINE API THIS FILE NEEDS. The guild windows are Lua over ~47 engine globals
+ (`GetGuildRosterInfo`, `GuildControlGetRankFlags`, `IsInGuild`, …) bound in
+ `benilla-ui/src/script/guild.rs`. Two of them are read at LOAD time, so this file does not
+ load standalone without them: `FriendsFrame_OnLoad` calls `SetGuildRosterSelection(0)` and
+ `GetGuildRosterMOTD()` (both the reference's own load-time reads), and
+ `GuildControlPopupFrame_OnLoad` calls
+ `GuildControlGetRankFlags()`. That is the reference's own shape, not ours.
-->
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ BenillaGuildRankCheckbox_OnClick(self)
+
+
+
+
+
+
+
+
@@ -1146,6 +2432,803 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RegisterForSave("SHOW_OFFLINE_GUILD_MEMBERS")
+ this:RegisterEvent("VARIABLES_LOADED")
+
+
+ if event == "VARIABLES_LOADED" then
+ SetGuildRosterShowOffline(SHOW_OFFLINE_GUILD_MEMBERS)
+ this:SetChecked(GetGuildRosterShowOffline())
+ end
+
+
+ BenillaGuildOfflineToggle_OnClick(self:GetChecked())
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ self.updateFunc = GuildStatus_Update
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GuildRoster()
+ BenillaGuild_HideSatellitesExcept("GuildInfoFrame")
+ BenillaFauxScrollFrame_OnMouseWheel(GuildListScrollFrame, arg1)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GuildControlPopupFrameDropDown_OnLoad()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ this:SetText(GuildControlGetRankName(1))
+ this:ClearFocus()
+
+ GuildControlPopup_MarkDirty()
+
+ self:ClearFocus()
+ GuildControlPopup_MarkDirty()
+
+ this:ClearFocus()
+
+
+
+
+
+ GuildControlPopupFrame_OnLoad()
+ GuildControlPopupFrame_OnShow()
+ GuildControlPopupFrame_OnHide()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GuildInfoFrame_ClaimFocus()
+ GuildInfoFrame_ClaimFocus()
+
+
+
+
+
+ this:ClearFocus()
+
+ GuildInfoEditBox_FollowCaret(self)
+
+
+
+
+
+
+
+ BenillaGuildPane_TooltipBackdrop(self)
+ GuildInfoFrame_Update()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ BenillaGuildPane_TooltipBackdrop(self, 0.5, 0.25)
+ StaticPopup_Show("SET_GUILDPLAYERNOTE")
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ BenillaGuildPane_TooltipBackdrop(self, 0.5, 0.25)
+ StaticPopup_Show("SET_GUILDOFFICERNOTE")
+
+
+
+
+
-
+
@@ -1179,4 +3271,19 @@
FriendsFrame_OnEvent()
+
+
+
+
+
+
+
+ this:RegisterEvent("GUILD_INVITE_REQUEST")
+ this:RegisterEvent("GUILD_INVITE_CANCEL")
+
+ BenillaGuildInviteDriver_OnEvent()
+
+
diff --git a/crates/benilla-app/assets/ui/GossipFrame.xml b/crates/benilla-app/assets/ui/GossipFrame.xml
index 2d444d76..e350270e 100644
--- a/crates/benilla-app/assets/ui/GossipFrame.xml
+++ b/crates/benilla-app/assets/ui/GossipFrame.xml
@@ -80,12 +80,28 @@
-- The Era icon-type string → the real Blizzard gossip-icon texture (resolves from the MPQ at
-- render). Unmapped types fall back to the chat bubble (the app already defaults unknowns to
-- "gossip", so this is belt-and-braces).
+ --
+ -- These are ALL TEN the 1.12 install ships under `Interface\GossipFrame\*GossipIcon.blp`.
+ -- The reference builds the path by pasting the type string straight in (ref
+ -- GossipFrame.lua:123), so the shipped art is the whole vocabulary a row can draw. The app
+ -- indexes the wire icon byte into the client's own name table (`ui_gossip::
+ -- GOSSIP_ICON_TYPES`, VERIFIED at 0x84b7ac — decisions 1331/1335); four of these
+ -- (healer/banker/petition/tabard) were unreachable before 1331, which is why the
+ -- innkeeper's bind line drew a speech bubble (B249).
+ --
+ -- The client's table has an ELEVENTH name, `auctioneer` at index 10, and 5875 ships no
+ -- texture for it — the reference draws nothing there. No key for it here on purpose: our
+ -- lookup then falls back to the chat bubble, which is the better wrong answer than a
+ -- missing texture, and `ui_gossip`'s test pins the absence so nobody invents a path.
BENILLA_GOSSIP_ICONS = {
gossip = "Interface\\GossipFrame\\GossipGossipIcon",
vendor = "Interface\\GossipFrame\\VendorGossipIcon",
taxi = "Interface\\GossipFrame\\TaxiGossipIcon",
trainer = "Interface\\GossipFrame\\TrainerGossipIcon",
+ healer = "Interface\\GossipFrame\\HealerGossipIcon",
binder = "Interface\\GossipFrame\\BinderGossipIcon",
+ banker = "Interface\\GossipFrame\\BankerGossipIcon",
+ petition = "Interface\\GossipFrame\\PetitionGossipIcon",
tabard = "Interface\\GossipFrame\\TabardGossipIcon",
battlemaster = "Interface\\GossipFrame\\BattleMasterGossipIcon",
}
diff --git a/crates/benilla-app/assets/ui/ItemButtonTemplate.xml b/crates/benilla-app/assets/ui/ItemButtonTemplate.xml
index c2a89794..470b84e1 100644
--- a/crates/benilla-app/assets/ui/ItemButtonTemplate.xml
+++ b/crates/benilla-app/assets/ui/ItemButtonTemplate.xml
@@ -164,10 +164,11 @@
-- The reference asks the texture itself whether the desaturating shader is available
-- (`local shaderSupported = icon:SetDesaturated(desaturated)`) and falls through to a
-- flat grey tint when it is not — 1.12 ran on fixed-function hardware where that was
- -- the ordinary answer. benilla has no `Texture:SetDesaturated` at all (it is the top
- -- row of the harness's missing-METHODS ranking, 61 corpus addons), so the feature test
- -- is on the METHOD's existence and the no-shader branch is the one we take. When the
- -- real verb lands this line starts using it with nothing else to change.
+ -- the ordinary answer. Since decision 1327 benilla's answer is YES: the icon greys to
+ -- luminance in the shader and wears the caller's own tint on top, which is what the
+ -- ref's own `(1, 0.65, 0.65, 0.65)` and `(locked, 0.5, 0.5, 0.5)` calls are asking
+ -- for. The `icon.SetDesaturated and` guard stays for a region leaf without the verb;
+ -- the flat-grey arm below is then the ref's own fallback, unchanged.
local shaderSupported = icon.SetDesaturated and icon:SetDesaturated(desaturated)
-- Undesaturated is full colour whatever the caller passed; desaturated is the caller's
-- own tint when they named one AND the shader took it, and the flat grey otherwise.
diff --git a/crates/benilla-app/assets/ui/MacroFrame.xml b/crates/benilla-app/assets/ui/MacroFrame.xml
index 61513144..ba632752 100644
--- a/crates/benilla-app/assets/ui/MacroFrame.xml
+++ b/crates/benilla-app/assets/ui/MacroFrame.xml
@@ -233,15 +233,20 @@
BenillaMacroFrameText_Resize()
end
- -- ref l.122-126. The engine calls nothing by this name; the spellbook's shift-click does
- -- (SpellBookFrame.xml, ref SpellBookFrame.lua:272-280) — the "shift click a spell in your
- -- spellbook with the macro UI open" line MACRO_HELP_TEXT_LINE5 advertises.
+ -- ref MacroFrame.lua:93-97. The engine calls nothing by this name; the spellbook's
+ -- SHIFT-click does (SpellBookFrame.xml, ref SpellBookFrame.lua:271-283) — the "Shift click
+ -- a spell in your spellbook with the macro UI open" line MACRO_HELP_TEXT_LINE5 advertises.
+ --
+ -- Three things here are the reference's, not oversights (B248 reported the first as a bug):
+ -- the line arrives WHOLE (`/cast `, never the bare name — that insert is a later
+ -- client's `ChatEdit_InsertLink`), it is appended with NO separator, and the visibility
+ -- gate is the BODY BOX's, so a shift-click with the name/icon popup up writes nothing.
+ -- The dirty flag and the scroll refit are the box's own OnTextChanged, exactly as they are
+ -- for a typed character — this function is the ref's three lines and stays them.
function BenillaMacroFrame_AddMacroLine(line)
local box = getglobal("BenillaMacroFrameText")
if box:IsVisible() then
box:SetText((box:GetText() or "") .. line)
- getglobal("BenillaMacroFrame").textChanged = 1
- BenillaMacroFrameText_Resize()
end
end
diff --git a/crates/benilla-app/assets/ui/MailFrame.xml b/crates/benilla-app/assets/ui/MailFrame.xml
index b569c055..121da0fb 100644
--- a/crates/benilla-app/assets/ui/MailFrame.xml
+++ b/crates/benilla-app/assets/ui/MailFrame.xml
@@ -21,9 +21,12 @@
RENDER APPROXIMATIONS (the only geometry-affecting deviations; every other measurement in the
markup below is the reference's own):
- · Unread/read row **desaturation**: the engine has no `SetDesaturation`, so the read icon +
- slot ring grey via vertex color (0.5) — ref MailFrame.lua l.130-141's SetVertexColor legs
- are exact, only the icon's desaturation is the vertex-grey stand-in.
+ · Unread/read row **desaturation**: the read icon + slot ring grey via vertex color (0.5)
+ — ref MailFrame.lua l.130-141's SetVertexColor legs are exact, only the icon's
+ desaturation is the vertex-grey stand-in. The engine HAS `SetDesaturation` since
+ decision 1327 (it did not when this was written); routing the read icon through it is a
+ known, unclaimed gap — it needs the ref's own l.130-141 legs read back first, so that
+ the restore side is the reference's and not an invention.
· The **body scroll**: `OpenMail/SendMailScrollFrame` (296×257 at 21,-97) is transcribed as a
plain Frame at that exact rect with the body FontString/EditBox INSIDE it (no live
scrollbar); a very long letter clips. The stationery backdrop + scrollbar-art geometry are
@@ -172,8 +175,9 @@
subjectText:SetText(subject)
local senderText = getglobal("MailItem"..i.."Sender")
senderText:SetText(sender)
- -- Unread gold / read grey (ref l.130-141; the read icon's SetDesaturation is our
- -- vertex-grey stand-in — the engine has no desaturation).
+ -- Unread gold / read grey (ref l.130-141; the read icon's SetDesaturation is
+ -- still our vertex-grey stand-in — see this file's header for why 1327's real
+ -- desaturation has not been wired in here yet).
if wasRead then
senderText:SetTextColor(0.75, 0.75, 0.75)
subjectText:SetTextColor(0.75, 0.75, 0.75)
diff --git a/crates/benilla-app/assets/ui/SpellBookFrame.xml b/crates/benilla-app/assets/ui/SpellBookFrame.xml
index 134c95ae..d566831d 100644
--- a/crates/benilla-app/assets/ui/SpellBookFrame.xml
+++ b/crates/benilla-app/assets/ui/SpellBookFrame.xml
@@ -50,9 +50,6 @@
slot glow while its form is up), but the trade-skill-window arm of the engine
predicate is unmodeled (the same named gap `ui_action`'s IsCurrentAction carries),
so those four events would re-read an arm that never fires — omitted with it.
- - The MacroFrame branch (`SpellButton_OnClick`'s shift-click "already in a macro editor"
- fork): no macro window exists — shift-click always falls to the ref's own "else"
- (`PickupSpell`), exactly what the ref itself does whenever no MacroFrame is visible.
- The `this.updateTooltip`/`OnUpdate` re-poll loop around the hover: `GameTooltip:SetSpell`
itself is LIVE (the engine channel, decision 0276's line law; it reads whichever book
`bookType` names — 1050), but its per-frame re-poll is dropped, since nothing in the
@@ -361,9 +358,15 @@
elseif IsShiftKeyDown() then
-- ref SpellButton_OnClick l.271-283 (LIVE since decision 0983): with the macro
-- editor open a shift-click APPENDS `/cast [()]` to the body instead of
- -- picking the spell up — the behaviour MACRO_HELP_TEXT_LINE5 advertises ("shift
+ -- picking the spell up — the behaviour MACRO_HELP_TEXT_LINE5 advertises ("Shift
-- click a spell in your spellbook with the macro UI open to add it to your macro").
-- The `/cast` alias comes from the shipped strings (SLASH_CAST1), never typed here.
+ --
+ -- DO NOT "FIX" THIS TO INSERT THE BARE NAME (B248, decision 1328). The whole
+ -- `/cast` line is what 1.12.1 writes; the bare-name-at-the-cursor insert people
+ -- remember is a LATER client's (`ChatEdit_InsertLink`). And the gate is SHIFT,
+ -- not the mouse button: a plain right click on this page casts, same as a left one
+ -- — pinned both ways in `ui_script::spellbook_tests`.
local macroFrame = getglobal("BenillaMacroFrame")
if macroFrame and macroFrame:IsVisible() then
local spellName, subSpellName = GetSpellName(id, SpellBookFrame.bookType)
diff --git a/crates/benilla-app/assets/ui/TalentFrame.xml b/crates/benilla-app/assets/ui/TalentFrame.xml
index 44e92d5f..be3c3fed 100644
--- a/crates/benilla-app/assets/ui/TalentFrame.xml
+++ b/crates/benilla-app/assets/ui/TalentFrame.xml
@@ -118,10 +118,15 @@
(Blizzard_TalentUITemplates.xml)'s own additions (the EmptySlot backdrop, RankBorder, Rank
text) — CharacterFrame.xml's own `BenillaPaperDollItemSlotTemplate` is the identical-shape
precedent for this whole reconstruction move.
- - `SetItemButtonTexture`/`SetItemButtonDesaturated`: no engine equivalent (ENGINE API note).
- The icon Texture child (`$parentIconTexture`) is `SetTexture`'d directly; desaturation is
- approximated by `SetVertexColor(0.65, 0.65, 0.65)` / restored to `(1, 1, 1)`
- (CharacterFrame.xml:873's identical gray-dim convention for its own equip slots).
+ - `SetItemButtonTexture`: no engine equivalent (ENGINE API note). The icon Texture child
+ (`$parentIconTexture`) is `SetTexture`'d directly.
+ - `SetItemButtonDesaturated` IS the ref's own verb here (ItemButtonTemplate.xml, loaded far
+ above this file in the .toc), called exactly as the ref calls it. It used to be open-coded
+ as `SetVertexColor(0.65, 0.65, 0.65)` / restored to `(1, 1, 1)`, because the engine had no
+ desaturation and a dim tint was the whole of what a grey-out could be. Decision 1327 gave
+ the engine `Texture:SetDesaturated`, so the ref's verb now lands as greyscale AND the 0.65
+ dim — B162: at 0 talent points every unlearned icon in the real client goes greyscale, and
+ a 0.65 multiply on colourful art does not read as greyed out at all.
Engine-gap notes (transcribed anyway per this file's own instructions, or worked around):
- `text=` XML attributes are a LITERAL, verbatim `SetText` in this engine — verified,
@@ -462,8 +467,7 @@
-- Talent must meet prereqs or the player must have no points to spend
if BenillaTalentFrame_SetPrereqs(tier, column, forceDesaturated, tierUnlocked,
GetTalentPrereqs(selectedTab, i)) and meetsPrereq then
- -- SetItemButtonDesaturated(button, nil): restore full color.
- getglobal(buttonName .. "IconTexture"):SetVertexColor(1, 1, 1)
+ SetItemButtonDesaturated(button, nil)
if rank < maxRank then
-- Rank is green if not maxed out
@@ -478,9 +482,7 @@
getglobal(buttonName .. "RankBorder"):Show()
getglobal(buttonName .. "Rank"):Show()
else
- -- SetItemButtonDesaturated(button, 1, 0.65, 0.65, 0.65): gray-dim the icon
- -- (this file's header comment, CharacterFrame.xml:873's precedent).
- getglobal(buttonName .. "IconTexture"):SetVertexColor(0.65, 0.65, 0.65)
+ SetItemButtonDesaturated(button, 1, 0.65, 0.65, 0.65)
getglobal(buttonName .. "Slot"):SetVertexColor(0.5, 0.5, 0.5)
if rank == 0 then
getglobal(buttonName .. "RankBorder"):Hide()
diff --git a/crates/benilla-app/assets/ui/TrainerFrame.xml b/crates/benilla-app/assets/ui/TrainerFrame.xml
index 742d38bd..9cb99288 100644
--- a/crates/benilla-app/assets/ui/TrainerFrame.xml
+++ b/crates/benilla-app/assets/ui/TrainerFrame.xml
@@ -538,18 +538,27 @@
(used for header rows, blanked per-row for services), the name, and the rank subtext beside it.
NO item icon (the list rows carry none; the spell icon is the DETAIL pane's). wires the
child regions + the fold highlight onto `self` (the QuestLogFrame.xml row-template shape).
+
+ THE ROW IS A FLOW, NOT TWO COLUMNS (B253). The name carries **no width**, so it never wraps:
+ a FontString given an explicit width wraps at it, and a 16px row cannot grow — which is how
+ "Handstitched Leather Pants" came to draw two lines over the row beneath it. The reference's
+ own row (`ClassTrainerFrameTemplates.xml`, read off the player's chain, never transcribed)
+ makes the name the Button's `` at width **0** and then SetPoints the subtext to
+ that text's RIGHT + 10 from Lua, per row — so a long name simply runs on and pushes its
+ "(Rank N)" along. We express the same law declaratively: no size on either string, the
+ subtext anchored to the name's right edge. The 160/188 fixed columns this replaces were
+ benilla's invention, and both halves of them were wrong — the cap wrapped, and the column
+ put the rank at a fixed x the reference never uses.
-->