- a 40-man raid cost +5 to +8 ms of CPU per frame — a population, not a hot function — and three cuts landed where it was measured: the rig palette upload sends one `write_buffer` per run, bridging the gaps parked bodies leave between live rigs (calls 93 → 3); a built material becomes an asset when something visible binds it, not when a spawner builds it — 9.9k material assets were alive for 36 drawn batches, so a material is realized only once bound and visible, readers that clone before binding doing it themselves through one new function in the engine's wall (172 → 173): a 17× cut in live GPU objects; and a body's batches spawn as material groups — a geared body stood as 12–27 mesh parts, its skin composite drawn as five separate batches, and two visible batches now join when nothing downstream can tell them apart: parts per body 17 → 12, the Stormwind raid leg 17.0–21.0 → 15.65–15.82 `cpu_ms` (1929, 1940) - the instruments that priced it, all on the probe line: wgpu's own resource census, because a sampled profile put roughly half of the pass encoder in usage trackers sized to the device's live buffer and texture counts — a term that scales with residency, not with what is drawn, and nothing printed the number; the active camera count, since every view is its own pass set; the UI's live texture-identity runs, one 2D draw each, which is the number a UI change moves, not the quad count; and parts and materials per dressed body on the dress census, the ceiling on merging a body's batches read off a real body instead of guessed from a model's batch table (1940) - the coin kit retires: every remaining money display — quest progress, the mail purse and postage, the trade recipient, the tooltip, the quest log, the trainer's purse and cost — moves onto the reference's `MoneyFrameTemplate`, each seat already matching ours; the progress panel's required-money flow is ported in full — `QuestFrame.lua:168-190` colours the FRAME red/white AND the LABEL black/dark-grey when the quest costs more than you hold, where ours had neither colour; the mail postage takes the reference's name and label, trade and mail issue no explicit update because the frames repaint on their own events, and the trainer's cost label is shown or hidden with its `local COSTS_LABEL` shadow deleted; with the trainer converted the hand-built coin trios have no consumer, so their templates and the `BenillaMoney_*` helpers are deleted and `MoneyFrame.xml` is the only money kit (1937) - the crowd-control ladder's aura exemption exists, and it is a join between two `Spell.dbc` records — each arm asks whether the spell being cast is itself an immunity covering the aura that blocks it (Ice Block cast while stunned), the school arm shifting because `School` is an index and `EffectMiscValue` a mask; what the scan mostly decides is WHICH message a refusal carries — a blocking aura the cast does not counter renames the reason to "Can't do that while %s" (1941); bevy_audio leaves the build — its plugin had been disabled since 0530, but disabling a plugin does not remove a crate, and rodio, a second cpal and an Ogg/Vorbis decoder were still linked into a client that mixes through kira (1932); and cpal has four stream errors, not two — a routine ALSA underrun was treated as "the device is gone", tearing the stream down on every hiccup; now only a lost or invalidated device rebuilds (1939) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
893 lines
61 KiB
XML
893 lines
61 KiB
XML
<Ui>
|
||
<!--
|
||
benilla's class/profession trainer window (decisions 0237/0247/0251), a faithful port of the
|
||
TBC-backported `Blizzard_TrainerUI` addon the director's client runs (0246): a client-sorted,
|
||
collapsible **skill-line tree** with a **dropdown** state filter and a real **scroll bar**. The
|
||
window art/geometry are the real 1.12 `ClassTrainerFrame` (MerchantFrame.xml's four-quadrant-slab
|
||
idiom); the list/sort/collapse/filter logic is transcribed from `Blizzard_TrainerUI.lua` onto
|
||
benilla's engine (`crates/benilla-ui/src/script/trainer.rs`, which builds the tree — decision
|
||
0247) + the shared faux-scroll kit (ScrollTemplates.xml, decision 0251). This REPLACES the
|
||
earlier flat-list placeholder (git has it): flat state-as-category rows + inline sort checkboxes
|
||
+ a chunk-local scroll offset, which stood in until the tree/scrollbar/dropdown landed.
|
||
|
||
Modeled on the two known-good dressed windows for the framing:
|
||
- MerchantFrame.xml — the 384×512 four-quadrant window art (no <Backdrop>: the art IS the
|
||
frame), the NPC-session lifecycle (RegisterEvent trio + PLAYER_MONEY, SHOW/UPDATE/CLOSED
|
||
dispatch, OnShow/OnHide PlaySound with CloseTrainer() on hide), the "npc" model-portrait
|
||
binding, and the reference's own two money frames (detail cost + player purse). The close X reuses
|
||
the minimize-button art (this engine ships no UIPanelCloseButton).
|
||
- QuestLogFrame.xml — the list-row Button template shape ($parent-named child regions wired in
|
||
<OnLoad>) and the persistent selection-highlight Frame (a child Frame whose ARTWORK Texture
|
||
renders when Show()n, moved onto the selected visible row).
|
||
|
||
WHAT'S NEW vs. the placeholder (decision 0247's tree, 0251's scroll bar):
|
||
- THE LIST IS A TREE. GetTrainerServiceInfo(i) now returns interleaved "header" rows (a skill
|
||
line's name, +/- fold) and service rows (name/state), 1-based into the VISIBLE tree. Update
|
||
renders each row by type; a header click folds its group, a service click selects it.
|
||
- THE FILTER IS A DROPDOWN. BenillaTrainerFilterDropDown (over UIDropDownMenu.xml's kit) with
|
||
three keepShownOnClick check rows (Available/Unavailable/Already Known) driving the engine's
|
||
state-visibility filter — replacing the three inline sort checkboxes. Its three states are
|
||
SAVED VARIABLES (decision 1128): defaults 1/1/0 at file scope — "Already Known" starts OFF,
|
||
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 (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).
|
||
- COLLAPSE WORKS. The "All" tab collapses/expands every group (Collapse/ExpandTrainerSkillLine(0));
|
||
its +/- icon reflects whether all groups are folded (the backport's numHeaders/notExpanded).
|
||
|
||
FLAT layout — a CHOICE, and it was written down here as a constraint that does not exist.
|
||
This block used to claim "a child <Frame> shown at runtime does not render its own <Layers>
|
||
FontStrings in this engine". That is FALSE, and it was propagated from here into two more
|
||
windows before anyone probed it (decision 1517): MailFrame's own SendMailTitleText lives in a
|
||
runtime-shown pane's Layers and has rendered correctly the whole time. The detail-pane
|
||
FontStrings + the title/greeting do live on BenillaTrainerFrame's OWN <Layers>, and that is
|
||
fine — it is simply one way to lay this out, not the only one available. Per-row regions live
|
||
on the row Button template. The detail is a FIXED (non-scrolling) pane
|
||
(our descriptions are empty for now) — no real ScrollFrame, unlike the ref's DetailScrollFrame.
|
||
|
||
Adaptations / deviations (flagged):
|
||
- TITLE: the trainer's NAME rides arg1 of TRAINER_SHOW/UPDATE (the app resolves the creature
|
||
name ask-once via the NameCache — the MerchantFrame vendor-name precedent); "Trainer" until
|
||
it lands.
|
||
- PORTRAIT: the "npc" unit-token model bake (MerchantFrame's binding — the interaction NPC's face).
|
||
- GlobalStrings the ref reads (REQUIRES_LABEL, TRAINER_REQ_*, COSTS_LABEL, the labels) are
|
||
defined LOCALLY below as the byte-exact 1.12 enUS values (interface.MPQ GlobalStrings.lua,
|
||
line-cited); NORMAL/GRAY/HIGHLIGHT_FONT_COLOR are Fonts.xml globals.
|
||
- The affordability RED-TINT of the cost is the reference's own SetMoneyFrameColor (1937).
|
||
- The profession-confirm StaticPopup is skipped for v1 (Train buys directly); a later refinement.
|
||
|
||
Registers at the left UIPanel slot like Gossip/Merchant/Quest/QuestLog (UiPanels.xml) — opening
|
||
it competes for/replaces those. It IS an NPC session: CloseTrainer() fires on hide, and the
|
||
standardized range guard (crate::ui_session) client-side-closes it when the player walks away.
|
||
-->
|
||
<Script><![CDATA[
|
||
-- ── GlobalStrings / labels the ref reads (byte-exact 1.12 enUS, interface.MPQ) ────────────────
|
||
-- Chunk-local upvalues: the global functions below close over them; inline XML handlers (separate
|
||
-- chunks) can't, so every handler routes to a function here. `_RED` variants redden only the
|
||
-- number/name (met = white, unmet = red), exactly as the real requirements line reads.
|
||
local PARENS_TEMPLATE = "(%s)" -- GlobalStrings.lua:2468
|
||
local REQUIRES_LABEL = "Requires:" -- REQUIRES_LABEL (2768)
|
||
local UNKNOWN_LABEL = "Unknown" -- UNKNOWN (3860)
|
||
local FILTER_LABEL = "Filter" -- FILTER (1297)
|
||
local TRAINER_REQ_LEVEL = "Level |cffffffff%d|r" -- TRAINER_REQ_LEVEL (3739)
|
||
local TRAINER_REQ_LEVEL_RED = "Level |cffff2020%d|r" -- TRAINER_REQ_LEVEL_RED (3740)
|
||
local TRAINER_REQ_SKILL_RANK = "%s (|cffffffff%d|r)" -- TRAINER_REQ_SKILL_RANK (3741)
|
||
local TRAINER_REQ_SKILL_RANK_RED = "%s (|cffff2020%d|r)" -- TRAINER_REQ_SKILL_RANK_RED (3742)
|
||
local TRAINER_REQ_ABILITY = "|cffffffff%s|r" -- TRAINER_REQ_ABILITY (3737)
|
||
local TRAINER_REQ_ABILITY_RED = "|cffff2020%s|r" -- TRAINER_REQ_ABILITY_RED (3738)
|
||
-- ── The state filter's three SAVED VARIABLES (decision 1128) ─────────────────────────────────
|
||
-- The reference's own globals, values and mechanism: Blizzard_TrainerUI.lua:6-9 assigns these
|
||
-- at FILE SCOPE and its `.toc` declares them `## SavedVariables`; the client executes the
|
||
-- saved file over them AFTER this file loads and BEFORE the load event, so a remembered value
|
||
-- wins over the default. benilla's ported UI is one FrameXML tree rather than addons, so the
|
||
-- declaration rides `RegisterForSave` (FrameXML's own API for its 38 globals) and the same
|
||
-- ordering is the app's load seam. File scope is load-bearing: assign these in OnLoad and the
|
||
-- default would clobber the restored value every launch.
|
||
TRAINER_FILTER_AVAILABLE = 1
|
||
TRAINER_FILTER_UNAVAILABLE = 1
|
||
TRAINER_FILTER_USED = 0
|
||
RegisterForSave("TRAINER_FILTER_AVAILABLE")
|
||
RegisterForSave("TRAINER_FILTER_UNAVAILABLE")
|
||
RegisterForSave("TRAINER_FILTER_USED")
|
||
|
||
local LABEL_AVAILABLE = "Available" -- AVAILABLE (84)
|
||
local LABEL_UNAVAILABLE = "Unavailable" -- UNAVAILABLE (3840)
|
||
local LABEL_USED = "Already Known" -- USED (3868)
|
||
-- The font colour codes the dropdown rows wrap their labels in (GlobalStrings.lua: the *_CODE
|
||
-- family) — green/red/gray, closed with |r.
|
||
local GREEN_CODE = "|cff20ff20"
|
||
local RED_CODE = "|cffff2020"
|
||
local GRAY_CODE = "|cff808080"
|
||
local CODE_CLOSE = "|r"
|
||
|
||
-- CLASS_TRAINER_SKILLS_DISPLAYED / CLASS_TRAINER_SKILL_HEIGHT (ref Blizzard_TrainerUI.lua:2-3).
|
||
local BENILLA_TRAINER_ROWS = 11
|
||
local BENILLA_TRAINER_ROW_HEIGHT = 16
|
||
|
||
-- ── Detail pane ─────────────────────────────────────────────────────────────────────────────
|
||
-- Nothing selected → blank every detail region: a FontString "hides" via SetText(""), while the
|
||
-- icon Button + coin frames Hide. Predates region Show/Hide (region.rs has the real pair now);
|
||
-- equivalent either way, so the blank-text idiom stays.
|
||
function BenillaTrainer_HideDetails()
|
||
getglobal("BenillaTrainerSkillName"):SetText("")
|
||
getglobal("BenillaTrainerSkillRequirements"):SetText("")
|
||
getglobal("BenillaTrainerSkillDescription"):SetText("")
|
||
getglobal("BenillaTrainerCostLabel"):Hide()
|
||
getglobal("BenillaTrainerSkillIcon"):Hide()
|
||
getglobal("ClassTrainerDetailMoneyFrame"):Hide()
|
||
getglobal("BenillaTrainerTrainButton"):Disable()
|
||
end
|
||
|
||
-- Fill the detail pane for the selected service `id` (1-based visible index) — the transcribed
|
||
-- ClassTrainer_SetSelection detail half: name/sub name, the Requires: string (built term-for-term
|
||
-- as the ref does), the icon, the cost coins + label, the description, and Train's enable state.
|
||
function BenillaTrainer_ShowDetails(id)
|
||
local name = GetTrainerServiceInfo(id)
|
||
getglobal("BenillaTrainerSkillName"):SetText(name or UNKNOWN_LABEL)
|
||
-- The detail header shows the spell NAME only — no "(Rank N)" beside it. A deliberate
|
||
-- divergence from the ref (which sets a ClassTrainerSubSkillName subtext here): the rank is
|
||
-- already on the spell's list row, so a second copy floated to the right of the name just
|
||
-- read as clutter (director's call).
|
||
|
||
local icon = getglobal("BenillaTrainerSkillIcon")
|
||
icon:SetNormalTexture(GetTrainerServiceIcon(id) or BENILLA_FALLBACK_ICON)
|
||
icon:Show()
|
||
|
||
-- Requirements string (ref term-for-term): level gate, skill gate, each prerequisite ability;
|
||
-- each red when unmet (hasReq nil). Our feed has no pet-learn / step requirements.
|
||
local requirements = ""
|
||
local separator = ""
|
||
local reqLevel = GetTrainerServiceLevelReq(id)
|
||
if reqLevel and reqLevel > 1 then
|
||
separator = ", "
|
||
if (UnitLevel("player") or 0) >= reqLevel then
|
||
requirements = requirements .. format(TRAINER_REQ_LEVEL, reqLevel)
|
||
else
|
||
requirements = requirements .. format(TRAINER_REQ_LEVEL_RED, reqLevel)
|
||
end
|
||
end
|
||
local skill, rank, hasReq = GetTrainerServiceSkillReq(id)
|
||
if skill then
|
||
if hasReq then
|
||
requirements = requirements .. separator .. format(TRAINER_REQ_SKILL_RANK, skill, rank)
|
||
else
|
||
requirements = requirements .. separator .. format(TRAINER_REQ_SKILL_RANK_RED, skill, rank)
|
||
end
|
||
separator = ", "
|
||
end
|
||
local numReq = GetTrainerServiceNumAbilityReq(id)
|
||
for i = 1, numReq do
|
||
local ability, abilityHas = GetTrainerServiceAbilityReq(id, i)
|
||
if ability then
|
||
if abilityHas then
|
||
requirements = requirements .. separator .. format(TRAINER_REQ_ABILITY, ability)
|
||
else
|
||
requirements = requirements .. separator .. format(TRAINER_REQ_ABILITY_RED, ability)
|
||
end
|
||
separator = ", "
|
||
end
|
||
end
|
||
if requirements ~= "" then
|
||
getglobal("BenillaTrainerSkillRequirements"):SetText(REQUIRES_LABEL .. " " .. requirements)
|
||
else
|
||
getglobal("BenillaTrainerSkillRequirements"):SetText("")
|
||
end
|
||
|
||
-- ref Blizzard_TrainerUI.lua (the "Money Frame and cost" block), in full (1937): a free
|
||
-- service hides the frame AND the label and pulls the description up to the label's TOPLEFT;
|
||
-- a costed one shows both, seats the description under the label, and tints the frame white
|
||
-- when the purse covers it, red when not. The frame is repainted either way.
|
||
local moneyCost = GetTrainerServiceCost(id)
|
||
local costFrame = getglobal("ClassTrainerDetailMoneyFrame")
|
||
local costLabel = getglobal("BenillaTrainerCostLabel")
|
||
local description = getglobal("BenillaTrainerSkillDescription")
|
||
if moneyCost and moneyCost > 0 then
|
||
costFrame:Show()
|
||
costLabel:Show()
|
||
description:SetPoint("TOPLEFT", costLabel, "BOTTOMLEFT", 0, -10)
|
||
if (GetMoney() or 0) >= moneyCost then
|
||
SetMoneyFrameColor("ClassTrainerDetailMoneyFrame", 1.0, 1.0, 1.0)
|
||
else
|
||
SetMoneyFrameColor("ClassTrainerDetailMoneyFrame", 1.0, 0.1, 0.1)
|
||
end
|
||
else
|
||
costFrame:Hide()
|
||
costLabel:Hide()
|
||
description:SetPoint("TOPLEFT", costLabel, "TOPLEFT", 0, 0)
|
||
end
|
||
MoneyFrame_Update("ClassTrainerDetailMoneyFrame", moneyCost or 0)
|
||
|
||
getglobal("BenillaTrainerSkillDescription"):SetText(GetTrainerServiceDescription(id))
|
||
|
||
-- Train enable (ref): only an available AND affordable service. The talent/profession-point
|
||
-- gate is dropped (talentPts always 0; the profession-confirm is a later refinement).
|
||
local _, _, serviceType = GetTrainerServiceInfo(id)
|
||
local affordable = (GetMoney() or 0) >= (moneyCost or 0)
|
||
if serviceType == "available" and affordable then
|
||
getglobal("BenillaTrainerTrainButton"):Enable()
|
||
else
|
||
getglobal("BenillaTrainerTrainButton"):Disable()
|
||
end
|
||
end
|
||
|
||
-- ── The list page ────────────────────────────────────────────────────────────────────────────
|
||
-- Pick the first learnable (available) service, else the first non-header service (ref
|
||
-- ClassTrainer_SelectFirstLearnableSkill — the backport hardcodes index 2, the first service
|
||
-- under the first header; we find it, robust to which group leads / a leading collapsed group).
|
||
local function selectFirstService()
|
||
local n = GetNumTrainerServices()
|
||
for i = 1, n do
|
||
local _, _, serviceType = GetTrainerServiceInfo(i)
|
||
if serviceType == "available" then
|
||
SelectTrainerService(i)
|
||
return
|
||
end
|
||
end
|
||
for i = 1, n do
|
||
local _, _, serviceType = GetTrainerServiceInfo(i)
|
||
if serviceType ~= "header" then
|
||
SelectTrainerService(i)
|
||
return
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Set the collapse-all tab's +/- icon from whether every group is folded (ref
|
||
-- ClassTrainerFrame_Update's numHeaders/notExpanded tail).
|
||
local function updateCollapseAllIcon(numServices)
|
||
local numHeaders, notExpanded = 0, 0
|
||
for i = 1, numServices do
|
||
local name, _, serviceType, isExpanded = GetTrainerServiceInfo(i)
|
||
if name and serviceType == "header" then
|
||
numHeaders = numHeaders + 1
|
||
if not isExpanded then notExpanded = notExpanded + 1 end
|
||
end
|
||
end
|
||
local b = getglobal("BenillaTrainerCollapseAllButton")
|
||
if numHeaders > 0 and notExpanded == numHeaders then
|
||
b.collapsed = 1
|
||
b:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-Up")
|
||
else
|
||
b.collapsed = nil
|
||
b:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-Up")
|
||
end
|
||
end
|
||
|
||
function BenillaTrainerFrame_Update()
|
||
getglobal("BenillaTrainerGreetingText"):SetText(GetTrainerGreetingText())
|
||
getglobal("ClassTrainerMoneyFrame"):Show() -- ref ClassTrainerFrame_Show; type PLAYER paints itself
|
||
|
||
local numServices = GetNumTrainerServices()
|
||
if numServices == 0 then
|
||
getglobal("BenillaTrainerCollapseAllButton"):Disable()
|
||
else
|
||
getglobal("BenillaTrainerCollapseAllButton"):Enable()
|
||
end
|
||
|
||
-- The faux-scroll bar reconciles to the row count, then hands back the top-row offset.
|
||
local scrollFrame = getglobal("BenillaTrainerListScrollFrame")
|
||
FauxScrollFrame_Update(scrollFrame, numServices, BENILLA_TRAINER_ROWS, BENILLA_TRAINER_ROW_HEIGHT)
|
||
local skillOffset = FauxScrollFrame_GetOffset(scrollFrame)
|
||
|
||
getglobal("BenillaTrainerSkillHighlight"):Hide()
|
||
|
||
-- Fill the visible rows (ref ClassTrainerFrame_Update's row loop). Header rows show the skill
|
||
-- line name + a +/- fold; service rows blank the fold, indent the name two spaces, and colour
|
||
-- name+subtext by state (available green / used gray / unavailable red).
|
||
for i = 1, BENILLA_TRAINER_ROWS do
|
||
local button = getglobal("BenillaTrainerService" .. i)
|
||
local index = i + skillOffset
|
||
if index <= numServices then
|
||
local name, subText, serviceType, isExpanded = GetTrainerServiceInfo(index)
|
||
if not name then name = UNKNOWN_LABEL end
|
||
-- Every row is unlocked here and the selected one re-locked below (ref l.183/190).
|
||
-- The lock is what paints a selected row's name white: the template's
|
||
-- `<HighlightFont inherits="GameFontHighlight">` instance wins over the state
|
||
-- colour SetTextColor writes, which reaches the NORMAL instance only. It reaches
|
||
-- nothing else on a service row — the branch below blanks that row's fold glow.
|
||
button:UnlockHighlight()
|
||
if serviceType == "header" then
|
||
button:SetText(name)
|
||
button:SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)
|
||
button.subtext:SetText("")
|
||
if isExpanded then
|
||
button:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-Up")
|
||
else
|
||
button:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-Up")
|
||
end
|
||
button.foldHighlight:SetTexture("Interface\\Buttons\\UI-PlusButton-Hilight")
|
||
else
|
||
button:SetNormalTexture("")
|
||
button.foldHighlight:SetTexture("")
|
||
button:SetText(" " .. name)
|
||
if subText and subText ~= "" then
|
||
button.subtext:SetText(format(PARENS_TEMPLATE, subText))
|
||
else
|
||
button.subtext:SetText("")
|
||
end
|
||
-- The rank rides the NAME's right edge, wherever the name ended (ref l.157,
|
||
-- B253's flow row) — re-anchored every repaint because the name's width is
|
||
-- the thing that changes.
|
||
button.subtext:SetPoint("LEFT", getglobal(button:GetName() .. "Text"), "RIGHT", 10, 0)
|
||
if serviceType == "available" then
|
||
button:SetTextColor(0, 1.0, 0)
|
||
button.subR, button.subG, button.subB = 0, 0.6, 0
|
||
elseif serviceType == "used" then
|
||
button:SetTextColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b)
|
||
button.subR, button.subG, button.subB = 0.5, 0.5, 0.5
|
||
else
|
||
button:SetTextColor(0.9, 0, 0)
|
||
button.subR, button.subG, button.subB = 0.6, 0, 0
|
||
end
|
||
button.subtext:SetTextColor(button.subR, button.subG, button.subB)
|
||
end
|
||
button.serviceIndex = index
|
||
button:Show()
|
||
else
|
||
button.serviceIndex = nil
|
||
button:UnlockHighlight()
|
||
button:SetText("")
|
||
button.subtext:SetText("")
|
||
button:SetNormalTexture("")
|
||
button.foldHighlight:SetTexture("")
|
||
button:Hide()
|
||
end
|
||
end
|
||
|
||
-- Selection highlight + detail (ref l.146-186). The glow rides the selected SERVICE row while
|
||
-- it is inside the visible window; a header selection shows no glow. The detail fills only for
|
||
-- a selected service.
|
||
local selection = GetTrainerSelectionIndex()
|
||
local selType
|
||
if selection >= 1 and selection <= numServices then
|
||
_, _, selType = GetTrainerServiceInfo(selection)
|
||
end
|
||
if selType and selType ~= "header"
|
||
and selection > skillOffset and selection <= skillOffset + BENILLA_TRAINER_ROWS then
|
||
local selButton = getglobal("BenillaTrainerService" .. (selection - skillOffset))
|
||
local hl = getglobal("BenillaTrainerSkillHighlight")
|
||
local tex = getglobal("BenillaTrainerSkillHighlightTexture")
|
||
if selType == "available" then
|
||
tex:SetVertexColor(0, 1.0, 0)
|
||
elseif selType == "used" then
|
||
tex:SetVertexColor(0.5, 0.5, 0.5)
|
||
else
|
||
tex:SetVertexColor(0.9, 0, 0)
|
||
end
|
||
hl:SetPoint("TOPLEFT", selButton, "TOPLEFT", 0, 0)
|
||
hl:Show()
|
||
-- The whole selected row goes white, and it is the ref's own pair of moves (l.183-184):
|
||
-- LockHighlight for the NAME (the button's label, so the white comes from its
|
||
-- HighlightFont instance, not from a repaint that the next Update would undo) and an
|
||
-- explicit whiten for the SUBTEXT, which is a child FontString the lock cannot reach.
|
||
-- An earlier revision called the white name a deliberate divergence "the ref does not
|
||
-- do"; the ref does it, through the lock this window had no engine method for at the
|
||
-- time (decision 1605). subR/subG/subB go white too so a hover-then-leave keeps the
|
||
-- subtext white.
|
||
selButton:LockHighlight()
|
||
selButton.subtext:SetTextColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
|
||
selButton.subR, selButton.subG, selButton.subB =
|
||
HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b
|
||
end
|
||
|
||
if selType and selType ~= "header" then
|
||
BenillaTrainer_ShowDetails(selection)
|
||
else
|
||
BenillaTrainer_HideDetails()
|
||
end
|
||
|
||
updateCollapseAllIcon(numServices)
|
||
end
|
||
|
||
-- The window's wheel handler (bubbled up the parent chain from a row) → one row via the faux kit.
|
||
function BenillaTrainerFrame_OnMouseWheel(delta)
|
||
BenillaFauxScrollFrame_OnMouseWheel(getglobal("BenillaTrainerListScrollFrame"), delta)
|
||
end
|
||
|
||
-- A row click: a header folds/unfolds its group (Collapse/ExpandTrainerSkillLine take the header's
|
||
-- display index); a service selects (ref ClassTrainerSkillButton_OnClick + SetSelection's header
|
||
-- branch). Left-button only.
|
||
function BenillaTrainerSkillButton_OnClick(button, mouseButton)
|
||
if mouseButton ~= "LeftButton" then return end
|
||
local index = button.serviceIndex
|
||
if not index then return end
|
||
local _, _, serviceType, isExpanded = GetTrainerServiceInfo(index)
|
||
if serviceType == "header" then
|
||
if isExpanded then
|
||
CollapseTrainerSkillLine(index)
|
||
else
|
||
ExpandTrainerSkillLine(index)
|
||
end
|
||
else
|
||
SelectTrainerService(index)
|
||
end
|
||
BenillaTrainerFrame_Update()
|
||
end
|
||
|
||
-- Hover recolors a service row's subtext to HIGHLIGHT, restoring its state colour on leave (ref
|
||
-- ClassTrainerSkillButton_OnEnter/OnLeave). Harmless over a header (its subtext is blank).
|
||
function BenillaTrainerSkillButton_OnEnter(button)
|
||
button.subtext:SetTextColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
|
||
end
|
||
|
||
function BenillaTrainerSkillButton_OnLeave(button)
|
||
button.subtext:SetTextColor(button.subR or 0.5, button.subG or 0.5, button.subB or 0.5)
|
||
end
|
||
|
||
-- ── The Train button ─────────────────────────────────────────────────────────────────────────
|
||
-- Buy the selected service, then repaint (the row flips to "used" once the app pushes the fresh
|
||
-- snapshot). The tradeskill profession-confirm dialog is a later refinement.
|
||
function BenillaTrainerTrainButton_OnClick()
|
||
BuyTrainerService(GetTrainerSelectionIndex())
|
||
BenillaTrainerFrame_Update()
|
||
end
|
||
|
||
-- ── The state-filter dropdown (ref ClassTrainerFrameFilterDropDown_*) ────────────────────────
|
||
function BenillaTrainerFilterDropDown_OnLoad()
|
||
UIDropDownMenu_Initialize(BenillaTrainerFilterDropDown, BenillaTrainerFilterDropDown_Initialize)
|
||
UIDropDownMenu_SetText(FILTER_LABEL, BenillaTrainerFilterDropDown)
|
||
UIDropDownMenu_SetWidth(96, BenillaTrainerFilterDropDown)
|
||
end
|
||
|
||
function BenillaTrainerFilterDropDown_Initialize()
|
||
local function addRow(kind, label, code)
|
||
local info = {}
|
||
info.text = code .. label .. CODE_CLOSE
|
||
info.value = kind
|
||
info.func = BenillaTrainerFilterDropDown_OnClick
|
||
info.checked = GetTrainerServiceTypeFilter(kind) and 1 or nil
|
||
info.keepShownOnClick = 1
|
||
UIDropDownMenu_AddButton(info)
|
||
end
|
||
addRow("available", LABEL_AVAILABLE, GREEN_CODE)
|
||
addRow("unavailable", LABEL_UNAVAILABLE, RED_CODE)
|
||
addRow("used", LABEL_USED, GRAY_CODE)
|
||
end
|
||
|
||
-- A row's func fires with the row button as `this`, carrying the PRE-click state (this.value /
|
||
-- this.checked): the kit's UIDropDownMenuButton_OnClick runs the func FIRST and only then, for a
|
||
-- keepShownOnClick row, flips the check texture + this.checked itself. So the func's whole job is
|
||
-- to flip the ENGINE filter to the inverse of the pre-click check, reset scroll, and repaint —
|
||
-- the visible check is the kit's to move, and the next open re-derives every row from
|
||
-- GetTrainerServiceTypeFilter anyway.
|
||
--
|
||
-- It must NOT re-Initialize the open menu here. Doing so re-ran AddButton, which sets
|
||
-- `button.checked` (and the Check texture) from the fresh engine state — so the kit's flip
|
||
-- immediately afterwards inverted the ALREADY-correct row and put it back. The check never
|
||
-- appeared to move, and because `this.checked` then read stale-true forever, a filter switched
|
||
-- off could never be switched back on.
|
||
function BenillaTrainerFilterDropDown_OnClick()
|
||
-- The engine filter AND the saved global, together: the global is what survives the
|
||
-- restart and what BenillaTrainerFrame_ApplyFilter pushes back after the per-packet
|
||
-- reset, so writing only the engine would lose the toggle at the next list (the ref's
|
||
-- own setglobal pair, Blizzard_TrainerUI.lua:526-530).
|
||
local on = 0
|
||
if not this.checked then
|
||
on = 1
|
||
end
|
||
setglobal("TRAINER_FILTER_" .. strupper(this.value), on)
|
||
SetTrainerServiceTypeFilter(this.value, on)
|
||
FauxScrollFrame_SetOffset(getglobal("BenillaTrainerListScrollFrame"), 0)
|
||
BenillaTrainerFrame_Update()
|
||
end
|
||
|
||
-- ── The collapse-all tab ─────────────────────────────────────────────────────────────────────
|
||
-- Fold / unfold every group (Collapse/ExpandTrainerSkillLine(0), ref
|
||
-- ClassTrainerCollapseAllButton_OnClick).
|
||
function BenillaTrainerCollapseAllButton_OnClick()
|
||
local b = getglobal("BenillaTrainerCollapseAllButton")
|
||
if b.collapsed then
|
||
b.collapsed = nil
|
||
ExpandTrainerSkillLine(0)
|
||
else
|
||
b.collapsed = 1
|
||
FauxScrollFrame_SetOffset(getglobal("BenillaTrainerListScrollFrame"), 0)
|
||
CollapseTrainerSkillLine(0)
|
||
end
|
||
BenillaTrainerFrame_Update()
|
||
end
|
||
|
||
-- ── The NPC-session lifecycle (MerchantFrame.xml's own shape) ────────────────────────────────
|
||
function BenillaTrainerFrame_OnLoad(frame)
|
||
-- The NPC portrait: the "npc" unit token → the trainer's live model bake
|
||
-- (crates/benilla/src/portrait, InteractNpc) — MerchantFrame's own binding.
|
||
SetPortraitTexture(getglobal(frame:GetName() .. "Portrait"), "npc")
|
||
frame:RegisterEvent("TRAINER_SHOW")
|
||
frame:RegisterEvent("TRAINER_UPDATE")
|
||
frame:RegisterEvent("TRAINER_CLOSED")
|
||
frame:RegisterEvent("PLAYER_MONEY")
|
||
-- The list repaint is wired in the scroll frame's own `<OnVerticalScroll>` below, the
|
||
-- reference's way; `updateFunc` was our kit's field and the reference has none (1860).
|
||
end
|
||
|
||
-- Push the three saved globals into the engine — the ref's ClassTrainerFrame <OnShow>
|
||
-- (Blizzard_TrainerUI.xml:531-533), and it is NOT redundant: the list builder rewrites the
|
||
-- engine's filter mask to its own default on every SMSG_TRAINER_LIST (byte-verified,
|
||
-- `0x4d75d9 mov ds:0xb73a1c,3`; decision 1128), so this is what puts the player's remembered
|
||
-- choice back. Called on show and at the top of TRAINER_SHOW — our repaint runs before
|
||
-- ShowUIPanel, so applying it only in OnShow would leave one frame painted under the reset.
|
||
function BenillaTrainerFrame_ApplyFilter()
|
||
SetTrainerServiceTypeFilter("available", TRAINER_FILTER_AVAILABLE)
|
||
SetTrainerServiceTypeFilter("unavailable", TRAINER_FILTER_UNAVAILABLE)
|
||
SetTrainerServiceTypeFilter("used", TRAINER_FILTER_USED)
|
||
end
|
||
|
||
-- The scroll frame's `<OnVerticalScroll>` lives in its own chunk and cannot see this
|
||
-- file's `local BENILLA_TRAINER_ROW_HEIGHT`, so the pitch is handed over here rather than
|
||
-- written as a literal in the XML (1860). The reference has the same shape with globals:
|
||
-- `QUESTLOG_QUEST_HEIGHT` and friends are file-scope globals in its own FrameXML.
|
||
function BenillaTrainerList_OnVerticalScroll()
|
||
FauxScrollFrame_OnVerticalScroll(BENILLA_TRAINER_ROW_HEIGHT, BenillaTrainerFrame_Update)
|
||
end
|
||
|
||
function BenillaTrainerFrame_SetTitle(name)
|
||
if name and name ~= "" then
|
||
getglobal("BenillaTrainerTitleText"):SetText(name)
|
||
else
|
||
getglobal("BenillaTrainerTitleText"):SetText("Trainer")
|
||
end
|
||
end
|
||
|
||
function BenillaTrainerFrame_OnEvent(frame)
|
||
if event == "TRAINER_SHOW" then
|
||
-- SHOW FIRST, then populate — the reference's own order, and not a style choice.
|
||
-- `FauxScrollFrame_Update` (the chain's since 1837) gates everything that sets the
|
||
-- bar's range on `frame:IsVisible()`, so a list repainted before its window is up
|
||
-- leaves `SetMinMaxValues` unset and both arrows dead. The reference relies on this
|
||
-- order hard enough to test it: `MerchantFrame.lua:22-27` does `ShowUIPanel(this)`
|
||
-- and then treats `not this:IsVisible()` as the panel system refusing, and closes.
|
||
-- Decision 1860.
|
||
ShowUIPanel(frame)
|
||
-- Fresh session: top of the list, a first selection (ref ClassTrainerFrame_Show).
|
||
BenillaTrainerFrame_ApplyFilter()
|
||
BenillaTrainerFrame_SetTitle(arg1)
|
||
FauxScrollFrame_SetOffset(getglobal("BenillaTrainerListScrollFrame"), 0)
|
||
if GetTrainerSelectionIndex() == 0 then
|
||
selectFirstService()
|
||
end
|
||
BenillaTrainerFrame_Update()
|
||
elseif event == "TRAINER_UPDATE" then
|
||
BenillaTrainerFrame_SetTitle(arg1)
|
||
-- Re-anchor the selection if it fell off the (possibly shorter) list.
|
||
if GetTrainerSelectionIndex() == 0 then
|
||
selectFirstService()
|
||
end
|
||
BenillaTrainerFrame_Update()
|
||
elseif event == "TRAINER_CLOSED" then
|
||
HideUIPanel(frame)
|
||
elseif event == "PLAYER_MONEY" then
|
||
-- A buy changes affordability without a TRAINER_UPDATE — repaint the detail's cost tint.
|
||
if BenillaTrainerFrame:IsVisible() then
|
||
BenillaTrainerFrame_Update()
|
||
end
|
||
end
|
||
end
|
||
|
||
function BenillaTrainerCloseButton_OnClick()
|
||
CloseTrainer()
|
||
HideUIPanel(getglobal("BenillaTrainerFrame"))
|
||
end
|
||
]]></Script>
|
||
|
||
<!--
|
||
One row (ref ClassTrainerSkillButtonTemplate): a 293×16 text row — the +/- fold slot at the left
|
||
(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). <OnLoad> 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 `<ButtonText>` 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.
|
||
-->
|
||
<Button name="BenillaTrainerServiceTemplate" virtual="true" hidden="true">
|
||
<Size><AbsDimension x="293" y="16"/></Size>
|
||
<Layers>
|
||
<Layer level="ARTWORK">
|
||
<!-- NO <Anchors> — Update SetPoints this to the row's own label every repaint, the
|
||
ref's own move (`skillSubText:SetPoint("LEFT", <row>Text, "RIGHT", 10, 0)`,
|
||
Blizzard_TrainerUI.lua l.157). A `relativeTo="$parentText"` here CANNOT work and
|
||
fails quietly: `<Layers>` is loader step 5 and the `<ButtonText>` is step 5b, so
|
||
the label does not exist yet and the anchor falls back to the row (the loader
|
||
does warn — `resolve_target`'s "does not resolve" line). The ref anchors it at
|
||
runtime for its own reason anyway: the rank must follow a name whose width is
|
||
only known once the name is measured (B253). -->
|
||
<FontString name="$parentSubText" inherits="GameFontNormalSmall" justifyH="LEFT"/>
|
||
</Layer>
|
||
</Layers>
|
||
<!-- THE SERVICE NAME IS THE BUTTON'S OWN ButtonText (the ref's `ClassTrainerSkill<n>Text`),
|
||
under the base's font TRIO — that is what turns a row white under the cursor and holds
|
||
the selected row white, with no script involved: `GameFontHighlight` is white, the engine
|
||
swaps the highlighted font instance in on hover, and `LockHighlight()` pins it for the
|
||
selection. It was a `$parentName` FontString until decision 1605, on a stale note that
|
||
`Button:SetTextColor`/`LockHighlight` had no engine method. Carries NO `<Size>` — B253's
|
||
flow row: the name measures to its own text and `$parentSubText` follows its right edge,
|
||
which is the reference's own `<ButtonText>`-at-width-0 plus per-row
|
||
`SetPoint("LEFT", <row>Text, "RIGHT", 10, 0)`. TradeSkillFrame.xml and CraftFrame.xml are
|
||
the twins (both fixed-column, so both keep an explicit width). -->
|
||
<ButtonText name="$parentText" justifyH="LEFT">
|
||
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="22" y="0"/></Offset></Anchor></Anchors>
|
||
</ButtonText>
|
||
<NormalFont inherits="GameFontNormal" justifyH="LEFT"/>
|
||
<HighlightFont inherits="GameFontHighlight" justifyH="LEFT"/>
|
||
<DisabledFont inherits="GameFontDisable" justifyH="LEFT"/>
|
||
<!-- The +/- fold slot + its hover glow: meaningful for header rows (Update sets +/- there),
|
||
blanked for services. -->
|
||
<NormalTexture file="Interface\Buttons\UI-MinusButton-UP">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</NormalTexture>
|
||
<HighlightTexture name="$parentHighlight" file="Interface\Buttons\UI-PlusButton-Hilight" alphaMode="ADD">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</HighlightTexture>
|
||
<Scripts>
|
||
<OnLoad>
|
||
self.subtext = getglobal(self:GetName() .. "SubText")
|
||
self.foldHighlight = self:GetHighlightTexture()
|
||
</OnLoad>
|
||
<OnClick>BenillaTrainerSkillButton_OnClick(self, arg1)</OnClick>
|
||
<OnEnter>BenillaTrainerSkillButton_OnEnter(self)</OnEnter>
|
||
<OnLeave>BenillaTrainerSkillButton_OnLeave(self)</OnLeave>
|
||
</Scripts>
|
||
</Button>
|
||
|
||
<!-- A panel action button (Train/Exit): the UIPanelButtonTemplate face, label via text=. -->
|
||
<Button name="BenillaTrainerPanelButtonTemplate" virtual="true">
|
||
<Size><AbsDimension x="80" 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>
|
||
|
||
<Frame name="BenillaTrainerFrame" hidden="true">
|
||
<!-- The real 384×512 window. No <Anchors> — ShowUIPanel SetPoints this to the left slot. -->
|
||
<Size><AbsDimension x="384" y="512"/></Size>
|
||
<Layers>
|
||
<!-- NPC portrait 60×60 TOPLEFT (7,-6) (ref), BACKGROUND so the window art's ring frames it.
|
||
Bound in OnLoad via SetPortraitTexture(region, "npc") — the trainer's live model bake. -->
|
||
<Layer level="BACKGROUND">
|
||
<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>
|
||
</Layer>
|
||
<!-- The four quadrant slabs that ARE the window art (ref): 256-wide left halves, 128-wide
|
||
right halves, each 256 tall, pinned to their corner, whole-texture. -->
|
||
<Layer level="BORDER">
|
||
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-TopLeft"><Size><AbsDimension x="256" y="256"/></Size><Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors></Texture>
|
||
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-TopRight"><Size><AbsDimension x="128" y="256"/></Size><Anchors><Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"/></Anchors></Texture>
|
||
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-BotLeft"><Size><AbsDimension x="256" y="256"/></Size><Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"/></Anchors></Texture>
|
||
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-BotRight"><Size><AbsDimension x="128" y="256"/></Size><Anchors><Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"/></Anchors></Texture>
|
||
<!-- Title: the NPC's name, fed as arg1 of TRAINER_SHOW/UPDATE via the NameCache; the
|
||
static "Trainer" shows until the creature-name query lands. -->
|
||
<FontString name="BenillaTrainerTitleText" inherits="GameFontNormal" justifyH="CENTER" text="Trainer">
|
||
<Anchors><Anchor point="TOP" relativePoint="TOP"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
|
||
</FontString>
|
||
<!-- Greeting (GetTrainerGreetingText), GameFontHighlight LEFT/TOP, 260×26, TOPLEFT (76,-38). -->
|
||
<FontString name="BenillaTrainerGreetingText" inherits="GameFontHighlight" justifyH="LEFT" justifyV="TOP">
|
||
<Size><AbsDimension x="260" y="26"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="76" y="-38"/></Offset></Anchor></Anchors>
|
||
</FontString>
|
||
</Layer>
|
||
<!-- The horizontal divider between the list and the detail pane (ref): 256+75 wide, 16 tall,
|
||
TOPLEFT (15,-275), sampled from UI-ClassTrainer-HorizontalBar by TexCoord. -->
|
||
<Layer level="ARTWORK">
|
||
<Texture name="BenillaTrainerHorizontalBar" file="Interface\ClassTrainerFrame\UI-ClassTrainer-HorizontalBar">
|
||
<Size><AbsDimension x="256" y="16"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="15" y="-275"/></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="BenillaTrainerHorizontalBar" relativePoint="RIGHT"/></Anchors>
|
||
<TexCoords left="0" right="0.29296875" top="0.25" bottom="0.5"/>
|
||
</Texture>
|
||
</Layer>
|
||
<!-- The FIXED detail-pane FontStrings (ref DetailScrollChildFrame), flattened onto this
|
||
window's own Layers (the flat-layout gap). Blanked/filled by Hide/ShowDetails.
|
||
|
||
THE WIDTHS ARE THE REFERENCE'S, not ours (B253). A FontString wraps at whatever
|
||
width it is given, so an invented narrow one is a wrap bug waiting for a long name:
|
||
the detail name at 150 broke "Handstitched Leather Pants" over two lines. The
|
||
reference's detail child is 296 wide with its name/requirements at **244** and its
|
||
description at **290**; our flattened anchors put the name at the same window x (68)
|
||
the child's 46 resolves to, so the same numbers land in the same place — and, unlike
|
||
the 290/300 they replace, they stay inside the pane instead of running past its
|
||
right edge at 358/330. -->
|
||
<Layer level="OVERLAY">
|
||
<FontString name="BenillaTrainerSkillName" inherits="GameFontNormal" justifyH="LEFT">
|
||
<Size><AbsDimension x="244" y="0"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="68" y="-293"/></Offset></Anchor></Anchors>
|
||
</FontString>
|
||
<FontString name="BenillaTrainerSkillRequirements" inherits="GameFontHighlightSmall" justifyH="LEFT">
|
||
<Size><AbsDimension x="244" y="0"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTrainerSkillName" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
|
||
</FontString>
|
||
<FontString name="BenillaTrainerCostLabel" inherits="GameFontNormalSmall" justifyH="LEFT" text="COSTS_LABEL">
|
||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="30" y="-340"/></Offset></Anchor></Anchors>
|
||
</FontString>
|
||
<FontString name="BenillaTrainerSkillDescription" inherits="GameFontHighlightSmall" justifyH="LEFT">
|
||
<Size><AbsDimension x="290" y="0"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTrainerCostLabel" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-10"/></Offset></Anchor></Anchors>
|
||
</FontString>
|
||
</Layer>
|
||
</Layers>
|
||
<Frames>
|
||
<!-- The top-right close X (ref a UIPanelCloseButton at TOPRIGHT (-29,-8); the minimize-button
|
||
art stands in). -->
|
||
<Button name="BenillaTrainerCloseButton">
|
||
<Size><AbsDimension x="32" y="32"/></Size>
|
||
<Anchors><Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-29" 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>BenillaTrainerCloseButton_OnClick()</OnClick></Scripts>
|
||
</Button>
|
||
|
||
<!-- The expand/collapse-all tab (ref ClassTrainerExpandButtonFrame): a tab-shaped frame
|
||
carrying the collapse-all button — now functional (Collapse/ExpandTrainerSkillLine(0)). -->
|
||
<Frame name="BenillaTrainerExpandButtonFrame">
|
||
<Size><AbsDimension x="54" y="32"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="15" y="-70"/></Offset></Anchor></Anchors>
|
||
<Layers><Layer level="BACKGROUND">
|
||
<Texture name="BenillaTrainerExpandTabLeft" file="Interface\ClassTrainerFrame\UI-ClassTrainer-ExpandTab-Left"><Size><AbsDimension x="8" y="32"/></Size><Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors></Texture>
|
||
<Texture name="BenillaTrainerExpandTabMiddle" file="Interface\QuestFrame\UI-QuestLogSortTab-Middle"><Size><AbsDimension x="38" y="32"/></Size><Anchors><Anchor point="LEFT" relativeTo="BenillaTrainerExpandTabLeft" relativePoint="RIGHT"><Offset><AbsDimension x="0" y="6"/></Offset></Anchor></Anchors></Texture>
|
||
<Texture file="Interface\QuestFrame\UI-QuestLogSortTab-Right"><Size><AbsDimension x="8" y="32"/></Size><Anchors><Anchor point="LEFT" relativeTo="BenillaTrainerExpandTabMiddle" relativePoint="RIGHT"/></Anchors></Texture>
|
||
</Layer></Layers>
|
||
<Frames>
|
||
<Button name="BenillaTrainerCollapseAllButton" text="All">
|
||
<Size><AbsDimension x="40" y="22"/></Size>
|
||
<Anchors><Anchor point="LEFT" relativeTo="BenillaTrainerExpandTabLeft" 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>
|
||
<NormalFont inherits="GameFontNormalSmall" justifyH="LEFT"/>
|
||
<Scripts><OnClick>BenillaTrainerCollapseAllButton_OnClick()</OnClick></Scripts>
|
||
</Button>
|
||
</Frames>
|
||
</Frame>
|
||
|
||
<!-- The state-filter dropdown (ref ClassTrainerFrameFilterDropDown): the UIDropDownMenu capsule
|
||
whose three keepShownOnClick check rows drive the engine's state-visibility filter —
|
||
replacing the three inline sort checkboxes. TOPRIGHT (-26,-64), the ref's spot. -->
|
||
<Frame name="BenillaTrainerFilterDropDown" inherits="UIDropDownMenuTemplate">
|
||
<Anchors><Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-26" y="-64"/></Offset></Anchor></Anchors>
|
||
<Scripts>
|
||
<OnLoad>BenillaTrainerFilterDropDown_OnLoad()</OnLoad>
|
||
<OnShow>BenillaTrainerFilterDropDown_OnLoad()</OnShow>
|
||
</Scripts>
|
||
</Frame>
|
||
|
||
<!-- The persistent selection highlight (ref ClassTrainerSkillHighlightFrame): 293×16,
|
||
ARTWORK-layer UI-Listbox-Highlight2. Declared BEFORE the rows so the row text paints OVER
|
||
the glow (same parent + level ⇒ declaration order is the z-tiebreak). Moved onto the
|
||
selected visible service row + vertex-colored by state. -->
|
||
<Frame name="BenillaTrainerSkillHighlight" hidden="true">
|
||
<Size><AbsDimension x="293" y="16"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
|
||
<Layers><Layer level="ARTWORK">
|
||
<Texture name="BenillaTrainerSkillHighlightTexture" file="Interface\Buttons\UI-Listbox-Highlight2">
|
||
<Anchors><Anchor point="TOPLEFT"/><Anchor point="BOTTOMRIGHT"/></Anchors>
|
||
</Texture>
|
||
</Layer></Layers>
|
||
</Frame>
|
||
|
||
<!-- 11 tree rows (ref ClassTrainerSkill1..11): 293×16, row1 TOPLEFT (22,-100); rows 2-11 chain
|
||
TOPLEFT→prior BOTTOMLEFT with no gap. -->
|
||
<Button name="BenillaTrainerService1" inherits="BenillaTrainerServiceTemplate">
|
||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="22" y="-100"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="BenillaTrainerService2" inherits="BenillaTrainerServiceTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTrainerService1" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||
<Button name="BenillaTrainerService3" inherits="BenillaTrainerServiceTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTrainerService2" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||
<Button name="BenillaTrainerService4" inherits="BenillaTrainerServiceTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTrainerService3" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||
<Button name="BenillaTrainerService5" inherits="BenillaTrainerServiceTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTrainerService4" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||
<Button name="BenillaTrainerService6" inherits="BenillaTrainerServiceTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTrainerService5" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||
<Button name="BenillaTrainerService7" inherits="BenillaTrainerServiceTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTrainerService6" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||
<Button name="BenillaTrainerService8" inherits="BenillaTrainerServiceTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTrainerService7" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||
<Button name="BenillaTrainerService9" inherits="BenillaTrainerServiceTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTrainerService8" relativePoint="BOTTOMLEFT"/></Anchors></Button>
|
||
<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 (`FauxScrollFrameTemplate`, the REFERENCE's since 1837):
|
||
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.
|
||
|
||
THE OWNER DECLARES `<OnVerticalScroll>`, which is how the reference's kit works and
|
||
the one thing our transcription did differently: ours carried a `frame.updateFunc`
|
||
field that `BenillaFauxScrollBar_OnValueChanged` read, and the reference has no such
|
||
field. Its chain is the bar's own OnValueChanged
|
||
(`this:GetParent():SetVerticalScroll(arg1)`, UIPanelTemplates.xml) -> this handler ->
|
||
`FauxScrollFrame_OnVerticalScroll(itemHeight, updateFunction)`, which is the only
|
||
thing that writes `frame.offset`. Without it the arrows and the thumb move the bar
|
||
and the list never follows (1860). 16 is the row pitch
|
||
(`BenillaTrainerServiceTemplate`). -->
|
||
<ScrollFrame 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>
|
||
<Scripts>
|
||
<OnVerticalScroll>BenillaTrainerList_OnVerticalScroll()</OnVerticalScroll>
|
||
</Scripts>
|
||
</ScrollFrame>
|
||
|
||
<!-- The detail spell icon (ref ClassTrainerSkillIcon): a 37×37 button over a 64×64 UI-EmptySlot
|
||
ring, at the detail area's top-left. Icon via SetNormalTexture; Hide()n when unselected.
|
||
|
||
The hover is the ref's own, verbatim (Blizzard_TrainerUI.xml:452-459): owner is the icon
|
||
button, anchor ANCHOR_RIGHT, subject `ClassTrainerFrame.selectedService` — the SELECTED
|
||
row, which is why the tooltip describes the selection no matter which row the mouse
|
||
passed over on its way here. GetTrainerSelectionIndex() is our engine-held equivalent of
|
||
that field; it reads 0 with nothing selected, and SetTrainerService no-ops on 0. This is
|
||
the ONLY SetTrainerService call site in the shipped client — the list rows carry no
|
||
tooltip of their own (their template's OnEnter only recolours the subtext). -->
|
||
<Button name="BenillaTrainerSkillIcon" hidden="true">
|
||
<Size><AbsDimension x="37" y="37"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="26" y="-291"/></Offset></Anchor></Anchors>
|
||
<Layers><Layer level="BACKGROUND">
|
||
<Texture file="Interface\Buttons\UI-EmptySlot"><Size><AbsDimension x="64" y="64"/></Size><Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="-13" y="13"/></Offset></Anchor></Anchors></Texture>
|
||
</Layer></Layers>
|
||
<Scripts>
|
||
<OnEnter>
|
||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||
GameTooltip:SetTrainerService(GetTrainerSelectionIndex())
|
||
GameTooltip:Show()
|
||
</OnEnter>
|
||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||
</Scripts>
|
||
</Button>
|
||
|
||
<!-- ref Blizzard_TrainerUI.xml:463-476, and the reference's own NAME: the cost, a
|
||
SmallMoneyFrameTemplate of type STATIC at the same LEFT->cost-label RIGHT (5,0) seat,
|
||
painted by MoneyFrame_Update on every detail refresh. Retires the detail coin trio (1937). -->
|
||
<Frame name="ClassTrainerDetailMoneyFrame" inherits="SmallMoneyFrameTemplate">
|
||
<Anchors><Anchor point="LEFT" relativeTo="BenillaTrainerCostLabel" relativePoint="RIGHT"><Offset><AbsDimension x="5" y="0"/></Offset></Anchor></Anchors>
|
||
<Scripts><OnLoad>SmallMoneyFrame_OnLoad(); MoneyFrame_SetType("STATIC");</OnLoad></Scripts>
|
||
</Frame>
|
||
|
||
<!-- ref Blizzard_TrainerUI.xml:338-345, and the reference's own NAME: the purse, a
|
||
SmallMoneyFrameTemplate with no OnLoad of its own (type PLAYER from the template; repaints
|
||
on PLAYER_MONEY). Same BOTTOMRIGHT->window BOTTOMLEFT (180,86) seat; the anchor targets OUR
|
||
window name until this file migrates. Retires BenillaTrainerMoneyCoin1..3 (1937). -->
|
||
<Frame name="ClassTrainerMoneyFrame" inherits="SmallMoneyFrameTemplate">
|
||
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaTrainerFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="180" y="86"/></Offset></Anchor></Anchors>
|
||
</Frame>
|
||
|
||
<!-- The Train button (ref ClassTrainerTrainButton): UIPanelButton face + "Train", 80×22, in
|
||
the detail area's bottom-right. Enabled only for an affordable available service. -->
|
||
<Button name="BenillaTrainerTrainButton" inherits="BenillaTrainerPanelButtonTemplate" text="Train">
|
||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="184" y="-409"/></Offset></Anchor></Anchors>
|
||
<Scripts><OnClick>BenillaTrainerTrainButton_OnClick()</OnClick></Scripts>
|
||
</Button>
|
||
<!-- The Exit button (ref ClassTrainerCancelButton): hides the window; OnHide fires CloseTrainer(). -->
|
||
<Button name="BenillaTrainerCancelButton" inherits="BenillaTrainerPanelButtonTemplate" text="Exit">
|
||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="265" y="-409"/></Offset></Anchor></Anchors>
|
||
<Scripts><OnClick>HideUIPanel(getglobal("BenillaTrainerFrame"))</OnClick></Scripts>
|
||
</Button>
|
||
</Frames>
|
||
<!-- The NPC-session lifecycle (MerchantFrame.xml's shape): OnHide calls CloseTrainer() — it ends
|
||
the client-side session whenever this window hides for ANY reason (ESC, close X, Exit,
|
||
TRAINER_CLOSED, or another left-slot window displacing it). The list wheel lives on the
|
||
WINDOW (the engine bubbles a spin up the parent chain, so a spin over a row reaches here). -->
|
||
<Scripts>
|
||
<OnLoad>BenillaTrainerFrame_OnLoad(self)</OnLoad>
|
||
<OnEvent>BenillaTrainerFrame_OnEvent(self)</OnEvent>
|
||
<OnMouseWheel>BenillaTrainerFrame_OnMouseWheel(arg1)</OnMouseWheel>
|
||
<OnShow>BenillaTrainerFrame_ApplyFilter(); PlaySound("igCharacterInfoOpen")</OnShow>
|
||
<OnHide>CloseTrainer(); PlaySound("igCharacterInfoClose")</OnHide>
|
||
</Scripts>
|
||
</Frame>
|
||
</Ui>
|