The core loads the stock GameMenuFrame.xml, and the layer reshapes it in place into benilla's menu, keeping every stock rung in the chain anchored TOP (#564)
The core now loads the stock `GameMenuFrame.xml` at its `FrameXML.toc` row, right after `UIPanelTemplates.xml`, and benilla's ESC menu look moves into the layer as `GameMenuAdapters.xml`, which reshapes the stock frame in place: a later file cannot take an occupied frame name (`0x701bd0`). - Every stock rung stays in the chain with its name and a `TOP` anchor. The three the menu does not show are hidden and laid over the rung above, since a zero height leaves a rung's bottom edge unresolved (`combineEdge 0x767440`). So an addon that inserts under Interface Options and re-anchors Key Bindings (ShaguTweaks), or under Macros and re-anchors Logout (MCP, MoveAnything), lays out as on 1.12.1, with no error. - The era rungs keep their look and behaviour: Options opens benilla's window; the `BenillaOptionsFrame`-aware wrappers of `ToggleGameMenu`, `IsOptionFrameOpen` and `UpdateMicroButtons` move into the layer; the non-stock names take the `Benilla` prefix. The core toc now names no file of ours. - With the layer off (`WOW_STOCK_UI=1`), the menu is the stock menu and every rung opens its stock window. Verified: a region dump of the open menu on main and here differs only in our own frames' names, their insertion order within one level (the rects do not overlap) and sub-1e-4 px float noise; new tests for ShaguTweaks' insert, MCP from the corpus, a pfUI-style skin and the stock-only menu, each failing on main; `scripts/gates.sh` and `scripts/smoke.sh` green; a live run opened the menu, opened Options and returned to the game, and MoveAnything's own insert landed under Macros with Logout below it. Built on macOS; no platform seam touched. Fixes #553 Fixes #540 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
22634279bd
commit
6e869eea4e
@@ -0,0 +1,132 @@
|
||||
<Ui>
|
||||
<!--
|
||||
The ESC menu: the stock GameMenuFrame.xml, reshaped in place as a 1.12 addon reshapes it.
|
||||
Deviation: the rungs are the 1.15 era client's ESC menu, because its settings screens
|
||||
replace 1.12's, which are much worse to use; the era's AddOns rung is dropped, since the
|
||||
character-select AddOns screen is the only addon UI.
|
||||
-->
|
||||
|
||||
<!-- The era's header font, which 1.12's Fonts.xml lacks. -->
|
||||
<Font name="BenillaGameFontNormalMed1" inherits="GameFontNormal" virtual="true">
|
||||
<FontHeight><AbsValue val="13"/></FontHeight>
|
||||
</Font>
|
||||
|
||||
<Script><![CDATA[
|
||||
-- ══ The frame: the era layout, 28+144+28 wide, 32 top, 28 bottom, 7 rungs of 21 and
|
||||
-- three gaps of 20 ═══════════════════════════════════════════════════════════════════════
|
||||
GameMenuFrame:SetWidth(200)
|
||||
GameMenuFrame:SetHeight(267)
|
||||
|
||||
-- The stock title (`GameMenuFrame.xml:36`) has no name: found as addons find it.
|
||||
for _, region in ipairs({ GameMenuFrame:GetRegions() }) do
|
||||
if region:GetObjectType() == "FontString" and region:GetText() == MAIN_MENU then
|
||||
region:SetFontObject(BenillaGameFontNormalMed1)
|
||||
end
|
||||
end
|
||||
|
||||
-- ERA_WINDOW_SCALE (OptionsFrame.xml) stands in for the era's UIParent scale; read at
|
||||
-- every show, then the stock OnShow.
|
||||
local Ref_GameMenuFrame_OnShow = GameMenuFrame:GetScript("OnShow")
|
||||
GameMenuFrame:SetScript("OnShow", function()
|
||||
this:SetScale(ERA_WINDOW_SCALE or 1)
|
||||
Ref_GameMenuFrame_OnShow()
|
||||
end)
|
||||
|
||||
-- ══ The rungs ═══════════════════════════════════════════════════════════════════════════
|
||||
-- Every stock rung stays, anchored TOP to a BOTTOM as in the stock file, so an addon's
|
||||
-- `SetPoint("TOP", …)` replaces its one anchor. The three the era menu lacks are hidden
|
||||
-- in place, with Sound and UI Options each laid over the rung above it, so they take no
|
||||
-- space: a width or height of 0 is no size in 1.12's layout, not a flat rect.
|
||||
|
||||
-- Literal: 1.12 has no GAMEMENU_OPTIONS; its OPTIONS_MENU is "Options Menu".
|
||||
GameMenuButtonOptions:SetText("Options")
|
||||
GameMenuButtonOptions:ClearAllPoints()
|
||||
GameMenuButtonOptions:SetPoint("TOP", GameMenuFrame, "TOP", 0, -32)
|
||||
GameMenuButtonOptions:SetScript("OnClick", function()
|
||||
PlaySound("igMainMenuOption")
|
||||
HideUIPanel(GameMenuFrame)
|
||||
ShowUIPanel(BenillaOptionsFrame)
|
||||
end)
|
||||
|
||||
for _, fold in ipairs({
|
||||
{ GameMenuButtonSoundOptions, GameMenuButtonOptions },
|
||||
{ GameMenuButtonUIOptions, GameMenuButtonSoundOptions },
|
||||
}) do
|
||||
local button, above = fold[1], fold[2]
|
||||
button:Hide()
|
||||
button:ClearAllPoints()
|
||||
button:SetPoint("TOP", above, "BOTTOM", 0, button:GetHeight())
|
||||
end
|
||||
-- Key Bindings keeps its stock anchor, 1 under UI Options: the era rungs hang from its
|
||||
-- top, so an addon that inserts a button above it and re-anchors it under that button
|
||||
-- (ShaguTweaks) moves them down as it moves the stock rungs.
|
||||
GameMenuButtonKeybindings:Hide()
|
||||
|
||||
-- The era's Edit Mode and Support, with nothing behind them yet: disabled.
|
||||
local editMode = CreateFrame("Button", "BenillaGameMenuButtonEditMode", GameMenuFrame,
|
||||
"GameMenuButtonTemplate")
|
||||
editMode:SetPoint("TOP", GameMenuButtonKeybindings, "TOP", 0, -19)
|
||||
editMode:SetText("Edit Mode")
|
||||
editMode:Disable()
|
||||
local support = CreateFrame("Button", "BenillaGameMenuButtonSupport", GameMenuFrame,
|
||||
"GameMenuButtonTemplate")
|
||||
support:SetPoint("TOP", editMode, "BOTTOM", 0, 0)
|
||||
support:SetText("Support")
|
||||
support:Disable()
|
||||
|
||||
GameMenuButtonMacros:SetPoint("TOP", support, "BOTTOM", 0, 0)
|
||||
GameMenuButtonLogout:SetPoint("TOP", GameMenuButtonMacros, "BOTTOM", 0, -20)
|
||||
GameMenuButtonQuit:SetPoint("TOP", GameMenuButtonLogout, "BOTTOM", 0, 0)
|
||||
GameMenuButtonContinue:SetPoint("TOP", GameMenuButtonQuit, "BOTTOM", 0, -20)
|
||||
|
||||
-- ══ Our options window in the stock "is an options window open?" checks ═══════════════
|
||||
-- Each wraps the stock function to add `BenillaOptionsFrame`.
|
||||
local Ref_IsOptionFrameOpen = IsOptionFrameOpen
|
||||
local Ref_ToggleGameMenu = ToggleGameMenu
|
||||
local Ref_UpdateMicroButtons = UpdateMicroButtons
|
||||
|
||||
-- Guarded for a test kit that loads the game menu without the options window.
|
||||
local function BenillaOptions_IsOpen()
|
||||
return BenillaOptionsFrame and BenillaOptionsFrame:IsVisible()
|
||||
end
|
||||
|
||||
-- `UIParent.lua:996`: a yes keeps the ESC sweep off the centre panel, which our window is.
|
||||
function IsOptionFrameOpen()
|
||||
if BenillaOptions_IsOpen() then
|
||||
return 1
|
||||
end
|
||||
return Ref_IsOptionFrameOpen()
|
||||
end
|
||||
|
||||
-- `UIParent.lua:1465-1497`: static popup, then an open options window, then the stock
|
||||
-- ladder. Our Close click is the discard-and-hide of the reference's `OptionsFrameCancel`.
|
||||
function ToggleGameMenu(clicked)
|
||||
if clicked then
|
||||
-- As the reference's `clicked` arm: close an options window, then toggle.
|
||||
if BenillaOptions_IsOpen() then
|
||||
BenillaOptionsFrameCloseButton:Click()
|
||||
end
|
||||
return Ref_ToggleGameMenu(clicked)
|
||||
end
|
||||
if StaticPopup_EscapePressed() then
|
||||
return
|
||||
end
|
||||
if BenillaOptions_IsOpen() then
|
||||
BenillaOptionsFrameCloseButton:Click()
|
||||
return
|
||||
end
|
||||
return Ref_ToggleGameMenu(clicked)
|
||||
end
|
||||
|
||||
-- `MainMenuBarMicroButtons.lua:47-58`: only ever pushes the button, so the stock decision
|
||||
-- stands and ours adds our window. Guarded for a test kit that loads no micro buttons.
|
||||
function UpdateMicroButtons()
|
||||
if Ref_UpdateMicroButtons then
|
||||
Ref_UpdateMicroButtons()
|
||||
end
|
||||
if MainMenuMicroButton and BenillaOptions_IsOpen() then
|
||||
MainMenuMicroButton:SetButtonState("PUSHED", 1)
|
||||
end
|
||||
end
|
||||
]]></Script>
|
||||
</Ui>
|
||||
@@ -1,216 +0,0 @@
|
||||
<Ui>
|
||||
<!--
|
||||
The game menu ESC opens, in the chrome of the reference's GameMenuFrame.xml.
|
||||
Deviation: the rungs are the 1.15 era client's ESC menu, because its settings screens
|
||||
replace 1.12's, which are much worse to use; the era's AddOns rung is dropped, since the
|
||||
character-select AddOns screen is the only addon UI. Addons address `GameMenuFrame` by name.
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
-- Label strings as `X = X or "..."`, so a stock GlobalStrings value wins where one exists.
|
||||
MAIN_MENU = MAIN_MENU or "Main Menu"
|
||||
HUD_EDIT_MODE_MENU = HUD_EDIT_MODE_MENU or "Edit Mode"
|
||||
GAMEMENU_SUPPORT = GAMEMENU_SUPPORT or "Support"
|
||||
MACROS = MACROS or "Macros"
|
||||
LOGOUT = LOGOUT or "Logout"
|
||||
EXIT_GAME = EXIT_GAME or "Exit Game"
|
||||
RETURN_TO_GAME = RETURN_TO_GAME or "Return to Game"
|
||||
CANCEL = CANCEL or "Cancel"
|
||||
|
||||
-- A rung with nothing behind it yet (Edit Mode, Support), disabled.
|
||||
function GameMenuButton_Pending(button, text)
|
||||
button:SetText(text)
|
||||
button:Disable()
|
||||
end
|
||||
|
||||
-- ══ Our options window in the stock "is an options window open?" checks ══
|
||||
-- Each wraps the stock body to add `BenillaOptionsFrame`; here because this file loads
|
||||
-- last, after MainMenuBarMicroButtons.xml.
|
||||
local Ref_IsOptionFrameOpen = IsOptionFrameOpen
|
||||
local Ref_ToggleGameMenu = ToggleGameMenu
|
||||
local Ref_UpdateMicroButtons = UpdateMicroButtons
|
||||
|
||||
-- Guarded for a test kit that loads the game menu without the options window.
|
||||
local function BenillaOptions_IsOpen()
|
||||
return BenillaOptionsFrame and BenillaOptionsFrame:IsVisible()
|
||||
end
|
||||
|
||||
-- `UIParent.lua:996`: a yes keeps the ESC sweep off the centre panel, which our window is.
|
||||
function IsOptionFrameOpen()
|
||||
if BenillaOptions_IsOpen() then
|
||||
return 1
|
||||
end
|
||||
return Ref_IsOptionFrameOpen()
|
||||
end
|
||||
|
||||
-- `UIParent.lua:1465-1497`: static popup, then an open options window, then the stock
|
||||
-- ladder. Our Close click is the discard-and-hide of the reference's `OptionsFrameCancel`.
|
||||
function ToggleGameMenu(clicked)
|
||||
if clicked then
|
||||
-- As the reference's `clicked` arm: close an options window, then toggle.
|
||||
if BenillaOptions_IsOpen() then
|
||||
BenillaOptionsFrameCloseButton:Click()
|
||||
end
|
||||
return Ref_ToggleGameMenu(clicked)
|
||||
end
|
||||
if StaticPopup_EscapePressed() then
|
||||
return
|
||||
end
|
||||
if BenillaOptions_IsOpen() then
|
||||
BenillaOptionsFrameCloseButton:Click()
|
||||
return
|
||||
end
|
||||
return Ref_ToggleGameMenu(clicked)
|
||||
end
|
||||
|
||||
-- `MainMenuBarMicroButtons.lua:47-58`: only ever pushes the button, so the stock decision
|
||||
-- stands and ours adds our window.
|
||||
function UpdateMicroButtons()
|
||||
if Ref_UpdateMicroButtons then
|
||||
Ref_UpdateMicroButtons()
|
||||
end
|
||||
if MainMenuMicroButton and BenillaOptions_IsOpen() then
|
||||
MainMenuMicroButton:SetButtonState("PUSHED", 1)
|
||||
end
|
||||
end
|
||||
|
||||
]]></Script>
|
||||
|
||||
<!-- The era's header font, which 1.12's Fonts.xml lacks. -->
|
||||
<Font name="GameFontNormalMed1" inherits="GameFontNormal" virtual="true">
|
||||
<FontHeight><AbsValue val="13"/></FontHeight>
|
||||
</Font>
|
||||
|
||||
|
||||
<!-- GameMenuFrame.xml:4's attributes but `movable`, inert there: it has no drag scripts. -->
|
||||
<Frame name="GameMenuFrame" parent="UIParent" frameStrata="DIALOG" toplevel="true" enableMouse="true" hidden="true">
|
||||
<!-- The era layout: 28+144+28 wide, 32 top, 28 bottom, 7 rungs of 21, 3 gaps of 20. -->
|
||||
<Size><AbsDimension x="200" y="267"/></Size>
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
|
||||
<BackgroundInsets><AbsInset left="11" right="12" top="12" bottom="11"/></BackgroundInsets>
|
||||
<TileSize><AbsValue val="32"/></TileSize>
|
||||
<EdgeSize><AbsValue val="32"/></EdgeSize>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="GameMenuFrameHeader" file="Interface\DialogFrame\UI-DialogBox-Header">
|
||||
<Size><AbsDimension x="256" y="64"/></Size>
|
||||
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="0" y="12"/></Offset></Anchor></Anchors>
|
||||
</Texture>
|
||||
<!-- The era's 13px font on the 1.12 seat, within half a unit of the era's. -->
|
||||
<FontString name="GameMenuFrameTitle" inherits="GameFontNormalMed1" text="MAIN_MENU">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeTo="GameMenuFrameHeader"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
|
||||
<!-- Literal: 1.12 has no GAMEMENU_OPTIONS; its OPTIONS_MENU is "Options Menu". -->
|
||||
<Button name="GameMenuButtonOptions" inherits="GameMenuButtonTemplate" text="Options">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="28" y="-32"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
PlaySound("igMainMenuOption")
|
||||
HideUIPanel(GameMenuFrame)
|
||||
ShowUIPanel(BenillaOptionsFrame)
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<Button name="GameMenuButtonEditMode" inherits="GameMenuButtonTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="GameMenuButtonOptions" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-20"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts><OnLoad>GameMenuButton_Pending(self, HUD_EDIT_MODE_MENU)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="GameMenuButtonSupport" inherits="GameMenuButtonTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="GameMenuButtonEditMode" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts><OnLoad>GameMenuButton_Pending(self, GAMEMENU_SUPPORT)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="GameMenuButtonMacros" inherits="GameMenuButtonTemplate" text="MACROS">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="GameMenuButtonSupport" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<!-- GameMenuFrame.xml:118-122. -->
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
PlaySound("igMainMenuOption")
|
||||
HideUIPanel(getglobal("GameMenuFrame"))
|
||||
ShowMacroFrame()
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- As the reference: disabled while a CAMP or QUIT countdown is up. -->
|
||||
<Button name="GameMenuButtonLogout" inherits="GameMenuButtonTemplate" text="LOGOUT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="GameMenuButtonMacros" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-20"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnShow>
|
||||
if not StaticPopup_Visible("CAMP") and not StaticPopup_Visible("QUIT") then
|
||||
self:Enable()
|
||||
else
|
||||
self:Disable()
|
||||
end
|
||||
</OnShow>
|
||||
<OnClick>
|
||||
PlaySound("igMainMenuLogout")
|
||||
Logout()
|
||||
HideUIPanel(GameMenuFrame)
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="GameMenuButtonQuit" inherits="GameMenuButtonTemplate" text="EXIT_GAME">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="GameMenuButtonLogout" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnShow>
|
||||
if not StaticPopup_Visible("CAMP") and not StaticPopup_Visible("QUIT") then
|
||||
self:Enable()
|
||||
else
|
||||
self:Disable()
|
||||
end
|
||||
</OnShow>
|
||||
<OnClick>
|
||||
PlaySound("igMainMenuQuit")
|
||||
Quit()
|
||||
HideUIPanel(GameMenuFrame)
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="GameMenuButtonContinue" inherits="GameMenuButtonTemplate" text="RETURN_TO_GAME">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="GameMenuButtonQuit" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-20"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick>
|
||||
PlaySound("igMainMenuContinue")
|
||||
HideUIPanel(GameMenuFrame)
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
<!-- The reference's OnShow/OnHide, guarded for a test that loads no action bar. -->
|
||||
<Scripts>
|
||||
<OnShow>
|
||||
-- ERA_WINDOW_SCALE (OptionsFrame.xml) stands in for the era's UIParent scale. A
|
||||
-- second OnShow on this frame would replace this one.
|
||||
this:SetScale(ERA_WINDOW_SCALE or 1)
|
||||
if UpdateMicroButtons then UpdateMicroButtons() end
|
||||
if Disable_BagButtons then Disable_BagButtons() end
|
||||
</OnShow>
|
||||
<OnHide>
|
||||
if UpdateMicroButtons then UpdateMicroButtons() end
|
||||
if Enable_BagButtons then Enable_BagButtons() end
|
||||
</OnHide>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
@@ -1256,7 +1256,7 @@
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<!-- Not GameFontNormalMed1: GameMenuFrame.xml declares that after this file. -->
|
||||
<!-- Not BenillaGameFontNormalMed1: GameMenuAdapters.xml declares it later. -->
|
||||
<FontString name="$parentTitleText" inherits="GameFontNormal" text="Options">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeTo="$parentHeader"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# The core interface, in load order: the reference's own `FrameXML.toc` files, off the player's
|
||||
# patch chain, and one bare filename of ours from `assets/ui`. Entry 0, Fonts.xml, loads alone
|
||||
# into every boot VM, from Startup on; the rest loads on entering the world, then benilla's layer
|
||||
# (`layer.toc`), then the third-party addons.
|
||||
# patch chain. Entry 0, Fonts.xml, loads alone into every boot VM, from Startup on; the rest loads
|
||||
# on entering the world, then benilla's layer (`layer.toc`), then the third-party addons.
|
||||
# The font objects thus exist before the login screen; the reference loads Fonts.xml with the
|
||||
# rest of FrameXML.
|
||||
# `inherits=`, `parent=` and a named `relativeTo=` resolve only against names declared above; a
|
||||
@@ -54,6 +53,9 @@ Interface\FrameXML\ComboFrame.xml
|
||||
Interface\FrameXML\UIPanelTemplates.lua
|
||||
|
||||
Interface\FrameXML\UIPanelTemplates.xml
|
||||
# Inherits `GameMenuButtonTemplate`. It sources UIParent.lua again, as at the reference's own row,
|
||||
# which rebuilds UIParent.lua's tables; no row between here and UIParent.xml writes into them.
|
||||
Interface\FrameXML\GameMenuFrame.xml
|
||||
# The reference's three options windows, loaded hidden so addons find their frames by name.
|
||||
# Deviation: the player opens the layer's own options window, because 1.12's settings screens are
|
||||
# much worse to use; the stock one holds `OptionsFrame`.
|
||||
@@ -148,7 +150,3 @@ Interface\FrameXML\HelpFrame.xml
|
||||
Interface\FrameXML\CinematicFrame.xml
|
||||
Interface\FrameXML\TabardFrame.xml
|
||||
Interface\FrameXML\TutorialFrame.xml
|
||||
# Ours, in place of the stock GameMenuFrame.xml until the layer reshapes the stock menu (#553).
|
||||
# Last: it wraps `ToggleGameMenu`, `IsOptionFrameOpen` and `UpdateMicroButtons` to count
|
||||
# `BenillaOptionsFrame`.
|
||||
GameMenuFrame.xml
|
||||
|
||||
@@ -11,6 +11,8 @@ ScrollTemplates.xml
|
||||
KeyBindingsPage.xml
|
||||
# `BenillaOptionsFrame`; its rows read the option globals the stock options windows set.
|
||||
OptionsFrame.xml
|
||||
# The ESC menu's era look on the stock `GameMenuFrame`, whose Options rung opens ours.
|
||||
GameMenuAdapters.xml
|
||||
# Wraps the stock bag verbs; the keyring is a container (id -2).
|
||||
ContainerFrameAdapters.xml
|
||||
# The pet page's autocast widgets in the pet action button's proportions.
|
||||
|
||||
@@ -181,10 +181,11 @@ fn escape_closes_the_options_window_before_opening_the_menu() {
|
||||
load_xml(&s, r"Interface\FrameXML\StaticPopup.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\GameMenuFrame.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml"); // the Keybindings page's faux-scroll kit
|
||||
load_xml(&s, "KeyBindingsPage.xml");
|
||||
load_xml(&s, "OptionsFrame.xml");
|
||||
load_xml(&s, "GameMenuFrame.xml");
|
||||
load_xml(&s, "GameMenuAdapters.xml");
|
||||
|
||||
s.run("ShowUIPanel(BenillaOptionsFrame)").unwrap();
|
||||
assert!(s
|
||||
@@ -469,7 +470,8 @@ fn an_addon_frame_registered_in_uispecialframes_closes_on_escape() {
|
||||
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\StaticPopup.xml");
|
||||
load_xml(&s, "GameMenuFrame.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\GameMenuFrame.xml");
|
||||
load_xml(&s, "GameMenuAdapters.xml");
|
||||
|
||||
s.run(
|
||||
r#"
|
||||
|
||||
@@ -401,7 +401,7 @@ fn the_shipped_frames_carry_the_references_flags() {
|
||||
assert!(
|
||||
// The floor guards the pairing, not the census: the paired count falls as each of our
|
||||
// files declaring a reference-named frame retires.
|
||||
compared > 5,
|
||||
compared >= 1,
|
||||
"only {compared} frames compared — the pairing broke, and the sweep guards nothing"
|
||||
);
|
||||
assert!(
|
||||
|
||||
@@ -0,0 +1,389 @@
|
||||
//! The stock ESC menu's rungs under the addons that reshape it (ShaguTweaks, MCP, pfUI), with the
|
||||
//! layer on and without it: every stock rung stays in the chain, anchored `TOP`, so an addon's
|
||||
//! insert lays out as on 1.12.1. Without the layer, the menu is the stock one.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use benilla_ui::script::UiScript;
|
||||
|
||||
use crate::local_state::test_env::ENV_LOCK;
|
||||
|
||||
/// The production in-game load with the ESC menu open: the layer on unless `stock_ui`, and the
|
||||
/// AddOns root filled by `addons`. Fails on any load error or raise.
|
||||
fn open_menu_with(tag: &str, stock_ui: bool, addons: impl FnOnce(&Path)) -> UiScript {
|
||||
let (mut s, failures) = super::layer_tests::production_load_with(tag, stock_ui, "", addons);
|
||||
assert!(failures.is_empty(), "load failures: {failures:#?}");
|
||||
s.run("ShowUIPanel(GameMenuFrame)").unwrap();
|
||||
s.resolve();
|
||||
assert!(s.eval::<bool>("return GameMenuFrame:IsVisible()").unwrap());
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
s
|
||||
}
|
||||
|
||||
/// An addon of `files`, each a name and its text, in the AddOns root.
|
||||
fn write_addon(root: &Path, name: &str, files: &[(&str, &str)]) {
|
||||
let dir = root.join(name);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
for (file, body) in files {
|
||||
std::fs::write(dir.join(file), body).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// The corpus addon `name`, copied whole into the AddOns root.
|
||||
fn copy_addon(corpus: &Path, root: &Path, name: &str) {
|
||||
let dir = root.join(name);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
for entry in std::fs::read_dir(corpus.join(name)).unwrap().flatten() {
|
||||
if entry.file_type().unwrap().is_file() {
|
||||
std::fs::copy(entry.path(), dir.join(entry.file_name())).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A shown rung's `(top, bottom)` down from the frame's top, and whether it is centred in it.
|
||||
fn rung(s: &UiScript, name: &str) -> (f64, f64, bool) {
|
||||
let (top, bottom, left, right, ftop, fleft, fright) = s
|
||||
.eval::<(f64, f64, f64, f64, f64, f64, f64)>(&format!(
|
||||
"local f = GameMenuFrame return {name}:GetTop(), {name}:GetBottom(), \
|
||||
{name}:GetLeft(), {name}:GetRight(), f:GetTop(), f:GetLeft(), f:GetRight()"
|
||||
))
|
||||
.unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
let centred = ((left - fleft) - (fright - right)).abs() < 1e-3;
|
||||
(ftop - top, ftop - bottom, centred)
|
||||
}
|
||||
|
||||
/// The shown rungs of the open menu, top to bottom: the children of `GameMenuFrame` that draw.
|
||||
fn shown_rungs(s: &UiScript) -> Vec<String> {
|
||||
let mut names: Vec<String> = s
|
||||
.eval(
|
||||
"local out = {} for _, c in ipairs({ GameMenuFrame:GetChildren() }) do \
|
||||
if c:IsVisible() then table.insert(out, c:GetName()) end end return out",
|
||||
)
|
||||
.unwrap();
|
||||
names.sort_by(|a, b| rung(s, a).0.total_cmp(&rung(s, b).0));
|
||||
names
|
||||
}
|
||||
|
||||
/// No two shown rungs overlap, each is centred, and each is 21 tall.
|
||||
fn assert_a_clean_ladder(s: &UiScript) -> Vec<String> {
|
||||
let names = shown_rungs(s);
|
||||
for pair in names.windows(2) {
|
||||
let (above, below) = (rung(s, &pair[0]), rung(s, &pair[1]));
|
||||
assert!(
|
||||
below.0 >= above.1 - 1e-3,
|
||||
"{} (down {}..{}) overlaps {} (down {}..{})",
|
||||
pair[1],
|
||||
below.0,
|
||||
below.1,
|
||||
pair[0],
|
||||
above.0,
|
||||
above.1
|
||||
);
|
||||
}
|
||||
for name in &names {
|
||||
let (top, bottom, centred) = rung(s, name);
|
||||
assert!(
|
||||
(bottom - top - 21.0).abs() < 1e-3,
|
||||
"{name} is {} tall",
|
||||
bottom - top
|
||||
);
|
||||
assert!(centred, "{name} is off centre in the menu");
|
||||
}
|
||||
names
|
||||
}
|
||||
|
||||
/// ShaguTweaks' insert (`config.lua:313-324`), restated in its own calls: narrow and deepen the
|
||||
/// menu, hang a new rung under Interface Options, then hang Key Bindings under the new rung.
|
||||
const SHAGU_ADVANCED_OPTIONS: &str = r#"
|
||||
local panel = CreateFrame("Frame", nil, UIParent)
|
||||
panel:Hide()
|
||||
GameMenuFrame:SetWidth(GameMenuFrame:GetWidth() - 10)
|
||||
GameMenuFrame:SetHeight(GameMenuFrame:GetHeight() + 10)
|
||||
local rung = CreateFrame("Button", "GameMenuButtonAdvancedOptions", GameMenuFrame, "GameMenuButtonTemplate")
|
||||
rung:SetPoint("TOP", GameMenuButtonUIOptions, "BOTTOM", 0, -1)
|
||||
rung:SetText("Advanced Options")
|
||||
rung:SetScript("OnClick", function() HideUIPanel(GameMenuFrame); panel:Show() end)
|
||||
GameMenuButtonKeybindings:ClearAllPoints()
|
||||
GameMenuButtonKeybindings:SetPoint("TOP", rung, "BOTTOM", 0, -1)
|
||||
"#;
|
||||
|
||||
fn shagu(root: &Path) {
|
||||
write_addon(
|
||||
root,
|
||||
"ZZShagu",
|
||||
&[
|
||||
("ZZShagu.toc", "## Interface: 11200\nconfig.lua\n"),
|
||||
("config.lua", SHAGU_ADVANCED_OPTIONS),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// ShaguTweaks anchors its Advanced Options under `GameMenuButtonUIOptions` and re-anchors
|
||||
/// `GameMenuButtonKeybindings` under it (`GameMenuFrame.xml:78`, `:93`): it loads without a raise,
|
||||
/// the button lands 1 under the Options rung, and every rung below moves down by its height and
|
||||
/// the gap, 22, as the stock rungs do on the stock menu.
|
||||
#[test]
|
||||
fn shagutweaks_advanced_options_lands_under_the_options_rung_and_pushes_the_rest_down() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let _l = ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
for stock_ui in [false, true] {
|
||||
let tag = if stock_ui { "shagu-stock" } else { "shagu" };
|
||||
let base = open_menu_with(&format!("{tag}-base"), stock_ui, |_| {});
|
||||
let s = open_menu_with(tag, stock_ui, shagu);
|
||||
let before = shown_rungs(&base);
|
||||
let after = assert_a_clean_ladder(&s);
|
||||
|
||||
let options = rung(&s, "GameMenuButtonOptions");
|
||||
let advanced = rung(&s, "GameMenuButtonAdvancedOptions");
|
||||
// Under the Options rung on ours, as the era menu has no Sound or UI Options rung.
|
||||
let above = if stock_ui {
|
||||
"GameMenuButtonUIOptions"
|
||||
} else {
|
||||
"GameMenuButtonOptions"
|
||||
};
|
||||
assert!(
|
||||
(advanced.0 - (rung(&s, above).1 + 1.0)).abs() < 1e-3,
|
||||
"stock_ui {stock_ui}: Advanced Options at {advanced:?}, 1 under {above}"
|
||||
);
|
||||
let at = after
|
||||
.iter()
|
||||
.position(|n| n == "GameMenuButtonAdvancedOptions")
|
||||
.expect("the button is shown");
|
||||
assert_eq!(after[at - 1], above, "stock_ui {stock_ui}: {after:?}");
|
||||
let mut expected = before.clone();
|
||||
expected.insert(at, "GameMenuButtonAdvancedOptions".into());
|
||||
assert_eq!(after, expected, "stock_ui {stock_ui}: the ladder");
|
||||
for name in &after[at + 1..] {
|
||||
let moved = (rung(&s, name).0 - options.0)
|
||||
- (rung(&base, name).0 - rung(&base, "GameMenuButtonOptions").0);
|
||||
assert!(
|
||||
(moved - 22.0).abs() < 1e-3,
|
||||
"stock_ui {stock_ui}: {name} moved {moved} down, not 22"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// MCP's AddOns button (`MCP.xml:108-127`) anchors `TOP` under Macros, sets Logout's `TOP` under
|
||||
/// itself without `ClearAllPoints` and grows the menu by 25: Logout's one `TOP` anchor is replaced,
|
||||
/// so Logout, Exit Game and Return to Game move down under it, as on the stock menu.
|
||||
#[test]
|
||||
fn mcps_addons_button_lands_under_macros_and_pushes_logout_down() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let corpus = benilla_formats::addon_corpus_or_skip!();
|
||||
let _l = ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
for stock_ui in [false, true] {
|
||||
let tag = if stock_ui { "mcp-stock" } else { "mcp" };
|
||||
let base = open_menu_with(&format!("{tag}-base"), stock_ui, |_| {});
|
||||
let s = open_menu_with(tag, stock_ui, |root| copy_addon(&corpus, root, "MCP"));
|
||||
let after = assert_a_clean_ladder(&s);
|
||||
|
||||
let macros = rung(&s, "GameMenuButtonMacros");
|
||||
let addons = rung(&s, "GameMenuButtonAddOns");
|
||||
let logout = rung(&s, "GameMenuButtonLogout");
|
||||
assert!(
|
||||
(addons.0 - (macros.1 + 1.0)).abs() < 1e-3,
|
||||
"stock_ui {stock_ui}: AddOns {addons:?} 1 under Macros {macros:?}"
|
||||
);
|
||||
assert!(
|
||||
(logout.0 - (addons.1 + 1.0)).abs() < 1e-3,
|
||||
"stock_ui {stock_ui}: Logout {logout:?} 1 under AddOns {addons:?}"
|
||||
);
|
||||
let at = after
|
||||
.iter()
|
||||
.position(|n| n == "GameMenuButtonAddOns")
|
||||
.unwrap();
|
||||
assert_eq!(after[at - 1], "GameMenuButtonMacros");
|
||||
assert_eq!(
|
||||
after[at + 1..],
|
||||
[
|
||||
"GameMenuButtonLogout",
|
||||
"GameMenuButtonQuit",
|
||||
"GameMenuButtonContinue"
|
||||
]
|
||||
);
|
||||
let height = |s: &UiScript| s.eval::<f64>("return GameMenuFrame:GetHeight()").unwrap();
|
||||
assert!(
|
||||
(height(&s) - height(&base) - 25.0).abs() < 1e-3,
|
||||
"stock_ui {stock_ui}: the menu grows by MCP's 25"
|
||||
);
|
||||
let span = |s: &UiScript, a: &str, b: &str| rung(s, b).0 - rung(s, a).0;
|
||||
for (a, b) in [
|
||||
("GameMenuButtonLogout", "GameMenuButtonQuit"),
|
||||
("GameMenuButtonQuit", "GameMenuButtonContinue"),
|
||||
] {
|
||||
assert!(
|
||||
(span(&s, a, b) - span(&base, a, b)).abs() < 1e-3,
|
||||
"stock_ui {stock_ui}: {b} still follows {a}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// pfUI's game-menu skin (`skins/blizzard/game_menu.lua`) in its shape: it narrows the menu by 30,
|
||||
/// finds the unnamed MAIN_MENU title among the regions, adds a button at the top, shifts
|
||||
/// `GameMenuButtonOptions`' own point down 22 through `GetPoint`, and skins every stock rung that
|
||||
/// exists. The rungs stay centred in the narrower frame, as the stock ones do, with no raise.
|
||||
const PFUI_SHAPED_SKIN: &str = r#"
|
||||
GameMenuFrame:SetWidth(GameMenuFrame:GetWidth() - 30)
|
||||
GameMenuFrame:SetHeight(GameMenuFrame:GetHeight() + 6)
|
||||
|
||||
local title
|
||||
for _, region in ipairs({ GameMenuFrame:GetRegions() }) do
|
||||
if region:GetObjectType() == "FontString" and region:GetDrawLayer() == "ARTWORK"
|
||||
and region:GetText() and string.find(region:GetText(), MAIN_MENU, 1) then
|
||||
title = region
|
||||
end
|
||||
end
|
||||
title:SetTextColor(1, 1, 1, 1)
|
||||
title:ClearAllPoints()
|
||||
title:SetPoint("TOP", GameMenuFrame, "TOP", 0, 16)
|
||||
|
||||
local button = CreateFrame("Button", "GameMenuButtonPFUI", GameMenuFrame, "GameMenuButtonTemplate")
|
||||
button:SetPoint("TOP", 0, -10)
|
||||
button:SetText("pfUI Config")
|
||||
|
||||
local point, relativeTo, relativePoint, xOffset, yOffset = GameMenuButtonOptions:GetPoint()
|
||||
GameMenuButtonOptions:SetPoint(point, relativeTo, relativePoint, xOffset, yOffset - 22)
|
||||
|
||||
ZZPFUI_SKINNED = 0
|
||||
for _, rung in pairs({ GameMenuButtonOptions, GameMenuButtonSoundOptions, GameMenuButtonUIOptions,
|
||||
GameMenuButtonKeybindings, GameMenuButtonMacros, GameMenuButtonRatings, GameMenuButtonLogout,
|
||||
GameMenuButtonQuit, GameMenuButtonContinue }) do
|
||||
if rung then ZZPFUI_SKINNED = ZZPFUI_SKINNED + 1 end
|
||||
end
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn a_pfui_shaped_skin_keeps_every_rung_centred_in_the_narrowed_menu() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let _l = ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
for stock_ui in [false, true] {
|
||||
let tag = if stock_ui { "pfui-stock" } else { "pfui" };
|
||||
let s = open_menu_with(tag, stock_ui, |root| {
|
||||
write_addon(
|
||||
root,
|
||||
"ZZpfUI",
|
||||
&[
|
||||
("ZZpfUI.toc", "## Interface: 11200\nskin.lua\n"),
|
||||
("skin.lua", PFUI_SHAPED_SKIN),
|
||||
],
|
||||
);
|
||||
});
|
||||
let after = assert_a_clean_ladder(&s);
|
||||
assert_eq!(
|
||||
after[0], "GameMenuButtonPFUI",
|
||||
"stock_ui {stock_ui}: {after:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
after[1], "GameMenuButtonOptions",
|
||||
"stock_ui {stock_ui}: {after:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return ZZPFUI_SKINNED").unwrap(),
|
||||
8,
|
||||
"stock_ui {stock_ui}: every stock rung is there to skin"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Booted without the layer, as `WOW_STOCK_UI=1` does, the ESC menu is the stock one: its eight
|
||||
/// rungs at the stock file's geometry (`GameMenuFrame.xml:5-185`), each opening its stock window.
|
||||
#[test]
|
||||
fn the_stock_ui_menu_is_the_stock_menu_and_every_rung_opens_its_stock_window() {
|
||||
use benilla_ui::script::SessionRequest;
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let _l = ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let mut s = open_menu_with("stock-menu", true, |_| {});
|
||||
let rungs = assert_a_clean_ladder(&s);
|
||||
assert_eq!(
|
||||
rungs,
|
||||
[
|
||||
"GameMenuButtonOptions",
|
||||
"GameMenuButtonSoundOptions",
|
||||
"GameMenuButtonUIOptions",
|
||||
"GameMenuButtonKeybindings",
|
||||
"GameMenuButtonMacros",
|
||||
"GameMenuButtonLogout",
|
||||
"GameMenuButtonQuit",
|
||||
"GameMenuButtonContinue",
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<(f64, f64)>("return GameMenuFrame:GetWidth(), GameMenuFrame:GetHeight()")
|
||||
.unwrap(),
|
||||
(195.0, 246.0)
|
||||
);
|
||||
// Options' centre 37 under the top, then 1-unit gaps, and 16 above Return to Game.
|
||||
let tops: Vec<f64> = rungs.iter().map(|n| rung(&s, n).0).collect();
|
||||
let want = [26.5, 48.5, 70.5, 92.5, 114.5, 136.5, 158.5, 195.5];
|
||||
for ((name, got), want) in rungs.iter().zip(&tops).zip(want) {
|
||||
assert!((got - want).abs() < 1e-3, "{name} at {got}, not {want}");
|
||||
}
|
||||
let labels: Vec<String> = s
|
||||
.eval(
|
||||
"local out = {} for _, n in ipairs({ 'Options', 'SoundOptions', 'UIOptions', \
|
||||
'Keybindings', 'Macros', 'Logout', 'Quit', 'Continue' }) do \
|
||||
table.insert(out, getglobal('GameMenuButton' .. n):GetText()) end return out",
|
||||
)
|
||||
.unwrap();
|
||||
let globals: Vec<String> = s
|
||||
.eval(
|
||||
"return { VIDEOOPTIONS_MENU, SOUNDOPTIONS_MENU, UIOPTIONS_MENU, KEY_BINDINGS, MACROS, \
|
||||
LOGOUT, EXIT_GAME, RETURN_TO_GAME }",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(labels, globals);
|
||||
assert!(!s
|
||||
.eval::<bool>("return BenillaGameMenuButtonEditMode ~= nil")
|
||||
.unwrap());
|
||||
|
||||
for (rung, window) in [
|
||||
("Options", "OptionsFrame"),
|
||||
("SoundOptions", "SoundOptionsFrame"),
|
||||
("UIOptions", "UIOptionsFrame"),
|
||||
("Keybindings", "KeyBindingFrame"),
|
||||
("Macros", "MacroFrame"),
|
||||
] {
|
||||
s.run("HideUIPanel(GameMenuFrame) ShowUIPanel(GameMenuFrame)")
|
||||
.unwrap();
|
||||
s.run(&format!("GameMenuButton{rung}:Click()")).unwrap();
|
||||
assert!(
|
||||
s.eval::<bool>(&format!(
|
||||
"return {window} ~= nil and {window}:IsVisible() and true or false"
|
||||
))
|
||||
.unwrap(),
|
||||
"{rung} opens {window}"
|
||||
);
|
||||
s.run(&format!("HideUIPanel({window})")).unwrap();
|
||||
// The stock video window's own show and hide read video CVars and verbs benilla does not
|
||||
// register yet (`OptionsFrame.lua:146`, `:260`); what the menu does is the click above.
|
||||
let errors = s.take_errors();
|
||||
assert!(
|
||||
errors
|
||||
.iter()
|
||||
.all(|e| rung == "Options" && e.contains("OptionsFrame.lua")),
|
||||
"{rung}: {errors:?}"
|
||||
);
|
||||
}
|
||||
for (rung, request) in [
|
||||
("Logout", SessionRequest::Logout),
|
||||
("Quit", SessionRequest::Quit),
|
||||
] {
|
||||
s.run("ShowUIPanel(GameMenuFrame)").unwrap();
|
||||
let _ = s.take_session_requests();
|
||||
s.run(&format!("GameMenuButton{rung}:Click()")).unwrap();
|
||||
assert_eq!(s.take_session_requests(), vec![request], "{rung}");
|
||||
}
|
||||
s.run("ShowUIPanel(GameMenuFrame) GameMenuButtonContinue:Click()")
|
||||
.unwrap();
|
||||
assert!(!s.eval::<bool>("return GameMenuFrame:IsVisible()").unwrap());
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Tests for our `GameMenuFrame.xml`, the frame ESC opens. The bags in its way are the stock
|
||||
//! Tests for the frame ESC opens: the stock `GameMenuFrame.xml` as the layer's
|
||||
//! `GameMenuAdapters.xml` reshapes it. The bags in its way are the stock
|
||||
//! `ContainerFrame1..12`, recycled, so tests ask [`bag_open`] rather than naming a frame.
|
||||
|
||||
use benilla_ui::script::{
|
||||
@@ -7,8 +8,8 @@ use benilla_ui::script::{
|
||||
|
||||
use super::test_ui::{bag_open, load_ui as load_xml, BAG_UI};
|
||||
|
||||
/// The menu over what it needs (`GameMenuButtonTemplate` is `UIPanelTemplates.xml:403`), with
|
||||
/// `extra` in its way, deduped so no file loads twice.
|
||||
/// The stock menu over what it needs (`GameMenuButtonTemplate` is `UIPanelTemplates.xml:403`),
|
||||
/// with `extra` in its way, then the layer's reshaping, deduped so no file loads twice.
|
||||
fn harness_with(extra: &[&str]) -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
@@ -24,10 +25,12 @@ fn harness_with(extra: &[&str]) -> UiScript {
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
// Ahead of `extra`, as the core loads before the layer: it sources UIParent.lua again.
|
||||
r"Interface\FrameXML\GameMenuFrame.xml",
|
||||
]
|
||||
.into_iter()
|
||||
.chain(extra.iter().copied())
|
||||
.chain(std::iter::once("GameMenuFrame.xml"))
|
||||
.chain(std::iter::once("GameMenuAdapters.xml"))
|
||||
.collect();
|
||||
let mut loaded = std::collections::HashSet::new();
|
||||
for file in files {
|
||||
@@ -58,8 +61,8 @@ fn bag_harness_with(before: &[&str], after: &[&str]) -> UiScript {
|
||||
/// replace 1.12's; without the AddOns rung, as the character-select screen is the only addon UI.
|
||||
const LADDER: [&str; 7] = [
|
||||
"GameMenuButtonOptions",
|
||||
"GameMenuButtonEditMode",
|
||||
"GameMenuButtonSupport",
|
||||
"BenillaGameMenuButtonEditMode",
|
||||
"BenillaGameMenuButtonSupport",
|
||||
"GameMenuButtonMacros",
|
||||
"GameMenuButtonLogout",
|
||||
"GameMenuButtonQuit",
|
||||
@@ -114,7 +117,10 @@ fn the_unbacked_entries_are_disabled_and_the_rest_are_live() {
|
||||
let s = harness();
|
||||
s.run("ShowUIPanel(GameMenuFrame)").unwrap();
|
||||
|
||||
for name in ["GameMenuButtonEditMode", "GameMenuButtonSupport"] {
|
||||
for name in [
|
||||
"BenillaGameMenuButtonEditMode",
|
||||
"BenillaGameMenuButtonSupport",
|
||||
] {
|
||||
assert!(
|
||||
!s.eval::<bool>(&format!("return {name}:IsEnabled() ~= 0"))
|
||||
.unwrap(),
|
||||
@@ -134,10 +140,10 @@ fn the_unbacked_entries_are_disabled_and_the_rest_are_live() {
|
||||
"{name} is live"
|
||||
);
|
||||
}
|
||||
// Edit Mode reads the era's `HUD_EDIT_MODE_MENU` and Continue 1.12's `RETURN_TO_GAME`; Options
|
||||
// is a literal, as 1.12's `OPTIONS_MENU` is "Options Menu".
|
||||
// Edit Mode is the era's label and Continue 1.12's `RETURN_TO_GAME`; Options is a literal, as
|
||||
// 1.12's `OPTIONS_MENU` is "Options Menu".
|
||||
assert_eq!(
|
||||
s.eval::<String>("return GameMenuButtonEditMode:GetText()")
|
||||
s.eval::<String>("return BenillaGameMenuButtonEditMode:GetText()")
|
||||
.unwrap(),
|
||||
"Edit Mode"
|
||||
);
|
||||
|
||||
@@ -35,10 +35,12 @@ pub(crate) fn harness() -> UiScript {
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
// Before our files, as the core loads before the layer: it sources UIParent.lua again.
|
||||
r"Interface\FrameXML\GameMenuFrame.xml",
|
||||
"ScrollTemplates.xml",
|
||||
"KeyBindingsPage.xml",
|
||||
"OptionsFrame.xml",
|
||||
"GameMenuFrame.xml",
|
||||
"GameMenuAdapters.xml",
|
||||
] {
|
||||
// Strict for ours: an unknown template only warns, so a skinless window would pass.
|
||||
if file == "KeyBindingsPage.xml" || file == "OptionsFrame.xml" {
|
||||
|
||||
@@ -28,17 +28,34 @@ f:SetScript("OnEvent", function() table.insert(ADDON_LOADED_LOG, arg1) end)
|
||||
/// A VM after the production in-game load, with one third-party addon, `ZZOrder`, in a hermetic
|
||||
/// AddOns root; the layer on unless `stock_ui`. Returns the VM and the load's failures.
|
||||
fn production_load(tag: &str, stock_ui: bool) -> (UiScript, Vec<String>) {
|
||||
let before = format!("{DEFINE_LOG}\n{ADDON_LOADED_LOG}");
|
||||
production_load_with(tag, stock_ui, &before, |root| {
|
||||
let dir = root.join("ZZOrder");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
std::fs::write(dir.join("ZZOrder.toc"), "## Interface: 11200\nprobe.lua\n").unwrap();
|
||||
std::fs::write(
|
||||
dir.join("probe.lua"),
|
||||
"ZZORDER_SAW_LAYER = BenillaOptionsFrame ~= nil\n",
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
}
|
||||
|
||||
/// The production in-game load, the layer on unless `stock_ui`, over a hermetic AddOns root that
|
||||
/// `addons` fills; `before` runs ahead of the load. The caller holds [`ENV_LOCK`]. Returns the VM
|
||||
/// and the load's failures.
|
||||
pub(super) fn production_load_with(
|
||||
tag: &str,
|
||||
stock_ui: bool,
|
||||
before: &str,
|
||||
addons: impl FnOnce(&std::path::Path),
|
||||
) -> (UiScript, Vec<String>) {
|
||||
let tmp = std::env::temp_dir().join(format!("benilla-layer-{tag}-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&tmp);
|
||||
let home = tmp.join("benilla-config");
|
||||
let dir = home.join("AddOns").join("ZZOrder");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
std::fs::write(dir.join("ZZOrder.toc"), "## Interface: 11200\nprobe.lua\n").unwrap();
|
||||
std::fs::write(
|
||||
dir.join("probe.lua"),
|
||||
"ZZORDER_SAW_LAYER = BenillaOptionsFrame ~= nil\n",
|
||||
)
|
||||
.unwrap();
|
||||
let root = home.join("AddOns");
|
||||
std::fs::create_dir_all(&root).unwrap();
|
||||
addons(&root);
|
||||
let _c = EnvGuard::unset("WOW_CAPTURE");
|
||||
let _h = EnvGuard::set("BENILLA_HOME", home.to_str().unwrap());
|
||||
|
||||
@@ -50,6 +67,8 @@ fn production_load(tag: &str, stock_ui: bool) -> (UiScript, Vec<String>) {
|
||||
exists: true,
|
||||
name: Some("Probesix".into()),
|
||||
level: 60,
|
||||
class: Some("Warrior".into()),
|
||||
class_file: Some("WARRIOR".into()),
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
@@ -57,8 +76,7 @@ fn production_load(tag: &str, stock_ui: bool) -> (UiScript, Vec<String>) {
|
||||
s.note_addon_info_reply(&[]);
|
||||
s.register_cvars(crate::cvars::registered_pairs());
|
||||
let mut failures = super::load_font_registry(&s);
|
||||
s.run(DEFINE_LOG).unwrap();
|
||||
s.run(ADDON_LOADED_LOG).unwrap();
|
||||
s.run(before).unwrap();
|
||||
// The layer passed in, not set through `WOW_STOCK_UI`: every test in this process reads it.
|
||||
failures.extend(super::manifest::load_ingame_ui_with(
|
||||
&mut s,
|
||||
@@ -109,12 +127,10 @@ fn the_layer_loads_after_every_stock_file_and_before_the_addons() {
|
||||
assert!(failures.is_empty(), "load failures: {failures:#?}");
|
||||
let log: Vec<String> = s.eval("return DEFINE_LOG").unwrap();
|
||||
|
||||
// The core's last stock row (TutorialFrame.lua), the core's own last row (GameMenuFrame.xml),
|
||||
// the layer's first file (ScrollTemplates.xml) and last (FrameXMLFixes.xml is all
|
||||
// redefinitions, so ScriptLogFrame.xml), then the addon.
|
||||
// The core's last row (TutorialFrame.lua), the layer's first file (ScrollTemplates.xml) and
|
||||
// last (FrameXMLFixes.xml is all redefinitions, so ScriptLogFrame.xml), then the addon.
|
||||
let order = [
|
||||
"TutorialFrame_OnHide",
|
||||
"GameMenuButton_Pending",
|
||||
"BenillaScrollBar_Step",
|
||||
"BenillaScriptLog_Toggle",
|
||||
"ZZORDER_SAW_LAYER",
|
||||
@@ -139,7 +155,7 @@ fn the_layer_loads_after_every_stock_file_and_before_the_addons() {
|
||||
|| n.strip_prefix("arg")
|
||||
.is_some_and(|d| d.len() == 1 && d.as_bytes()[0].is_ascii_digit())
|
||||
};
|
||||
let late: Vec<&String> = log[at[2]..]
|
||||
let late: Vec<&String> = log[at[1]..]
|
||||
.iter()
|
||||
.filter(|n| stock.contains(n.as_str()) && !dispatch(n))
|
||||
.collect();
|
||||
@@ -235,6 +251,7 @@ fn the_stock_ui_switch_boots_the_core_without_the_layer() {
|
||||
"BenillaScrollBar_Step",
|
||||
"KeyBindings_OnHostKey",
|
||||
"BenillaOptionsFrame_SelectCategory",
|
||||
"BenillaGameMenuButtonEditMode",
|
||||
"BENILLA_BAG_WAS_OPEN",
|
||||
"BenillaScriptLog_Toggle",
|
||||
] {
|
||||
@@ -245,12 +262,11 @@ fn the_stock_ui_switch_boots_the_core_without_the_layer() {
|
||||
}
|
||||
assert!(!s.eval::<bool>("return BenillaOptionsFrame ~= nil").unwrap());
|
||||
assert!(!s.eval::<bool>("return ZZORDER_SAW_LAYER").unwrap());
|
||||
// The host's calls into the layer, and the game menu's Options rung.
|
||||
// The host's calls into the layer.
|
||||
for body in [
|
||||
super::ERRORS_TOGGLE.to_string(),
|
||||
super::ERRORS_CLEAR.to_string(),
|
||||
super::host_key_capture("CTRL-J"),
|
||||
"GameMenuButtonOptions:Click()".to_string(),
|
||||
] {
|
||||
s.run(&body).unwrap_or_else(|e| panic!("{body}: {e}"));
|
||||
}
|
||||
|
||||
@@ -817,6 +817,8 @@ mod tooltip_template_tests;
|
||||
#[cfg(test)]
|
||||
mod escape_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod game_menu_addon_tests;
|
||||
#[cfg(test)]
|
||||
mod game_menu_tests;
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ fn harness_on(mut s: UiScript) -> UiScript {
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
// Before our files, as the core loads before the layer: it sources UIParent.lua again.
|
||||
r"Interface\FrameXML\GameMenuFrame.xml",
|
||||
// The stock options kit, hidden: `UIOptionsFrame_Init` assigns the option globals our
|
||||
// rows capture at OnLoad, and these declare the tables the Graphics rows and census read.
|
||||
r"Interface\FrameXML\OptionsFrameTemplates.xml",
|
||||
@@ -42,7 +44,7 @@ fn harness_on(mut s: UiScript) -> UiScript {
|
||||
"ScrollTemplates.xml", // the page scroll and the Keybindings list
|
||||
"KeyBindingsPage.xml", // the Keybindings page's templates and script
|
||||
"OptionsFrame.xml",
|
||||
"GameMenuFrame.xml",
|
||||
"GameMenuAdapters.xml",
|
||||
] {
|
||||
// Our window loads strict: a missing template there fails instead of only warning.
|
||||
if loaded(&s, file) {
|
||||
|
||||
@@ -1322,10 +1322,9 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// The core's chain rows are the reference's own `FrameXML.toc` rows, every one, but the stock
|
||||
/// `GameMenuFrame.xml`, which ours stands in for until the layer reshapes the stock menu. A
|
||||
/// row the stock toc lacks runs a file the reference does not, or twice: `UIParent.xml:4`
|
||||
/// sources `LocaleProperties.lua`, which has no row.
|
||||
/// The core's chain rows are the reference's own `FrameXML.toc` rows, every one. A row the
|
||||
/// stock toc lacks runs a file the reference does not, or twice: `UIParent.xml:4` sources
|
||||
/// `LocaleProperties.lua`, which has no row.
|
||||
#[test]
|
||||
fn the_cores_chain_rows_are_the_stock_tocs_rows() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
@@ -1337,7 +1336,6 @@ mod tests {
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
.filter(|l| !l.is_empty() && !l.starts_with('#'))
|
||||
.filter(|l| !l.eq_ignore_ascii_case("GameMenuFrame.xml"))
|
||||
.map(|l| format!("interface\\framexml\\{}", l.to_ascii_lowercase()))
|
||||
.collect();
|
||||
let mut core: Vec<String> = super::super::addons::Addon::core()
|
||||
|
||||
@@ -486,7 +486,7 @@ fn every_shipped_text_attribute_answers_against_the_real_global_strings() {
|
||||
};
|
||||
|
||||
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("assets/ui");
|
||||
let mut keys = 0;
|
||||
let mut swept = 0;
|
||||
for entry in std::fs::read_dir(&dir).expect("assets/ui").flatten() {
|
||||
let path = entry.path();
|
||||
if path.extension().is_none_or(|e| e != "xml") {
|
||||
@@ -505,7 +505,6 @@ fn every_shipped_text_attribute_answers_against_the_real_global_strings() {
|
||||
"{file}: text=\"{value}\" is shaped like a GlobalStrings key but the real \
|
||||
GlobalStrings.lua has no such string — it would render as its own key name"
|
||||
);
|
||||
keys += 1;
|
||||
} else {
|
||||
assert!(
|
||||
resolved.is_none(),
|
||||
@@ -513,10 +512,11 @@ fn every_shipped_text_attribute_answers_against_the_real_global_strings() {
|
||||
the loader would silently show that string's value instead of these words"
|
||||
);
|
||||
}
|
||||
swept += 1;
|
||||
}
|
||||
}
|
||||
// A floor, so the sweep cannot pass by matching nothing.
|
||||
assert!(keys >= 1, "only {keys} key-shaped text= values swept");
|
||||
assert!(swept >= 1, "only {swept} text= values swept");
|
||||
}
|
||||
|
||||
/// No shipped script hands a GlobalStrings key to a text sink as its words: `SetText` cannot tell
|
||||
@@ -1096,7 +1096,7 @@ fn every_declared_parent_really_attaches() {
|
||||
let declared = shipped_frame_parents();
|
||||
assert!(
|
||||
// A sanity floor for the scan, not a census.
|
||||
declared.len() >= 2,
|
||||
!declared.is_empty(),
|
||||
"only {} parent declarations found — the scan broke",
|
||||
declared.len()
|
||||
);
|
||||
@@ -1445,7 +1445,7 @@ fn the_stock_sound_options_window_loads_hidden_and_the_alias_is_gone() {
|
||||
);
|
||||
|
||||
// `IsOptionFrameOpen` names the stock options windows, all hidden; it sees ours through the
|
||||
// wrapper `GameMenuFrame.xml` installs.
|
||||
// wrapper `GameMenuAdapters.xml` installs.
|
||||
assert!(
|
||||
s.eval::<bool>("return IsOptionFrameOpen() and true or false")
|
||||
.unwrap(),
|
||||
@@ -1503,7 +1503,7 @@ fn the_stock_sound_window_turns_a_box_off_and_stores_a_slider_as_lua_text() {
|
||||
|
||||
/// The stock Video Options window loads hidden (`OptionsFrame.xml:5`) and owns the `OptionsFrame`
|
||||
/// name; ours is `BenillaOptionsFrame`, which the ESC menu opens and the stock "options window
|
||||
/// open?" consumers see only through the `GameMenuFrame.xml` wrappers.
|
||||
/// open?" consumers see only through the `GameMenuAdapters.xml` wrappers.
|
||||
#[test]
|
||||
fn the_stock_video_options_window_loads_hidden_and_owns_its_own_name() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
@@ -1989,8 +1989,7 @@ fn the_stock_options_windows_load_and_save_are_reachable_for_addons() {
|
||||
}
|
||||
|
||||
/// benilla's own interface is one layer, and it does not grow: `layer.toc` names exactly these
|
||||
/// files, all ours (none off the chain), and `assets/ui` holds nothing else but the two manifests
|
||||
/// and the core's one file. A window is migrated, not authored: point `benilla.toc` at
|
||||
/// files, all ours (none off the chain), and `assets/ui` holds nothing else but the two manifests. A window is migrated, not authored: point `benilla.toc` at
|
||||
/// `Interface\FrameXML\<Window>.xml`, delete ours, and build the engine verbs the stock file
|
||||
/// calls; what changes the stock UI goes in one of these files.
|
||||
#[test]
|
||||
@@ -1999,6 +1998,7 @@ fn the_layer_does_not_grow() {
|
||||
"ScrollTemplates.xml",
|
||||
"KeyBindingsPage.xml",
|
||||
"OptionsFrame.xml",
|
||||
"GameMenuAdapters.xml",
|
||||
"ContainerFrameAdapters.xml",
|
||||
"SpellBookAdapters.xml",
|
||||
"ScriptLogFrame.xml",
|
||||
@@ -2020,44 +2020,28 @@ fn the_layer_does_not_grow() {
|
||||
found.sort();
|
||||
let mut shipped: Vec<String> = LAYER
|
||||
.iter()
|
||||
.chain(&["benilla.toc", "layer.toc", "GameMenuFrame.xml"])
|
||||
.chain(&["benilla.toc", "layer.toc"])
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
shipped.sort();
|
||||
assert_eq!(
|
||||
found, shipped,
|
||||
"assets/ui holds the two manifests, the layer's files and the core's one file, nothing else"
|
||||
"assets/ui holds the two manifests and the layer's files, nothing else"
|
||||
);
|
||||
}
|
||||
|
||||
/// The core is the stock UI: `benilla.toc` names the reference's own files off the chain and one
|
||||
/// file of ours, `GameMenuFrame.xml`, which stands in for the stock one until the layer reshapes
|
||||
/// the stock menu, and whose row says so.
|
||||
/// The core is the stock UI: `benilla.toc` names the reference's own files off the chain and no
|
||||
/// file of ours.
|
||||
#[test]
|
||||
fn the_core_toc_names_no_file_of_ours_but_the_game_menu() {
|
||||
fn the_core_toc_names_no_file_of_ours() {
|
||||
let core = super::addons::Addon::core().toc.files;
|
||||
let ours: Vec<&String> = core
|
||||
.iter()
|
||||
.filter(|f| !super::reference_ui::is_chain_entry(f))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
ours,
|
||||
["GameMenuFrame.xml"],
|
||||
"benilla.toc names a file of ours: benilla's own interface is the layer (layer.toc), \
|
||||
which loads after every stock file"
|
||||
);
|
||||
let toc = super::content::read(super::manifest::MANIFEST).expect("benilla.toc");
|
||||
let lines: Vec<&str> = toc.lines().collect();
|
||||
let at = lines
|
||||
.iter()
|
||||
.position(|l| l.trim() == "GameMenuFrame.xml")
|
||||
.expect("the row");
|
||||
assert!(
|
||||
lines[..at]
|
||||
.iter()
|
||||
.rev()
|
||||
.take_while(|l| l.starts_with('#'))
|
||||
.any(|l| l.contains("#553")),
|
||||
"GameMenuFrame.xml's row names the issue that retires it"
|
||||
ours.is_empty(),
|
||||
"benilla.toc names a file of ours, {ours:?}: benilla's own interface is the layer \
|
||||
(layer.toc), which loads after every stock file"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,9 +130,16 @@ fn load_entry(s: &UiScript, entry: &str, strict_templates: bool, no_warnings: bo
|
||||
s.run(MICRO_BUTTON_STAND_INS)
|
||||
.expect("the micro-button stand-ins");
|
||||
}
|
||||
if leaf.eq_ignore_ascii_case("UIParent.xml") && super::reference_ui::is_chain_entry(&path) {
|
||||
// The stock GameMenuFrame.xml sources UIParent.lua again, which drops the stand-ins' wrappers.
|
||||
if (leaf.eq_ignore_ascii_case("UIParent.xml") || leaf.eq_ignore_ascii_case("GameMenuFrame.xml"))
|
||||
&& super::reference_ui::is_chain_entry(&path)
|
||||
{
|
||||
s.run(UIPARENT_STAND_INS).expect("the UIParent stand-ins");
|
||||
}
|
||||
if leaf.eq_ignore_ascii_case("GameMenuFrame.xml") && super::reference_ui::is_chain_entry(&path)
|
||||
{
|
||||
s.run(GAME_MENU_STAND_INS).expect("the game menu stand-ins");
|
||||
}
|
||||
if leaf.eq_ignore_ascii_case("UIOptionsFrame.xml") {
|
||||
s.run(UIOPTIONS_STAND_INS)
|
||||
.expect("the stock options window's stand-ins");
|
||||
@@ -241,7 +248,7 @@ pub(super) const UIPARENT_STAND_INS: &str = r#"
|
||||
-- names are real, and all three options windows are the REFERENCE's own files,
|
||||
-- loaded hidden — including `OptionsFrame`, the video window, which used to be our own
|
||||
-- window's name. Ours is `BenillaOptionsFrame` now and is not in this list: it is not a name
|
||||
-- the reference indexes, and the wrappers in `GameMenuFrame.xml` are what tell these two
|
||||
-- the reference indexes, and the wrappers in `GameMenuAdapters.xml` are what tell these two
|
||||
-- functions about it. A KIT is a prefix of the manifest and may load none of the four, which
|
||||
-- is what these stand-ins are for.
|
||||
local function benilla_seat_options()
|
||||
@@ -263,6 +270,15 @@ pub(super) const UIPARENT_STAND_INS: &str = r#"
|
||||
end
|
||||
"#;
|
||||
|
||||
/// What a kit owes stock `GameMenuFrame.xml`: its OnShow and OnHide (`GameMenuFrame.xml:188-195`)
|
||||
/// call the micro-button and bag-bar verbs unguarded, from files a kit may stop short of. Plain
|
||||
/// global writes, so the real definitions replace them in any order.
|
||||
pub(super) const GAME_MENU_STAND_INS: &str = r#"
|
||||
UpdateMicroButtons = UpdateMicroButtons or function() end
|
||||
Disable_BagButtons = Disable_BagButtons or function() end
|
||||
Enable_BagButtons = Enable_BagButtons or function() end
|
||||
"#;
|
||||
|
||||
/// What a kit owes stock `UIOptionsFrame.xml`: its `VARIABLES_LOADED` arm
|
||||
/// (`UIOptionsFrame.lua:193-227`) calls six functions from files a kit may not load, and a kit
|
||||
/// fires the event itself. Functions, so seating them at load is safe in any order.
|
||||
|
||||
@@ -42,7 +42,10 @@ pub fn load_ui(script: &UiScript, entry: &str) {
|
||||
.run(MICRO_BUTTON_STAND_INS)
|
||||
.expect("the micro-button stand-ins");
|
||||
}
|
||||
if leaf.eq_ignore_ascii_case("UIParent.xml") && entry.contains('\\') {
|
||||
// The stock GameMenuFrame.xml sources UIParent.lua again, which drops the stand-ins' wrappers.
|
||||
if (leaf.eq_ignore_ascii_case("UIParent.xml") || leaf.eq_ignore_ascii_case("GameMenuFrame.xml"))
|
||||
&& entry.contains('\\')
|
||||
{
|
||||
script
|
||||
.run(UIPARENT_STAND_INS)
|
||||
.expect("the UIParent stand-ins");
|
||||
@@ -123,7 +126,7 @@ const UIPARENT_STAND_INS: &str = r#"
|
||||
-- names are real, and all three options windows are the REFERENCE's own files,
|
||||
-- loaded hidden — including `OptionsFrame`, the video window, which used to be our own
|
||||
-- window's name. Ours is `BenillaOptionsFrame` now and is not in this list: it is not a name
|
||||
-- the reference indexes, and the wrappers in `GameMenuFrame.xml` are what tell these two
|
||||
-- the reference indexes, and the wrappers in `GameMenuAdapters.xml` are what tell these two
|
||||
-- functions about it. A KIT is a prefix of the manifest and may load none of the four, which
|
||||
-- is what these stand-ins are for.
|
||||
local function benilla_seat_options()
|
||||
|
||||
Reference in New Issue
Block a user