Files
benilla-octo/crates/benilla-app/assets/ui/ScrollTemplates.xml
T
SamandClaude Fable 5 9cc89bce74 hotfix: the lua-src fork ships, and the build resolves again
- the workspace pointed at third_party/lua-src (the Lua 5.1 fork the addon VM builds on) and the export left that folder behind — every build of today's snapshots broke at dependency resolution
- third_party/ now ships, and the sync gained a gate that refuses to publish an export that does not resolve as a cargo workspace

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 19:11:22 +07:00

542 lines
34 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<Ui>
<!--
benilla's reusable scroll kits — TWO of them, sharing one scrollbar, one pair of arrow
helpers and one optional TROUGH (`BenillaScrollTroughTemplate`, at the foot: the recessed
channel a bar rides in — 1.12 authors that art per window, so an owner declares one beside
its bar and the kit shows and hides the two together).
The **faux-scroll** kit (this header) re-binds a fixed set of rows to a moving data
offset; the **real-scroll** kit (second half of the script block) pans and CLIPS a
variable-height child through the engine's `<ScrollFrame>` (decision 0112) — the one a gossip
menu of wrapped options needs, since its content height isn't known until it is laid out.
Only their OnValueChanged differs, and an instance overrides the template's (the loader
SetScripts every `<Scripts>` block in order, so the instance's handler wins).
The faux kit (decisions 0247/0250/0251): a draggable scroll bar over
the `Slider` primitive (0250) plus the fixed-row "faux scroll" Lua layer — the reference's
`FauxScrollFrame`, which never really scrolls a child: it re-binds a fixed set of row buttons to
a moving data offset. First customer: the class trainer's skill-line list (TrainerFrame.xml).
The quest log's chunk-local faux-scroll upvalue is a later migration onto this.
DESIGN — collapsed from the real client's three-part decomposition (0250: FauxScrollFrame =
ScrollFrame + Slider + Lua). The real `FauxScrollFrameTemplate` routes the scrollbar's value
through its parent `ScrollFrame` (`OnValueChanged → parent:SetVerticalScroll(v)`; the
ScrollFrame's `OnVerticalScroll` then derives `offset = floor(v/itemHeight + 0.5)`). That
ScrollFrame adds exactly one thing over the Slider: a *scroll-range clamp* on the value. We
don't need it — the offset comes straight off the Slider value here — and in benilla it would be
a liability: the clamp reads the scroll child's RESOLVED height, which lags a tick behind the
`SetHeight` the faux update does, so a content resize + scroll in one tick could clamp against a
stale range. So benilla's faux frame is a plain `<Frame>` + a `Slider`: fewer moving parts, no
dependency on within-tick resolved rects, and the identical on-screen result (a smooth,
pixel-granularity thumb whose position maps 1:1 to the scroll offset). See 0251 for the full
rationale + the divergence from 0250's described (real-client) shape.
VALUE MODEL. The scrollbar's value is in PIXELS: `[0, (numItems − numToDisplay) * itemHeight]`,
`valueStep = itemHeight`. The row offset is `floor(value / itemHeight + 0.5)` — sub-pixel
rounding, so the thumb (drawn at the raw value fraction) and the list (drawn at the rounded
offset) never visibly disagree. The frame carries `frame.offset` (the top visible row, 0-based)
and `frame.updateFunc` (the owner's list-repaint, set by the owner's OnLoad).
-->
<Script><![CDATA[
-- Step the bar one row (the slider's valueStep) — the sensible step for an item list (the
-- generic ref scrollbar steps half its height; a discrete row list wants one). Silent by
-- design: the ref plays UChatScrollButton only on the arrow *buttons'* OnClick, never on the
-- wheel (ScrollFrameTemplate_OnMouseWheel is soundless). Both the wheel and the arrows call
-- this, so the click lives on the arrow OnClick, keeping wheel-scroll silent.
function BenillaScrollBar_Step(scrollBar, dir)
local step = scrollBar:GetValueStep()
if step <= 0 then step = 1 end
scrollBar:SetValue(scrollBar:GetValue() + dir * step)
end
-- Grey the up arrow at the top and the down arrow at the bottom (ref
-- ScrollFrame_OnScrollRangeChanged's arrow handling), from the slider's own value/range.
function BenillaScrollBar_UpdateArrows(scrollBar)
local minV, maxV = scrollBar:GetMinMaxValues()
local val = scrollBar:GetValue()
local up = getglobal(scrollBar:GetName() .. "ScrollUpButton")
local down = getglobal(scrollBar:GetName() .. "ScrollDownButton")
if val <= minV then up:Disable() else up:Enable() end
if val >= maxV then down:Disable() else down:Enable() end
end
-- The scrollbar's OnValueChanged: derive the row offset from the (pixel) value and, only when
-- it actually moved, repaint the owner's list. The change-gate is load-bearing: the owner's
-- repaint calls _Update below, which SetValue()s the thumb back to offset*itemHeight — a value
-- that maps to the SAME offset, so this handler no-ops and the cycle stops after one hop (the
-- Slider's own SetValue change-gate stops the value side, decision 0250).
--
-- THE SECOND HALF IS FOR ADDONS, and it is the reference's own wiring. The ref's
-- `UIPanelScrollBarTemplate` routes its value through the ScrollFrame it rides
-- (`<OnValueChanged>this:GetParent():SetVerticalScroll(arg1)</OnValueChanged>`,
-- ref-UIPanelTemplates.xml l.156-158), and that frame's `<OnVerticalScroll>` is where an
-- addon puts `FauxScrollFrame_OnVerticalScroll(itemHeight, MyUpdate)`. Our OWN faux frames
-- are plain `<Frame>`s (the 0251 divergence in the header above), so they have no
-- SetVerticalScroll and this line never fires for them — but an addon's is declared
-- `<ScrollFrame ... inherits="FauxScrollFrameTemplate">`, and the instance's own tag wins
-- over the template's on inherit (framexml.rs `merge`), so it really is one. Duck-typed for
-- exactly that reason.
--
-- GAP, stated where it bites: `SetVerticalScroll` clamps into `[0, GetVerticalScrollRange()]`
-- (scrollframe.rs), and that range comes from the frame's SCROLL CHILD — which the ref's
-- `FauxScrollFrameTemplate` declares as `<ScrollChild><Frame name="$parentScrollChildFrame">`
-- and our XML loader has no `<ScrollChild>` element for. Until it does, an addon frame's
-- range reads 0, so the handler fires with arg1 = 0 and its list stays at the top. The
-- offset our own kit computes above is unaffected — it comes straight off the Slider, which
-- is the whole point of 0251.
function BenillaFauxScrollBar_OnValueChanged(scrollBar, value)
local frame = scrollBar:GetParent()
local step = scrollBar:GetValueStep()
if step <= 0 then step = 1 end
local newOffset = floor(value / step + 0.5)
if newOffset ~= frame.offset then
frame.offset = newOffset
if frame.updateFunc then frame.updateFunc() end
end
BenillaScrollBar_UpdateArrows(scrollBar)
if frame.SetVerticalScroll then frame:SetVerticalScroll(value) end
end
-- OnLoad: a fresh scroll frame is at the top with an empty range and both arrows greyed
-- (ref UIPanelTemplates.lua l.244-252, verbatim but for the one added line). The ref's own
-- name, because this is the ref's own function: `FauxScrollFrameTemplate` inherits
-- `UIPanelScrollFrameTemplate`, whose `<OnLoad>` is exactly this.
--
-- The added line is `SetValueStep(1)`: our bar's step is also its ARROW/WHEEL step
-- (BenillaScrollBar_Step), so a bar that has not been through an _Update yet would step by
-- nothing. The ref has no such coupling — its arrows move by half the bar's height.
-- The ScrollingEdit trio (ref UIPanelTemplates.lua l.307-317) — a multi-line EditBox
-- inside a ScrollFrame keeps the scroll child's rect in step as the text grows, and tracks
-- where the caret is so the OnUpdate can follow it.
--
-- Published under the REFERENCE's names because that is what an addon's XML wires: our
-- corpus hits it through `InviteOMaticOptionFormSpamPanelAIGSpamEditE...:OnTextChanged`,
-- which raised `attempt to call global 'ScrollingEdit_OnTextChanged'` the moment anyone
-- typed. Both take an OPTIONAL scroll frame and fall back to `this:GetParent()`, exactly as
-- the reference does — an addon that wires the bare name relies on that fallback.
--
-- `ScrollingEdit_OnUpdate`'s caret-follow scroll is NOT here: it needs the caret's own
-- offset from the font engine, which our EditBox does not report yet. Named rather than
-- stubbed silently — a stub that scrolls nowhere would look like a working caret-follow.
function ScrollingEdit_OnTextChanged(scrollFrame)
local f = scrollFrame or (this and this:GetParent())
if f and f.UpdateScrollChildRect then f:UpdateScrollChildRect() end
end
function ScrollingEdit_OnCursorChanged(x, y, w, h)
if not this then return end
this.cursorOffset = y
this.cursorHeight = h
end
function ScrollFrame_OnLoad()
getglobal(this:GetName() .. "ScrollBarScrollDownButton"):Disable()
getglobal(this:GetName() .. "ScrollBarScrollUpButton"):Disable()
local scrollBar = getglobal(this:GetName() .. "ScrollBar")
scrollBar:SetMinMaxValues(0, 0)
scrollBar:SetValueStep(1)
scrollBar:SetValue(0)
this.offset = 0
end
-- The frame's `<OnVerticalScroll>` body, as the reference writes it (UIPanelTemplates.lua
-- l.228-233, verbatim): the frame scrolled, so seat the bar at the new pixel value, round it
-- to a row, and repaint. `this`/`arg1` are the handler globals our dispatcher sets
-- (script/event.rs `invoke_with_globals`), so the ref's body runs unchanged.
--
-- Nothing in benilla's own FrameXML calls this — our six faux lists take the offset straight
-- off the Slider (BenillaFauxScrollBar_OnValueChanged above). It is here because it is the
-- shape 27 corpus addons write into their own XML, and it is reached through the
-- SetVerticalScroll passthrough there, whose one gap is documented at that spot.
function FauxScrollFrame_OnVerticalScroll(itemHeight, updateFunction)
local scrollbar = getglobal(this:GetName() .. "ScrollBar")
scrollbar:SetValue(arg1)
this.offset = floor((arg1 / itemHeight) + 0.5)
updateFunction()
end
-- The offset getters/setters the owner's list code reads (ref UIPanelTemplates.lua
-- l.235-241). _SetOffset diverges by one line, deliberately: the ref writes `frame.offset`
-- and leaves the thumb where it was, which is a latent desync until the next _Update; ours
-- syncs the bar, so a programmatic reset (top-of-list on open) moves it there and then.
function FauxScrollFrame_GetOffset(frame)
return frame.offset or 0
end
function FauxScrollFrame_SetOffset(frame, offset)
frame.offset = offset
local scrollBar = getglobal(frame:GetName() .. "ScrollBar")
scrollBar:SetValue(offset * scrollBar:GetValueStep())
end
-- Seat a trough ON ITS BAR. THE SOCKETS, and why this is not a free choice: the channel art
-- is not a plain tube — each end carries a 16-tall SOCKET that the arrow button drops into,
-- bounded by the frame's outer cap and an inner crossbar. Measured texel-exact off
-- UI-Character-ScrollBar (64x256):
-- top piece (left column) : rows 0-3 CAP · rows 4-19 SOCKET · rows 20-21 crossbar
-- bottom piece (right column): rows 85-86 crossbar · rows 87-102 SOCKET · rows 104-106 CAP
-- Both pieces draw 1:1 anchored to their own end of the trough, and the arrow buttons sit 16
-- outside the slider's ends — so a socket lands on its arrow only when the trough OVERHANGS
-- the bar by 21 above and 20 below (4 rows of cap at the top against 3+1 at the bottom; that
-- 1 is the whole asymmetry). Those are the ref's own numbers, reached from the other side:
-- ReputationFrame hangs its trough at the scroll frame's TOPRIGHT (-2,+5) / BOTTOMRIGHT
-- (-2,-4) against a bar seated (6,-16)/(6,+16) — 21, 20, and the 8-left hang
-- (ref-ReputationFrame.xml l.573-599 against ref-UIPanelTemplates.xml l.166-181). Miss it by
-- even 4 and the arrows ride OUT of their sockets onto the caps with a sliver of bare socket
-- showing beside them, which is exactly what B224 reported.
--
-- Seated HERE, off the bar, rather than restated in each owner's XML: it is one law, and an
-- owner made to re-derive it in offsets is an owner that can get it wrong — which is how
-- B224 happened. So a trough carries NO anchors of its own; declare it before the bar (so it
-- draws beneath) and name it `<barname>Trough`, and that is the whole contract. Lazy,
-- because the bar is declared after the trough and does not exist at the trough's OnLoad.
function BenillaScrollTrough_Seat(trough, scrollBar)
if trough.seated then return end
trough.seated = true
trough:ClearAllPoints()
trough:SetPoint("TOPLEFT", scrollBar, "TOPLEFT", -8, 21)
trough:SetPoint("BOTTOMRIGHT", scrollBar, "BOTTOMRIGHT", 7, -20)
end
-- Show/hide a bar TOGETHER WITH ITS TROUGH — the recessed channel it rides in, if the
-- owner authored one beside it (a `<barname>Trough` frame on BenillaScrollTroughTemplate
-- below). Every path that shows or hides a bar goes through here, so a window that fits
-- shows neither and one that overflows shows both — and so a trough is seated on its bar
-- before it can ever be visible, whichever way the first call goes.
--
-- Why the trough is OPT-IN rather than part of the bar template: 1.12 authors trough art
-- PER WINDOW, never in the shared scroll kit — `UIPanelScrollBarTemplate` carries only the
-- two arrow buttons and the knob, and the ref's FrameXML then hangs loose trough textures
-- beside 29 individual bars, in two different skins (UI-Character-ScrollBar for the
-- dialog/paperdoll family, UI-ClassTrainer-ScrollBar for the trainer family). The era's own
-- MinimalScrollBar went the other way and made its Track part of the bar; this pair keeps
-- the era's COUPLING (track lives and dies with the bar) on 1.12's per-window SKIN.
function BenillaScrollBar_SetShown(scrollBar, shown)
local trough = getglobal(scrollBar:GetName() .. "Trough")
if trough then BenillaScrollTrough_Seat(trough, scrollBar) end
if shown then
scrollBar:Show()
if trough then trough:Show() end
else
scrollBar:Hide()
if trough then trough:Hide() end
end
end
-- Reconcile the bar to a freshly-sized list — the reference's own `FauxScrollFrame_Update`
-- (UIPanelTemplates.lua l.160-226), whose signature and return value are transcribed whole
-- because 28 corpus addons call it with all ten arguments and read the result:
--
-- frame, numItems, numToDisplay, valueStep the list itself (our four)
-- button, smallWidth, bigWidth a `<name>1..numToDisplay` row family to
-- NARROW when the bar takes its gutter
-- highlightFrame, smallHighlightWidth, and the moving highlight over them
-- bigHighlightWidth
-- → showScrollBar 1 when the bar is up, nil when it is not
--
-- `valueStep` is what benilla called `itemHeight` before this landed; same number, the ref's
-- name. The shrink/widen tail is pure ref and no benilla window passes it — our lists are
-- laid out to their with-bar width from the start.
--
-- TWO DELIBERATE DIVERGENCES from the ref body, both named where they are:
--
-- 1 · The ref gates its whole middle section on `frame:IsVisible()` right after showing or
-- hiding the frame — reading back a state it just wrote. That is a quirk, not a
-- mechanism: it is true exactly when `showScrollBar` is, EXCEPT when an ancestor is
-- hidden, in which case the range/step/value silently do not get set and the bar is
-- stale the next time the window opens. benilla repaints lists while their window is
-- closed (a feed arriving is not a click), so we gate on `showScrollBar` — what the ref
-- means — and the offset clamp runs unconditionally.
-- 2 · The bar is shown/hidden through BenillaScrollBar_SetShown, not by the frame alone:
-- ours also carries the TROUGH (see its own note above; the ref has no trough in this
-- kit at all). The frame's own Show/Hide is kept as well, because it is what an addon
-- reads back — and because for us it is the same pixels either way, the bar being the
-- only thing a faux frame contains.
function FauxScrollFrame_Update(frame, numItems, numToDisplay, valueStep, button, smallWidth, bigWidth, highlightFrame, smallHighlightWidth, bigHighlightWidth)
local frameName = frame:GetName()
local scrollBar = getglobal(frameName .. "ScrollBar")
local maxOffset = numItems - numToDisplay
if maxOffset < 0 then maxOffset = 0 end
if not frame.offset or frame.offset > maxOffset then frame.offset = maxOffset end
if frame.offset < 0 then frame.offset = 0 end
local showScrollBar
if numItems > numToDisplay then showScrollBar = 1 end
scrollBar:SetValueStep(valueStep)
scrollBar:SetMinMaxValues(0, maxOffset * valueStep)
scrollBar:SetValue(frame.offset * valueStep)
BenillaScrollBar_UpdateArrows(scrollBar)
if showScrollBar then frame:Show() else frame:Hide() end
BenillaScrollBar_SetShown(scrollBar, showScrollBar)
-- The ref sizes the scroll child to the whole list (l.179-191) — which is what gives a
-- real ScrollFrame its scroll range. GUARDED, because our loader has no `<ScrollChild>`
-- element, so `$parentScrollChildFrame` exists only if the owner declared one itself.
-- (This is the same gap BenillaFauxScrollBar_OnValueChanged documents; closing it is a
-- loader change, not a FrameXML one.)
local scrollChildFrame = getglobal(frameName .. "ScrollChildFrame")
if scrollChildFrame then
if numItems > 0 then
scrollChildFrame:SetHeight(numItems * valueStep)
scrollChildFrame:Show()
else
scrollChildFrame:Hide()
end
end
-- Shrink because the bar is shown / widen because it is hidden (ref l.205-223).
if showScrollBar then
if highlightFrame then highlightFrame:SetWidth(smallHighlightWidth) end
if button then
for i = 1, numToDisplay do getglobal(button .. i):SetWidth(smallWidth) end
end
else
if highlightFrame then highlightFrame:SetWidth(bigHighlightWidth) end
if button then
for i = 1, numToDisplay do getglobal(button .. i):SetWidth(bigWidth) end
end
end
return showScrollBar
end
-- A wheel spin over the list → one row (WoW convention: +1 up, −1 down). Called from the
-- owner window's OnMouseWheel (the engine bubbles the spin up the parent chain, so the handler
-- lives on the window, not a child — TrainerFrame.xml's own note).
--
-- KEEPS the `Benilla` prefix on purpose: the reference has no faux-scroll wheel function to
-- take the name of. Its `ScrollFrameTemplate_OnMouseWheel` is the REAL-scroll one and moves
-- by half the bar's height; its faux owners each write a wheel body of their own inline.
function BenillaFauxScrollFrame_OnMouseWheel(frame, delta)
local scrollBar = getglobal(frame:GetName() .. "ScrollBar")
BenillaScrollBar_Step(scrollBar, -delta)
end
-- ══════════════════════════ The REAL-scroll kit ══════════════════════════════════════════
--
-- The other half of the file: a true `<ScrollFrame>` (engine mechanism, decision 0112) that
-- pans and CLIPS a variable-height child, as against the faux kit above (fixed rows re-bound
-- to a data offset). Windows whose content height is unknown until it is laid out — a gossip
-- menu of wrapped options, a quest's body text — need this one; a uniform list wants the faux.
--
-- SHAPE, from the reference `UIPanelScrollFrameTemplate` (Interface\FrameXML\
-- UIPanelTemplates.xml l.166-211, extracted via mpqx): the scrollbar is a SLIDER that rides
-- its ScrollFrame as a child, so `bar:GetParent()` IS the frame it scrolls — no name
-- derivation, and `$parentBar` gives SyncBar its handle. The ref's OnVerticalScroll drives
-- the bar's value and greys the two arrows at the ends; ours does the same through the
-- shared arrow helper above.
BENILLA_SCROLL_STEP = 20 -- px per arrow click / wheel notch
BENILLA_SCROLL_PAD = 20 -- breathing room under the last element in the child
-- The bar moved (drag, arrow, or SyncBar): pan the frame it rides. The loop this could form
-- (bar → scroll → OnVerticalScroll → SyncBar → bar) dead-ends in one hop — SyncBar writes the
-- value the bar already holds, and the Slider's SetValue change-gate (decision 0250) drops it.
function BenillaScrollBar_OnValueChanged(bar, value)
bar:GetParent():SetVerticalScroll(value)
BenillaScrollBar_UpdateArrows(bar)
end
-- Reconcile a ScrollFrame's bar to its live range (ref ScrollFrame_OnScrollRangeChanged):
-- the bar spans [0, range] in pixels, seats at the current scroll, and hides entirely when
-- the content fits. Wired to both OnScrollRangeChanged and OnVerticalScroll by the owner.
function BenillaScroll_SyncBar(sf)
local bar = getglobal(sf:GetName() .. "Bar")
if not bar then return end
local range = sf:GetVerticalScrollRange()
bar:SetValueStep(BENILLA_SCROLL_STEP)
bar:SetMinMaxValues(0, range)
bar:SetValue(sf:GetVerticalScroll())
BenillaScrollBar_UpdateArrows(bar)
BenillaScrollBar_SetShown(bar, range > 0)
end
-- Pan by `delta` px; the engine clamps to [0, range].
function BenillaScroll_Step(sf, delta)
sf:SetVerticalScroll(sf:GetVerticalScroll() + delta)
BenillaScroll_SyncBar(sf)
end
-- WoW wheel convention: +1 per notch up, -1 down — up lifts the content.
function BenillaScroll_OnMouseWheel(sf, delta)
BenillaScroll_Step(sf, -delta * BENILLA_SCROLL_STEP)
end
-- A widget's resolved bottom edge for the content sum — nil when it isn't contributing
-- (hidden frame / blank FontString / not yet resolved this frame).
function BenillaScroll_WidgetBottom(w)
if not w then return nil end
if w.IsShown then
if not w:IsShown() then return nil end
elseif w.GetText then
local t = w:GetText()
if not t or t == "" then return nil end
end
return w:GetBottom()
end
-- Size a scroll child to its MEASURED content — the resolved bottom of the lowest visible
-- widget, plus padding — instead of a fixed budget, and tell the frame (which fires
-- OnScrollRangeChanged → the owner's SyncBar). Frame-late by nature: rects resolve after Lua
-- runs, so owners re-run this from an OnUpdate until it stops changing. Returns true when it
-- actually resized (only a real change writes: SetHeight invalidates layout, and an
-- every-frame write would fight the resolver).
function BenillaScroll_ResizeChild(scrollName, widgets)
local sf = getglobal(scrollName)
local child = getglobal(scrollName .. "Child")
local top = child:GetTop()
if not top then return false end
local bottom = nil
for i = 1, table.getn(widgets) do
local b = BenillaScroll_WidgetBottom(getglobal(widgets[i]))
if b and (not bottom or b < bottom) then bottom = b end
end
local height = 1
if bottom then height = (top - bottom) + BENILLA_SCROLL_PAD end
if height < 1 then height = 1 end
if math.abs(child:GetHeight() - height) < 1 then return false end
child:SetHeight(height)
sf:UpdateScrollChildRect()
return true
end
]]></Script>
<!--
The scroll bar: a `Slider` (0250) 16px wide, its up/down step buttons pinned above/below the
track, and the knob thumb. The value is in pixels (see the header); the thumb draws at the
value fraction along the track (engine-side, decision 0250 §4), draggable (§5). The step-button
+ knob art is the stock 1.12 scrollbar kit, each texture cropped to its centred 16px (the
`0.25..0.75` TexCoords the ref's `UIPanelScrollBarButton` applies).
-->
<Slider name="BenillaScrollBarTemplate" virtual="true">
<Size><AbsDimension x="16" y="0"/></Size>
<Frames>
<Button name="$parentScrollUpButton">
<Size><AbsDimension x="16" y="16"/></Size>
<Anchors><Anchor point="BOTTOM" relativePoint="TOP"/></Anchors>
<NormalTexture file="Interface\Buttons\UI-ScrollBar-ScrollUpButton-Up"><TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/></NormalTexture>
<PushedTexture file="Interface\Buttons\UI-ScrollBar-ScrollUpButton-Down"><TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/></PushedTexture>
<DisabledTexture file="Interface\Buttons\UI-ScrollBar-ScrollUpButton-Disabled"><TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/></DisabledTexture>
<HighlightTexture file="Interface\Buttons\UI-ScrollBar-ScrollUpButton-Highlight" alphaMode="ADD"><TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/></HighlightTexture>
<Scripts><OnClick>BenillaScrollBar_Step(this:GetParent(), -1); PlaySound("UChatScrollButton")</OnClick></Scripts>
</Button>
<Button name="$parentScrollDownButton">
<Size><AbsDimension x="16" y="16"/></Size>
<Anchors><Anchor point="TOP" relativePoint="BOTTOM"/></Anchors>
<NormalTexture file="Interface\Buttons\UI-ScrollBar-ScrollDownButton-Up"><TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/></NormalTexture>
<PushedTexture file="Interface\Buttons\UI-ScrollBar-ScrollDownButton-Down"><TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/></PushedTexture>
<DisabledTexture file="Interface\Buttons\UI-ScrollBar-ScrollDownButton-Disabled"><TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/></DisabledTexture>
<HighlightTexture file="Interface\Buttons\UI-ScrollBar-ScrollDownButton-Highlight" alphaMode="ADD"><TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/></HighlightTexture>
<Scripts><OnClick>BenillaScrollBar_Step(this:GetParent(), 1); PlaySound("UChatScrollButton")</OnClick></Scripts>
</Button>
</Frames>
<Scripts>
<OnValueChanged>BenillaFauxScrollBar_OnValueChanged(self, arg1)</OnValueChanged>
</Scripts>
<ThumbTexture name="$parentThumbTexture" file="Interface\Buttons\UI-ScrollBar-Knob">
<Size><AbsDimension x="16" y="16"/></Size>
<TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/>
</ThumbTexture>
</Slider>
<!--
The scroll bar's TROUGH — the recessed channel the bar rides in: the "background and
border" a bare arrows+knob bar is missing. An owner declares one of these BESIDE its bar,
named `<barname>Trough` and WITH NO ANCHORS — it seats itself on the bar the first time
BenillaScrollBar_SetShown touches it (BenillaScrollTrough_Seat above, where the socket law
and the ref citations live). Declare it BEFORE the bar so it draws beneath.
THE ART, and why it is sliced this way. `Interface\PaperDollInfoFrame\UI-Character-ScrollBar`
is a 64x256 sheet holding TWO 31-wide channel pieces, measured texel-exact from the file —
rails at x 2-5 and 25-30 with a 19-wide interior between them, and each piece bearing a
16-tall SOCKET for its arrow button:
· LEFT column (u 0 .. 31/64): rows 0-3 the solid TOP cap, 4-19 the up-arrow socket,
20-21 the crossbar that closes it, then plain channel from row 22 to the bottom edge.
· RIGHT column (u 33/64 .. 1): plain channel, then rows 85-86 the crossbar, 87-102 the
down-arrow socket, 104-106 the solid BOTTOM cap (nothing below 107).
The ref composes a trough from exactly two quads — the whole left column plus the right
column's bottom 108 rows — which reaches 362 units and no further, and only works because
its scroll frames are short enough for the two to OVERLAP. Ours must span a 563-tall page,
so the same art is cut as a THREE-SLICE instead: the two socket ends at their natural
heights and a plain-channel slice stretched between them. The middle is constant down its
whole length, so stretching it is exact — there is no seam to find.
The black `Fill` is the ref's own trough backing, not an invention: ItemTextFrame draws a
0.8-alpha black quad behind its channel art for exactly this (ref-ItemTextFrame.xml's
`Middle`). It sits in BACKGROUND and the channel art in BORDER — two layers, because within
ONE layer the client re-sorts overlapping quads by texture handle and their order is not
ours to depend on (order.rs). The bar's own furniture stays clear above both: button state
textures are ARTWORK, the knob is OVERLAY.
-->
<Frame name="BenillaScrollTroughTemplate" virtual="true" hidden="true">
<Size><AbsDimension x="31"/></Size>
<Layers>
<Layer level="BACKGROUND">
<!-- The channel interior: x 6..24 of the 31-wide piece, i.e. inset 6 from each edge. -->
<Texture name="$parentFill">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
</Anchors>
<Color r="0" g="0" b="0" a="0.8"/>
</Texture>
</Layer>
<Layer level="BORDER">
<!-- Cap + up-arrow socket + crossbar: left column, rows 0..22 (v 0 .. 23/256). -->
<Texture name="$parentTop" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
<Size><AbsDimension y="23"/></Size>
<Anchors>
<Anchor point="TOPLEFT"/>
<Anchor point="TOPRIGHT"/>
</Anchors>
<TexCoords left="0" right="0.484375" top="0" bottom="0.08984375"/>
</Texture>
<!-- Crossbar + down-arrow socket + cap: right column, rows 82..107
(v 82/256 .. 108/256) — the mirror of the top's 23, cut to put row 107 on the
trough's bottom edge exactly as ReputationFrame's own 108/0.421875 does, which
is what lands the socket on the arrow (BenillaScrollTrough_Seat's law). -->
<Texture name="$parentBottom" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
<Size><AbsDimension y="26"/></Size>
<Anchors>
<Anchor point="BOTTOMLEFT"/>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<TexCoords left="0.515625" right="1.0" top="0.3203125" bottom="0.421875"/>
</Texture>
<!-- The stretched run between them: left column rows 30..70, plain rails only.
Declared LAST because it anchors to the two above by name, and an anchor whose
target is declared later falls back to the parent (ItemTextFrame's own note). -->
<Texture name="$parentMiddle" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentTop" relativePoint="BOTTOMLEFT"/>
<Anchor point="BOTTOMRIGHT" relativeTo="$parentBottom" relativePoint="TOPRIGHT"/>
</Anchors>
<TexCoords left="0" right="0.484375" top="0.1171875" bottom="0.2734375"/>
</Texture>
</Layer>
</Layers>
</Frame>
<!--
The faux-scroll container: a plain `<Frame>` carrying the offset state + a right-hand scroll bar
(inset 16px top/bottom so the arrow buttons sit inside the track span — the ref
UIPanelScrollFrameTemplate's scrollbar anchors). An owner instantiates this and, in its own
OnLoad, sets `frame.updateFunc` to its list-repaint.
-->
<Frame name="FauxScrollFrameTemplate" virtual="true">
<Frames>
<Slider name="$parentScrollBar" inherits="BenillaScrollBarTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="6" y="-16"/></Offset></Anchor>
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="6" y="16"/></Offset></Anchor>
</Anchors>
</Slider>
</Frames>
<Scripts>
<OnLoad>ScrollFrame_OnLoad()</OnLoad>
</Scripts>
</Frame>
</Ui>