Part of #41: the last of five `benilla-app` pieces. This one is everything the others did not cover: the addon harness, the settings registry, the glue screens (login, realm list, character select and create), key bindings, the minimap, nameplates, the loading screen, transports, the performance instruments, the smaller modules, the integration tests, and the UI asset files (`OptionsFrame.xml`, `benilla.toc` and the rest). Their comments are rewritten to the rule in `docs/METHOD.md`, which is what the code does and the reference fact behind it, in a line. 27,351 comment lines become 11,010. Fixes #33. Fixes #103. - **The mechanical strip.** The citations that were only a private record number go first, and each edit is shown to be a pure removal. - **Wrong facts corrected.** The rewrite corrects the comments that stated wrong facts. Among them: - The weapon trail's emissive is 0 (`0x70ca30`): the trail is a type-5 render record, so its ambient-only light term is exact. - The camera-style dropdown writes "1", "2", "0" (`UIOptionsFrame.lua:525,536,547`); several register-site addresses in the settings table are corrected. - The graphics settings are held until `RestartGx`, as in 1.12; `Fonts.xml`'s early load no longer carries a false reason. - vmangos sends a player-name invalidation after a rename (`CharacterHandler.cpp:839`), and the cinematic camera's announce packet has an empty handler there (`MiscHandler.cpp:919`). - Stock FrameXML and GlueXML line citations are checked against the stock 1.12 files, and vmangos line citations against the pinned revision. - **Deviation labels.** "Deviation:" now marks only a choice with its recorded reason. Declining to copy a reference defect is stated plainly, and so is an unbuilt behaviour or an untraced one. - **Verification.** Every file strips to identical code, apart from test literals in `login/`, `death/` and `ui_script/death_tests.rs` that now use placeholder account and character names, and `cargo fmt --check` is clean. Six second readers compared every file old against new, restored the facts the rewrite had dropped, and checked each new citation against the code, the stock files, vmangos and the reference. A second commit corrects one comment in `benilla-ui` (`unit_is_grey`: the reference's exclusion is players only). A third lowers `ui_script_wall`'s ceiling from 45 to 40: five gameplay files named `UiScript` only in comments the rewrite removed, and the ratchet asks for the number to follow. Behaviour differences the rewrite surfaced are filed as their own issues (#339 to #353 among them). String literals are untouched; they are #92. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
443 lines
20 KiB
XML
443 lines
20 KiB
XML
<Ui>
|
|
<!--
|
|
The options window's Keybindings page: templates and script; its rows live in
|
|
OptionsFrame.xml, which loads after. Only commands the engine dispatches appear.
|
|
Deviation: the layout is the era Settings panel's Keybindings page (collapsible category
|
|
sections), because 1.12's settings screens are much worse to use. Capture follows 1.12's
|
|
Blizzard_BindingUI: the next key or mouse 3/4/5 press binds, left and right clicks only
|
|
select, and ESC while armed binds Escape (its OnKeyDown has no ESCAPE filter).
|
|
Deviation: every bind, unbind and reset saves at once, as the era panel commits, where
|
|
1.12 had Okay and Cancel; only Reset To Default and leaving the character set confirm.
|
|
-->
|
|
|
|
<!-- 1.12's KeyBindingFrameBindingButtonTemplate; all five buttons reach the capture. -->
|
|
<Button name="KeyBindingButtonTemplate" virtual="true">
|
|
<Size><AbsDimension x="180" y="22"/></Size>
|
|
<ButtonText name="$parentText"/>
|
|
<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>
|
|
<HighlightTexture file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></HighlightTexture>
|
|
<NormalFont inherits="GameFontHighlightSmall"/>
|
|
<HighlightFont inherits="GameFontHighlightSmall"/>
|
|
<DisabledFont inherits="GameFontDisableSmall"/>
|
|
<Scripts>
|
|
<OnLoad>this:RegisterForClicks("LeftButtonUp", "RightButtonUp", "MiddleButtonUp", "Button4Up", "Button5Up")</OnLoad>
|
|
<OnClick>KeyBindingButton_OnClick(arg1)</OnClick>
|
|
</Scripts>
|
|
</Button>
|
|
|
|
<!-- A binding row or a section header in 1.12's QuestLogTitleButton dress, since the era's
|
|
expand arrows are not in 1.12; 25 tall, the era's row height. -->
|
|
<Frame name="KeyBindingRowTemplate" virtual="true">
|
|
<Size><AbsDimension x="560" y="25"/></Size>
|
|
<Layers>
|
|
<Layer level="BACKGROUND">
|
|
<FontString name="$parentDescription" inherits="GameFontNormalSmall" justifyH="LEFT">
|
|
<Size><AbsDimension x="170" y="24"/></Size>
|
|
<Anchors><Anchor point="LEFT"/></Anchors>
|
|
</FontString>
|
|
</Layer>
|
|
</Layers>
|
|
<Frames>
|
|
<Button name="$parentHeader" hidden="true">
|
|
<Anchors>
|
|
<Anchor point="TOPLEFT"/>
|
|
<Anchor point="BOTTOMRIGHT"/>
|
|
</Anchors>
|
|
<Layers>
|
|
<Layer level="ARTWORK">
|
|
<Texture name="$parentIcon">
|
|
<Size><AbsDimension x="16" y="16"/></Size>
|
|
<Anchors><Anchor point="LEFT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor></Anchors>
|
|
</Texture>
|
|
</Layer>
|
|
</Layers>
|
|
<ButtonText name="$parentText" justifyH="LEFT">
|
|
<Anchors>
|
|
<Anchor point="TOPLEFT"><Offset><AbsDimension x="24" y="1"/></Offset></Anchor>
|
|
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="1"/></Offset></Anchor>
|
|
</Anchors>
|
|
</ButtonText>
|
|
<NormalFont inherits="GameFontNormal"/>
|
|
<HighlightFont inherits="GameFontHighlight"/>
|
|
<Scripts>
|
|
<OnClick>KeyBindings_ToggleSection(this.sectionIndex)</OnClick>
|
|
</Scripts>
|
|
</Button>
|
|
<Button name="$parentKey1Button" inherits="KeyBindingButtonTemplate" id="1">
|
|
<Anchors><Anchor point="LEFT"><Offset><AbsDimension x="175" y="0"/></Offset></Anchor></Anchors>
|
|
</Button>
|
|
<Button name="$parentKey2Button" inherits="KeyBindingButtonTemplate" id="2">
|
|
<Anchors><Anchor point="LEFT" relativeTo="$parentKey1Button" relativePoint="RIGHT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
|
|
</Button>
|
|
</Frames>
|
|
</Frame>
|
|
|
|
<Script><![CDATA[
|
|
-- ── Strings ── a 1.12 GlobalStrings global, or the fallback for a harness without them.
|
|
function KeyBindings_String(name, fallback)
|
|
local s = getglobal(name)
|
|
if s then return s end
|
|
return fallback
|
|
end
|
|
|
|
-- ── Key display ── 1.12's GetBindingText in full form: modifier prefixes stay tokens, the
|
|
-- base key reads its KEY_* global. "CTRL--" is CTRL plus the minus key.
|
|
function KeyBindings_KeyText(key)
|
|
if not key then
|
|
return NORMAL_FONT_COLOR_CODE .. KeyBindings_String("NOT_BOUND", "Not Bound") .. FONT_COLOR_CODE_CLOSE
|
|
end
|
|
local prefix = ""
|
|
local rest = key
|
|
while true do
|
|
local p = string.sub(rest, 1, 4)
|
|
if p == "ALT-" then
|
|
prefix = prefix .. "ALT-"
|
|
rest = string.sub(rest, 5)
|
|
elseif string.sub(rest, 1, 5) == "CTRL-" then
|
|
prefix = prefix .. "CTRL-"
|
|
rest = string.sub(rest, 6)
|
|
elseif string.sub(rest, 1, 6) == "SHIFT-" then
|
|
prefix = prefix .. "SHIFT-"
|
|
rest = string.sub(rest, 7)
|
|
else
|
|
break
|
|
end
|
|
end
|
|
return prefix .. KeyBindings_String("KEY_" .. rest, rest)
|
|
end
|
|
|
|
-- ── Page state ── `searchPainted` maps OptionsFrame.xml's search rows to binding indices.
|
|
KeyBindingsPage = { sections = {}, items = {} }
|
|
|
|
KEYBINDINGS_ROW_BASE = "BenillaOptionsFrameContainerBodyKeybindingsRow"
|
|
KEYBINDINGS_DISPLAYED = 19
|
|
KEYBINDINGS_ROW_HEIGHT = 25
|
|
KEYBINDINGS_SEARCH_ROWS = 8
|
|
|
|
-- ── Section model ── one section per category in Bindings.xml order, collapsed at first
|
|
-- as in the era; a rebuild keeps each state.
|
|
function KeyBindings_BuildSections()
|
|
local page = KeyBindingsPage
|
|
local was = {}
|
|
for _, s in ipairs(page.sections) do
|
|
was[s.token] = s.expanded
|
|
end
|
|
page.sections = {}
|
|
local byToken = {}
|
|
for i = 1, GetNumBindings() do
|
|
local _, category = GetBinding(i)
|
|
local sec = byToken[category]
|
|
if not sec then
|
|
sec = { token = category, indices = {}, expanded = was[category] }
|
|
byToken[category] = sec
|
|
table.insert(page.sections, sec)
|
|
end
|
|
table.insert(sec.indices, i)
|
|
end
|
|
KeyBindings_Flatten()
|
|
end
|
|
|
|
-- The visible list the faux scroll runs over.
|
|
function KeyBindings_Flatten()
|
|
local page = KeyBindingsPage
|
|
page.items = {}
|
|
for n, sec in ipairs(page.sections) do
|
|
table.insert(page.items, { header = n })
|
|
if sec.expanded then
|
|
for _, i in ipairs(sec.indices) do
|
|
table.insert(page.items, { bind = i })
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- A header click, voiced with the checkbox sound.
|
|
function KeyBindings_ToggleSection(n)
|
|
local sec = KeyBindingsPage.sections[n]
|
|
if not sec then return end
|
|
PlaySound("igMainMenuOptionCheckBoxOn")
|
|
sec.expanded = not sec.expanded
|
|
KeyBindings_Flatten()
|
|
KeyBindingsPage_Update()
|
|
end
|
|
|
|
-- Silent, for the capture fixture.
|
|
function KeyBindings_ExpandSection(n, on)
|
|
local sec = KeyBindingsPage.sections[n]
|
|
if not sec then return end
|
|
sec.expanded = on and true or false
|
|
KeyBindings_Flatten()
|
|
end
|
|
|
|
-- ── Painters ── shared by the list and the search rows, which capture in place.
|
|
function KeyBindings_PaintBindRow(name, index)
|
|
local command, _, key1, key2 = GetBinding(index)
|
|
local b1 = getglobal(name .. "Key1Button")
|
|
local b2 = getglobal(name .. "Key2Button")
|
|
b1.commandName = command
|
|
b2.commandName = command
|
|
b1:SetText(KeyBindings_KeyText(key1))
|
|
b2:SetText(KeyBindings_KeyText(key2))
|
|
if key1 then b1:SetAlpha(1) else b1:SetAlpha(0.8) end
|
|
if key2 then b2:SetAlpha(1) else b2:SetAlpha(0.8) end
|
|
local desc = getglobal(name .. "Description")
|
|
desc:SetText(KeyBindings_String("BINDING_NAME_" .. command, command))
|
|
desc:Show()
|
|
local page = KeyBindingsPage
|
|
if page.selected == command and page.keyID == 1 then
|
|
b1:LockHighlight()
|
|
else
|
|
b1:UnlockHighlight()
|
|
end
|
|
if page.selected == command and page.keyID == 2 then
|
|
b2:LockHighlight()
|
|
else
|
|
b2:UnlockHighlight()
|
|
end
|
|
b1:Show()
|
|
b2:Show()
|
|
getglobal(name .. "Header"):Hide()
|
|
end
|
|
|
|
function KeyBindings_PaintHeaderRow(name, n)
|
|
local sec = KeyBindingsPage.sections[n]
|
|
local header = getglobal(name .. "Header")
|
|
header.sectionIndex = n
|
|
header:SetText(KeyBindings_String(sec.token, sec.token))
|
|
local icon = getglobal(name .. "HeaderIcon")
|
|
if sec.expanded then
|
|
icon:SetTexture("Interface\\Buttons\\UI-MinusButton-Up")
|
|
else
|
|
icon:SetTexture("Interface\\Buttons\\UI-PlusButton-Up")
|
|
end
|
|
header:Show()
|
|
getglobal(name .. "Description"):Hide()
|
|
getglobal(name .. "Key1Button"):Hide()
|
|
getglobal(name .. "Key2Button"):Hide()
|
|
end
|
|
|
|
-- ── The list repaint (1.12 KeyBindingFrame_Update) ──
|
|
function KeyBindingsPage_Update()
|
|
local page = KeyBindingsPage
|
|
local scroll = getglobal("BenillaOptionsFrameContainerBodyKeybindingsScrollFrame")
|
|
local offset = FauxScrollFrame_GetOffset(scroll)
|
|
for i = 1, KEYBINDINGS_DISPLAYED do
|
|
local name = KEYBINDINGS_ROW_BASE .. i
|
|
local row = getglobal(name)
|
|
local item = page.items[offset + i]
|
|
if item and item.header then
|
|
KeyBindings_PaintHeaderRow(name, item.header)
|
|
row:Show()
|
|
elseif item then
|
|
KeyBindings_PaintBindRow(name, item.bind)
|
|
row:Show()
|
|
else
|
|
row:Hide()
|
|
end
|
|
end
|
|
FauxScrollFrame_Update(scroll, table.getn(page.items),
|
|
KEYBINDINGS_DISPLAYED, KEYBINDINGS_ROW_HEIGHT)
|
|
-- Our trough follows the bar `FauxScrollFrame_Update` has just shown or hidden.
|
|
BenillaScrollTrough_Follow(scroll:GetName())
|
|
KeyBindings_UpdateUnbindKey()
|
|
end
|
|
|
|
-- The page entry: a rebind or a set switch may have changed the table.
|
|
function KeyBindingsPage_Refresh()
|
|
KeyBindings_BuildSections()
|
|
local box = getglobal("BenillaOptionsFrameContainerBodyKeybindingsCharacterRowCheck")
|
|
if GetCurrentBindingSet() == 2 then
|
|
box:SetChecked(1)
|
|
else
|
|
box:SetChecked(nil)
|
|
end
|
|
getglobal("BenillaOptionsFrameContainerBodyKeybindingsScrollFrameScrollBar"):SetValue(0)
|
|
KeyBindingsPage_Update()
|
|
end
|
|
|
|
function KeyBindings_Output(text)
|
|
getglobal("BenillaOptionsFrameContainerBodyKeybindingsOutput"):SetText(text)
|
|
end
|
|
|
|
-- ── Selection ── arms the host's capture, which hands its chord to KeyBindings_OnHostKey.
|
|
function KeyBindings_SetSelected(command, keyID)
|
|
KeyBindingsPage.selected = command
|
|
KeyBindingsPage.keyID = keyID
|
|
BenillaBindCapture(command ~= nil)
|
|
end
|
|
|
|
function KeyBindings_UpdateUnbindKey()
|
|
local unbind = getglobal("BenillaOptionsFrameContainerUnbind")
|
|
if KeyBindingsPage.selected then
|
|
unbind:Enable()
|
|
else
|
|
unbind:Disable()
|
|
end
|
|
end
|
|
|
|
-- Every search row: a bind from one can unbind a victim shown on another.
|
|
function KeyBindings_RepaintVisible()
|
|
if KeyBindingsPage.searchPainted then
|
|
for name, index in pairs(KeyBindingsPage.searchPainted) do
|
|
KeyBindings_PaintBindRow(name, index)
|
|
end
|
|
KeyBindings_UpdateUnbindKey()
|
|
else
|
|
KeyBindingsPage_Update()
|
|
end
|
|
end
|
|
|
|
function KeyBindingButton_OnClick(button)
|
|
-- As 1.12: while armed, a middle, 4 or 5 press is a bind the capture takes after this.
|
|
local page = KeyBindingsPage
|
|
if page.selected then
|
|
if button ~= "LeftButton" and button ~= "RightButton" then
|
|
return
|
|
end
|
|
if page.selected == this.commandName and page.keyID == this:GetID() then
|
|
KeyBindings_SetSelected(nil)
|
|
KeyBindings_Output("")
|
|
else
|
|
KeyBindings_SetSelected(this.commandName, this:GetID())
|
|
KeyBindings_Output(string.format(
|
|
KeyBindings_String("BIND_KEY_TO_COMMAND", "Press Key to Bind to Command -> %s"),
|
|
KeyBindings_String("BINDING_NAME_" .. this.commandName, this.commandName)))
|
|
end
|
|
else
|
|
KeyBindings_SetSelected(this.commandName, this:GetID())
|
|
KeyBindings_Output(string.format(
|
|
KeyBindings_String("BIND_KEY_TO_COMMAND", "Press Key to Bind to Command -> %s"),
|
|
KeyBindings_String("BINDING_NAME_" .. this.commandName, this.commandName)))
|
|
end
|
|
KeyBindings_RepaintVisible()
|
|
end
|
|
|
|
function KeyBindings_Commit()
|
|
SaveBindings(GetCurrentBindingSet())
|
|
end
|
|
|
|
-- `chord` is ALT-CTRL-SHIFT-TOKEN; 1.12's OnKeyDown armed branch, then the live commit.
|
|
function KeyBindings_OnHostKey(chord)
|
|
local page = KeyBindingsPage
|
|
if not page.selected then
|
|
return
|
|
end
|
|
local selected = page.selected
|
|
local oldAction = GetBindingAction(chord)
|
|
if oldAction ~= "" and oldAction ~= selected then
|
|
local k1, k2 = GetBindingKey(oldAction)
|
|
if (not k1 or k1 == chord) and (not k2 or k2 == chord) then
|
|
KeyBindings_Output(string.format(
|
|
KeyBindings_String("KEY_UNBOUND_ERROR", "|cffff0000%s Function is Now Unbound!|r"),
|
|
KeyBindings_String("BINDING_NAME_" .. oldAction, oldAction)))
|
|
else
|
|
KeyBindings_Output(KeyBindings_String("KEY_BOUND", "Key Bound Successfully"))
|
|
end
|
|
else
|
|
KeyBindings_Output(KeyBindings_String("KEY_BOUND", "Key Bound Successfully"))
|
|
end
|
|
local key1, key2 = GetBindingKey(selected)
|
|
if key1 then SetBinding(key1) end
|
|
if key2 then SetBinding(key2) end
|
|
if page.keyID == 1 then
|
|
KeyBindings_SetOrRestore(chord, selected, key1)
|
|
if key2 then SetBinding(key2, selected) end
|
|
else
|
|
if key1 then SetBinding(key1, selected) end
|
|
KeyBindings_SetOrRestore(chord, selected, key2)
|
|
end
|
|
KeyBindings_Commit()
|
|
KeyBindings_SetSelected(nil)
|
|
KeyBindings_RepaintVisible()
|
|
end
|
|
|
|
-- 1.12's KeyBindingFrame_SetBinding, message and all; SetBinding refuses only a non-key.
|
|
function KeyBindings_SetOrRestore(key, command, oldKey)
|
|
if not SetBinding(key, command) then
|
|
if oldKey then SetBinding(oldKey, command) end
|
|
KeyBindings_Output(KeyBindings_String(
|
|
"KEYBINDINGFRAME_MOUSEWHEEL_ERROR",
|
|
"Can't bind mousewheel to actions with up and down states"))
|
|
end
|
|
end
|
|
|
|
-- 1.12's Unbind Key body, then the live commit.
|
|
function KeyBindings_Unbind()
|
|
local page = KeyBindingsPage
|
|
local key1, key2 = GetBindingKey(page.selected)
|
|
if key1 then SetBinding(key1) end
|
|
if key2 then SetBinding(key2) end
|
|
if key1 and page.keyID == 1 then
|
|
if key2 then SetBinding(key2, page.selected) end
|
|
else
|
|
if key1 then SetBinding(key1, page.selected) end
|
|
end
|
|
KeyBindings_Commit()
|
|
KeyBindings_SetSelected(nil)
|
|
KeyBindings_Output("")
|
|
KeyBindings_RepaintVisible()
|
|
end
|
|
|
|
-- ── Character-specific set ── checking saves set 2 at once (LoadBindings(2) falls back
|
|
-- to the account set); unchecking confirms first, as the era does. The accept loads set 1
|
|
-- before saving it, or the character binds would be written into the account set.
|
|
function KeyBindings_CharacterToggle(box)
|
|
PlaySound("igMainMenuOptionCheckBoxOn")
|
|
if box:GetChecked() then
|
|
LoadBindings(2)
|
|
SaveBindings(2)
|
|
KeyBindings_SetSelected(nil)
|
|
KeyBindings_Output("")
|
|
KeyBindingsPage_Refresh()
|
|
else
|
|
box:SetChecked(1)
|
|
StaticPopup_Show("CONFIRM_DELETING_CHARACTER_SPECIFIC_BINDINGS")
|
|
end
|
|
end
|
|
|
|
StaticPopupDialogs["CONFIRM_DELETING_CHARACTER_SPECIFIC_BINDINGS"] = {
|
|
text = KeyBindings_String("CONFIRM_DELETING_CHARACTER_SPECIFIC_BINDINGS",
|
|
"Really switch to general key bindings? All key bindings specific to this character will be permanantly deleted."),
|
|
button1 = KeyBindings_String("OKAY", "Okay"),
|
|
button2 = KeyBindings_String("CANCEL", "Cancel"),
|
|
OnAccept = function()
|
|
LoadBindings(1)
|
|
SaveBindings(1)
|
|
KeyBindings_SetSelected(nil)
|
|
KeyBindings_Output("")
|
|
KeyBindingsPage_Refresh()
|
|
end,
|
|
}
|
|
-- Reset To Default confirms, as the era's does; our string, since 1.12 has none.
|
|
StaticPopupDialogs["CONFIRM_RESET_KEYBINDINGS"] = {
|
|
text = "Are you sure you want to reset all keybindings to their defaults?",
|
|
button1 = KeyBindings_String("OKAY", "Okay"),
|
|
button2 = KeyBindings_String("CANCEL", "Cancel"),
|
|
OnAccept = function()
|
|
PlaySound("igMainMenuOptionCheckBoxOn")
|
|
LoadBindings(0)
|
|
KeyBindings_Commit()
|
|
KeyBindings_SetSelected(nil)
|
|
KeyBindings_Output("")
|
|
KeyBindingsPage_Refresh()
|
|
end,
|
|
}
|
|
|
|
-- ── Search ── a binding's tag is its display name uppercased, as the era's.
|
|
-- A static pool of KEYBINDINGS_SEARCH_ROWS rows, where the era's results scroll.
|
|
function KeyBindings_SearchMatches(words)
|
|
local out = {}
|
|
for i = 1, GetNumBindings() do
|
|
local command = GetBinding(i)
|
|
local tag = string.upper(KeyBindings_String("BINDING_NAME_" .. command, command))
|
|
local s = OptionsSearch_Score(tag, words)
|
|
if s then
|
|
table.insert(out, { score = s, index = i })
|
|
end
|
|
end
|
|
return out
|
|
end
|
|
]]></Script>
|
|
</Ui>
|