Files
benilla-octo/crates/benilla-app/assets/ui/GossipFrame.xml
T
SamandClaude Opus 5 9a184d3476 the guild window opens: roster, ranks, notes and the message of the day
- 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) <noreply@anthropic.com>
2026-08-16 17:38:54 +07:00

527 lines
40 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<Ui>
<!--
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 `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.
A hidden window that shows on GOSSIP_SHOW and hides on GOSSIP_CLOSED. It wears the real
QuestFrame parchment: four quadrant slabs (UI-QuestGreeting-{TopLeft,TopRight,BotLeft,BotRight},
ref l.13-44) at the real 384×512 (ref l.128); the greeting body and the row list quoted from
the real GossipGreetingText / GossipTitleButtonTemplate (ref l.86-125, 223-264).
QUEST ROWS (decision 0088 §3, filled in): the real `GossipFrameUpdate` (ref l.24-29) fills ONE
shared `GossipTitleButton` pool through three sequential passes over a single `buttonIndex`
counter — `GossipFrameAvailableQuestsUpdate` (ref l.63-85) first, `GossipFrameActiveQuestsUpdate`
(ref l.87-109) second, `GossipFrameOptionsUpdate` (ref l.111-128) third — so quest rows always
land ABOVE the option rows, and the two blocks share the very same row buttons/anchors (a
`titleButton.type` field picked at fill time routes the click: `SelectGossipAvailableQuest` /
`SelectGossipActiveQuest` / `SelectGossipOption`, ref l.53-61). We mirror that shape exactly but
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 `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
collapse to two on our seam — see gossip.rs's doc comment). Per-row icon: active → the real
`Interface\GossipFrame\ActiveQuestIcon` (ref l.99), available → `Interface\GossipFrame\
AvailableQuestIcon` (ref l.75) — both verified present in the MPQ list. We DO run the ref's
per-row `GossipResize` auto-height (ref l.130-132, `SetHeight(GetTextHeight()+2)`) — see
`BenillaGossipRow_Resize` below — but skip its "too many quests" message()
(ref l.67-69/91-93/115-117): the shared 8-row cap below silently drops the overflow exactly
like the option rows always did.
ENGINE-SLICE CAVEAT (this branch predates the dressing arc's engine slice — named `<Font>`
objects are not implemented here yet, loader.rs "named Font objects are not implemented in v1").
The real window's `inherits="QuestFont"` FontStrings are transcribed faithfully below (they are
part of the quoted geometry), but until the font-object slice + our Fonts.xml land on this line
the loader warns "unknown template 'QuestFont'" and the strings render in the default font —
no worse than before (nothing here inherited a font). They light up automatically when the
slice merges. No <TexCoords> is used: every quadrant samples its whole texture (the real gossip
window uses TexCoords only on the material/patch art we omit), so this window needs none of it.
The NPC name rides in the title banner (ref GossipFrameNpcNameText, l.157-178): a GameFontHighlight
FontString centred at the window TOP -23, mirroring the merchant's pattern — the app resolves the
gossip NPC's guid through the NameCache and delivers the name as arg1 of GOSSIP_SHOW; empty until
it lands. The GOODBYE button rides bottom-right (ref GossipFrameGreetingGoodbyeButton, l.206-222),
wired to the same close path as the close button.
The scroll frame IS here now (ref l.223-436): the greeting + the 32-row pool ride a real
`<ScrollFrame>`, so a menu taller than the parchment scrolls instead of spilling out of the
window. See the ScrollFrame's own comment below. Nothing of the real window is knowingly
omitted any more except the BotLeftPatch material art.
UIPanel slot system (decision 0084 §2): carries NO hardcoded <Anchors> — ShowUIPanel/SetLeftFrame
SetPoints it to the left slot (TOPLEFT UIParent, 0, -104) on open; every show goes through
ShowUIPanel, every hide through HideUIPanel, never Show()/Hide() directly.
-->
<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 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.
BENILLA_GOSSIP_MAX = 32
-- Every widget that can be the LOWEST visible thing in the scroll child, for the content
-- measure (BenillaScroll_ResizeChild, ScrollTemplates.xml). Built once at load, like the
-- quest window's per-panel lists.
BENILLA_GOSSIP_SCROLL_WIDGETS = (function()
local t = { "GossipGreetingText" }
for i = 1, BENILLA_GOSSIP_MAX do table.insert(t, "GossipRow" .. i) end
return t
end)()
-- 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",
}
-- The real per-row quest icons (ref l.75/99) — active (in-progress/turn-in) vs available
-- (not yet accepted), the same split `GossipQuestRow.active` already carries off the wire.
BENILLA_GOSSIP_QUEST_ICON_ACTIVE = "Interface\\GossipFrame\\ActiveQuestIcon"
BENILLA_GOSSIP_QUEST_ICON_AVAILABLE = "Interface\\GossipFrame\\AvailableQuestIcon"
function BenillaGossipRow_OnLoad(button)
button.icon = getglobal(button:GetName() .. "Icon")
button.label = getglobal(button:GetName() .. "Label")
end
-- The ref's GossipResize (ref l.130-132: `SetHeight(GetTextHeight() + 2)`) — a row is as
-- tall as its OWN wrapped label. Load-bearing, not cosmetic: the rows hang off each other
-- in one static chain (each on the previous row's BOTTOMLEFT), so a row left at the
-- template's 16px while its label draws three wrapped lines puts the next row's text
-- straight through this one's — every long option overprinting its neighbour (the
-- director's judgement-menu screenshot). The reference's own LEFT-anchored label is only
-- correct BECAUSE of this resize: centring the text on a row sized to the text is the same
-- as top-aligning it.
--
-- Deviation from the ref, the same one the tab-fit carries (UiPanels.xml
-- `BenillaTabButton_OnUpdate`): the client's GetTextHeight is a synchronous font-engine
-- call, ours is the measure round-trip's answer and lands a frame late — 0 until the host
-- has measured THIS text. A zero keeps the current height (never collapses the row) and
-- the frame's OnUpdate below re-runs the fit until it settles.
function BenillaGossipRow_Resize(button)
local h = button.label:GetHeight()
if h <= 0 or button.benillaFitHeight == h then return end
button.benillaFitHeight = h
button:SetHeight(h + 2)
end
-- Size the scroll child to the rows actually on it, so the bar ranges over the real content
-- (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("GossipGreetingScroll", BENILLA_GOSSIP_SCROLL_WIDGETS)
end
-- The convergence driver (the tab-fit / quest-panel idiom): while the menu is up, re-fit
-- every visible row until its measure lands, after which the guard above makes this a
-- no-op compare per row per frame. OnUpdate only fires on effectively-visible frames, so
-- a closed gossip window costs nothing. The rows fit FIRST and the child is measured after,
-- 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("GossipRow" .. i)
if button:IsShown() then
BenillaGossipRow_Resize(button)
end
end
BenillaGossipFrame_SizeScrollChild()
end
-- One shared row serves either a quest or an option depending on what
-- BenillaGossipFrame_Update assigned it this repaint (button.kind, set there) — mirrors the
-- ref's titleButton.type dispatch (ref l.53-61), collapsed onto our seam's two intents
-- (SelectGossipQuest covers both the ref's Available/Active clicks, gossip.rs's doc comment).
function BenillaGossipRow_OnClick(button)
if button.kind == "quest" then
SelectGossipQuest(button.itemIndex)
else
SelectGossipOption(button.itemIndex)
end
end
function BenillaGossipFrame_Update()
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("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
-- IS its quest-list index, so no separate local counter is needed.
local title, active = GetGossipQuestInfo(i)
button.kind = "quest"
button.itemIndex = i
button.label:SetText(title)
BenillaGossipRow_Resize(button)
button.icon:SetTexture(active and BENILLA_GOSSIP_QUEST_ICON_ACTIVE or BENILLA_GOSSIP_QUEST_ICON_AVAILABLE)
button:Enable()
button:Show()
elseif i <= numQuests + numOptions then
-- Option rows fill after every quest row (ref l.29 runs last) — itemIndex is the
-- row's position WITHIN the options list, not the shared pool slot.
local optIndex = i - numQuests
local label = opts[(optIndex - 1) * 2 + 1]
local otype = opts[(optIndex - 1) * 2 + 2]
button.kind = "option"
button.itemIndex = optIndex
button.label:SetText(label)
-- A label whose measure is already cached (a revisit to this NPC) fits on this
-- very repaint; a fresh string fits on the next OnUpdate tick.
BenillaGossipRow_Resize(button)
button.icon:SetTexture(BENILLA_GOSSIP_ICONS[otype] or BENILLA_GOSSIP_ICONS.gossip)
-- Coded (password) options are greyed and unselectable in v1 (decision 0081).
if IsGossipOptionCoded(optIndex) then
button:Disable()
else
button:Enable()
end
button:Show()
else
button:Hide()
end
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("GossipGreetingScroll"):SetVerticalScroll(0)
BenillaGossipFrame_SizeScrollChild()
end
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.
SetPortraitTexture(getglobal(frame:GetName() .. "Portrait"), "npc")
frame:RegisterEvent("GOSSIP_SHOW")
frame:RegisterEvent("GOSSIP_CLOSED")
end
-- 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("GossipNpcNameText"):SetText(name or "")
end
function GossipFrame_OnEvent()
local frame = this
if event == "GOSSIP_SHOW" then
BenillaGossipFrame_SetTitle(arg1)
BenillaGossipFrame_Update()
ShowUIPanel(frame)
elseif event == "GOSSIP_CLOSED" then
HideUIPanel(frame)
end
end
function BenillaGossipCloseButton_OnClick()
-- 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("GossipFrame"))
end
]]></Script>
<!--
One shared gossip row (ref GossipTitleButtonTemplate, l.86-125): the real 300×16 template, used
by the REAL client for quest rows AND option rows alike (a single pool, ref l.24-29/63-128) — we
do the same rather than carrying two near-identical templates. The type icon sits at the row's
TOPLEFT (16×16; ref $parentGossipIcon, l.92-102) — the real XML defaults it to
UI-Quest-BulletPoint, but the real client (like us) overrides it per row kind at Update time, so
we carry no default file and let BenillaGossipFrame_Update set it (quest → Active/AvailableQuestIcon,
ref l.75/99; option → the per-type GossipIcon). The label is QuestFont, LEFT, at LEFT +20 (ref
ButtonText, l.112-124). The highlight is the real UI-QuestTitleHighlight ADD glow (ref l.111).
-->
<Button name="BenillaGossipRowTemplate" virtual="true">
<Size><AbsDimension x="300" y="16"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentIcon">
<Size><AbsDimension x="16" y="16"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
</Anchors>
</Texture>
</Layer>
<Layer level="ARTWORK">
<FontString name="$parentLabel" inherits="QuestFont" justifyH="LEFT">
<Size><AbsDimension x="275" y="0"/></Size>
<Anchors>
<Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="20" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<HighlightTexture file="Interface\QuestFrame\UI-QuestTitleHighlight" alphaMode="ADD"/>
<Scripts>
<OnClick>BenillaGossipRow_OnClick(self)</OnClick>
</Scripts>
</Button>
<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>
<Layers>
<!-- The four QuestGreeting quadrant slabs that ARE the parchment art (ref l.13-44): 256-wide
left halves, 128-wide right halves, each 256 tall, pinned to their corner. No <TexCoords>
— each samples its whole texture. The solid-colour fill is gone (the parchment is
opaque). -->
<Layer level="BACKGROUND">
<!-- NPC portrait 60×60 TOPLEFT (7,-6) (ref GossipFramePortrait), declared BEFORE the
parchment so it draws UNDER it and the parchment's ring frames the portrait (the empty
ring showed the scene through the hole before). Bound in OnLoad, circle-masked. -->
<Texture name="$parentPortrait">
<Size><AbsDimension x="60" y="60"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="7" y="-6"/></Offset></Anchor>
</Anchors>
</Texture>
<Texture file="Interface\QuestFrame\UI-QuestGreeting-TopLeft">
<Size><AbsDimension x="256" y="256"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
</Texture>
<Texture file="Interface\QuestFrame\UI-QuestGreeting-TopRight">
<Size><AbsDimension x="128" y="256"/></Size>
<Anchors><Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"/></Anchors>
</Texture>
<Texture file="Interface\QuestFrame\UI-QuestGreeting-BotLeft">
<Size><AbsDimension x="256" y="256"/></Size>
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"/></Anchors>
</Texture>
<Texture file="Interface\QuestFrame\UI-QuestGreeting-BotRight">
<Size><AbsDimension x="128" y="256"/></Size>
<Anchors><Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"/></Anchors>
</Texture>
<!-- Ref GossipFrame.xml:191-202: gossip has no bottom-LEFT button, but the shared
UI-QuestGreeting-BotLeft art bakes the button recess in — the ref covers it
with this patch (the director's "bottom left hole"). -->
<Texture file="Interface\QuestFrame\UI-Quest-BotLeftPatch">
<Size><AbsDimension x="128" y="64"/></Size>
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="22" y="68"/></Offset></Anchor></Anchors>
</Texture>
</Layer>
<Layer level="ARTWORK">
<!-- NPC name banner (ref GossipFrameNpcNameText l.157-178): GameFontHighlight, centred
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="GossipNpcNameText" inherits="GameFontHighlight" justifyH="CENTER">
<Anchors>
<Anchor point="TOP" relativePoint="TOP"><Offset><AbsDimension x="0" y="-23"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!--
The greeting + row list live inside a REAL ScrollFrame — the reference's own structure
(ref-GossipFrame.xml l.223-436, extracted via mpqx this session), which benilla omitted
on the grounds that "our short greeting needs no scrolling". A gossip menu of wrapped
roleplay options is exactly the case that breaks: once each row grew to its text
(BenillaGossipRow_Resize), four long options ran off the parchment and out of the
window (the director's second screenshot). The ref's answer is this scroll frame, and
it is now ours: 300x334 at the window's TOPLEFT +(23,-81) (ref l.223-233) over a child
the same size (ref l.235-239), the greeting at the child's +(10,-10) (ref l.241-252),
and the row chain hanging off the greeting exactly as before (ref l.256-269).
Content taller than the frame is CLIPPED to it and panned (engine mechanism, decision
0112) instead of overflowing; BenillaGossipFrame_SizeScrollChild sizes the child to the
measured content each repaint, which re-ranges the bar through OnScrollRangeChanged.
The ref grows its child with a 15px GossipSpacerFrame under the last row (ref l.421-432,
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="GossipGreetingScroll">
<Size><AbsDimension x="300" y="334"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="23" y="-81"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:SetScrollChild(getglobal(self:GetName() .. "Child"))</OnLoad>
<OnMouseWheel>BenillaScroll_OnMouseWheel(this, arg1)</OnMouseWheel>
<OnVerticalScroll>BenillaScroll_SyncBar(this)</OnVerticalScroll>
<OnScrollRangeChanged>BenillaScroll_SyncBar(this)</OnScrollRangeChanged>
</Scripts>
<Frames>
<!-- The scrollbar rides the ScrollFrame as a child, the ref's UIPanelScrollFrameTemplate
shape (ref-UIPanelTemplates.xml l.166-181: $parentScrollBar, TOPLEFT->TOPRIGHT
+(6,-16) / BOTTOMLEFT->BOTTOMRIGHT +(6,16)) — so the bar's PARENT is the frame it
scrolls, with no name derivation. It wears the shared Slider bar (drag + arrows,
decision 0250); only OnValueChanged is overridden, off the faux kit's row-offset
handler onto the real-scroll one. Hidden until the content overflows (SyncBar). -->
<Slider name="$parentBar" inherits="BenillaScrollBarTemplate" hidden="true">
<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>
<Scripts><OnValueChanged>BenillaScrollBar_OnValueChanged(self, arg1)</OnValueChanged></Scripts>
</Slider>
<Frame name="$parentChild">
<Size><AbsDimension x="300" y="334"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
<Layers>
<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="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>
</Layer>
</Layers>
<Frames>
<!-- The shared row pool, now the reference's full NUMGOSSIPBUTTONS = 32
(ref-GossipFrame.lua l.1; ours capped at 8 only because an overflowing
menu had nowhere to go). Quest rows fill it first, option rows after
(decision 0088 sec.3). Row 1 hangs off the GREETING's BOTTOMLEFT +(-10,-20),
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="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>
</ScrollFrame>
<!-- 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="GossipCloseButton">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors>
<Anchor point="CENTER" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-42" y="-31"/></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>BenillaGossipCloseButton_OnClick()</OnClick>
</Scripts>
</Button>
<!-- GOODBYE button (ref GossipFrameGreetingGoodbyeButton l.206-222): the real red
UIPanelButtonTemplate, 78×22, BOTTOMRIGHT of the window -39,73. Our engine ships no
UIPanelButtonTemplate, so the button face is built directly from the very textures that
template resolves to — UI-Panel-Button-{Up,Down,Highlight} (all verified in the MPQ),
stretched over the button as its Normal/Pushed/Highlight art. The "Goodbye" label
(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="GossipGoodbyeButton">
<Size><AbsDimension x="78" y="22"/></Size>
<Anchors>
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-39" y="73"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="OVERLAY">
<FontString name="$parentText" inherits="GameFontNormal" justifyH="CENTER" text="Goodbye">
<Anchors>
<Anchor point="CENTER" relativePoint="CENTER"/>
</Anchors>
</FontString>
</Layer>
</Layers>
<!-- The button face occupies only the top-left 0.625×0.6875 of each file — the real
UIPanelButton*Texture templates slice it with exactly these TexCoords
(ref-UIPanelTemplates.xml l.3-17). Drawing the whole file (transparent padding
included) squashed the face into the scuffed blob the director's A/B caught. -->
<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>
<HighlightTexture file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD">
<TexCoords left="0" right="0.625" top="0" bottom="0.6875"/>
</HighlightTexture>
<Scripts>
<OnClick>BenillaGossipCloseButton_OnClick()</OnClick>
</Scripts>
</Button>
</Frames>
<Scripts>
<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. -->
<OnUpdate>BenillaGossipFrame_OnUpdate()</OnUpdate>
<!-- OnShow/OnHide, quoted from the real GossipFrame.xml frame Scripts (l.445/454): the
kits igQuestListOpen/Close, and — load-bearing — CloseGossip() on hide (the real
GossipFrame_OnHide). CloseGossip ends the gossip session whenever the frame hides,
INCLUDING when another NPC window displaces it at the UIPanel slot (SetLeftFrame →
oldFrame:Hide() → OnHide); without it the GossipState resource stayed set after a
vendor/quest window replaced the menu, and gossip would not reopen until the
range-guard reset it (decision 0095). No-packet client-side clear; idempotent with the
close button, which also calls it. Never fires at load (hidden="true"). -->
<OnShow>PlaySound("igQuestListOpen")</OnShow>
<OnHide>CloseGossip(); PlaySound("igQuestListClose")</OnHide>
</Scripts>
</Frame>
</Ui>