From 8d3d805ec8a25ea4d6cf29d412113705efe6c65b Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 16 Aug 2026 17:38:54 +0700 Subject: [PATCH] the guild window opens: roster, ranks, notes and the message of the day MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - the social window's third tab is live — the roster in both its views, the member card with its promote/demote/remove verbs, the rank editor's thirteen permissions, the guild information board, and the invite, leave, kick and note dialogs behind them - setting your hearthstone at an innkeeper now asks before it takes and says so afterwards, and the innkeeper's gossip lines get their own icons back - unavailable talents grey out, and every greyed icon in the client now goes through the same luminance fold the original used - the buff tooltip's remaining-time line reads gold with the client's own hour/minute ladder, the macro editor takes a shift-click to paste a spell, trainer rows repaint after a purchase, and a long book page stops flickering its ellipsis Co-Authored-By: Claude Opus 5 (1M context) --- crates/benilla-app/assets/ui/BagFrame.xml | 3 +- .../benilla-app/assets/ui/BinderConfirm.xml | 69 + crates/benilla-app/assets/ui/FriendsFrame.xml | 2265 ++++++++++++++++- crates/benilla-app/assets/ui/GossipFrame.xml | 16 + .../assets/ui/ItemButtonTemplate.xml | 9 +- crates/benilla-app/assets/ui/MacroFrame.xml | 15 +- crates/benilla-app/assets/ui/MailFrame.xml | 14 +- .../benilla-app/assets/ui/SpellBookFrame.xml | 11 +- crates/benilla-app/assets/ui/TalentFrame.xml | 20 +- crates/benilla-app/assets/ui/TrainerFrame.xml | 32 +- crates/benilla-app/assets/ui/UiPanels.xml | 108 +- crates/benilla-app/assets/ui/UnitPopup.xml | 50 +- crates/benilla-app/assets/ui/benilla.toc | 4 + crates/benilla-app/src/capture/mod.rs | 4 + .../benilla-app/src/capture/probe_binder.rs | 621 +++++ crates/benilla-app/src/capture/probe_book.rs | 262 ++ crates/benilla-app/src/dev.rs | 13 + crates/benilla-app/src/lib.rs | 12 + crates/benilla-app/src/net.rs | 76 + crates/benilla-app/src/net/apply.rs | 47 +- crates/benilla-app/src/net/apply/npc.rs | 10 +- crates/benilla-app/src/net/apply/session.rs | 8 + crates/benilla-app/src/net/io.rs | 29 + crates/benilla-app/src/quest_markers/query.rs | 122 +- crates/benilla-app/src/shaders/ui_quad.wgsl | 42 +- crates/benilla-app/src/ui_binder.rs | 323 +++ crates/benilla-app/src/ui_gossip.rs | 131 +- crates/benilla-app/src/ui_guild/feed.rs | 528 ++++ crates/benilla-app/src/ui_guild/lines.rs | 354 +++ crates/benilla-app/src/ui_guild/mod.rs | 977 +++++++ crates/benilla-app/src/ui_guild/sort.rs | 362 +++ crates/benilla-app/src/ui_pass.rs | 74 +- .../benilla-app/src/ui_script/binder_tests.rs | 105 + .../benilla-app/src/ui_script/buff_tests.rs | 86 + .../benilla-app/src/ui_script/extract/mod.rs | 74 + .../benilla-app/src/ui_script/extract/text.rs | 11 +- .../src/ui_script/friends_tests.rs | 41 +- .../src/ui_script/game_menu_tests.rs | 15 +- .../benilla-app/src/ui_script/guild_tests.rs | 1433 +++++++++++ crates/benilla-app/src/ui_script/mod.rs | 9 + .../src/ui_script/pet_bar_tests.rs | 17 +- .../src/ui_script/spellbook_tests.rs | 177 ++ .../src/ui_script/trainer_tests.rs | 252 ++ crates/benilla-app/src/ui_text/atlas.rs | 51 + crates/benilla-app/src/ui_text/layout/mod.rs | 206 +- crates/benilla-app/src/ui_text/mod.rs | 293 +++ crates/benilla-app/src/ui_trainer/mod.rs | 28 +- crates/benilla-app/src/ui_trainer/tests.rs | 53 + crates/benilla-app/src/ui_unit.rs | 12 + crates/benilla-app/tests/talent_frame.rs | 69 +- crates/benilla-protocol/src/events.rs | 36 +- crates/benilla-protocol/src/events/decode.rs | 15 + .../benilla-protocol/src/messages/binder.rs | 96 + crates/benilla-protocol/src/messages/guild.rs | 865 +++++++ crates/benilla-protocol/src/messages/mod.rs | 13 + .../benilla-protocol/src/messages/opcode.rs | 60 +- .../benilla-protocol/src/messages/packet.rs | 49 + crates/benilla-protocol/src/messages/parse.rs | 39 +- .../src/messages/update_object/fields/mod.rs | 13 + .../messages/update_object/fields/player.rs | 22 + .../src/world/writer/binder.rs | 24 + .../src/world/writer/guild.rs | 167 ++ .../benilla-protocol/src/world/writer/mod.rs | 3 + crates/benilla-protocol/tests/guild.rs | 736 ++++++ crates/benilla-ui/src/script/binder.rs | 62 + crates/benilla-ui/src/script/extract.rs | 17 +- crates/benilla-ui/src/script/guild.rs | 1009 ++++++++ crates/benilla-ui/src/script/mod.rs | 8 + crates/benilla-ui/src/script/model.rs | 37 +- crates/benilla-ui/src/script/region/paint.rs | 63 +- .../src/script/tests/reference_surface.rs | 31 +- crates/benilla-ui/src/script/tests/regions.rs | 149 ++ crates/benilla-ui/src/script/tests/tooltip.rs | 86 + .../src/script/tests/tooltip_spell.rs | 125 +- crates/benilla-ui/src/script/tooltip/mod.rs | 90 + crates/benilla-ui/src/script/tooltip_spell.rs | 53 +- crates/benilla-ui/src/script/types.rs | 10 +- crates/benilla-ui/src/script/unit/mod.rs | 5 + 78 files changed, 13155 insertions(+), 271 deletions(-) create mode 100644 crates/benilla-app/assets/ui/BinderConfirm.xml create mode 100644 crates/benilla-app/src/capture/probe_binder.rs create mode 100644 crates/benilla-app/src/capture/probe_book.rs create mode 100644 crates/benilla-app/src/ui_binder.rs create mode 100644 crates/benilla-app/src/ui_guild/feed.rs create mode 100644 crates/benilla-app/src/ui_guild/lines.rs create mode 100644 crates/benilla-app/src/ui_guild/mod.rs create mode 100644 crates/benilla-app/src/ui_guild/sort.rs create mode 100644 crates/benilla-app/src/ui_script/binder_tests.rs create mode 100644 crates/benilla-app/src/ui_script/guild_tests.rs create mode 100644 crates/benilla-protocol/src/messages/binder.rs create mode 100644 crates/benilla-protocol/src/messages/guild.rs create mode 100644 crates/benilla-protocol/src/world/writer/binder.rs create mode 100644 crates/benilla-protocol/src/world/writer/guild.rs create mode 100644 crates/benilla-protocol/tests/guild.rs create mode 100644 crates/benilla-ui/src/script/binder.rs create mode 100644 crates/benilla-ui/src/script/guild.rs 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. -->