Files
benilla-octo/crates/benilla-app/assets/ui/ScrollTemplates.xml
T
SamandClaude Opus 5.5 5b85c2adaf ui: drop BENILLA_FALLBACK_ICON, which nothing reads
Its readers were our own ActionBar and group-loot files, and both were
retired when those windows moved to the stock FrameXML. The stock files
leave a missing texture empty.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 19:55:51 +07:00

282 lines
16 KiB
XML

<Ui>
<!--
Benilla's scroll kits, sharing one `Slider` bar, its arrow helpers and an optional trough:
a faux-scroll kit that re-binds a fixed set of rows to a moving data offset, and a
real-scroll kit that pans and clips a `<ScrollFrame>` child whose height is known only after
layout. A real-scroll bar overrides the template's OnValueChanged on its instance, which
wins because the loader runs every `<Scripts>` block in order.
Faux model: the value is pixels, `[0, (numItems - numToDisplay) * itemHeight]` in steps of
`itemHeight`; the parent holds the 0-based top row in `frame.offset` and its repaint in
`frame.updateFunc`, set by the owner's OnLoad.
Deviation: the faux parent is a plain `<Frame>`, not the reference's ScrollFrame relay,
which adds nothing because the reference's `SetVerticalScroll` stores the bar's value as is.
`FauxScrollFrameTemplate` and the `FauxScrollFrame_*`, `ScrollFrame_*` and
`ScrollingEdit_*` API are the stock `UIPanelTemplates` on the player's chain, which loads
after this file, so its declarations win.
-->
<Script><![CDATA[
-- Steps the bar by its valueStep. Deviation: one row on a faux list, where the reference
-- bar's arrows step half its height. Soundless: the reference plays UChatScrollButton on
-- an arrow click, never on the wheel, so the arrows' OnClick plays it.
function BenillaScrollBar_Step(scrollBar, dir)
local step = scrollBar:GetValueStep()
if step <= 0 then step = 1 end
scrollBar:SetValue(scrollBar:GetValue() + dir * step)
end
-- Greys each arrow at its end of the range (reference: ScrollFrame_OnScrollRangeChanged).
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 faux bar's OnValueChanged: the top row is the value rounded to the nearest row, so
-- thumb and rows agree, and `updateFunc` runs only when that row changes, which also ends
-- the loop when a repaint re-seats the thumb.
-- A ScrollFrame parent also gets the value through SetVerticalScroll, as the reference's
-- `UIPanelScrollBarTemplate` routes it (UIPanelTemplates.xml l.156-158).
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
-- Seats a trough on its bar with the reference's ReputationFrame hang, 8 left, 21 above and
-- 20 below (ReputationFrame.xml l.573-599 against UIPanelTemplates.xml l.166-181), which
-- puts each art socket on its arrow button. Lazy, because the bar is declared after its
-- 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
-- Shows or hides a bar and its `<barname>Trough` together, seating the trough first; show
-- and hide a bar only through here. The trough is opt-in because 1.12 draws trough art per
-- window: `UIPanelScrollBarTemplate` carries only the arrows and the knob.
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
-- The same for a stock `FauxScrollFrameTemplate` frame, whose bar is the reference's: seats
-- `<frame>ScrollBarTrough` and mirrors the frame's shown state. Call it after
-- `FauxScrollFrame_Update`, which shows or hides the frame.
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
-- One row per wheel notch (+1 up) on a faux list, called from the owner window's
-- OnMouseWheel since the spin bubbles up the parent chain. Benilla-named: the reference has
-- no faux wheel function (its `ScrollFrameTemplate_OnMouseWheel` moves half the bar).
function BenillaFauxScrollFrame_OnMouseWheel(frame, delta)
local scrollBar = getglobal(frame:GetName() .. "ScrollBar")
BenillaScrollBar_Step(scrollBar, -delta)
end
-- ══════════════════════════ The real-scroll kit ══════════════════════════════════════════
-- Shaped on the reference `UIPanelScrollFrameTemplate` (UIPanelTemplates.xml l.166-211):
-- the bar is a `Slider` child, `$parentBar`, so `bar:GetParent()` is the frame it pans.
BENILLA_SCROLL_STEP = 20 -- px per arrow click or wheel notch
BENILLA_SCROLL_PAD = 20 -- px of room below the child's last element
-- Pans the frame the bar rides. The loop back through OnVerticalScroll and SyncBar ends in
-- one hop: SyncBar writes the value the bar already holds, which Slider:SetValue ignores.
function BenillaScrollBar_OnValueChanged(bar, value)
bar:GetParent():SetVerticalScroll(value)
BenillaScrollBar_UpdateArrows(bar)
end
-- Fits the bar to the frame's live range, [0, range] px, and hides it when the content fits
-- (reference: ScrollFrame_OnScrollRangeChanged). Owners call it from OnScrollRangeChanged
-- and OnVerticalScroll.
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
-- Pans by `delta` px, clamped here: SetVerticalScroll stores any value, as the reference's
-- does; the reference's ScrollFrameTemplate_OnMouseWheel clamps through its bar's range.
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
-- The wheel's delta is +1 per notch up, and up scrolls toward the top.
function BenillaScroll_OnMouseWheel(sf, delta)
BenillaScroll_Step(sf, -delta * BENILLA_SCROLL_STEP)
end
-- A widget's resolved bottom, or nil if hidden, a blank FontString, or not laid out yet.
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
-- Sizes the scroll child to its lowest visible widget plus BENILLA_SCROLL_PAD and updates
-- the frame's range. Rects resolve after Lua runs, so owners re-run it from OnUpdate until
-- it returns false; it writes only on a real change, since SetHeight invalidates layout.
function BenillaScroll_ResizeChild(scrollName, widgets)
local sf = getglobal(scrollName)
-- The frame's own child, not a derived name: the reference pairs `<X>ScrollFrame` with
-- `<X>ScrollChildFrame`.
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 bar: a 16px `Slider` with step buttons above and below the track, on the stock 1.12
scrollbar art cropped to its centred 16px (the `0.25..0.75` TexCoords of the reference's
`UIPanelScrollBarButton`).
-->
<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>
<!--
A bar's trough, the recessed channel it rides in; the reference's scroll kit has none.
Declare it before its bar, so it draws beneath, named `<barname>Trough` and with no
anchors: BenillaScrollTrough_Seat seats it on the bar.
`UI-Character-ScrollBar` (64x256) holds two 31-wide channel pieces, the left column
(u 0 .. 31/64) capped at the top and the right (u 33/64 .. 1) at the bottom, each with a
16-tall socket for its arrow button: rows 4-19 at the top, rows 87-102 at the bottom.
Deviation: cut as a three-slice (both socket ends at natural height, the plain channel
stretched between) where the reference overlaps two quads, since two quads reach only 362
units and a page here is 563 tall.
The black fill is the reference's trough backing (ItemTextFrame's 0.8-alpha `Middle`). It
is in BACKGROUND and the art in BORDER because the client orders overlapping quads within
one layer by texture handle; the bar's buttons (ARTWORK) and knob (OVERLAY) draw above both.
-->
<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, 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), ending on v 0.421875 as ReputationFrame's does. -->
<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: an anchor to a later sibling falls back to the parent. -->
<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>
</Ui>