- 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>
542 lines
34 KiB
XML
542 lines
34 KiB
XML
<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>
|