Files
benilla-octo/crates/benilla-app/assets/ui/StackSplit.xml
T
SamandClaude Fable 5 14bd5ea1a4 the bank is the reference's own window, and far sight freezes your body
- the bank window is the reference's own `BankFrame.xml` run off the player's chain — our 890-line copy is deleted whole; putting bags into the bank's bag slots works (the two verbs its buttons are wired to now exist), the purse is a real `SmallMoneyFrameTemplate` splitting 987654321 copper into three coin buttons instead of one string, `BANKFRAME_OPENED` loses the argument the real client never fires, and the bank stops auto-opening your bags while the vendor and mailbox keep doing it; the empty-slot click still plays the bag sound, because the reference's own line plays it on every click the cursor did not consume — named as faithful rather than quietly fixed
- underneath it, the bank's inventory band is a VIEW over the container feed, not a second copy — inventory ids 40..69 had answered nil because the doll array stops at 24, so the stock file swapped in would have drawn an empty bank; the six bank bag slots become real inventory slots (64..=69) fed off the player descriptor, the three bag verbs are carved from the binary (`PutItemInBackpack` IS `PutItemInBag(0xFF)` through a ten-byte thunk, the occupied-by-an-ordinary-item leg auto-stores with no destination slot at all, and the return is the Lua number 1 or nil — the nil being the whole reason a plain left-click opens a bag), and the swap caught `BankButtonIDToInvSlotID`'s bag arm six slots wrong: it took a bag number 1..6 where the reference passes the button's own id, which is the container id
- far sight freezes your own body while the view is out — the fifth conjunct of the movement predicates' shared precondition, joining the health term so the walk, the turn and the follow teardown go down together; both halves are load-bearing: the `IsActivePlayer` half is what keeps a Mind Control victim walking (possession rides the same PLAYER_FARSIGHT field), and the latch half is the RESOLVED engage rather than the raw field — and the camera is untouched, you still look around freely, which is the whole point of the spell
- the reset offer no longer outlives the character that earned it — log out after walking out of the Deadmines and your next character was offered a reset the first one had earned — and the one lockout leg no probe can ever reach (vmangos writes a last-instance map only from raid binds, which the client's handler discards as not-a-party-dungeon) is now covered end to end through the real system, its module doc saying plainly: tested and never observed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:18:51 +07:00

315 lines
17 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>
<!--
The stack-split spinner (decision 0216 §6/§D, scratchpad `ref-framexml/StackSplitFrame.xml`/
`.lua`, re-expressed onto our own cursor seam — `SplitContainerItem`/
`OpenStackSplitFrame` already exist in benilla-ui's `cursor.rs`; this file is purely the
window + the Left/Right/Okay/Cancel plumbing around it). One shared instance, like the real
client's `StackSplitFrame` — any owner (a bag slot today; the paper doll / bank / mail slices
reuse it unmodified later) sets `owner.SplitStack = function(owner, n) ... end` before calling
`OpenStackSplitFrame`, and Okay calls it generically (`StackSplitFrameOkay_Click`).
Textures: `Interface\MoneyFrame\UI-MoneyFrame` (the little parchment plate) and
`Interface\MoneyFrame\Arrow-Left/Right-Up/Down/Disabled` are the ref's own — both already
resolve through the shared sprite loader the money-purse coin icons use
(`Interface\MoneyFrame\UI-MoneyIcons`, BagFrame.xml/MerchantFrame.xml), so they're quoted
verbatim rather than substituted. The Okay/Cancel button face (`UI-Panel-Button-*`) is our
own local virtual template, NOT a cross-file `inherits` of `BenillaQuestPanelButtonTemplate`
(QuestFrame.xml) — this file loads BEFORE QuestFrame.xml (see ui_script/mod.rs's load order),
so that template isn't registered yet; the house convention already answered this exact case
for GossipFrame/QuestFrame/QuestLogFrame ("our engine ships no UIPanelButtonTemplate, so we
draw it") — this is one more independent draw of the same real art.
TYPED-DIGIT ENTRY IS LIVE (decision 1319). It used to be this file's headline deferral
("this engine has no plain-frame OnChar/OnKeyDown capture, only EditBox focus"), and the
engine now has the delivery walk that unblocks it (`script::keyboard`, off wow-re's
byte-verified `frame-key-script-delivery.md`). The handlers below are OUR OWN spinner
logic, written from the widget's behaviour, not a transcription of the reference's Lua
(their text is never copied into this repo). `enableKeyboard` on the frame is
what puts it in the walk's bucket at all.
NAMED DEFERRAL:
- The ref's shift-click "insert the item link into the chat editbox" (StackSplitFrame isn't
even opened in that branch — ContainerFrame.lua:568-569 diverts to
`ChatFrameEditBox:Insert` instead of `OpenStackSplitFrame`) never triggers here: the
shift-click gesture itself has no plumbing in this engine (no modifier-key global exists
at all), so there is no path that
reaches this branch to defer. `EditBox:Insert` DOES exist on our engine (editbox.rs) for
when that gap closes.
-->
<Script><![CDATA[
-- OpenStackSplitFrame(maxStack, owner, anchor, anchorTo) — ref StackSplitFrame.lua:2-24,
-- verbatim shape. `owner` must already carry `owner.SplitStack = function(owner, n) ... end`
-- (the reference's own `ContainerFrameItemButton_OnClick` and `BankFrameItemButton_OnLoad`
-- both set it, and both are sourced since 1751). maxStack < 2 hides instead of showing (a
-- 1-stack has nothing to split) — the ref's own guard.
function OpenStackSplitFrame(maxStack, owner, anchor, anchorTo)
local frame = StackSplitFrame
if frame.owner then
frame.owner.hasStackSplit = nil
end
frame.maxStack = maxStack
if frame.maxStack < 2 then
frame:Hide()
return
end
frame.owner = owner
owner.hasStackSplit = true
frame.split = 1
-- `typed` distinguishes "1 because nothing has been typed yet" from "1 because the
-- player typed it": the first digit REPLACES the seed, later digits append. Cleared on
-- every open, so a dialog reopened after typing starts fresh.
frame.typed = nil
StackSplitText:SetText(frame.split)
StackSplitLeftButton:Disable()
StackSplitRightButton:Enable()
-- ref l.21-22: ClearAllPoints first — SetPoint only replaces an anchor of the SAME
-- point, and the anchor callers choose (BagFrame.xml passes "BOTTOMRIGHT") differs from
-- this frame's authored placeholder ("CENTER"), so skipping this would leave both live.
frame:ClearAllPoints()
frame:SetPoint(anchor, owner, anchorTo, 0, 0)
frame:Show()
end
-- UpdateStackSplitFrame(maxStack) — ref l.26-52: the owner's stack shrank/grew out from
-- under an OPEN spinner (e.g. a server correction). Not called by anything in benilla yet
-- (no caller has a live reason to resize an open spinner today) — kept so a later caller
-- (the paper doll / bank slice) drops in without a new function.
function UpdateStackSplitFrame(maxStack)
local frame = StackSplitFrame
frame.maxStack = maxStack
if frame.maxStack < 2 then
if frame.owner then
frame.owner.hasStackSplit = nil
end
frame:Hide()
return
end
if frame.split > frame.maxStack then
frame.split = frame.maxStack
StackSplitText:SetText(frame.split)
end
if frame.split == frame.maxStack then
StackSplitRightButton:Disable()
else
StackSplitRightButton:Enable()
end
if frame.split == 1 then
StackSplitLeftButton:Disable()
else
StackSplitLeftButton:Enable()
end
end
-- The arrows go through the shared setter (below) rather than nudging the count and the
-- two buttons themselves: with typed entry live there are now three writers of
-- `frame.split`, and three copies of the enable/disable rule is how they drift apart.
function BenillaStackSplitLeft_Click()
BenillaStackSplitSet(StackSplitFrame.split - 1)
end
function BenillaStackSplitRight_Click()
BenillaStackSplitSet(StackSplitFrame.split + 1)
end
function BenillaStackSplitOkay_Click()
local frame = StackSplitFrame
if frame.owner then
frame.owner.SplitStack(frame.owner, frame.split)
end
frame:Hide()
end
function BenillaStackSplitCancel_Click()
StackSplitFrame:Hide()
end
-- The one place the count is written, so the buttons' enable state and the label can
-- never disagree with `frame.split` (the arrow handlers below call it too). Clamped to
-- [1, maxStack] because every caller can overshoot: typing 9 into a 5-stack, or the
-- arrows at their ends.
function BenillaStackSplitSet(n)
local frame = StackSplitFrame
if n < 1 then n = 1 end
if n > frame.maxStack then n = frame.maxStack end
frame.split = n
StackSplitText:SetText(n)
if n <= 1 then
StackSplitLeftButton:Disable()
else
StackSplitLeftButton:Enable()
end
if n >= frame.maxStack then
StackSplitRightButton:Disable()
else
StackSplitRightButton:Enable()
end
end
-- Typed digits (decision 1319). The engine hands OnChar the literal character, so this is
-- a digit test and an append: the first keystroke replaces the seeded 1, each later one
-- shifts a decimal place in. A digit that would overshoot the stack is CLAMPED rather than
-- rejected -- typing 9 against a 5-stack leaves 5, which is what the player meant -- and
-- clamping keeps `typed` set, so a following digit still appends against the clamp.
-- Anything that is not a digit is ignored here; the frame consumes the key regardless
-- (the engine's existence gate decides that, not this handler's return).
function StackSplitFrame_OnChar()
local c = arg1
if c < "0" or c > "9" then return end
local frame = StackSplitFrame
local digit = tonumber(c)
local n
if frame.typed then
n = frame.split * 10 + digit
else
n = digit
end
-- A leading 0 types nothing: the count can never be 0, so it stays 1 and unlatched,
-- and the next digit still reads as the first one.
if n < 1 then return end
frame.typed = true
BenillaStackSplitSet(n)
end
-- The editing/commit keys for typed entry. BACKSPACE drops the last digit (and unlatches
-- at the end, so the next digit starts over rather than appending to a stale 1); ENTER
-- commits exactly as Okay does and ESCAPE cancels, which is what a player expects of a
-- dialog they are typing into. The arrow keys drive the spinner. Every other key is
-- swallowed by the frame -- the reference's own consequence of having a key script bound
-- at all, and the reason this dialog must not stay open invisibly.
function StackSplitFrame_OnKeyDown()
local key = arg1
local frame = StackSplitFrame
if key == "BACKSPACE" then
local n = floor(frame.split / 10)
if n < 1 then
frame.typed = nil
BenillaStackSplitSet(1)
else
BenillaStackSplitSet(n)
end
elseif key == "ENTER" then
BenillaStackSplitOkay_Click()
elseif key == "ESCAPE" then
BenillaStackSplitCancel_Click()
elseif key == "LEFT" then
BenillaStackSplitLeft_Click()
elseif key == "RIGHT" then
BenillaStackSplitRight_Click()
end
end
-- OnHide (ref l.158-162): ANY hide — Okay, Cancel, or ToggleGameMenu's chain hook below —
-- clears the owner's `hasStackSplit` flag (ref ItemButtonTemplate's OnHide, ContainerFrame.xml
-- l.35-39, checks `this.hasStackSplit` to auto-close the spinner if the SLOT itself goes away
-- mid-split — no consumer needs that half here, but the flag's own clear is this function's
-- job regardless of which button caused the hide).
function StackSplitFrame_OnHide()
local frame = StackSplitFrame
if frame.owner then
frame.owner.hasStackSplit = nil
end
end
]]></Script>
<!-- The Okay/Cancel face — our own local draw (see the file header) of the real
`UIPanelButtonTemplate` art (`Interface\Buttons\UI-Panel-Button-*`). -->
<Button name="BenillaStackSplitButtonTemplate" virtual="true">
<Size><AbsDimension x="64" y="24"/></Size>
<NormalFont inherits="GameFontNormal"/>
<HighlightFont inherits="GameFontHighlight"/>
<DisabledFont inherits="GameFontDisable"/>
<NormalTexture file="Interface\Buttons\UI-Panel-Button-Up"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></NormalTexture>
<PushedTexture file="Interface\Buttons\UI-Panel-Button-Down"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></PushedTexture>
<DisabledTexture file="Interface\Buttons\UI-Panel-Button-Disabled"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></DisabledTexture>
<HighlightTexture file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></HighlightTexture>
</Button>
<!-- The frame attrs are the ref's own (l.4), and `toplevel` is load-bearing: Bagnon's bag
windows sit in the SAME HIGH stratum, and without the bit the dialog's Show raises
nothing — it loses the level tie to the later-shown bag window and the plate draws
UNDERNEATH the slots while the child buttons (level+1) poke through (the director's
08-14 Bagnon report; decision 1318). With the bit, Show runs the verified raise (compact,
then top-occupied+1). `enableKeyboard` alone is left off: its only consumer is the
typed-digit deferral in the file header. -->
<Frame name="StackSplitFrame" frameStrata="HIGH" parent="UIParent" toplevel="true" enableMouse="true" enableKeyboard="true" hidden="true">
<!-- ref StackSplitFrame.xml l.4-7: 172×96. The CENTER anchor here is only the pre-first-open
placeholder — OpenStackSplitFrame always repositions before the first Show. -->
<Size><AbsDimension x="172" y="96"/></Size>
<Anchors>
<Anchor point="CENTER" relativePoint="CENTER"/>
</Anchors>
<Layers>
<Layer level="BACKGROUND">
<!-- ref l.9-15 verbatim: the plate, authored 256×32 with NO anchors. The size is
VESTIGIAL and stays that way by the byte-verified law (decision 1310): an
anchor-less region gets the creation-path implicit SetAllPoints(parent), whose
two corners pin all four edges and leave the authored size unread — the
TexCoords crop exactly 172×96 out of the 256×128 art, the frame's own size,
drawn 1:1 filling the dialog. 1308 dropped this size to dodge the old centered
fallback (B180's squashed bar); with the fallback corrected the ref's own text
is back, and the bag test pins the render either way. -->
<Texture file="Interface\MoneyFrame\UI-MoneyFrame">
<Size><AbsDimension x="256" y="32"/></Size>
<TexCoords left="0" right="0.671875" top="0" bottom="0.75"/>
</Texture>
<!-- ref l.16-24: the count, GameFontHighlight, right-justified, RIGHT -50/+18. -->
<FontString name="StackSplitText" inherits="GameFontHighlight" justifyH="RIGHT">
<Anchors>
<Anchor point="RIGHT" relativePoint="RIGHT"><Offset><AbsDimension x="-50" y="18"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- ref l.28-47: the left/right arrows, 16×16, RIGHT/LEFT-of-CENTER ±59/64, +18. -->
<Button name="StackSplitLeftButton">
<Size><AbsDimension x="16" y="16"/></Size>
<Anchors>
<Anchor point="RIGHT" relativePoint="CENTER"><Offset><AbsDimension x="-59" y="18"/></Offset></Anchor>
</Anchors>
<NormalTexture file="Interface\MoneyFrame\Arrow-Left-Up"/>
<PushedTexture file="Interface\MoneyFrame\Arrow-Left-Down"/>
<DisabledTexture file="Interface\MoneyFrame\Arrow-Left-Disabled"/>
<Scripts>
<OnClick>BenillaStackSplitLeft_Click()</OnClick>
</Scripts>
</Button>
<Button name="StackSplitRightButton">
<Size><AbsDimension x="16" y="16"/></Size>
<Anchors>
<Anchor point="LEFT" relativePoint="CENTER"><Offset><AbsDimension x="64" y="18"/></Offset></Anchor>
</Anchors>
<NormalTexture file="Interface\MoneyFrame\Arrow-Right-Up"/>
<PushedTexture file="Interface\MoneyFrame\Arrow-Right-Down"/>
<DisabledTexture file="Interface\MoneyFrame\Arrow-Right-Disabled"/>
<Scripts>
<OnClick>BenillaStackSplitRight_Click()</OnClick>
</Scripts>
</Button>
<!-- ref l.68-84: Okay, RIGHT-of-BOTTOM -3/+32. -->
<Button name="StackSplitButton1" inherits="BenillaStackSplitButtonTemplate" text="OKAY">
<Anchors>
<Anchor point="RIGHT" relativePoint="BOTTOM"><Offset><AbsDimension x="-3" y="32"/></Offset></Anchor>
</Anchors>
<Scripts>
<OnClick>BenillaStackSplitOkay_Click()</OnClick>
</Scripts>
</Button>
<!-- ref l.85-101: Cancel, LEFT-of-BOTTOM +5/+32. -->
<Button name="StackSplitButton2" inherits="BenillaStackSplitButtonTemplate" text="CANCEL">
<Anchors>
<Anchor point="LEFT" relativePoint="BOTTOM"><Offset><AbsDimension x="5" y="32"/></Offset></Anchor>
</Anchors>
<Scripts>
<OnClick>BenillaStackSplitCancel_Click()</OnClick>
</Scripts>
</Button>
</Frames>
<Scripts>
<OnHide>StackSplitFrame_OnHide()</OnHide>
<OnChar>StackSplitFrame_OnChar()</OnChar>
<OnKeyDown>StackSplitFrame_OnKeyDown()</OnKeyDown>
</Scripts>
</Frame>
</Ui>