- every translucent UI pixel over the 3D world composited in the wrong space: the world lane and the UI lane each blend in gamma bytes the way the reference's fixed-function device does, but the one blend that mixes them — the UI camera's finished image onto the sRGB swapchain — ran in linear, so a chat tab whose body is black at alpha 102/255 left the scene at 77.5% of its brightness instead of 60%, and the dock's hover glow (an ADD with no plate under it) read as a lozenge floating on grass; the world camera now renders to an off-screen image drawn as the first quad of the UI pass, so every UI blend over the world is the same blend as every UI blend over UI — after, the selected tab plate lands at 0.593–0.600 of the bare scene against 0.60 predicted, and bare-world pixels move at most 1 LSB - the backdrop image is un-encoded `Rgba16Float`, because the UI arc composites in gamma and takes its one decode at the end — pre-encoding would land a second encode in that chain, and quantizing un-encoded values to 8 bits is the banding collapse measured long ago; and hiding the UI keeps the world: the toggle used to retire every batch in the pass, which would now be a black screen — hidden means the two UI lanes draw nothing while the backdrop, which is the world, still draws, with the no-world glue screens pinned on the old behaviour - a button's highlighted label is its own font instance: the client swaps which of three embedded instances the label reads — it never merges colour across states — so the `highlight_color.or(normal_color)` fallback is gone, and `LockHighlight()` now enters the label, not just the highlight texture (the reference blanks a recipe row's highlight texture to "" and then locks the row it selected — with nothing left to pin, the lock's only possible effect is the label, and that is the white text on a selected recipe) - tradeskill, craft and class-trainer rows read white on hover and on select: all three windows carried the row name as a child FontString no per-state font can reach, behind a "no engine method" note that was true when written and stale ever since — the name is now the button's own `<ButtonText>` under the base template's `<NormalFont>`/`<HighlightFont>`/`<DisabledFont>` trio, and the windows call `SetTextColor`/`LockHighlight`/`UnlockHighlight` where the reference calls them Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
896 lines
61 KiB
XML
896 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 BenillaMoney_* coin rig (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 coins (ref SetMoneyFrameColor) is BenillaMoney_SetColor.
|
||
- 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 COSTS_LABEL = "Cost:" -- COSTS_LABEL (667)
|
||
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"):SetText("")
|
||
getglobal("BenillaTrainerSkillIcon"):Hide()
|
||
BenillaMoney_Clear("BenillaTrainerDetailMoney")
|
||
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
|
||
|
||
-- Cost (ref): a free service shows no coins/label; otherwise the "Cost:" label + coins,
|
||
-- reddened when unaffordable (BenillaMoney_SetColor: white when GetMoney() >= cost, else red).
|
||
local moneyCost = GetTrainerServiceCost(id)
|
||
if moneyCost and moneyCost > 0 then
|
||
getglobal("BenillaTrainerCostLabel"):SetText(COSTS_LABEL)
|
||
BenillaMoney_Set("BenillaTrainerDetailMoney", moneyCost, true)
|
||
if (GetMoney() or 0) >= moneyCost then
|
||
BenillaMoney_SetColor("BenillaTrainerDetailMoney", 1.0, 1.0, 1.0)
|
||
else
|
||
BenillaMoney_SetColor("BenillaTrainerDetailMoney", 1.0, 0.1, 0.1)
|
||
end
|
||
else
|
||
getglobal("BenillaTrainerCostLabel"):SetText("")
|
||
BenillaMoney_Clear("BenillaTrainerDetailMoney")
|
||
end
|
||
|
||
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())
|
||
BenillaMoney_Set("BenillaTrainerMoney", GetMoney())
|
||
|
||
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 faux-scroll frame repaints the list through this window's Update.
|
||
getglobal("BenillaTrainerListScrollFrame").updateFunc = BenillaTrainerFrame_Update
|
||
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
|
||
|
||
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
|
||
-- 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()
|
||
ShowUIPanel(frame)
|
||
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
|
||
BenillaMoney_Set("BenillaTrainerMoney", GetMoney())
|
||
-- 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 money coin slot (copied from MerchantFrame.xml's rig): BenillaMoney_Set fills three,
|
||
highest-denomination-first (ltr) or copper-first (rtl), shrinking each to its number + 13px coin.
|
||
-->
|
||
<Frame name="BenillaTrainerCoinTemplate" virtual="true">
|
||
<Size><AbsDimension x="13" y="13"/></Size>
|
||
<Layers><Layer level="OVERLAY">
|
||
<Texture name="$parentIcon" file="Interface\MoneyFrame\UI-MoneyIcons"><Size><AbsDimension x="13" y="13"/></Size><Anchors><Anchor point="RIGHT" relativePoint="RIGHT"/></Anchors></Texture>
|
||
<FontString name="$parentNum" inherits="NumberFontNormal" justifyH="RIGHT"><Size><AbsDimension x="20" y="13"/></Size><Anchors><Anchor point="RIGHT" relativePoint="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor></Anchors></FontString>
|
||
</Layer></Layers>
|
||
</Frame>
|
||
|
||
<!-- 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">
|
||
<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 scroll-wheel catcher (this file's header note) — MUST be first so every real row
|
||
below out-ranks it in hit-testing; it gives the list's EMPTY tail a hit-target whose
|
||
spin also bubbles to the window. -->
|
||
<Button name="BenillaTrainerWheelCatcher">
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"/>
|
||
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"/>
|
||
</Anchors>
|
||
<Scripts><OnMouseWheel>BenillaTrainerFrame_OnMouseWheel(arg1)</OnMouseWheel></Scripts>
|
||
</Button>
|
||
|
||
<!-- 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, ScrollTemplates.xml): invisible
|
||
container over the 11 rows; only its right-hand Slider scroll bar renders. Sized to the
|
||
11-row span so the bar sits in the window's right gutter. updateFunc is set in
|
||
BenillaTrainerFrame_OnLoad. -->
|
||
<Frame name="BenillaTrainerListScrollFrame" inherits="FauxScrollFrameTemplate">
|
||
<Size><AbsDimension x="293" y="176"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="22" y="-100"/></Offset></Anchor></Anchors>
|
||
</Frame>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- The detail cost coins (ref ClassTrainerDetailMoneyFrame), left-to-right off the "Cost:"
|
||
label; BenillaMoney_Set("BenillaTrainerDetailMoney", cost, true) fills them. Coin1's LEFT
|
||
pins to the label's RIGHT (not a magic absolute x): each coin auto-shrinks to numW+13 and
|
||
its number left-aligns at this LEFT edge, growing rightward — so the number abuts the
|
||
":" with a fixed 4px gap regardless of the amount's digit count (the ref's cost-label →
|
||
money-frame anchoring; the old absolute x reserved a wide 1-vs-2-digit gap). -->
|
||
<Frame name="BenillaTrainerDetailMoneyCoin1" inherits="BenillaTrainerCoinTemplate">
|
||
<Anchors><Anchor point="LEFT" relativeTo="BenillaTrainerCostLabel" relativePoint="RIGHT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor></Anchors>
|
||
</Frame>
|
||
<Frame name="BenillaTrainerDetailMoneyCoin2" inherits="BenillaTrainerCoinTemplate">
|
||
<Anchors><Anchor point="LEFT" relativeTo="BenillaTrainerDetailMoneyCoin1" relativePoint="RIGHT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor></Anchors>
|
||
</Frame>
|
||
<Frame name="BenillaTrainerDetailMoneyCoin3" inherits="BenillaTrainerCoinTemplate">
|
||
<Anchors><Anchor point="LEFT" relativeTo="BenillaTrainerDetailMoneyCoin2" relativePoint="RIGHT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor></Anchors>
|
||
</Frame>
|
||
|
||
<!-- The player purse (ref ClassTrainerMoneyFrame): the rightmost coin (Coin1) pins its
|
||
BOTTOMRIGHT to the window BOTTOMLEFT + (180, 86); Coin2/Coin3 chain left. Filled
|
||
copper-first by BenillaMoney_Set("BenillaTrainerMoney", GetMoney()). -->
|
||
<Frame name="BenillaTrainerMoneyCoin1" inherits="BenillaTrainerCoinTemplate">
|
||
<Anchors><Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="180" y="86"/></Offset></Anchor></Anchors>
|
||
</Frame>
|
||
<Frame name="BenillaTrainerMoneyCoin2" inherits="BenillaTrainerCoinTemplate">
|
||
<Anchors><Anchor point="RIGHT" relativeTo="BenillaTrainerMoneyCoin1" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor></Anchors>
|
||
</Frame>
|
||
<Frame name="BenillaTrainerMoneyCoin3" inherits="BenillaTrainerCoinTemplate">
|
||
<Anchors><Anchor point="RIGHT" relativeTo="BenillaTrainerMoneyCoin2" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></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>
|