Files
benilla-octo/crates/benilla-app/assets/ui/ScrollTemplates.xml
T
SamandClaude Fable 5.1 7735447d32 the window tab goes stock, and the ferry keeps its rider at the seam
- the window tab is the reference's own: `CharacterFrameTemplates.xml` loads off the chain at the reference's toc l.21 — the reference never gives it a toc line, `UIOptionsFrame.xml` includes it, and ours is not the reference's options window, so the row is explicit — and our `CharacterFrameTabButtonTemplate` with its `BenillaTab_Room`/`BenillaTabButton_OnUpdate` settle is deleted, 192 lines; the stock template fits a tab ONCE, in `<OnShow>`, through `PanelTemplates_TabResize(0)`, which needs a `GetStringWidth` that answers inside the Lua call that asked — the engine's synchronous measurer is that, so the settle that re-fit from `OnUpdate` until a frame-late round-trip landed is dead weight; retired with it, the director's call: 1002's structural clamp (`benillaTabRightInset`/`benillaTabPadding`/`benillaTabMaxWidth`, a tab never past its window's drawn right edge) — the reference's only caps are the numbers each window passes `TabResize`, and nothing had set those fields since the macro window went stock; 1004's highlight law is unchanged and now the reference's own text (`LEFT +10`/`RIGHT −10`, two opposing anchors that never consult the width, so both `SetWidth` lines are dead code there and here); measured live on the frame the window opens, main's 115·115·115·115 becomes 89.8·99.3·70.8·75.6 on the character row, 77.9·66.1·70.8·66.1 on the social, 89.8·85.0 on the merchant — each its label's `GetStringWidth()` + 40 — and one frame later the two builds agree exactly, so what goes is a one-frame 115 px flash on every open of every one of those windows (1993)
- the ferry seam: `tick_transports` leaves an off-map transport's transform unwritten — the reference's own freeze, `0x5f5248`'s leg map read against the current map and the return at `0x5f5279` past the pose stores — but 0455's riding worldport composes the rider's world pose as `boat.translation + boat.rotation * local_pos` from exactly that transform, so a client whose path clock had not crossed put the body at Booty Bay's coordinates on Kalimdor, which want ZERO tiles of Kalimdor's WDT: the reported `0/0 resident`, which cannot clear (`is_ready` needs `total > 0`) and cannot re-raise (`focus_resident` takes its vacuous off-grid arm); the fall is a second bug in the same window: `SelfMoveMessage` dropped the wire's `ON_TRANSPORT` tail and `apply_self_move` re-derived the deck-local pose as `world − boat.translation`, which against a frozen source-continent boat is the distance between two continents — consistent while the boat stays frozen, a ~13,000 yd fling off the deck the instant it writes an honest pose, `update_attachment` keeping the ride because the body is airborne and no settle hold to catch it; the rider's deck-local pose now comes off the wire, and `first_cycle_on_map` + `reseek_ridden_transport_at_worldport` seek our clock to the head of the destination map's leg and write the pose in the worldport frame itself, a clock that already agreed left untouched; cleared with numbers: the timetable matches vmangos's to 18 ms over path 241's 350 818 ms cycle, the period is bit-identical to the DB row, the live skew is 32 and 144 ms on the Orgrimmar zeppelin; two divergences kept knowingly — we spare the ridden transport where `SMSG_NEW_WORLD` (`0x401bc0`) destroys the whole object manager, and we hide an off-map boat the reference draws; an off-grid view focus names its map and tile once, a ridden transport whose map disagrees with `CurrentMap` for three frames warns, and the sea-crossing probe sends itself to a dock (`WOW_PROBE_DOCK=x,y,z`) because the fleet's seams are not interchangeable — one path crosses mid-cycle, another at the wrap; why the director saw 38.5 s and not a few frames is still open (N08, 2026)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 19:36:48 +07:00

421 lines
28 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 nothing to the value on its way through: 0251 believed it clamped to the
scroll range and called the clamp a liability, and decision 2017 read the bytes — the
reference's `SetVerticalScroll` stores what the bar hands it. So the relay is a pure
pass-through, and benilla's faux frame is a plain `<Frame>` + a `Slider`: fewer moving parts
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.
--
-- The engine's SetVerticalScroll takes the bar's value as given (decision 2017 — the
-- reference's never reads the scroll range), so an addon frame's `<OnVerticalScroll>`
-- fires with this very value and its `FauxScrollFrame_OnVerticalScroll` derives the offset
-- from it, the reference's path end to end. The offset our own kit computes above is the
-- same number by construction — 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
-- 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
-- **Our trough, following the REFERENCE's bar.** `BenillaScrollBar_SetShown` above is the
-- Benilla kit's own pairing and nothing in the reference's faux kit calls it — the
-- reference has no trough at all, which is exactly why this template stays ours (1837's
-- manifest note). Since the faux frames took the chain's `FauxScrollFrameTemplate` their
-- bar is `UIPanelScrollBarTemplate`, so an owner that wants its trough asks for it here,
-- right after its `FauxScrollFrame_Update`. Seating is idempotent and the visibility
-- simply mirrors the bar's, which the reference's `_Update` has already decided by
-- showing or hiding the scroll FRAME.
--
-- Decision 1860. Named after the bar rather than the frame because the trough is declared
-- `<barname>Trough` — one law, stated once, exactly as BenillaScrollTrough_Seat wants it.
function BenillaScrollTrough_Follow(scrollFrameName)
local sf = getglobal(scrollFrameName)
if not sf then return end
local bar = getglobal(scrollFrameName .. "ScrollBar")
if not bar then return end
local trough = getglobal(bar:GetName() .. "Trough")
if not trough then return end
BenillaScrollTrough_Seat(trough, bar)
if sf:IsShown() then trough:Show() else trough:Hide() end
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, clamped to [0, range] HERE: the engine's SetVerticalScroll stores
-- what it is given (decision 2017 — the reference's never reads the range), and the
-- reference's own wheel handler bounds itself the same way, through its bar's [min, max]
-- (ScrollFrameTemplate_OnMouseWheel → scrollBar:SetValue).
function BenillaScroll_Step(sf, delta)
local target = sf:GetVerticalScroll() + delta
local range = sf:GetVerticalScrollRange()
if target > range then target = range end
if target < 0 then target = 0 end
sf:SetVerticalScroll(target)
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)
-- ASK THE FRAME, don't guess a name. This used to be
-- `getglobal(scrollName .. "Child")`, which only ever worked because every caller wore
-- a private name in that exact shape — the reference's own convention is
-- `<X>ScrollFrame` / `<X>ScrollChildFrame`, not a suffix, so the derivation broke the
-- moment QuestFrame took the reference's names. A ScrollFrame knows its own child.
local child = sf:GetScrollChild()
if not child then return false end
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>
<!-- ══ Carried here from UiPanels.xml when that file went stock (decision 1988) ══
The placeholder icon our UIParent.lua counterpart held and the reference's does not,
seated in the earliest file of ours every consumer loads after. The window tab template
and its settle stood here too until 1993 put them on the chain. -->
<Script><![CDATA[
-- The placeholder icon for an item/spell whose texture has not arrived yet (Craft, Quest,
-- QuestLog, Trainer and TradeSkill read it). Was ActionBar.xml's (stock since 1938), then
-- UiPanels.xml's (stock since 1988); this file is the earliest of ours every reader loads
-- after. A benilla name, so nothing stock reads it.
BENILLA_FALLBACK_ICON = "Interface\\Icons\\INV_Misc_QuestionMark"
]]></Script>
<!-- `FauxScrollFrameTemplate` and the `FauxScrollFrame_*`/`ScrollFrame_*`/`ScrollingEdit_*`
API are the REFERENCE's, off the player's chain (`Interface\FrameXML\UIPanelTemplates`,
seated below this file — so its declarations win). Our transcriptions stood here dead
from the day that entry landed and are gone (1860). What stays is
`BenillaScrollBarTemplate` + `BenillaScrollTroughTemplate` and their helpers, which are
ours: six windows inherit the bar directly and the reference has no trough at all. -->
</Ui>