diff --git a/crates/benilla-app/assets/ui/FrameXMLFixes.xml b/crates/benilla-app/assets/ui/FrameXMLFixes.xml
new file mode 100644
index 00000000..26cfff83
--- /dev/null
+++ b/crates/benilla-app/assets/ui/FrameXMLFixes.xml
@@ -0,0 +1,33 @@
+
+
+
+
diff --git a/crates/benilla-app/assets/ui/benilla.toc b/crates/benilla-app/assets/ui/benilla.toc
index 9170c73a..6b27b073 100644
--- a/crates/benilla-app/assets/ui/benilla.toc
+++ b/crates/benilla-app/assets/ui/benilla.toc
@@ -1,6 +1,7 @@
-# The built-in interface, in load order: a bare filename is ours, from `assets/ui`; a path is
-# the reference's own file, off the player's patch chain. Entry 0, Fonts.xml, loads alone into
-# every boot VM, from Startup on; the rest loads on entering the world, then third-party addons.
+# 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.
# 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
@@ -19,8 +20,6 @@ Interface\FrameXML\Fonts.xml
Interface\FrameXML\GlobalStrings.lua
# `Localize()` runs at load and overwrites GlobalStrings keys, so this follows them.
Interface\FrameXML\Localization.xml
-# `GetText` is a Lua global, not an engine binding: the client's `0x703bf0` calls it.
-Interface\FrameXML\LocaleProperties.lua
Interface\FrameXML\BasicControls.xml
# Before UIParent.xml, whose `LowHealthFrame` has `parent="WorldFrame"`.
Interface\FrameXML\WorldFrame.xml
@@ -52,15 +51,12 @@ Interface\FrameXML\TargetFrame.xml
Interface\FrameXML\PetFrame.xml
# Anchors `relativeTo="TargetFrame"`.
Interface\FrameXML\ComboFrame.xml
-# Ours: the scroll parts of our options window and script log.
-ScrollTemplates.xml
-
Interface\FrameXML\UIPanelTemplates.lua
Interface\FrameXML\UIPanelTemplates.xml
# The reference's three options windows, loaded hidden so addons find their frames by name.
-# Deviation: the player opens our own options window, `OptionsFrame.xml` below, because 1.12's
-# settings screens are much worse to use; the stock one holds `OptionsFrame`.
+# 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`.
Interface\FrameXML\OptionsFrame.xml
Interface\FrameXML\SoundOptionsFrame.xml
# Inherits `OptionsCheckButtonTemplate`, and declares `UIOptionsFrameCheckButtons`, which
@@ -70,12 +66,6 @@ Interface\FrameXML\UIOptionsFrame.xml
Interface\FrameXML\StaticPopup.xml
Interface\FrameXML\CoinPickupFrame.xml
-# Ours: the Keybindings page's templates and script, which OptionsFrame.xml uses at load.
-KeyBindingsPage.xml
-
-# Ours: `BenillaOptionsFrame`; its rows read the option globals the stock windows above set.
-OptionsFrame.xml
-
# `MainMenuBar`, `MainMenuBarArtFrame` and `MainMenuExpBar`, which later files read at load.
Interface\FrameXML\MainMenuBar.xml
@@ -115,16 +105,14 @@ Interface\FrameXML\PaperDollFrame.xml
Interface\FrameXML\PetPaperDollFrame.xml
Interface\FrameXML\MainMenuBarBagButtons.xml
Interface\FrameXML\StackSplitFrame.xml
-# Ours: wraps the stock bag verbs; the keyring is a container (id -2).
-ContainerFrameAdapters.xml
Interface\FrameXML\ReputationFrame.xml
Interface\FrameXML\SkillFrame.xml
# Brings HonorFrameTemplates.xml, whose row templates `Blizzard_InspectUI` inherits.
Interface\FrameXML\HonorFrame.xml
Interface\FrameXML\DressUpFrame.xml
+# `FrameXML.toc` has it between ItemButtonTemplate.xml and SpellBookFrame.xml.
+Interface\FrameXML\StatsFrame.xml
Interface\FrameXML\SpellBookFrame.xml
-# Ours: the pet page's autocast widgets in the pet action button's proportions.
-SpellBookAdapters.xml
# RaidFrame.xml names `parent="FriendsFrame"` and MailFrame.xml inherits `FriendsFrameTabTemplate`.
Interface\FrameXML\FriendsFrame.xml
Interface\FrameXML\RaidFrame.xml
@@ -156,12 +144,11 @@ Interface\FrameXML\BonusActionBarFrame.xml
Interface\FrameXML\WorldStateFrame.xml
# `ClassTrainerListScrollFrameTemplate`, which on-demand addons inherit.
Interface\FrameXML\ClassTrainerFrameTemplates.xml
-# Ours: the `/errors` script error log and our slash commands.
-ScriptLogFrame.xml
Interface\FrameXML\HelpFrame.xml
Interface\FrameXML\CinematicFrame.xml
Interface\FrameXML\TabardFrame.xml
Interface\FrameXML\TutorialFrame.xml
-# Ours: the ESC game menu. Last: it wraps `ToggleGameMenu`, `IsOptionFrameOpen` and
-# `UpdateMicroButtons` to count `BenillaOptionsFrame`.
+# 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
diff --git a/crates/benilla-app/assets/ui/layer.toc b/crates/benilla-app/assets/ui/layer.toc
new file mode 100644
index 00000000..17843450
--- /dev/null
+++ b/crates/benilla-app/assets/ui/layer.toc
@@ -0,0 +1,21 @@
+# benilla's own interface, one layer on the stock one (docs/METHOD.md): every file is ours, from
+# `assets/ui`, and loads in this order after every `benilla.toc` row and before the third-party
+# addons. It is listed in no addon list, fires no `ADDON_LOADED` and cannot be switched off; a dev
+# build boots without it under `WOW_STOCK_UI=1`. A test names its files, so it does not grow.
+
+## Interface: 11200
+
+# The scroll parts of our options window and script log.
+ScrollTemplates.xml
+# The Keybindings page's templates and script, which OptionsFrame.xml uses at load.
+KeyBindingsPage.xml
+# `BenillaOptionsFrame`; its rows read the option globals the stock options windows set.
+OptionsFrame.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.
+SpellBookAdapters.xml
+# The `/errors` script error log and our slash commands.
+ScriptLogFrame.xml
+# Our fixes to the stock Lua.
+FrameXMLFixes.xml
diff --git a/crates/benilla-app/src/bindings.rs b/crates/benilla-app/src/bindings.rs
index f2743f6d..7981c3c1 100644
--- a/crates/benilla-app/src/bindings.rs
+++ b/crates/benilla-app/src/bindings.rs
@@ -484,7 +484,7 @@ fn latch_and_dispatch(
if let Some(chord_str) = captured {
// The Keybindings page's own handler, not a binding body.
if let Some(s) = script.as_mut() {
- if let Err(e) = s.run(&format!("KeyBindings_OnHostKey(\"{chord_str}\")")) {
+ if let Err(e) = s.run(&crate::ui_script::host_key_capture(&chord_str)) {
warn!("bindings(capture): {e}");
}
}
diff --git a/crates/benilla-app/src/bindings/commands.rs b/crates/benilla-app/src/bindings/commands.rs
index 76c2459b..37f4222e 100644
--- a/crates/benilla-app/src/bindings/commands.rs
+++ b/crates/benilla-app/src/bindings/commands.rs
@@ -1731,9 +1731,10 @@ pub(crate) static ABSENT: &[Absent] = &[
// ── Misc ────────────────────────────────────────────────────────────────────────────
// The nine `hidden="true" debug="true"` rows: benilla's instruments for these answer to the
// dev plane's chords, not to a binding.
+ // Stock `StatsFrame.lua` defines `ToggleStats`; the readout it shows is `GetDebugStats`.
absent!(
"TOGGLESTATS",
- ["ToggleStats"],
+ ["GetDebugStats"],
"a dev-plane instrument on a dev chord"
),
absent!(
diff --git a/crates/benilla-app/src/ui_chat/input/parse.rs b/crates/benilla-app/src/ui_chat/input/parse.rs
index 6094d78d..f000aa5a 100644
--- a/crates/benilla-app/src/ui_chat/input/parse.rs
+++ b/crates/benilla-app/src/ui_chat/input/parse.rs
@@ -291,13 +291,13 @@ fn slash_command(index: SlashIndex, args: &str) -> ParsedChat {
// `/reload` is the rebuild `/console reloadUI` runs (`0x4035f0`, which reads no arguments).
S::ReloadUi => ParsedChat::ReloadUi,
S::ConvertRaid => ParsedChat::ConvertRaid,
- // `/errors`, benilla's own: toggles the script error log, a FrameXML window;
- // `/errors clear` empties it.
+ // `/errors`, benilla's own: toggles the layer's script error log; `/errors clear` empties
+ // it. A no-op when the run boots without the layer.
S::ScriptErrors => ParsedChat::Lua {
body: if args.trim().eq_ignore_ascii_case("clear") {
- "BenillaScriptLog_Clear()".into()
+ crate::ui_script::ERRORS_CLEAR.into()
} else {
- "BenillaScriptLog_Toggle()".into()
+ crate::ui_script::ERRORS_TOGGLE.into()
},
},
// `SlashCmdList["CONSOLE"]` (`ChatFrame.lua:671`) is `ConsoleExec(msg)`. A typed line runs
diff --git a/crates/benilla-app/src/ui_script/addons.rs b/crates/benilla-app/src/ui_script/addons.rs
index bacb360d..38ed6680 100644
--- a/crates/benilla-app/src/ui_script/addons.rs
+++ b/crates/benilla-app/src/ui_script/addons.rs
@@ -1,8 +1,8 @@
//! Where interfaces come from: addon discovery under [`root`] and the load walk ([`Walk::load`]).
-//! An [`Addon`] is a name, a parsed `.toc` and the [`Source`] its files come from. benilla's own
-//! interface is one too, with the compiled-in tree as its source, but not for the lifecycle: like
-//! FrameXML it loads outside `AddOn_Load` ([`super::manifest::load_ingame_ui`]) and gets no
-//! `ADDON_LOADED`.
+//! An [`Addon`] is a name, a parsed `.toc` and the [`Source`] its files come from. The core
+//! interface and benilla's layer are two more, with the compiled-in tree as their source, but not
+//! for the lifecycle: like FrameXML they load outside `AddOn_Load`
+//! ([`super::manifest::load_ingame_ui`]), get no `ADDON_LOADED` and have no registry row.
use std::collections::HashSet;
use std::path::{Path, PathBuf};
@@ -23,7 +23,8 @@ pub(crate) const LOAD_INSTRUCTION_BUDGET: u64 = 200_000_000;
/// Where one interface's files come from.
pub(super) enum Source {
- /// benilla's own interface: the compiled-in tree, shadowed by `assets/ui` in a dev build.
+ /// The core's and the layer's own files: the compiled-in tree, shadowed by `assets/ui` in a
+ /// dev build.
Builtin,
/// The AddOns root on disk, not this addon's own folder: a request under `Interface/AddOns/`
/// maps onto it, so a dependent can reach a shared library addon beside it.
@@ -34,8 +35,8 @@ pub(super) enum Source {
/// One loadable interface: a name, its parsed manifest, and where its files come from.
pub(super) struct Addon {
- /// The folder name (`"benilla"` for the builtin): what the AddOn API keys on and what
- /// `ADDON_LOADED` carries.
+ /// The folder name (`"benilla"` for the core and the layer, which have no registry row): what
+ /// the AddOn API keys on and what `ADDON_LOADED` carries.
pub(super) name: String,
/// The parsed `.toc`: the ordered file list plus every directive.
pub(super) toc: Toc,
@@ -48,15 +49,23 @@ impl Addon {
Addon { name, toc, source }
}
- /// benilla's own interface, which is in the binary and so cannot be missing.
- pub(super) fn builtin() -> Self {
- let toc = content::read(super::manifest::MANIFEST)
+ /// The core interface, [`super::manifest::MANIFEST`], which is in the binary and so cannot
+ /// be missing.
+ pub(super) fn core() -> Self {
+ Self::shipped(super::manifest::MANIFEST)
+ }
+
+ /// benilla's layer, [`super::manifest::LAYER_MANIFEST`], in the binary as the core is.
+ pub(super) fn layer() -> Self {
+ Self::shipped(super::manifest::LAYER_MANIFEST)
+ }
+
+ /// The interface one shipped `.toc` names.
+ fn shipped(manifest: &str) -> Self {
+ let toc = content::read(manifest)
.map(|t| Toc::parse(&t))
.unwrap_or_else(|| {
- error!(
- "ui_script: {} is not in the shipped UI — no interface will load",
- super::manifest::MANIFEST
- );
+ error!("ui_script: {manifest} is not in the shipped UI — it will not load");
Toc::default()
});
Addon {
@@ -67,7 +76,7 @@ impl Addon {
}
/// One file's bytes, by a path already resolved into the source's path space. A `Dir` reads
- /// under the AddOns root, then the chain ([`read_addon_file`]); only the flat builtin falls
+ /// under the AddOns root, then the chain ([`read_addon_file`]); only the flat shipped tree falls
/// back to the basename, which for a `Dir` would rescue an escaping path. Bytes, not text: a
/// `.lua` reaches Lua as it is on disk.
fn read(&self, req: &str) -> Option> {
@@ -85,7 +94,7 @@ impl Addon {
}
/// The base this addon's manifest entries resolve against: `Interface/AddOns/` for a
- /// `Dir`, empty for the builtin's flat tree and the chain's full paths.
+ /// `Dir`, empty for the shipped flat tree and the chain's full paths.
fn prefix(&self) -> String {
match &self.source {
Source::Builtin | Source::Chain => String::new(),
@@ -95,7 +104,7 @@ impl Addon {
/// The chunk name, which addons parse: `"@%s"` (`0x8716e0`) over the resolved install path,
/// built by `0x704bc0` for every `.lua`, as for `