diff --git a/crates/benilla-app/assets/ui/ContainerFrameAdapters.xml b/crates/benilla-app/assets/ui/ContainerFrameAdapters.xml
index 26466581..8a7882ff 100644
--- a/crates/benilla-app/assets/ui/ContainerFrameAdapters.xml
+++ b/crates/benilla-app/assets/ui/ContainerFrameAdapters.xml
@@ -10,6 +10,8 @@
-- The stock body, captured before it is shadowed; nil when no client data was sourced.
local refItemButtonOnEnter = ContainerFrameItemButton_OnEnter
+ -- The stock name, redefined: the stock item buttons call it, and our keyring slot needs
+ -- the bag-slot tooltip.
function ContainerFrameItemButton_OnEnter(button)
if not button then
button = this
@@ -72,7 +74,8 @@
end
end
- -- After the stock ContainerFrame.lua has loaded, so its callers reach these.
+ -- The stock names, redefined after the stock ContainerFrame.lua has loaded, so its callers
+ -- (the vendor, bank, mail and trade windows) reach these.
OpenBackpack = BenillaOpenBackpack
CloseBackpack = BenillaCloseBackpack
CloseAllBags = BenillaCloseAllBags
diff --git a/crates/benilla-app/assets/ui/GameMenuAdapters.xml b/crates/benilla-app/assets/ui/GameMenuAdapters.xml
index 6962cddf..43cf5fea 100644
--- a/crates/benilla-app/assets/ui/GameMenuAdapters.xml
+++ b/crates/benilla-app/assets/ui/GameMenuAdapters.xml
@@ -24,11 +24,11 @@
end
end
- -- ERA_WINDOW_SCALE (OptionsFrame.xml) stands in for the era's UIParent scale; read at
- -- every show, then the stock OnShow.
+ -- BENILLA_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)
+ this:SetScale(BENILLA_ERA_WINDOW_SCALE or 1)
Ref_GameMenuFrame_OnShow()
end)
diff --git a/crates/benilla-app/assets/ui/KeyBindingsPage.xml b/crates/benilla-app/assets/ui/KeyBindingsPage.xml
index a89fa332..03d4de7e 100644
--- a/crates/benilla-app/assets/ui/KeyBindingsPage.xml
+++ b/crates/benilla-app/assets/ui/KeyBindingsPage.xml
@@ -4,14 +4,16 @@
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).
+ Blizzard_BindingUI: while a capsule is selected the window is keyboard-enabled, so the
+ next key binds through its OnKeyDown, and a mouse 3/4/5 click or a wheel notch 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.
-->
-
-
-
+
-
+
@@ -75,7 +77,7 @@
-
+
@@ -984,7 +984,7 @@
-
+
@@ -1020,11 +1020,11 @@
-
+
-
+
@@ -1032,7 +1032,7 @@
-
+
@@ -1058,7 +1058,7 @@
-
+
@@ -1096,21 +1096,21 @@
- OptionsCheckbox_OnClick(this)
- OptionsRow_Hover(this:GetParent(), 1)
- OptionsRow_Hover(this:GetParent(), nil)
+ BenillaOptionsCheckbox_OnClick(this)
+ BenillaOptionsRow_Hover(this:GetParent(), 1)
+ BenillaOptionsRow_Hover(this:GetParent(), nil)
- OptionsRow_Hover(this, 1)
- OptionsRow_Hover(this, nil)
+ BenillaOptionsRow_Hover(this, 1)
+ BenillaOptionsRow_Hover(this, nil)
-
+
@@ -1172,22 +1172,22 @@
- OptionsSlider_OnValueChanged(this, arg1)
- OptionsRow_Hover(this:GetParent():GetParent(), 1)
- OptionsRow_Hover(this:GetParent():GetParent(), nil)
+ BenillaOptionsSlider_OnValueChanged(this, arg1)
+ BenillaOptionsRow_Hover(this:GetParent():GetParent(), 1)
+ BenillaOptionsRow_Hover(this:GetParent():GetParent(), nil)
- OptionsRow_Hover(this, 1)
- OptionsRow_Hover(this, nil)
+ BenillaOptionsRow_Hover(this, 1)
+ BenillaOptionsRow_Hover(this, nil)
-
+
@@ -1221,8 +1221,8 @@
- OptionsRow_Hover(this, 1)
- OptionsRow_Hover(this, nil)
+ BenillaOptionsRow_Hover(this, 1)
+ BenillaOptionsRow_Hover(this, nil)
@@ -1327,7 +1327,7 @@
- OptionsTab_FitWidth(self)
+ BenillaOptionsTab_FitWidth(this)
@@ -1355,8 +1355,8 @@
- self:Disable()
- OptionsTab_FitWidth(self)
+ this:Disable()
+ BenillaOptionsTab_FitWidth(this)
@@ -1370,56 +1370,56 @@
-
+
- getglobal(self:GetName() .. "Label"):SetText("Gameplay")
+ getglobal(this:GetName() .. "Label"):SetText("Gameplay")
-
+
- OptionsCategoryRow_OnLoad(self, "Controls", "Controls")
+ BenillaOptionsCategoryRow_OnLoad(this, "Controls", "Controls")
-
+
- OptionsCategoryRow_OnLoad(self, "Interface", "Interface")
+ BenillaOptionsCategoryRow_OnLoad(this, "Interface", "Interface")
-
+
- OptionsCategoryRow_OnLoad(self, "ActionBars", "Action Bars")
+ BenillaOptionsCategoryRow_OnLoad(this, "ActionBars", "Action Bars")
-
+
- OptionsCategoryRow_OnLoad(self, "Combat", "Combat")
+ BenillaOptionsCategoryRow_OnLoad(this, "Combat", "Combat")
-
+
- OptionsCategoryRow_OnLoad(self, "Chat", "Chat")
+ BenillaOptionsCategoryRow_OnLoad(this, "Chat", "Chat")
-
+
- OptionsCategoryRow_OnLoad(self, "Keybindings", "Keybindings")
+ BenillaOptionsCategoryRow_OnLoad(this, "Keybindings", "Keybindings")
-
+
- OptionsCategoryRow_OnLoad(self, "Nameplates", "Nameplates")
+ BenillaOptionsCategoryRow_OnLoad(this, "Nameplates", "Nameplates")
-
+
- getglobal(self:GetName() .. "Label"):SetText("System")
+ getglobal(this:GetName() .. "Label"):SetText("System")
-
+
- OptionsCategoryRow_OnLoad(self, "Graphics", "Graphics")
+ BenillaOptionsCategoryRow_OnLoad(this, "Graphics", "Graphics")
-
+
- OptionsCategoryRow_OnLoad(self, "Audio", "Audio")
+ BenillaOptionsCategoryRow_OnLoad(this, "Audio", "Audio")
@@ -1444,37 +1444,37 @@
-
+
-
+
- self:Disable()
+ this:Disable()
if BenillaOptionsFrame.selectedCategory == "Keybindings" then
- StaticPopup_Show("CONFIRM_RESET_KEYBINDINGS")
+ StaticPopup_Show("BENILLA_CONFIRM_RESET_KEYBINDINGS")
else
- OptionsPage_Defaults(BenillaOptionsFrame.selectedCategory)
+ BenillaOptionsPage_Defaults(BenillaOptionsFrame.selectedCategory)
end
-
+
- self:SetText(KeyBindings_String("UNBIND", "Unbind Key"))
- self:Disable()
+ this:SetText(BenillaKeyBindings_String("UNBIND", "Unbind Key"))
+ this:Disable()
PlaySound("igMainMenuOptionCheckBoxOn")
- KeyBindings_Unbind()
+ BenillaKeyBindings_Unbind()
@@ -1486,9 +1486,16 @@
- self:SetScrollChild(BenillaOptionsFrameContainerBody)
- OptionsScroll_Fit()
- BenillaScroll_OnMouseWheel(this, arg1)
+ this:SetScrollChild(BenillaOptionsFrameContainerBody)
+ BenillaOptionsScroll_Fit()
+
+
+ if BenillaKeyBindingsPage.selected then
+ BenillaKeyBindings_OnMouseWheel(arg1)
+ else
+ BenillaScroll_OnMouseWheel(this, arg1)
+ end
+
BenillaScroll_SyncBar(this)
BenillaScroll_SyncBar(this)
@@ -1496,7 +1503,7 @@
+ BenillaOptionsScroll_Fit sizes it. -->
@@ -1516,185 +1523,185 @@
-
+
- OptionsRow_OnLoad(self, "deselectOnClick", "Sticky Targeting",
+ BenillaOptionsRow_OnLoad(this, "deselectOnClick", "Sticky Targeting",
"OPTION_TOOLTIP_GAMEFIELD_DESELECT")
- self.invert = 1
+ this.invert = 1
-
+
- OptionsRow_OnLoad(self, "assistAttack", "Attack on assist",
+ BenillaOptionsRow_OnLoad(this, "assistAttack", "Attack on assist",
"OPTION_TOOLTIP_ASSIST_ATTACK")
-
+
- OptionsRow_OnLoad(self, "autoClearAFK", "Auto Clear AFK",
+ BenillaOptionsRow_OnLoad(this, "autoClearAFK", "Auto Clear AFK",
"OPTION_TOOLTIP_CLEAR_AFK")
-
+
- OptionsRow_OnLoad(self, "BlockTrades", "Block Trades",
+ BenillaOptionsRow_OnLoad(this, "BlockTrades", "Block Trades",
"OPTION_TOOLTIP_BLOCK_TRADES")
-
+
- OptionsRow_OnLoad(self, "autoSelfCast", "Auto Self Cast",
+ BenillaOptionsRow_OnLoad(this, "autoSelfCast", "Auto Self Cast",
"OPTION_TOOLTIP_AUTO_SELF_CAST")
-
+
- OptionsRow_OnLoad(self, "autoLootDefault", "Auto Loot")
+ BenillaOptionsRow_OnLoad(this, "autoLootDefault", "Auto Loot")
-
+
- OptionsRow_OnLoad(self, "mouseInvertPitch", "Invert Mouse",
+ BenillaOptionsRow_OnLoad(this, "mouseInvertPitch", "Invert Mouse",
"OPTION_TOOLTIP_INVERT_MOUSE")
-
+
- OptionsRow_OnLoad(self, "mousespeed", "Mouse Sensitivity",
+ BenillaOptionsRow_OnLoad(this, "mousespeed", "Mouse Sensitivity",
"OPTION_TOOLTIP_MOUSE_SENSITIVITY")
- OptionsSliderRow_Setup(self, 0.5, 1.5, 0.05)
+ BenillaOptionsSliderRow_Setup(this, 0.5, 1.5, 0.05)
-
+
- OptionsRow_OnLoad(self, "cameraSmoothStyle",
+ BenillaOptionsRow_OnLoad(this, "cameraSmoothStyle",
"Camera Following Style", "OPTION_TOOLTIP_CAMERA1")
- OptionsDropdownRow_Setup(self, {
+ BenillaOptionsDropdownRow_Setup(this, {
{ text = "Smart", value = "1", tip = "OPTION_TOOLTIP_CAMERA1" },
{ text = "Always", value = "2", tip = "OPTION_TOOLTIP_CAMERA2" },
{ text = "Never", value = "0", alt = "3", tip = "OPTION_TOOLTIP_CAMERA3" },
})
-
+
- OptionsRow_OnLoad(self, "cameraTerrainTilt", "Follow Terrain",
+ BenillaOptionsRow_OnLoad(this, "cameraTerrainTilt", "Follow Terrain",
"OPTION_TOOLTIP_FOLLOW_TERRAIN")
-
+
- OptionsRow_OnLoad(self, "cameraBobbing", "Head Bob",
+ BenillaOptionsRow_OnLoad(this, "cameraBobbing", "Head Bob",
"OPTION_TOOLTIP_HEAD_BOB")
-
+
- OptionsRow_OnLoad(self, "cameraYawSmoothSpeed", "Auto-Follow Speed",
+ BenillaOptionsRow_OnLoad(this, "cameraYawSmoothSpeed", "Auto-Follow Speed",
"OPTION_TOOLTIP_AUTO_FOLLOW_SPEED")
- OptionsSliderRow_Setup(self, 90, 270, 10, "raw")
+ BenillaOptionsSliderRow_Setup(this, 90, 270, 10, "raw")
-
+
- OptionsRow_OnLoad(self, "cameraWaterCollision", "Water Collision",
+ BenillaOptionsRow_OnLoad(this, "cameraWaterCollision", "Water Collision",
"OPTION_TOOLTIP_WATER_COLLISION")
-
+
- OptionsRow_OnLoad(self, "cameraPivot", "Smart Pivot",
+ BenillaOptionsRow_OnLoad(this, "cameraPivot", "Smart Pivot",
"OPTION_TOOLTIP_SMART_PIVOT")
-
+
- OptionsRow_OnLoad(self, "cameraYawMoveSpeed", "Mouse Look Speed",
+ BenillaOptionsRow_OnLoad(this, "cameraYawMoveSpeed", "Mouse Look Speed",
"OPTION_TOOLTIP_MOUSE_LOOK_SPEED")
- OptionsSliderRow_Setup(self, 90, 270, 10, "raw")
- self.partner = { cvar = "cameraPitchMoveSpeed", scale = 0.5 }
+ BenillaOptionsSliderRow_Setup(this, 90, 270, 10, "raw")
+ this.partner = { cvar = "cameraPitchMoveSpeed", scale = 0.5 }
-
+
- OptionsRow_OnLoad(self, "cameraDistanceMaxFactor",
+ BenillaOptionsRow_OnLoad(this, "cameraDistanceMaxFactor",
"Max Camera Distance", "OPTION_TOOLTIP_MAX_FOLLOW_DIST")
- OptionsSliderRow_Setup(self, 1.0, 2.0, 0.1, "yards")
+ BenillaOptionsSliderRow_Setup(this, 1.0, 2.0, 0.1, "yards")
@@ -1706,104 +1713,104 @@
-
+
- OptionsRow_OnLoad(self, "MasterSoundEffects", "Enable All Sound",
+ BenillaOptionsRow_OnLoad(this, "MasterSoundEffects", "Enable All Sound",
"OPTION_TOOLTIP_ENABLE_ALL_SOUND")
- self.soundQuirk = 1
+ this.soundQuirk = 1
-
+
- OptionsRow_OnLoad(self, "MasterVolume", "Master Volume",
+ BenillaOptionsRow_OnLoad(this, "MasterVolume", "Master Volume",
"OPTION_TOOLTIP_MASTER_VOLUME")
-
+
- OptionsRow_OnLoad(self, "SoundVolume", "Sound Volume",
+ BenillaOptionsRow_OnLoad(this, "SoundVolume", "Sound Volume",
"OPTION_TOOLTIP_SOUND_VOLUME", 1)
- self.parentRow = "RowMaster"
+ this.parentRow = "RowMaster"
-
+
- OptionsRow_OnLoad(self, "MusicVolume", "Music Volume",
+ BenillaOptionsRow_OnLoad(this, "MusicVolume", "Music Volume",
"OPTION_TOOLTIP_MUSIC_VOLUME", 1)
- self.parentRow = "RowMaster"
+ this.parentRow = "RowMaster"
-
+
- OptionsRow_OnLoad(self, "AmbienceVolume", "Ambience Volume",
+ BenillaOptionsRow_OnLoad(this, "AmbienceVolume", "Ambience Volume",
"OPTION_TOOLTIP_AMBIENCE_VOLUME", 1)
- self.parentRow = "RowMaster"
+ this.parentRow = "RowMaster"
-
+
- OptionsRow_OnLoad(self, "EnableMusic", "Enable Music",
+ BenillaOptionsRow_OnLoad(this, "EnableMusic", "Enable Music",
"OPTION_TOOLTIP_ENABLE_MUSIC")
- self.soundQuirk = 1
+ this.soundQuirk = 1
-
+
- OptionsRow_OnLoad(self, "EnableAmbience", "Enable Ambience",
+ BenillaOptionsRow_OnLoad(this, "EnableAmbience", "Enable Ambience",
"OPTION_TOOLTIP_ENABLE_AMBIENCE")
- self.soundQuirk = 1
+ this.soundQuirk = 1
-
+
- OptionsRow_OnLoad(self, "EnableErrorSpeech", "Enable Error Speech",
+ BenillaOptionsRow_OnLoad(this, "EnableErrorSpeech", "Enable Error Speech",
"OPTION_TOOLTIP_ENABLE_ERROR_SPEECH")
- self.soundQuirk = 1
+ this.soundQuirk = 1
-
+
- OptionsRow_OnLoad(self, "Sound_EnableSoundWhenGameIsInBG",
+ BenillaOptionsRow_OnLoad(this, "Sound_EnableSoundWhenGameIsInBG",
"Enable Sound in Background",
"BENILLA_TOOLTIP_BACKGROUND_SOUND")
- self.soundQuirk = 1
+ this.soundQuirk = 1
@@ -1817,44 +1824,44 @@
-
+
- OptionsRow_OnLoad(self, "uiScale", "UI Scale",
+ BenillaOptionsRow_OnLoad(this, "uiScale", "UI Scale",
"OPTION_TOOLTIP_UI_SCALE")
- OptionsSliderRow_Setup(self, OptionsFrameSliders[1].minValue,
+ BenillaOptionsSliderRow_Setup(this, OptionsFrameSliders[1].minValue,
OptionsFrameSliders[1].maxValue,
OptionsFrameSliders[1].valueStep)
-- A live write would rescale the slider mid-drag.
- self.deferred = 1
+ this.deferred = 1
-
+
- OptionsRow_OnLoad(self, "farclip", "Terrain Distance",
+ BenillaOptionsRow_OnLoad(this, "farclip", "Terrain Distance",
"OPTION_TOOLTIP_FARCLIP")
- OptionsSliderRow_Setup(self, OptionsFrameSliders[2].minValue,
+ BenillaOptionsSliderRow_Setup(this, OptionsFrameSliders[2].minValue,
OptionsFrameSliders[2].maxValue,
OptionsFrameSliders[2].valueStep, "raw")
-
+
- OptionsRow_OnLoad(self, "WorldDetail", "Environment Detail",
+ BenillaOptionsRow_OnLoad(this, "WorldDetail", "Environment Detail",
"OPTION_TOOLTIP_ENVIRONMENT_DETAIL")
-- Stop names from the era quality ladder.
- OptionsSliderRow_Setup(self, OptionsFrameSliders[3].minValue,
+ BenillaOptionsSliderRow_Setup(this, OptionsFrameSliders[3].minValue,
OptionsFrameSliders[3].maxValue,
OptionsFrameSliders[3].valueStep, nil,
{ "Low", "Medium", "High" })
@@ -1863,7 +1870,7 @@
-
+
@@ -1872,33 +1879,33 @@
-- OptionsFrame_SetDefaults' default (l.444-445);
-- `or 1` covers a VM with no CVars.
local gd = tonumber(GetCVarDefault("gamma")) or 1
- OptionsNumFuncRow_OnLoad(self, GetGamma, SetGamma,
+ BenillaOptionsNumFuncRow_OnLoad(this, GetGamma, SetGamma,
tostring(1 - gd), "Brightness",
"BENILLA_TOOLTIP_BRIGHTNESS")
- OptionsSliderRow_Setup(self, OptionsFrameSliders[6].minValue,
+ BenillaOptionsSliderRow_Setup(this, OptionsFrameSliders[6].minValue,
OptionsFrameSliders[6].maxValue,
OptionsFrameSliders[6].valueStep, "travel")
-
+
- OptionsRow_OnLoad(self, "weatherDensity", "Weather Intensity",
+ BenillaOptionsRow_OnLoad(this, "weatherDensity", "Weather Intensity",
"OPTION_TOOLTIP_WEATHER_DETAIL")
-- Our names for `0x67b870`'s spawn-rate gains
-- `{0.1, 0.33, 0.66, 1.0}` (`[0x8680ec]`).
- OptionsSliderRow_Setup(self, OptionsFrameSliders[9].minValue,
+ BenillaOptionsSliderRow_Setup(this, OptionsFrameSliders[9].minValue,
OptionsFrameSliders[9].maxValue,
OptionsFrameSliders[9].valueStep, nil,
{ "Minimal", "Low", "Medium", "Full" })
-
+
@@ -1907,15 +1914,15 @@
-- Deviation: modern Classic's two-entry Display
-- Mode, as there is no exclusive fullscreen;
-- gxWindow "0" is the borderless window.
- OptionsRow_OnLoad(self, "gxWindow", "Display Mode",
+ BenillaOptionsRow_OnLoad(this, "gxWindow", "Display Mode",
"BENILLA_TOOLTIP_DISPLAY_MODE")
- OptionsDropdownRow_Setup(self, {
+ BenillaOptionsDropdownRow_Setup(this, {
{ text = "Windowed (Fullscreen)", value = "0" },
{ text = "Windowed", value = "1" },
})
-
+
@@ -1923,11 +1930,11 @@
-- Not deferred despite gxRestart: vsync changes
-- live.
- OptionsRow_OnLoad(self, "gxVSync", "Vertical Sync",
+ BenillaOptionsRow_OnLoad(this, "gxVSync", "Vertical Sync",
"OPTION_TOOLTIP_VERTICAL_SYNC")
-
+
@@ -1937,10 +1944,10 @@
-- the backbuffer), and no Multisampling, which
-- misses alpha-tested cutouts. Deferred: a write
-- rebuilds the render target.
- OptionsRow_OnLoad(self, "renderScale", "Render Scale",
+ BenillaOptionsRow_OnLoad(this, "renderScale", "Render Scale",
"BENILLA_TOOLTIP_RENDER_SCALE")
- OptionsSliderRow_Setup(self, 0.5, 2.0, 0.05)
- self.deferred = 1
+ BenillaOptionsSliderRow_Setup(this, 0.5, 2.0, 0.05)
+ this.deferred = 1
@@ -1952,7 +1959,7 @@
local info = OptionsFrameSliders[i]
local row = getglobal(this:GetName() .. name)
if ( info and row ) then
- OptionsSliderRow_Setup(row, info.minValue, info.maxValue,
+ BenillaOptionsSliderRow_Setup(row, info.minValue, info.maxValue,
info.valueStep, row.fmt, row.stops)
end
end
@@ -1966,227 +1973,227 @@
-
+
- OptionsGlobalRow_OnLoad(self, "SHOW_COMBAT_TEXT",
+ BenillaOptionsGlobalRow_OnLoad(this, "SHOW_COMBAT_TEXT",
"Enable Floating Combat Text", "OPTION_TOOLTIP_SHOW_COMBAT_TEXT")
- self.applyFunc = "CombatText_UpdateOrLoad"
+ this.applyFunc = "BenillaCombatText_UpdateOrLoad"
-
+
- OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_FLOAT_MODE",
+ BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_FLOAT_MODE",
"Combat Text Float Mode", "OPTION_TOOLTIP_COMBAT_TEXT_MODE", 1)
- OptionsDropdownRow_Setup(self, {
+ BenillaOptionsDropdownRow_Setup(this, {
{ text = "Scroll Up", value = "1" },
{ text = "Scroll Down", value = "2" },
{ text = "Arc", value = "3" },
})
- self.parentRow = "RowCombatText"
- self.applyFunc = "CombatText_UpdateOrLoad"
+ this.parentRow = "RowCombatText"
+ this.applyFunc = "BenillaCombatText_UpdateOrLoad"
-
+
- OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_LOW_HEALTH_MANA",
+ BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_LOW_HEALTH_MANA",
"Show Low Mana and Health",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_LOW_HEALTH_MANA", 1)
- self.parentRow = "RowCombatText"
- self.applyFunc = "CombatText_UpdateOrLoad"
+ this.parentRow = "RowCombatText"
+ this.applyFunc = "BenillaCombatText_UpdateOrLoad"
-
+
- OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_AURAS",
+ BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_AURAS",
"Show Auras", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_AURAS", 1)
- self.parentRow = "RowCombatText"
- self.applyFunc = "CombatText_UpdateOrLoad"
+ this.parentRow = "RowCombatText"
+ this.applyFunc = "BenillaCombatText_UpdateOrLoad"
-
+
- OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_AURA_FADE",
+ BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_AURA_FADE",
"Show Fading Auras", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_AURA_FADE", 1)
- self.parentRow = "RowCombatText"
- self.applyFunc = "CombatText_UpdateOrLoad"
+ this.parentRow = "RowCombatText"
+ this.applyFunc = "BenillaCombatText_UpdateOrLoad"
-
+
- OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_COMBAT_STATE",
+ BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_COMBAT_STATE",
"Show Combat State",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_COMBAT_STATE", 1)
- self.parentRow = "RowCombatText"
- self.applyFunc = "CombatText_UpdateOrLoad"
+ this.parentRow = "RowCombatText"
+ this.applyFunc = "BenillaCombatText_UpdateOrLoad"
-
+
- OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_DODGE_PARRY_MISS",
+ BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_DODGE_PARRY_MISS",
"Show Dodge/Parries/Misses",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_DODGE_PARRY_MISS", 1)
- self.parentRow = "RowCombatText"
- self.applyFunc = "CombatText_UpdateOrLoad"
+ this.parentRow = "RowCombatText"
+ this.applyFunc = "BenillaCombatText_UpdateOrLoad"
-
+
- OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_RESISTANCES",
+ BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_RESISTANCES",
"Show Damage Reduction",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_RESISTANCES", 1)
- self.parentRow = "RowCombatText"
- self.applyFunc = "CombatText_UpdateOrLoad"
+ this.parentRow = "RowCombatText"
+ this.applyFunc = "BenillaCombatText_UpdateOrLoad"
-
+
- OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_REPUTATION",
+ BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_REPUTATION",
"Show Reputation",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_REPUTATION", 1)
- self.parentRow = "RowCombatText"
- self.applyFunc = "CombatText_UpdateOrLoad"
+ this.parentRow = "RowCombatText"
+ this.applyFunc = "BenillaCombatText_UpdateOrLoad"
-
+
- OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_REACTIVES",
+ BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_REACTIVES",
"Show Reactive Spells and Abilities",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_REACTIVES", 1)
- self.parentRow = "RowCombatText"
- self.applyFunc = "CombatText_UpdateOrLoad"
+ this.parentRow = "RowCombatText"
+ this.applyFunc = "BenillaCombatText_UpdateOrLoad"
-
+
- OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_FRIENDLY_NAMES",
+ BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_FRIENDLY_NAMES",
"Show Friendly Healer Names",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_FRIENDLY_NAMES", 1)
- self.parentRow = "RowCombatText"
- self.applyFunc = "CombatText_UpdateOrLoad"
+ this.parentRow = "RowCombatText"
+ this.applyFunc = "BenillaCombatText_UpdateOrLoad"
-
+
- OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_COMBO_POINTS",
+ BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_COMBO_POINTS",
"Show Combo Points",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_COMBO_POINTS", 1)
- self.parentRow = "RowCombatText"
- self.applyFunc = "CombatText_UpdateOrLoad"
+ this.parentRow = "RowCombatText"
+ this.applyFunc = "BenillaCombatText_UpdateOrLoad"
-
+
- OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_MANA",
+ BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_MANA",
"Show Energy Gains",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_MANA", 1)
- self.parentRow = "RowCombatText"
- self.applyFunc = "CombatText_UpdateOrLoad"
+ this.parentRow = "RowCombatText"
+ this.applyFunc = "BenillaCombatText_UpdateOrLoad"
-
+
- OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_HONOR_GAINED",
+ BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_HONOR_GAINED",
"Show Honor Gained",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_HONOR_GAINED", 1)
- self.parentRow = "RowCombatText"
- self.applyFunc = "CombatText_UpdateOrLoad"
+ this.parentRow = "RowCombatText"
+ this.applyFunc = "BenillaCombatText_UpdateOrLoad"
-
+
- OptionsRow_OnLoad(self, "CombatDamage", "Show Target Damage",
+ BenillaOptionsRow_OnLoad(this, "CombatDamage", "Show Target Damage",
"OPTION_TOOLTIP_SHOW_DAMAGE")
-
+
- OptionsRow_OnLoad(self, "CombatLogPeriodicSpells", "Periodic Damage",
+ BenillaOptionsRow_OnLoad(this, "CombatLogPeriodicSpells", "Periodic Damage",
"OPTION_TOOLTIP_LOG_PERIODIC_EFFECTS", 1)
- self.parentRow = "RowShowDamage"
+ this.parentRow = "RowShowDamage"
-
+
- OptionsRow_OnLoad(self, "PetMeleeDamage", "Pet Damage",
+ BenillaOptionsRow_OnLoad(this, "PetMeleeDamage", "Pet Damage",
"OPTION_TOOLTIP_SHOW_PET_MELEE_DAMAGE", 1)
- self.parentRow = "RowShowDamage"
- self.partner = { cvar = "PetSpellDamage" }
+ this.parentRow = "RowShowDamage"
+ this.partner = { cvar = "PetSpellDamage" }
@@ -2200,13 +2207,13 @@
-
+
- OptionsFuncRow_OnLoad(self,
+ BenillaOptionsFuncRow_OnLoad(this,
function() return SHOW_MULTI_ACTIONBAR_1 end,
function(value)
local on
@@ -2220,13 +2227,13 @@
"0", "Show Bottom Left ActionBar", "OPTION_TOOLTIP_SHOW_MULTIBAR1")
-
+
- OptionsFuncRow_OnLoad(self,
+ BenillaOptionsFuncRow_OnLoad(this,
function() return SHOW_MULTI_ACTIONBAR_2 end,
function(value)
local on
@@ -2240,13 +2247,13 @@
"0", "Show Bottom Right ActionBar", "OPTION_TOOLTIP_SHOW_MULTIBAR2")
-
+
- OptionsFuncRow_OnLoad(self,
+ BenillaOptionsFuncRow_OnLoad(this,
function() return SHOW_MULTI_ACTIONBAR_3 end,
function(value)
local on
@@ -2261,13 +2268,13 @@
-
+
- OptionsFuncRow_OnLoad(self,
+ BenillaOptionsFuncRow_OnLoad(this,
function() return SHOW_MULTI_ACTIONBAR_4 end,
function(value)
local on
@@ -2281,25 +2288,25 @@
"0", "Show Right ActionBar 2", "OPTION_TOOLTIP_SHOW_MULTIBAR4", 1)
-
+
- OptionsGlobalRow_OnLoad(self, "ALWAYS_SHOW_MULTIBARS",
+ BenillaOptionsGlobalRow_OnLoad(this, "ALWAYS_SHOW_MULTIBARS",
"Always Show ActionBars", "OPTION_TOOLTIP_ALWAYS_SHOW_MULTIBARS")
-- UIOptionsFrame.xml:680-683's click arm.
- self.applyFunc = "MultiActionBar_UpdateGridVisibility"
+ this.applyFunc = "MultiActionBar_UpdateGridVisibility"
-
+
- OptionsGlobalRow_OnLoad(self, "LOCK_ACTIONBAR",
+ BenillaOptionsGlobalRow_OnLoad(this, "LOCK_ACTIONBAR",
"Lock ActionBars", "OPTION_TOOLTIP_LOCK_ACTIONBAR")
@@ -2314,153 +2321,153 @@
-
+
- OptionsRow_OnLoad(self, "UberTooltips", "Enhanced Tooltips",
+ BenillaOptionsRow_OnLoad(this, "UberTooltips", "Enhanced Tooltips",
"OPTION_TOOLTIP_USE_UBERTOOLTIPS")
-
+
- OptionsRow_OnLoad(self, "statusBarText",
+ BenillaOptionsRow_OnLoad(this, "statusBarText",
"Status Bar Text", "OPTION_TOOLTIP_STATUS_BAR")
- self.cvarEvent = "STATUS_BAR_TEXT"
+ this.cvarEvent = "STATUS_BAR_TEXT"
-
+
- OptionsGlobalRow_OnLoad(self, "SHOW_TARGET_OF_TARGET",
+ BenillaOptionsGlobalRow_OnLoad(this, "SHOW_TARGET_OF_TARGET",
"Show Target of Target", "OPTION_TOOLTIP_SHOW_TARGET_OF_TARGET")
- self.applyFunc = "TargetofTarget_Update"
+ this.applyFunc = "TargetofTarget_Update"
-
+
- OptionsGlobalRow_OnLoad(self, "SHOW_TARGET_OF_TARGET_STATE",
+ BenillaOptionsGlobalRow_OnLoad(this, "SHOW_TARGET_OF_TARGET_STATE",
"Show When", "OPTION_TOOLTIP_TARGETOFTARGET5", 1)
- OptionsDropdownRow_Setup(self, {
+ BenillaOptionsDropdownRow_Setup(this, {
{ text = "Raid", value = "1", tip = "OPTION_TOOLTIP_TARGETOFTARGET1" },
{ text = "Party", value = "2", tip = "OPTION_TOOLTIP_TARGETOFTARGET2" },
{ text = "Solo", value = "3", tip = "OPTION_TOOLTIP_TARGETOFTARGET3" },
{ text = "Party or Raid", value = "4", tip = "OPTION_TOOLTIP_TARGETOFTARGET4" },
{ text = "Always", value = "5", tip = "OPTION_TOOLTIP_TARGETOFTARGET5" },
})
- self.applyFunc = "TargetofTarget_Update"
+ this.applyFunc = "TargetofTarget_Update"
-
+
- OptionsFuncRow_OnLoad(self, ShowingCloak, ShowCloak, "1",
+ BenillaOptionsFuncRow_OnLoad(this, ShowingCloak, ShowCloak, "1",
"Show Cloak", "OPTION_TOOLTIP_SHOW_CLOAK")
-
+
- OptionsFuncRow_OnLoad(self, ShowingHelm, ShowHelm, "1",
+ BenillaOptionsFuncRow_OnLoad(this, ShowingHelm, ShowHelm, "1",
"Show Helm", "OPTION_TOOLTIP_SHOW_HELM")
-
+
- OptionsGlobalRow_OnLoad(self, "QUEST_FADING_DISABLE",
+ BenillaOptionsGlobalRow_OnLoad(this, "QUEST_FADING_DISABLE",
"Instant Quest Text", "OPTION_TOOLTIP_SHOW_QUEST_FADING")
-
+
- OptionsGlobalRow_OnLoad(self, "SHOW_BUFF_DURATIONS",
+ BenillaOptionsGlobalRow_OnLoad(this, "SHOW_BUFF_DURATIONS",
"Buff Durations", "OPTION_TOOLTIP_SHOW_BUFF_DURATION")
- self.applyFunc = "BuffButtons_UpdatePositions"
+ this.applyFunc = "BuffButtons_UpdatePositions"
-
+
- OptionsGlobalRow_OnLoad(self, "AUTO_QUEST_WATCH",
+ BenillaOptionsGlobalRow_OnLoad(this, "AUTO_QUEST_WATCH",
"Automatic Quest Tracking", "OPTION_TOOLTIP_AUTO_QUEST_WATCH")
-
+
- OptionsRow_OnLoad(self, "profanityFilter", "Profanity Filter",
+ BenillaOptionsRow_OnLoad(this, "profanityFilter", "Profanity Filter",
"OPTION_TOOLTIP_PROFANITY_FILTER")
-
+
- OptionsFuncRow_OnLoad(self, TutorialsEnabled,
+ BenillaOptionsFuncRow_OnLoad(this, TutorialsEnabled,
BenillaOptionsFrame_SetTutorialsEnabled, "1",
"Show Tutorials", "OPTION_TOOLTIP_SHOW_TUTORIALS")
-
+
- OptionsRow_OnLoad(self, "showGameTips",
+ BenillaOptionsRow_OnLoad(this, "showGameTips",
"Show Loading Screen Tips",
"OPTION_TOOLTIP_SHOW_TIPOFTHEDAY")
-
+
- OptionsGlobalRow_OnLoad(self, "SHOW_NEWBIE_TIPS",
+ BenillaOptionsGlobalRow_OnLoad(this, "SHOW_NEWBIE_TIPS",
"Show Detailed Tooltips", "OPTION_TOOLTIP_SHOW_NEWBIE_TIPS")
@@ -2474,19 +2481,19 @@
-
+
- OptionsRow_OnLoad(self, "guildMemberNotify",
+ BenillaOptionsRow_OnLoad(this, "guildMemberNotify",
"Guild Member Alert",
"OPTION_TOOLTIP_GUILDMEMBER_ALERT")
-
+
@@ -2494,41 +2501,41 @@
- OptionsGlobalRow_OnLoad(self, "REMOVE_CHAT_DELAY",
+ BenillaOptionsGlobalRow_OnLoad(this, "REMOVE_CHAT_DELAY",
"Remove Chat Hover Delay",
"OPTION_TOOLTIP_REMOVE_CHAT_DELAY")
- self.applyFunc = "ChatFrame_ApplyMouseOverDelay"
+ this.applyFunc = "BenillaChatFrame_ApplyMouseOverDelay"
-
+
- OptionsRow_OnLoad(self, "ChatBubbles",
+ BenillaOptionsRow_OnLoad(this, "ChatBubbles",
"Show Chat Bubbles", "OPTION_TOOLTIP_CHAT_BUBBLES")
-
+
- OptionsRow_OnLoad(self, "showLootSpam",
+ BenillaOptionsRow_OnLoad(this, "showLootSpam",
"Detailed Loot Information",
"OPTION_TOOLTIP_SHOW_LOOT_SPAM")
-
+
- OptionsRow_OnLoad(self, "ChatBubblesParty",
+ BenillaOptionsRow_OnLoad(this, "ChatBubblesParty",
"Show Party Chat Bubbles", "OPTION_TOOLTIP_PARTY_CHAT_BUBBLES")
@@ -2536,15 +2543,15 @@
and writes it (l.337). The filter drops a
SpamMessages.dbc match in every chat type but
CHAT_MSG_FILTERED (`0x49aad2`). -->
-
+
- OptionsRow_OnLoad(self, "spamFilter", "Disable Spam Filter",
+ BenillaOptionsRow_OnLoad(this, "spamFilter", "Disable Spam Filter",
"OPTION_TOOLTIP_DISABLE_SPAM_FILTER")
- self.invert = 1
+ this.invert = 1
@@ -2558,56 +2565,56 @@
-
+
- OptionsRow_OnLoad(self, "nameplateShowEnemies",
+ BenillaOptionsRow_OnLoad(this, "nameplateShowEnemies",
"Enemy Name Plates")
-
+
- OptionsRow_OnLoad(self, "nameplateShowFriends",
+ BenillaOptionsRow_OnLoad(this, "nameplateShowFriends",
"Friendly Name Plates")
-
+
- OptionsRow_OnLoad(self, "UnitNamePlayer", "Player Names",
+ BenillaOptionsRow_OnLoad(this, "UnitNamePlayer", "Player Names",
"OPTION_TOOLTIP_SHOW_PLAYER_NAMES")
-
+
- OptionsRow_OnLoad(self, "UnitNamePlayerGuild", "Player Guild Names",
+ BenillaOptionsRow_OnLoad(this, "UnitNamePlayerGuild", "Player Guild Names",
"OPTION_TOOLTIP_SHOW_GUILD_NAMES", 1)
- self.parentRow = "RowPlayerNames"
+ this.parentRow = "RowPlayerNames"
-
+
- OptionsRow_OnLoad(self, "UnitNameNPC", "NPC Names",
+ BenillaOptionsRow_OnLoad(this, "UnitNameNPC", "NPC Names",
"OPTION_TOOLTIP_SHOW_NPC_NAMES")
-
+
- OptionsRow_OnLoad(self, "UnitNameOwn", "Show Own Name",
+ BenillaOptionsRow_OnLoad(this, "UnitNameOwn", "Show Own Name",
"OPTION_TOOLTIP_SHOW_OWN_NAME")
@@ -2653,10 +2660,10 @@
- KeyBindings_CharacterToggle(this)
+ BenillaKeyBindings_CharacterToggle(this)
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
- GameTooltip:SetText(KeyBindings_String("CHARACTER_SPECIFIC_KEYBINDING_TOOLTIP",
+ GameTooltip:SetText(BenillaKeyBindings_String("CHARACTER_SPECIFIC_KEYBINDING_TOOLTIP",
"Click this to toggle between general key bindings and key bindings specific to this character."), nil, nil, nil, nil, 1)
GameTooltip:Hide()
@@ -2666,119 +2673,119 @@
getglobal(this:GetName() .. "Label"):SetText(
- KeyBindings_String("CHARACTER_SPECIFIC_KEYBINDINGS", "Character Specific Key Bindings"))
+ BenillaKeyBindings_String("CHARACTER_SPECIFIC_KEYBINDINGS", "Character Specific Key Bindings"))
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -2796,92 +2803,98 @@
ScrollFrame_OnLoad()
-
- FauxScrollFrame_OnVerticalScroll(KEYBINDINGS_ROW_HEIGHT, KeyBindingsPage_Update)
+ FauxScrollFrame_OnVerticalScroll(BENILLA_KEYBINDINGS_ROW_HEIGHT, BenillaKeyBindingsPage_Update)
- if not KeyBindingsPage.selected then
+ if BenillaKeyBindingsPage.selected then
+ BenillaKeyBindings_OnMouseWheel(arg1)
+ else
ScrollFrameTemplate_OnMouseWheel(arg1)
end
-
+
- if not KeyBindingsPage.selected then
+ if BenillaKeyBindingsPage.selected then
+ BenillaKeyBindings_OnMouseWheel(arg1)
+ else
BenillaFauxScrollFrame_OnMouseWheel(getglobal(this:GetName() .. "ScrollFrame"), arg1)
end
+ BenillaKeyBindings_OnKeyDown(arg1)
-
-
+
+
- self.categoryKey = "Controls"
- getglobal(self:GetName() .. "Label"):SetText("Controls")
+ this.categoryKey = "Controls"
+ getglobal(this:GetName() .. "Label"):SetText("Controls")
-
+
- self.categoryKey = "Audio"
- getglobal(self:GetName() .. "Label"):SetText("Audio")
+ this.categoryKey = "Audio"
+ getglobal(this:GetName() .. "Label"):SetText("Audio")
-
+
- self.categoryKey = "Graphics"
- getglobal(self:GetName() .. "Label"):SetText("Graphics")
+ this.categoryKey = "Graphics"
+ getglobal(this:GetName() .. "Label"):SetText("Graphics")
-
+
- self.categoryKey = "Combat"
- getglobal(self:GetName() .. "Label"):SetText("Combat")
+ this.categoryKey = "Combat"
+ getglobal(this:GetName() .. "Label"):SetText("Combat")
-
+
- self.categoryKey = "ActionBars"
- getglobal(self:GetName() .. "Label"):SetText("Action Bars")
+ this.categoryKey = "ActionBars"
+ getglobal(this:GetName() .. "Label"):SetText("Action Bars")
-
+
- self.categoryKey = "Interface"
- getglobal(self:GetName() .. "Label"):SetText("Interface")
+ this.categoryKey = "Interface"
+ getglobal(this:GetName() .. "Label"):SetText("Interface")
-
+
- self.categoryKey = "Chat"
- getglobal(self:GetName() .. "Label"):SetText("Chat")
+ this.categoryKey = "Chat"
+ getglobal(this:GetName() .. "Label"):SetText("Chat")
-
+
- self.categoryKey = "Nameplates"
- getglobal(self:GetName() .. "Label"):SetText("Nameplates")
+ this.categoryKey = "Nameplates"
+ getglobal(this:GetName() .. "Label"):SetText("Nameplates")
-
+
- self.categoryKey = "Keybindings"
- getglobal(self:GetName() .. "Label"):SetText("Keybindings")
+ this.categoryKey = "Keybindings"
+ getglobal(this:GetName() .. "Label"):SetText("Keybindings")
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
@@ -2988,11 +3001,11 @@
-
+
- self:Disable()
- OptionsPage_Apply()
+ this:Disable()
+ BenillaOptionsPage_Apply()
@@ -3003,13 +3016,22 @@
BenillaOptionsFrame_SelectCategory(this.selectedCategory or "Controls")
if UpdateMicroButtons then UpdateMicroButtons() end
-
+
+
+ this:EnableKeyboard(false)
+ this:EnableMouseWheel(false)
+
+ BenillaKeyBindings_OnKeyDown()
+ BenillaKeyBindings_OnKeyDown(arg1)
+ BenillaKeyBindings_OnMouseWheel(arg1)
+
BenillaOptionsFrameSearchBox:SetText("")
- OptionsPage_DiscardPending()
- KeyBindings_SetSelected(nil)
- KeyBindings_Output("")
+ BenillaOptionsPage_DiscardPending()
+ BenillaKeyBindings_SetSelected(nil)
+ BenillaKeyBindings_Output("")
if UpdateMicroButtons then UpdateMicroButtons() end
diff --git a/crates/benilla-app/assets/ui/ScriptLogFrame.xml b/crates/benilla-app/assets/ui/ScriptLogFrame.xml
index 375c1dcc..ef460f43 100644
--- a/crates/benilla-app/assets/ui/ScriptLogFrame.xml
+++ b/crates/benilla-app/assets/ui/ScriptLogFrame.xml
@@ -1,10 +1,10 @@
diff --git a/crates/benilla-app/assets/ui/ScrollTemplates.xml b/crates/benilla-app/assets/ui/ScrollTemplates.xml
index 0a7cfd07..19a3f3e8 100644
--- a/crates/benilla-app/assets/ui/ScrollTemplates.xml
+++ b/crates/benilla-app/assets/ui/ScrollTemplates.xml
@@ -183,7 +183,7 @@
- BenillaFauxScrollBar_OnValueChanged(self, arg1)
+ BenillaFauxScrollBar_OnValueChanged(this, arg1)
diff --git a/crates/benilla-app/src/bindings.rs b/crates/benilla-app/src/bindings.rs
index 7981c3c1..b61d58d8 100644
--- a/crates/benilla-app/src/bindings.rs
+++ b/crates/benilla-app/src/bindings.rs
@@ -15,10 +15,6 @@
//! An addon's `Bindings.xml` rows ([`benilla_ui::bindings_xml`]) dispatch here too: a resolved
//! chord names a [`Bound`], either a registry [`Cmd`] or an index into the addon table.
//!
-//! While the Keybindings page has a capsule selected (`BenillaBindCapture`), raw input is
-//! canonicalized (`ALT-CTRL-SHIFT-`) and handed to the page, by the 1.12 law: lone
-//! modifiers ignored, left and right clicks stay UI clicks, ESC binds like any key.
-//!
//! Persistence: `benilla-config/bindings/account.txt` and `-.txt` ([`store`]); the
//! character file's existence is the character-set state, as in the reference.
@@ -442,7 +438,6 @@ fn latch_and_dispatch(
// affordances: a player build keeps the reference's `CTRL-SHIFT-P` fallback to `SHIFT-P`.
let dev_plane = ctrl && shift && !alt && crate::run_mode::dev_affordances();
- let armed = script.as_ref().is_some_and(|s| s.bind_capture_armed());
// A binding body runs as `ExecuteBinding` runs it, so the movement functions' gate passes.
let run_lua = |script: &mut Option>, lua: &str, tag: &str| {
if let Some(s) = script.as_mut() {
@@ -452,47 +447,6 @@ fn latch_and_dispatch(
}
};
- // ── The capture seam ── a Keybindings capsule is selected: swallow raw input and hand the
- // chord string to the page's Lua. The 1.12 law: lone modifiers and unknown keys ignored,
- // left and right stay UI clicks, the wheel is a chord. Super is not a 1.12 modifier, so a
- // Super press is ignored outright.
- if armed {
- let mut captured: Option = None;
- for ev in keyboard.read() {
- if ev.state != ButtonState::Pressed || ev.repeat || sup {
- continue;
- }
- if let Some(token) = chord::key_token(ev.key_code) {
- captured = Some(chord::chord_string(alt, ctrl, shift, token));
- }
- }
- for b in [MouseButton::Middle, MouseButton::Forward, MouseButton::Back] {
- if buttons.just_pressed(b) && !sup {
- if let Some(token) = chord::mouse_token(b) {
- captured = Some(chord::chord_string(alt, ctrl, shift, token));
- }
- }
- }
- if scroll.delta.y != 0.0 && !sup {
- let token = if scroll.delta.y > 0.0 {
- "MOUSEWHEELUP"
- } else {
- "MOUSEWHEELDOWN"
- };
- captured = Some(chord::chord_string(alt, ctrl, shift, token));
- }
- 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(&crate::ui_script::host_key_capture(&chord_str)) {
- warn!("bindings(capture): {e}");
- }
- }
- }
- // Releases still unlatch below so a key held across the arm cannot stick; nothing new
- // latches or fires while armed.
- }
-
// ── Who owns this frame's keys ── a focused EditBox eats every key while it holds focus; a
// shown keyboard frame ate the keys in `capture.consumed`. Both suppress a press and nothing
// else: in the reference a focused box makes the movement handlers no-ops, so the direction
@@ -533,7 +487,7 @@ fn latch_and_dispatch(
if !repeat {
state.down.push(key);
}
- if armed || (typing && !arrow_exempt) || eaten || sup || repeat {
+ if (typing && !arrow_exempt) || eaten || sup || repeat {
continue;
}
if state.latched.iter().any(|&(k, _)| k == BindKey::Key(key)) {
@@ -579,7 +533,6 @@ fn latch_and_dispatch(
MouseButton::Back,
] {
if buttons.just_pressed(b)
- && !armed
&& !sup
&& hover.0.is_none()
&& !state.latched.iter().any(|&(k, _)| k == BindKey::Mouse(b))
@@ -619,7 +572,7 @@ fn latch_and_dispatch(
let wheel = wheel_lines(scroll.unit, scroll.delta.y);
// Over chrome only (`PointerOverUiPanel`): the wheel still zooms with the cursor on a
// nameplate, a mouse-enabled widget but not a panel.
- if wheel != 0.0 && !armed && !sup && !over_ui.0 {
+ if wheel != 0.0 && !sup && !over_ui.0 {
let (key, amount) = if wheel > 0.0 {
(BindKey::WheelUp, wheel)
} else {
@@ -1071,59 +1024,9 @@ mod tests {
);
}
- /// The armed capture seam, driven by a real wheel event.
- #[test]
- fn an_armed_capture_takes_a_wheel_notch() {
- let mut script = UiScript::new().expect("VM");
- script.register_bindings(®istry_commands());
- script
- .run(
- r#"CAPTURED = nil
- function KeyBindings_OnHostKey(chord) CAPTURED = chord end
- BenillaBindCapture(true)"#,
- )
- .expect("arm");
- let mut app = vm_harness(script);
-
- app.world_mut().write_message(MouseWheel {
- unit: MouseScrollUnit::Line,
- x: 0.0,
- y: 1.0,
- window: Entity::PLACEHOLDER,
- });
- app.update();
- assert_eq!(
- lua_str(&app, "tostring(CAPTURED)"),
- "MOUSEWHEELUP",
- "a wheel notch while armed is a binding key"
- );
- assert!(
- !state(&app).fired(cmd::CAMERA_ZOOM_IN),
- "the armed seam swallows the notch — it must not also zoom"
- );
-
- // Down, and a modified notch.
- app.world_mut().write_message(MouseWheel {
- unit: MouseScrollUnit::Line,
- x: 0.0,
- y: -1.0,
- window: Entity::PLACEHOLDER,
- });
- app.update();
- assert_eq!(lua_str(&app, "tostring(CAPTURED)"), "MOUSEWHEELDOWN");
- press_key(&mut app, KeyCode::ShiftLeft);
- app.world_mut().write_message(MouseWheel {
- unit: MouseScrollUnit::Line,
- x: 0.0,
- y: 1.0,
- window: Entity::PLACEHOLDER,
- });
- app.update();
- assert_eq!(lua_str(&app, "tostring(CAPTURED)"), "SHIFT-MOUSEWHEELUP");
- }
-
- /// The whole wheel-bind path: the real Keybindings page, a capsule armed by a click, a real
- /// notch, then the bound chord dispatching.
+ /// The whole wheel-bind path: the real Keybindings page, a capsule armed by a click, a notch
+ /// over the page (its OnMouseWheel, as 1.12's window binds it), then the bound chord
+ /// dispatching.
#[test]
fn a_wheel_notch_binds_through_the_real_page_and_then_dispatches() {
benilla_formats::wow_data_or_skip!();
@@ -1141,27 +1044,27 @@ mod tests {
.unwrap(),
crate::ui_script::keybindings_tests::label(&s, "BINDING_NAME_JUMP", "JUMP")
);
- assert!(s.bind_capture_armed());
+ s.resolve();
+ let (x, y): (f32, f32) = s
+ .eval(&format!(
+ "return ({ROW}5:GetLeft() + {ROW}5:GetRight()) / 2, \
+ ({ROW}5:GetTop() + {ROW}5:GetBottom()) / 2"
+ ))
+ .unwrap();
+ s.mouse_wheel(x, y, 1.0);
+ assert_eq!(
+ s.eval::(r#"return GetBindingAction("MOUSEWHEELUP")"#)
+ .unwrap(),
+ "JUMP",
+ "a notch on an armed capsule is a bind"
+ );
+ assert!(
+ s.eval::("return BenillaKeyBindingsPage.selected == nil")
+ .unwrap(),
+ "the completed bind disarms"
+ );
let mut app = vm_harness(s);
- app.world_mut().write_message(MouseWheel {
- unit: MouseScrollUnit::Line,
- x: 0.0,
- y: 1.0,
- window: Entity::PLACEHOLDER,
- });
- app.update();
- {
- let s = app.world().non_send_resource::();
- assert_eq!(
- s.eval::(r#"return GetBindingAction("MOUSEWHEELUP")"#)
- .unwrap(),
- "JUMP",
- "a notch on an armed capsule is a bind"
- );
- assert!(!s.bind_capture_armed(), "the completed bind disarms");
- }
-
// The bound chord now dispatches: the next notch jumps rather than binding.
app.world_mut().write_message(MouseWheel {
unit: MouseScrollUnit::Line,
diff --git a/crates/benilla-app/src/bindings/chord.rs b/crates/benilla-app/src/bindings/chord.rs
index f64e9a75..e0af1768 100644
--- a/crates/benilla-app/src/bindings/chord.rs
+++ b/crates/benilla-app/src/bindings/chord.rs
@@ -78,22 +78,6 @@ impl Chord {
}
}
-/// Build the canonical chord string from live modifier state and a base token.
-pub(crate) fn chord_string(alt: bool, ctrl: bool, shift: bool, token: &str) -> String {
- let mut s = String::new();
- if alt {
- s.push_str("ALT-");
- }
- if ctrl {
- s.push_str("CTRL-");
- }
- if shift {
- s.push_str("SHIFT-");
- }
- s.push_str(token);
- s
-}
-
/// The 1.12 token for a physical key; `None` for a key the reference names `UNKNOWN` and for the
/// modifiers, which are only prefixes (`IsKeyPressIgnoredForBinding`). Both Enters are `ENTER`.
pub(crate) fn key_token(k: KeyCode) -> Option<&'static str> {
@@ -235,29 +219,17 @@ pub(crate) fn normalize_key(k: KeyCode) -> KeyCode {
}
}
-/// The 1.12 token for a mouse button: BUTTON4 is winit's `Forward` and BUTTON5 its `Back`, so the
-/// 1.12 default `BUTTON4 TOGGLEAUTORUN` sits on `Forward`. Which physical button the reference
-/// calls BUTTON4 is untraced.
-pub(crate) fn mouse_token(b: MouseButton) -> Option<&'static str> {
- Some(match b {
- MouseButton::Left => "BUTTON1",
- MouseButton::Right => "BUTTON2",
- MouseButton::Middle => "BUTTON3",
- MouseButton::Forward => "BUTTON4",
- MouseButton::Back => "BUTTON5",
- // The reference names further buttons `BUTTON` (`0x4b6aa0`'s bit-scan fallback).
- // winit passes the platform's button number, so the sixth button is `Other(5)`.
- MouseButton::Other(n) => return EXTRA_BUTTONS.get(usize::from(n).wrapping_sub(5)).copied(),
- })
-}
-
-/// `BUTTON6` to `BUTTON20`, indexed by `Other(n) - 5`.
+/// `BUTTON6` to `BUTTON20`, indexed by `Other(n) - 5`: the reference names further buttons
+/// `BUTTON` (`0x4b6aa0`'s bit-scan fallback), and winit passes the platform's button number, so
+/// the sixth button is `Other(5)`.
const EXTRA_BUTTONS: &[&str] = &[
"BUTTON6", "BUTTON7", "BUTTON8", "BUTTON9", "BUTTON10", "BUTTON11", "BUTTON12", "BUTTON13",
"BUTTON14", "BUTTON15", "BUTTON16", "BUTTON17", "BUTTON18", "BUTTON19", "BUTTON20",
];
-/// Token to base input: the inverse of [`key_token`] and [`mouse_token`], plus the wheel pair.
+/// Token to base input: the inverse of [`key_token`], the mouse buttons and the wheel pair.
+/// BUTTON4 is winit's `Forward` and BUTTON5 its `Back`, so the 1.12 default `BUTTON4
+/// TOGGLEAUTORUN` sits on `Forward`; which physical button the reference calls BUTTON4 is untraced.
fn token_key(t: &str) -> Option {
use KeyCode::*;
if let Some(b) = match t {
@@ -455,15 +427,11 @@ mod tests {
"{k:?} names '{token}', which SetBinding refuses"
);
}
- for b in [
- MouseButton::Left,
- MouseButton::Right,
- MouseButton::Middle,
- MouseButton::Forward,
- MouseButton::Back,
+ for token in [
+ "BUTTON1", "BUTTON2", "BUTTON3", "BUTTON4", "BUTTON5", "BUTTON20",
] {
- let token = mouse_token(b).expect("named");
- assert!(normalize_binding_key(token).is_some(), "{b:?} → '{token}'");
+ assert!(token_key(token).is_some(), "'{token}' names no button");
+ assert!(normalize_binding_key(token).is_some(), "'{token}'");
}
for token in ["MOUSEWHEELUP", "MOUSEWHEELDOWN"] {
assert!(normalize_binding_key(token).is_some());
@@ -626,10 +594,6 @@ mod tests {
})
);
assert_eq!(Chord::parse("BOGUS"), None);
- assert_eq!(
- chord_string(true, false, true, "PAGEDOWN"),
- "ALT-SHIFT-PAGEDOWN"
- );
assert_eq!(
Chord::parse("ALT-SHIFT-PAGEDOWN").unwrap(),
Chord {
diff --git a/crates/benilla-app/src/bindings/commands.rs b/crates/benilla-app/src/bindings/commands.rs
index 6f71d030..b8a32735 100644
--- a/crates/benilla-app/src/bindings/commands.rs
+++ b/crates/benilla-app/src/bindings/commands.rs
@@ -1129,9 +1129,9 @@ const TABLE: &[Spec] = &[
None
),
// ── MultiActionBar (BINDING_HEADER_MULTIACTIONBAR) ──────────────────────────────────
- // The four multibars' `runOnUp` pairs (`Bindings.xml:799-1134`), stock MultiActionBars.lua's.
- // Deviation: 1.12 files bars 2-4 under the spacer headers `BLANK`, `BLANK2` and `BLANK3`; they
- // sit under MULTIACTIONBAR here because this page's sections are named.
+ // The four multibars' `runOnUp` pairs (`Bindings.xml:799-1134`), stock MultiActionBars.lua's;
+ // bars 2-4 under the spacer headers `BLANK`, `BLANK2` and `BLANK3`, as `Bindings.xml:883`,
+ // `:967` and `:1051` file them.
spec!(
"MULTIACTIONBAR1BUTTON1",
MULTIACTIONBAR,
@@ -1254,7 +1254,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR2BUTTON1",
- MULTIACTIONBAR,
+ BLANK,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarBottomRight", 1)"#,
r#"MultiActionButtonUp("MultiBarBottomRight", 1)"#
@@ -1264,7 +1264,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR2BUTTON2",
- MULTIACTIONBAR,
+ BLANK,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarBottomRight", 2)"#,
r#"MultiActionButtonUp("MultiBarBottomRight", 2)"#
@@ -1274,7 +1274,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR2BUTTON3",
- MULTIACTIONBAR,
+ BLANK,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarBottomRight", 3)"#,
r#"MultiActionButtonUp("MultiBarBottomRight", 3)"#
@@ -1284,7 +1284,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR2BUTTON4",
- MULTIACTIONBAR,
+ BLANK,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarBottomRight", 4)"#,
r#"MultiActionButtonUp("MultiBarBottomRight", 4)"#
@@ -1294,7 +1294,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR2BUTTON5",
- MULTIACTIONBAR,
+ BLANK,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarBottomRight", 5)"#,
r#"MultiActionButtonUp("MultiBarBottomRight", 5)"#
@@ -1304,7 +1304,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR2BUTTON6",
- MULTIACTIONBAR,
+ BLANK,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarBottomRight", 6)"#,
r#"MultiActionButtonUp("MultiBarBottomRight", 6)"#
@@ -1314,7 +1314,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR2BUTTON7",
- MULTIACTIONBAR,
+ BLANK,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarBottomRight", 7)"#,
r#"MultiActionButtonUp("MultiBarBottomRight", 7)"#
@@ -1324,7 +1324,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR2BUTTON8",
- MULTIACTIONBAR,
+ BLANK,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarBottomRight", 8)"#,
r#"MultiActionButtonUp("MultiBarBottomRight", 8)"#
@@ -1334,7 +1334,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR2BUTTON9",
- MULTIACTIONBAR,
+ BLANK,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarBottomRight", 9)"#,
r#"MultiActionButtonUp("MultiBarBottomRight", 9)"#
@@ -1344,7 +1344,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR2BUTTON10",
- MULTIACTIONBAR,
+ BLANK,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarBottomRight", 10)"#,
r#"MultiActionButtonUp("MultiBarBottomRight", 10)"#
@@ -1354,7 +1354,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR2BUTTON11",
- MULTIACTIONBAR,
+ BLANK,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarBottomRight", 11)"#,
r#"MultiActionButtonUp("MultiBarBottomRight", 11)"#
@@ -1364,7 +1364,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR2BUTTON12",
- MULTIACTIONBAR,
+ BLANK,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarBottomRight", 12)"#,
r#"MultiActionButtonUp("MultiBarBottomRight", 12)"#
@@ -1375,7 +1375,7 @@ const TABLE: &[Spec] = &[
// The vertical bars: `MultiBarRight` is bar 3, `MultiBarLeft` bar 4.
spec!(
"MULTIACTIONBAR3BUTTON1",
- MULTIACTIONBAR,
+ BLANK2,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarRight", 1)"#,
r#"MultiActionButtonUp("MultiBarRight", 1)"#
@@ -1385,7 +1385,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR3BUTTON2",
- MULTIACTIONBAR,
+ BLANK2,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarRight", 2)"#,
r#"MultiActionButtonUp("MultiBarRight", 2)"#
@@ -1395,7 +1395,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR3BUTTON3",
- MULTIACTIONBAR,
+ BLANK2,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarRight", 3)"#,
r#"MultiActionButtonUp("MultiBarRight", 3)"#
@@ -1405,7 +1405,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR3BUTTON4",
- MULTIACTIONBAR,
+ BLANK2,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarRight", 4)"#,
r#"MultiActionButtonUp("MultiBarRight", 4)"#
@@ -1415,7 +1415,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR3BUTTON5",
- MULTIACTIONBAR,
+ BLANK2,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarRight", 5)"#,
r#"MultiActionButtonUp("MultiBarRight", 5)"#
@@ -1425,7 +1425,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR3BUTTON6",
- MULTIACTIONBAR,
+ BLANK2,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarRight", 6)"#,
r#"MultiActionButtonUp("MultiBarRight", 6)"#
@@ -1435,7 +1435,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR3BUTTON7",
- MULTIACTIONBAR,
+ BLANK2,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarRight", 7)"#,
r#"MultiActionButtonUp("MultiBarRight", 7)"#
@@ -1445,7 +1445,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR3BUTTON8",
- MULTIACTIONBAR,
+ BLANK2,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarRight", 8)"#,
r#"MultiActionButtonUp("MultiBarRight", 8)"#
@@ -1455,7 +1455,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR3BUTTON9",
- MULTIACTIONBAR,
+ BLANK2,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarRight", 9)"#,
r#"MultiActionButtonUp("MultiBarRight", 9)"#
@@ -1465,7 +1465,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR3BUTTON10",
- MULTIACTIONBAR,
+ BLANK2,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarRight", 10)"#,
r#"MultiActionButtonUp("MultiBarRight", 10)"#
@@ -1475,7 +1475,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR3BUTTON11",
- MULTIACTIONBAR,
+ BLANK2,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarRight", 11)"#,
r#"MultiActionButtonUp("MultiBarRight", 11)"#
@@ -1485,7 +1485,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR3BUTTON12",
- MULTIACTIONBAR,
+ BLANK2,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarRight", 12)"#,
r#"MultiActionButtonUp("MultiBarRight", 12)"#
@@ -1495,7 +1495,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR4BUTTON1",
- MULTIACTIONBAR,
+ BLANK3,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarLeft", 1)"#,
r#"MultiActionButtonUp("MultiBarLeft", 1)"#
@@ -1505,7 +1505,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR4BUTTON2",
- MULTIACTIONBAR,
+ BLANK3,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarLeft", 2)"#,
r#"MultiActionButtonUp("MultiBarLeft", 2)"#
@@ -1515,7 +1515,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR4BUTTON3",
- MULTIACTIONBAR,
+ BLANK3,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarLeft", 3)"#,
r#"MultiActionButtonUp("MultiBarLeft", 3)"#
@@ -1525,7 +1525,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR4BUTTON4",
- MULTIACTIONBAR,
+ BLANK3,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarLeft", 4)"#,
r#"MultiActionButtonUp("MultiBarLeft", 4)"#
@@ -1535,7 +1535,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR4BUTTON5",
- MULTIACTIONBAR,
+ BLANK3,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarLeft", 5)"#,
r#"MultiActionButtonUp("MultiBarLeft", 5)"#
@@ -1545,7 +1545,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR4BUTTON6",
- MULTIACTIONBAR,
+ BLANK3,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarLeft", 6)"#,
r#"MultiActionButtonUp("MultiBarLeft", 6)"#
@@ -1555,7 +1555,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR4BUTTON7",
- MULTIACTIONBAR,
+ BLANK3,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarLeft", 7)"#,
r#"MultiActionButtonUp("MultiBarLeft", 7)"#
@@ -1565,7 +1565,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR4BUTTON8",
- MULTIACTIONBAR,
+ BLANK3,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarLeft", 8)"#,
r#"MultiActionButtonUp("MultiBarLeft", 8)"#
@@ -1575,7 +1575,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR4BUTTON9",
- MULTIACTIONBAR,
+ BLANK3,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarLeft", 9)"#,
r#"MultiActionButtonUp("MultiBarLeft", 9)"#
@@ -1585,7 +1585,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR4BUTTON10",
- MULTIACTIONBAR,
+ BLANK3,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarLeft", 10)"#,
r#"MultiActionButtonUp("MultiBarLeft", 10)"#
@@ -1595,7 +1595,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR4BUTTON11",
- MULTIACTIONBAR,
+ BLANK3,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarLeft", 11)"#,
r#"MultiActionButtonUp("MultiBarLeft", 11)"#
@@ -1605,7 +1605,7 @@ const TABLE: &[Spec] = &[
),
spec!(
"MULTIACTIONBAR4BUTTON12",
- MULTIACTIONBAR,
+ BLANK3,
Kind::EdgeUpDown(
r#"MultiActionButtonDown("MultiBarLeft", 12)"#,
r#"MultiActionButtonUp("MultiBarLeft", 12)"#
@@ -2101,6 +2101,35 @@ mod tests {
);
}
+ /// Each registered command sits under the header the install's `Bindings.xml` files it under:
+ /// the last `header=` at or before its row, so the host's `HEADER_*` rows (a category change
+ /// opens one) are the reference's, the spacers over multibars 2-4 included.
+ #[test]
+ fn every_command_sits_under_its_bindings_xml_header() {
+ let Some(binds) = install_bindings() else {
+ return;
+ };
+ let mut section = String::new();
+ let mut under = std::collections::HashMap::new();
+ for b in &binds {
+ if b.platform.as_deref().is_some_and(|p| p != "windows") {
+ continue;
+ }
+ if let Some(h) = &b.header {
+ section = format!("BINDING_HEADER_{h}");
+ }
+ under.insert(b.name.clone(), section.clone());
+ }
+ for spec in SPECS {
+ assert_eq!(
+ under.get(spec.name).map(String::as_str),
+ Some(spec.category),
+ "{} is filed under the wrong header",
+ spec.name
+ );
+ }
+ }
+
/// The install's own `Bindings.xml`, parsed.
fn install_bindings() -> Option> {
let data = benilla_formats::wow_data_or_skip!(None);
diff --git a/crates/benilla-app/src/capture/fixtures.rs b/crates/benilla-app/src/capture/fixtures.rs
index 88ef42a1..36f377d1 100644
--- a/crates/benilla-app/src/capture/fixtures.rs
+++ b/crates/benilla-app/src/capture/fixtures.rs
@@ -1036,7 +1036,7 @@ pub(super) fn seed_ui_fixture(
if let Err(e) = script.run(
"ShowUIPanel(BenillaOptionsFrame); \
BenillaOptionsFrameCategoryListRowKeybindings:Click(); \
- KeyBindings_ExpandSection(1, true); KeyBindingsPage_Update()",
+ BenillaKeyBindings_ExpandSection(1, true); BenillaKeyBindingsPage_Update()",
) {
warn!("capture: ui-keybindings seed failed: {e}");
}
diff --git a/crates/benilla-app/src/ui_chat/commands.rs b/crates/benilla-app/src/ui_chat/commands.rs
index 48fa6fdb..8f187250 100644
--- a/crates/benilla-app/src/ui_chat/commands.rs
+++ b/crates/benilla-app/src/ui_chat/commands.rs
@@ -2,7 +2,9 @@
//! player's own strings define, read off the UI VM's globals the way `ChatEdit_ParseText` walks
//! them (`ChatFrame.lua:2164-2200`): `SLASH_` for the actions, `EMOTE_CMD` →
//! `EMOTE_TOKEN` for the emotes. An action wins over an emote alias of the same name, as the
-//! reference tries `SlashCmdList` first; benilla's own commands, literals, go in after both.
+//! reference tries `SlashCmdList` first; benilla's dev instruments, literals, go in after both.
+//! benilla's player commands (`/reload`, `/errors`) are its layer's `SlashCmdList` rows, which a
+//! line this table does not know reaches through the drain's unknown-command arm.
use std::collections::HashMap;
@@ -50,16 +52,6 @@ pub(crate) enum SlashIndex {
MacroHelp,
/// `/console `: `ConsoleExec(msg)`, as the reference's handler (`ChatFrame.lua:671`).
Console,
- /// `/reload`, the rebuild `/console reloadUI` runs. Deviation: 1.12 has no such alias (later
- /// clients added it); kept so a player can reload the interface after toggling addons.
- ReloadUi,
- /// `/errors`, `/err`: opens benilla's script error log. Deviation: 1.12 has only the
- /// `ScriptErrors` modal, which shows a burst's first error and keeps none; addon users need
- /// the list, so it is in every build.
- ScriptErrors,
- /// `/convertraid`: convert the party to a raid (`CMSG_GROUP_RAID_CONVERT`, leader only).
- /// Deviation: 1.12's only trigger is the Raid tab's Convert button (`RaidFrame.xml`).
- ConvertRaid,
}
impl SlashIndex {
@@ -101,15 +93,11 @@ impl SlashIndex {
Self::MacroUi => "MACRO",
Self::MacroHelp => "MACROHELP",
Self::Console => "CONSOLE",
- // No shipped strings under these three keys: their aliases are literals in `build`.
- Self::ReloadUi => "RELOADUI",
- Self::ScriptErrors => "BENILLASCRIPTERRORS",
- Self::ConvertRaid => "BENILLACONVERTRAID",
}
}
/// Every registered index; any other command answers `HELP_TEXT_SIMPLE`, as unknown ones do.
- const ALL: [Self; 38] = [
+ const ALL: [Self; 35] = [
Self::Reply,
Self::Join,
Self::Leave,
@@ -145,9 +133,6 @@ impl SlashIndex {
Self::MacroUi,
Self::MacroHelp,
Self::Console,
- Self::ReloadUi,
- Self::ScriptErrors,
- Self::ConvertRaid,
];
}
@@ -199,8 +184,8 @@ pub(crate) enum Command {
#[derive(Resource, Default)]
pub(crate) struct SlashCommands {
by_alias: HashMap,
- /// Aliases per source, logged at boot: `(slash, emote, benilla additions, dev instruments)`.
- counts: (usize, usize, usize, usize),
+ /// Aliases per source, logged at boot: `(slash, emote, dev instruments)`.
+ counts: (usize, usize, usize),
}
impl SlashCommands {
@@ -246,27 +231,7 @@ impl SlashCommands {
}
let emote_aliases = by_alias.len() - slash_aliases;
- // 3 · benilla's player-facing additions, in every build; after the walks, so shipped wins.
- insert(
- &mut by_alias,
- "reload",
- Command::Slash(SlashIndex::ReloadUi),
- );
- for alias in ["errors", "err"] {
- insert(
- &mut by_alias,
- alias,
- Command::Slash(SlashIndex::ScriptErrors),
- );
- }
- insert(
- &mut by_alias,
- "convertraid",
- Command::Slash(SlashIndex::ConvertRaid),
- );
- let added_aliases = by_alias.len() - slash_aliases - emote_aliases;
-
- // 4 · the dev instruments, dev builds only; a player build answers them as unknown.
+ // 3 · the dev instruments, dev builds only; a player build answers them as unknown.
let before_dev = by_alias.len();
if crate::run_mode::dev_affordances() {
for dev in DevCmd::ALL {
@@ -279,13 +244,13 @@ impl SlashCommands {
Self {
by_alias,
- counts: (slash_aliases, emote_aliases, added_aliases, dev_aliases),
+ counts: (slash_aliases, emote_aliases, dev_aliases),
}
}
/// Distinct aliases per source (the shipped strings repeat: `EMOTE87_CMD1` and `_CMD2` are
/// both `"/sit"`); a player build reports 0 dev aliases.
- pub(super) fn counts(&self) -> (usize, usize, usize, usize) {
+ pub(super) fn counts(&self) -> (usize, usize, usize) {
self.counts
}
}
@@ -298,6 +263,23 @@ fn insert(map: &mut HashMap, alias: &str, cmd: Command) {
}
}
+/// The dev instruments as `SlashCmdList` rows (`BENILLA_`), so a typed one reaches the
+/// drain through the stock `ChatEdit_ParseText`; registered on a freshly loaded VM in a build with
+/// dev affordances only, so a player build answers them `HELP_TEXT_SIMPLE`, as 1.12 does.
+pub(crate) fn register_dev_commands(script: &benilla_ui::script::UiScript, dev: bool) {
+ if !dev {
+ return;
+ }
+ for cmd in DevCmd::ALL {
+ let aliases: Vec = cmd.aliases().iter().map(|a| format!("/{a}")).collect();
+ let aliases: Vec<&str> = aliases.iter().map(String::as_str).collect();
+ let key = format!("BENILLA_{}", cmd.aliases()[0].to_ascii_uppercase());
+ if let Err(e) = script.register_host_slash_command(&key, &aliases) {
+ warn!("chat: registering /{}: {e}", cmd.aliases()[0]);
+ }
+ }
+}
+
/// Build the table in `PostStartup`, after `Startup` has inserted the VM and the emote catalog.
pub(crate) fn build_slash_commands(
mut commands: Commands,
@@ -314,11 +296,11 @@ pub(crate) fn build_slash_commands(
|name| globals.get::(name).ok().filter(|s| !s.is_empty()),
|token| emotes.text_id(token),
);
- let (slash, emote, added, dev) = table.counts();
+ let (slash, emote, dev) = table.counts();
// The shipped 1.12 data gives the counts `real_alias_table_resolves_the_shipped_commands` pins.
info!(
"chat: slash table — {slash} command aliases, {emote} emote aliases, \
- {added} benilla additions, {dev} instrument aliases"
+ {dev} instrument aliases"
);
if emote == 0 {
error!("chat: NO emote aliases — every /wave-style command is dead (GlobalStrings?)");
diff --git a/crates/benilla-app/src/ui_chat/input/mod.rs b/crates/benilla-app/src/ui_chat/input/mod.rs
index 418eca08..8d26b348 100644
--- a/crates/benilla-app/src/ui_chat/input/mod.rs
+++ b/crates/benilla-app/src/ui_chat/input/mod.rs
@@ -169,8 +169,8 @@ pub(super) fn drain_chat_input(
return;
};
let mut queue = engine_verbs(&mut script, emotes.as_deref());
- // benilla's own commands, from the `SlashCmdList` entries in `ScriptLogFrame.xml` once the
- // stock `ChatEdit_ParseText` has found no built-in, and probe lines.
+ // The dev instruments, from their host `SlashCmdList` rows once the stock `ChatEdit_ParseText`
+ // has found no built-in (dev builds), and probe lines.
for raw in script.take_chat_input() {
let msg = raw.trim();
if msg.is_empty() {
@@ -537,11 +537,6 @@ pub(super) fn drain_chat_input(
let _ = commands.0.send(ClientCommand::GroupInvite { name });
}
}
- // The server judges it: a raid-typed `SMSG_GROUP_LIST`, or an
- // `SMSG_PARTY_COMMAND_RESULT` error.
- ParsedChat::ConvertRaid => {
- let _ = commands.0.send(ClientCommand::GroupRaidConvert);
- }
ParsedChat::Uninvite { name } => {
if let Some(name) =
name.or_else(|| target_player_name(&selection, &names, &commands))
@@ -800,10 +795,6 @@ pub(super) fn drain_chat_input(
ParsedChat::Quit => {
script.queue_session_request(benilla_ui::script::SessionRequest::Quit)
}
- // The deferred rebuild `ReloadUI()` queues.
- ParsedChat::ReloadUi => {
- script.queue_session_request(benilla_ui::script::SessionRequest::ReloadUi)
- }
// Deferred to the world, since a command is `fn(&mut World, &str)`; its lines print
// as system text.
ParsedChat::Console { line } => {
diff --git a/crates/benilla-app/src/ui_chat/input/parse.rs b/crates/benilla-app/src/ui_chat/input/parse.rs
index f000aa5a..59928bb1 100644
--- a/crates/benilla-app/src/ui_chat/input/parse.rs
+++ b/crates/benilla-app/src/ui_chat/input/parse.rs
@@ -1,8 +1,8 @@
//! What a submitted slash line means: [`parse_line`] resolves it through the boot-built command
//! table ([`super::super::commands`], the shipped `GlobalStrings.lua` aliases) into a
-//! [`ParsedChat`] for [`super`] to execute. A typed line reaches it only through benilla's own
-//! `SlashCmdList` entries, once the stock `ChatEdit_ParseText` finds no built-in; a probe's line
-//! reaches it directly.
+//! [`ParsedChat`] for [`super`] to execute. A typed line reaches it only through the dev
+//! instruments' `SlashCmdList` rows (dev builds), once the stock `ChatEdit_ParseText` finds no
+//! built-in; a probe's line reaches it directly.
use crate::ui_chat::commands::{Command, DevCmd, SlashCommands, SlashIndex};
@@ -49,9 +49,6 @@ pub(in crate::ui_chat) enum ParsedChat {
Liquid,
/// `/reaction [name]`: every input and rung of the reaction ladder for one unit.
Reaction { name: Option },
- /// `/convertraid` (`CMSG_GROUP_RAID_CONVERT`), benilla's own: 1.12 converts only from the
- /// RaidFrame's Convert button.
- ConvertRaid,
/// `/help`.
Help,
/// An `EmotesText` command (`/wave` is 101), sent as `CMSG_TEXT_EMOTE` at the selection.
@@ -102,8 +99,6 @@ pub(in crate::ui_chat) enum ParsedChat {
Follow { name: Option },
/// `/macrohelp`: `ChatFrame_DisplayMacroHelpText`'s five `MACRO_HELP_TEXT_LINE`s.
MacroHelp,
- /// `/reload`, benilla's own: the in-world rebuild `ReloadUI()` queues.
- ReloadUi,
/// A `/console` line the CVar store did not consume, for the `ConsoleCommand` registry
/// ([`crate::console::execute`]).
Console { line: String },
@@ -288,18 +283,6 @@ fn slash_command(index: SlashIndex, args: &str) -> ParsedChat {
body: "ShowMacroFrame()".into(),
},
S::MacroHelp => ParsedChat::MacroHelp,
- // `/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 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") {
- crate::ui_script::ERRORS_CLEAR.into()
- } else {
- crate::ui_script::ERRORS_TOGGLE.into()
- },
- },
// `SlashCmdList["CONSOLE"]` (`ChatFrame.lua:671`) is `ConsoleExec(msg)`. A typed line runs
// it; one that skipped the edit box calls the same verb, so a CVar line writes the CVar
// and anything else comes back through `engine_verbs`.
diff --git a/crates/benilla-app/src/ui_chat/tests.rs b/crates/benilla-app/src/ui_chat/tests.rs
index 195755f6..7bb9ecca 100644
--- a/crates/benilla-app/src/ui_chat/tests.rs
+++ b/crates/benilla-app/src/ui_chat/tests.rs
@@ -1437,7 +1437,11 @@ fn real_alias_table_resolves_the_shipped_commands() {
);
}
assert_eq!(parse_line("/macrohelp"), ParsedChat::MacroHelp);
- assert_eq!(parse_line("/convertraid"), ParsedChat::ConvertRaid);
+ // 1.12 has none of these: `/convertraid` (the Raid tab's button converts), and benilla's
+ // `/reload` and `/errors`, which are its layer's `SlashCmdList` rows, not this table's.
+ for line in ["/convertraid", "/reload", "/errors", "/err"] {
+ assert_eq!(parse_line(line), ParsedChat::Unknown, "{line}");
+ }
// `/console` from a line that skipped the stock edit box forwards to the stock handler's verb.
assert_eq!(
parse_line("/console fpsJournal 1"),
@@ -1476,9 +1480,8 @@ fn real_alias_table_resolves_the_shipped_commands() {
}
}
// The shipped surface: 68 distinct aliases over 36 `SlashCmdList` indices and 225 emote
- // commands over 169 `EmotesText` names (aliases repeat; EMOTE27 "UNUSED" has no row). Then
- // benilla's own `/reload`, `/errors`, `/err` and `/convertraid`, which are not 1.12 commands,
- // in every build, and 7 instrument aliases, in dev builds only.
+ // commands over 169 `EmotesText` names (aliases repeat; EMOTE27 "UNUSED" has no row), then 7
+ // instrument aliases, in dev builds only.
let instruments = if crate::run_mode::dev_affordances() {
7
} else {
@@ -1486,8 +1489,8 @@ fn real_alias_table_resolves_the_shipped_commands() {
};
assert_eq!(
table.counts(),
- (68, 225, 4, instruments),
- "(slash, emote, benilla addition, instrument) aliases"
+ (68, 225, instruments),
+ "(slash, emote, instrument) aliases"
);
}
diff --git a/crates/benilla-app/src/ui_script/game_menu_tests.rs b/crates/benilla-app/src/ui_script/game_menu_tests.rs
index cf5b07aa..e3a3e243 100644
--- a/crates/benilla-app/src/ui_script/game_menu_tests.rs
+++ b/crates/benilla-app/src/ui_script/game_menu_tests.rs
@@ -719,8 +719,8 @@ fn backpack() -> ContainerState {
}
}
-/// The menu wears the options window's `ERA_WINDOW_SCALE` on show, as the era client draws the two
-/// at one density; loaded with OptionsFrame.xml, which defines it.
+/// The menu wears the options window's `BENILLA_ERA_WINDOW_SCALE` on show, as the era client draws
+/// the two at one density; loaded with OptionsFrame.xml, which defines it.
#[test]
fn the_menu_rides_the_shared_era_window_scale() {
let _data = benilla_formats::wow_data_or_skip!();
@@ -737,7 +737,7 @@ fn the_menu_rides_the_shared_era_window_scale() {
s.resolve();
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
let k = s.eval::("return GameMenuFrame:GetScale()").unwrap();
- let want = s.eval::("return ERA_WINDOW_SCALE").unwrap();
+ let want = s.eval::("return BENILLA_ERA_WINDOW_SCALE").unwrap();
assert!((k - want).abs() < 1e-6, "menu scale {k} != knob {want}");
assert!((want - 0.78).abs() < 1e-6, "the knob itself moved: {want}");
}
diff --git a/crates/benilla-app/src/ui_script/input.rs b/crates/benilla-app/src/ui_script/input.rs
index c2f6897e..55a88a6b 100644
--- a/crates/benilla-app/src/ui_script/input.rs
+++ b/crates/benilla-app/src/ui_script/input.rs
@@ -47,6 +47,18 @@ impl PointerFeed<'_> {
}
}
+/// The buttons the pointer feed hands the UI, by the names its handlers read (`arg1` of `OnClick`,
+/// `OnMouseDown` and `OnMouseUp`, and the ones `RegisterForClicks` takes,
+/// `Blizzard_BindingUI.xml:10`); buttons 4 and 5 on the physical buttons the binding chords call
+/// BUTTON4 and BUTTON5 (`bindings::chord`).
+const UI_MOUSE_BUTTONS: [(MouseButton, &str); 5] = [
+ (MouseButton::Left, "LeftButton"),
+ (MouseButton::Right, "RightButton"),
+ (MouseButton::Middle, "MiddleButton"),
+ (MouseButton::Forward, "Button4"),
+ (MouseButton::Back, "Button5"),
+];
+
/// One `OnMouseWheel` call per whole notch, `arg1 = ±1`, the fraction carried in `notches`: a
/// trackpad gesture arrives as a `Pixel` trickle, and the stock handlers act on the sign alone
/// (`ScrollFrameTemplate_OnMouseWheel` scrolls half a pane per call, `UIPanelTemplates.lua:150`).
@@ -162,11 +174,7 @@ pub(super) fn feed_ui_input(
);
}
}
- for (btn, name) in [
- (MouseButton::Left, "LeftButton"),
- (MouseButton::Right, "RightButton"),
- (MouseButton::Middle, "MiddleButton"),
- ] {
+ for (btn, name) in UI_MOUSE_BUTTONS {
if buttons.just_pressed(btn) {
// A left press that would drop into the world (`world_drop_click`: any payload over
// terrain or nothing) is consumed now: the drop fires on the release, but the world
@@ -390,6 +398,32 @@ mod tests {
assert!(sum <= 1.0, "…and they may move at most one notch: {sum}");
}
+ /// Every button 1.12's frames take reaches the UI, under the name its binding chord agrees
+ /// with, so the Key Bindings page binds mouse 4 and 5 from a click.
+ #[test]
+ fn the_ui_takes_all_five_buttons_under_the_chords_names() {
+ let names: Vec<&str> = UI_MOUSE_BUTTONS.iter().map(|(_, n)| *n).collect();
+ assert_eq!(
+ names,
+ [
+ "LeftButton",
+ "RightButton",
+ "MiddleButton",
+ "Button4",
+ "Button5"
+ ]
+ );
+ for (i, (button, _)) in UI_MOUSE_BUTTONS.iter().enumerate() {
+ let chord = crate::bindings::chord::Chord::parse(&format!("BUTTON{}", i + 1));
+ assert_eq!(
+ chord.map(|c| c.key),
+ Some(crate::bindings::chord::BindKey::Mouse(*button)),
+ "BUTTON{} is {button:?}",
+ i + 1
+ );
+ }
+ }
+
#[test]
fn a_line_notch_fires_once() {
let mut s = wheel_pane();
diff --git a/crates/benilla-app/src/ui_script/keybindings_tests.rs b/crates/benilla-app/src/ui_script/keybindings_tests.rs
index f1f44410..46ead745 100644
--- a/crates/benilla-app/src/ui_script/keybindings_tests.rs
+++ b/crates/benilla-app/src/ui_script/keybindings_tests.rs
@@ -11,17 +11,7 @@ use crate::bindings::commands::SPECS;
/// does.
pub(crate) fn harness() -> UiScript {
let mut s = UiScript::new().unwrap();
- let cmds: Vec = SPECS
- .iter()
- .map(|spec| KeybindCommand {
- name: spec.name,
- category: spec.category,
- run_on_up: spec.run_on_up(),
- default1: spec.d1,
- default2: spec.d2,
- })
- .collect();
- s.register_bindings(&cmds);
+ register_specs(&mut s);
s.set_screen_size(1024.0, 768.0);
for file in [
"Interface\\FrameXML\\GlobalStrings.lua",
@@ -54,10 +44,27 @@ pub(crate) fn harness() -> UiScript {
s
}
+/// The host's command registry, as `seed_bindings_for_vm` registers it.
+pub(crate) fn register_specs(s: &mut UiScript) {
+ let cmds: Vec = SPECS
+ .iter()
+ .map(|spec| KeybindCommand {
+ name: spec.name,
+ category: spec.category,
+ run_on_up: spec.run_on_up(),
+ default1: spec.d1,
+ default2: spec.d2,
+ })
+ .collect();
+ s.register_bindings(&cmds);
+}
+
/// A label as the page resolves it: the GlobalStrings global named `token`, else `raw`.
pub(crate) fn label(s: &UiScript, token: &str, raw: &str) -> String {
- s.eval::(&format!(r#"return KeyBindings_String("{token}", "{raw}")"#))
- .unwrap()
+ s.eval::(&format!(
+ r#"return BenillaKeyBindings_String("{token}", "{raw}")"#
+ ))
+ .unwrap()
}
/// Open the options window on the Keybindings page.
@@ -69,6 +76,53 @@ pub(crate) fn on_page(s: &mut UiScript) {
const ROW: &str = "BenillaOptionsFrameContainerBodyKeybindingsRow";
+/// Whether the window takes keys and the wheel: a selected capsule arms it, as 1.12's
+/// keyboard-enabled `KeyBindingFrame` (`Blizzard_BindingUI.xml:73`).
+pub(crate) fn armed(s: &UiScript) -> bool {
+ s.eval::(
+ "return BenillaOptionsFrame:IsKeyboardEnabled() == 1 \
+ and BenillaOptionsFrame:IsMouseWheelEnabled() == 1 \
+ and BenillaKeyBindingsPage.selected ~= nil",
+ )
+ .unwrap()
+}
+
+/// A key press as the host feeds it (`ui_script::input`): ENTER, ESCAPE and TAB through the named
+/// walk, every other key through the frame walk by its 1.12 name. Answers whether a frame took it.
+pub(crate) fn press(s: &mut UiScript, key: &str) -> bool {
+ match key {
+ "ENTER" | "ESCAPE" | "TAB" => s.key_input(key),
+ _ => s.frame_key_input(key),
+ }
+}
+
+/// A frame's centre in screen pixels, where the pointer feed hits it.
+pub(crate) fn centre(s: &mut UiScript, frame: &str) -> (f32, f32) {
+ s.resolve();
+ let (l, r, t, b, k): (f64, f64, f64, f64, f64) = s
+ .eval(&format!(
+ "local f = {frame} \
+ local k = (f.GetEffectiveScale and f or f:GetParent()):GetEffectiveScale() \
+ return f:GetLeft(), f:GetRight(), f:GetTop(), f:GetBottom(), k"
+ ))
+ .unwrap();
+ (((l + r) * 0.5 * k) as f32, ((t + b) * 0.5 * k) as f32)
+}
+
+/// A click of `button` on `frame` through the pointer feed.
+pub(crate) fn click(s: &mut UiScript, frame: &str, button: &str) {
+ let (x, y) = centre(s, frame);
+ s.mouse_move(x, y);
+ s.mouse_button(x, y, button, true);
+ s.mouse_button(x, y, button, false);
+}
+
+/// One wheel notch over the page, through the pointer feed.
+pub(crate) fn wheel(s: &mut UiScript, delta: f32) {
+ let (x, y) = centre(s, "BenillaOptionsFrameContainerBodyKeybindings");
+ s.mouse_wheel(x, y, delta);
+}
+
#[test]
fn the_page_is_an_options_category_with_the_collapsed_honest_tree() {
benilla_formats::wow_data_or_skip!();
@@ -90,10 +144,12 @@ fn the_page_is_an_options_category_with_the_collapsed_honest_tree() {
assert!(s
.eval::("return BenillaOptionsFrameContainerDefaults:IsEnabled() ~= 0")
.unwrap());
- // The categories in `Bindings.xml` order, each a header collapsed by default, as in the era.
+ // The categories in `Bindings.xml` order, each a header collapsed by default, as in the era;
+ // the spacer headers over multibars 2-4 continue the section above (the page's deviation).
let mut expected: Vec<&str> = Vec::new();
for spec in SPECS {
- if !expected.contains(&spec.category) {
+ if !expected.contains(&spec.category) && !spec.category.starts_with("BINDING_HEADER_BLANK")
+ {
expected.push(spec.category);
}
}
@@ -125,7 +181,7 @@ fn the_page_is_an_options_category_with_the_collapsed_honest_tree() {
r#"BINDING_HEADER_MOVEMENT = "Movement Keys"
BINDING_NAME_MOVEANDSTEER = "Move and Steer"
KEY_BUTTON3 = "Middle Mouse"
- KeyBindingsPage_Update()"#,
+ BenillaKeyBindingsPage_Update()"#,
)
.unwrap();
assert_eq!(
@@ -168,21 +224,23 @@ fn the_capture_flow_binds_steals_and_refuses_like_112() {
on_page(&mut s);
s.run(&format!("{ROW}1Header:Click()")).unwrap(); // expand Movement
s.take_keybind_requests();
- // Row 3 is MOVEFORWARD (W, UP); selecting its Key 1 capsule arms the capture.
- assert!(!s.bind_capture_armed());
+ // Unarmed, the window takes no key: it reaches the game's bindings.
+ assert!(!armed(&s));
+ assert!(!press(&mut s, "J"), "an unarmed page lets a key through");
+ // Row 3 is MOVEFORWARD (W, UP); selecting its Key 1 capsule arms the window.
s.run(&format!("{ROW}3Key1Button:Click()")).unwrap();
- assert!(
- s.bind_capture_armed(),
- "a selected capsule arms the capture"
- );
+ assert!(armed(&s), "a selected capsule arms the window");
assert!(
s.eval::("return BenillaOptionsFrameContainerUnbind:IsEnabled() ~= 0")
.unwrap(),
"Unbind arms with the selection"
);
+ // A lone modifier is no key (`Blizzard_BindingUI.lua:172-174`): still armed.
+ assert!(press(&mut s, "SHIFT"), "the armed window takes it");
+ assert!(armed(&s), "…and waits for a real key");
// J, a key no command holds, takes slot 1 from W; UP stays in slot 2 and the bind saves.
- s.run(r#"KeyBindings_OnHostKey("J")"#).unwrap();
- assert!(!s.bind_capture_armed(), "a completed bind disarms");
+ assert!(press(&mut s, "J"), "the armed window takes the key");
+ assert!(!armed(&s), "a completed bind disarms");
assert!(s
.eval::(
r#"local k1, k2 = GetBindingKey("MOVEFORWARD"); return k1 == "J" and k2 == "UP""#
@@ -202,7 +260,7 @@ fn the_capture_flow_binds_steals_and_refuses_like_112() {
// T is ATTACKTARGET's only key, so taking it names the victim in red (1.12's
// `KEY_UNBOUND_ERROR`, `Blizzard_BindingUI.lua:185-190`).
s.run(&format!("{ROW}3Key2Button:Click()")).unwrap();
- s.run(r#"KeyBindings_OnHostKey("T")"#).unwrap();
+ press(&mut s, "T");
assert_eq!(
s.eval::(r#"return GetBindingAction("T")"#).unwrap(),
"MOVEFORWARD"
@@ -214,9 +272,28 @@ fn the_capture_flow_binds_steals_and_refuses_like_112() {
.contains(&victim),
"the newly-bare victim is named"
);
+ // A chord: the modifiers held at the press prefix the key, ALT-CTRL-SHIFT order
+ // (`Blizzard_BindingUI.lua:175-183`).
+ s.run(&format!("{ROW}3Key2Button:Click()")).unwrap();
+ s.set_modifiers(true, true, false);
+ press(&mut s, "K");
+ s.set_modifiers(false, false, false);
+ assert_eq!(
+ s.eval::(r#"return GetBindingAction("CTRL-SHIFT-K")"#)
+ .unwrap(),
+ "MOVEFORWARD"
+ );
+ // ESC while armed binds Escape: 1.12's armed branch has no ESCAPE filter.
+ s.run(&format!("{ROW}3Key2Button:Click()")).unwrap();
+ assert!(press(&mut s, "ESCAPE"));
+ assert_eq!(
+ s.eval::(r#"return GetBindingAction("ESCAPE")"#)
+ .unwrap(),
+ "MOVEFORWARD"
+ );
// The wheel binds onto a press+release command too: `0x4b7490` never reads the command.
s.run(&format!("{ROW}3Key1Button:Click()")).unwrap();
- s.run(r#"KeyBindings_OnHostKey("MOUSEWHEELUP")"#).unwrap();
+ wheel(&mut s, 1.0);
assert!(
s.eval::(r#"local k1 = GetBindingKey("MOVEFORWARD"); return k1 == "MOUSEWHEELUP""#)
.unwrap(),
@@ -234,7 +311,7 @@ fn the_capture_flow_binds_steals_and_refuses_like_112() {
// A key string the engine rejects restores the slot's old key and shows the only refusal text
// 1.12 has, the wheel's (`KeyBindingFrame_SetBinding`, `Blizzard_BindingUI.lua:260-270`).
s.run(&format!("{ROW}3Key1Button:Click()")).unwrap();
- s.run(r#"KeyBindings_OnHostKey("SCROLLLOCK")"#).unwrap();
+ press(&mut s, "SCROLLLOCK");
assert!(
s.eval::(r#"local k1 = GetBindingKey("MOVEFORWARD"); return k1 == "MOUSEWHEELUP""#)
.unwrap(),
@@ -246,15 +323,130 @@ fn the_capture_flow_binds_steals_and_refuses_like_112() {
"Can't bind mousewheel to actions with up and down states"
);
s.run(&format!("{ROW}3Key1Button:Click()")).unwrap();
- assert!(s.bind_capture_armed());
+ assert!(armed(&s));
s.run(&format!(r#"{ROW}3Key1Button:Click("RightButton")"#))
.unwrap();
- assert!(!s.bind_capture_armed(), "right-click deselects");
- // Hiding the window disarms: an armed capture with no window would swallow all input.
+ assert!(!armed(&s), "right-click deselects");
+ // Hiding the window disarms.
s.run(&format!("{ROW}3Key1Button:Click()")).unwrap();
- assert!(s.bind_capture_armed());
+ assert!(armed(&s));
s.run("HideUIPanel(BenillaOptionsFrame)").unwrap();
- assert!(!s.bind_capture_armed(), "OnHide disarms");
+ assert!(!armed(&s), "OnHide disarms");
+ assert!(s.errors().is_empty(), "{:?}", s.errors());
+}
+
+/// Mouse buttons 3-5 bind through the clicks 1.12's window takes: a capsule's own `OnClick`
+/// (`Blizzard_BindingUI.lua:246`) and the window's (`Blizzard_BindingUI.xml:538-540`), here the
+/// page's `OnMouseUp`; left and right clicks only select.
+#[test]
+fn mouse_buttons_bind_through_the_capsules_and_the_page() {
+ benilla_formats::wow_data_or_skip!();
+ let mut s = harness();
+ on_page(&mut s);
+ s.run(&format!("{ROW}1Header:Click()")).unwrap(); // expand Movement
+ let capsule = format!("{ROW}3Key1Button");
+ click(&mut s, &capsule, "LeftButton");
+ assert!(armed(&s), "a left click selects");
+ click(&mut s, &capsule, "MiddleButton");
+ assert!(!armed(&s));
+ assert_eq!(
+ s.eval::(r#"return GetBindingAction("BUTTON3")"#)
+ .unwrap(),
+ "MOVEFORWARD",
+ "a middle click on the armed capsule binds BUTTON3"
+ );
+ // Over the page, away from any capsule: mouse 4 with SHIFT held.
+ click(&mut s, &capsule, "LeftButton");
+ s.set_modifiers(true, false, false);
+ click(&mut s, &format!("{ROW}3Description"), "Button4");
+ s.set_modifiers(false, false, false);
+ assert_eq!(
+ s.eval::(r#"return GetBindingAction("SHIFT-BUTTON4")"#)
+ .unwrap(),
+ "MOVEFORWARD"
+ );
+ // Unarmed, a middle click on a capsule selects it, as 1.12's does.
+ click(&mut s, &format!("{ROW}4Key2Button"), "MiddleButton");
+ assert!(armed(&s));
+ s.run("BenillaKeyBindings_SetSelected(nil)").unwrap();
+ assert!(s.errors().is_empty(), "{:?}", s.errors());
+}
+
+/// Loading the stock `Blizzard_BindingUI` (an addon manager's Load button does) defines its own
+/// `KeyBindingButton_OnClick` and popup (`Blizzard_BindingUI.lua:228`, `:10`) and changes no other
+/// window: the page keeps binding and leaving the character set. The stock window reads the 1.12
+/// list: a `HEADER_*` row draws a section header, a command's keys fill Key 1 and Key 2.
+#[test]
+fn the_page_works_after_the_stock_binding_ui_loads_and_the_stock_window_reads_the_list() {
+ benilla_formats::wow_data_or_skip!();
+ let _l = crate::local_state::test_env::ENV_LOCK
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner);
+ let (mut s, failures) =
+ super::layer_tests::production_load_with("bindingui", false, "", |_| {});
+ assert!(failures.is_empty(), "load failures: {failures:#?}");
+ register_specs(&mut s);
+ s.run(r#"assert(LoadAddOn("Blizzard_BindingUI") == 1)"#)
+ .unwrap();
+ assert!(s
+ .eval::("return KeyBindingFrame ~= nil and KeyBindingButton_OnClick ~= nil")
+ .unwrap());
+
+ on_page(&mut s);
+ s.run(&format!("{ROW}1Header:Click()")).unwrap(); // expand Movement
+ s.take_keybind_requests();
+ click(&mut s, &format!("{ROW}3Key1Button"), "LeftButton");
+ assert!(armed(&s), "the page's capsule still arms its own window");
+ assert!(
+ s.eval::("return KeyBindingFrame.selected == nil")
+ .unwrap(),
+ "the stock window is untouched"
+ );
+ press(&mut s, "J");
+ assert_eq!(
+ s.eval::(r#"return GetBindingAction("J")"#).unwrap(),
+ "MOVEFORWARD"
+ );
+ assert_eq!(s.take_keybind_requests(), vec![KeybindRequest::Save(1)]);
+ // The character set, both ways, through the page's own popup.
+ let check = "BenillaOptionsFrameContainerBodyKeybindingsCharacterRowCheck";
+ s.run(&format!("{check}:Click()")).unwrap();
+ assert_eq!(s.current_binding_set(), 2);
+ s.run(&format!("{check}:Click()")).unwrap();
+ s.run("StaticPopup1Button1:Click()").unwrap();
+ assert_eq!(
+ s.current_binding_set(),
+ 1,
+ "leaving the character set works"
+ );
+ assert!(s.errors().is_empty(), "{:?}", s.errors());
+ s.run("HideUIPanel(BenillaOptionsFrame)").unwrap();
+
+ // The stock window over the same list.
+ s.run("ShowUIPanel(KeyBindingFrame)").unwrap();
+ assert!(s
+ .eval::("return KeyBindingFrame:IsVisible()")
+ .unwrap());
+ assert!(s
+ .eval::(
+ "return KeyBindingFrameBinding1Header:IsVisible() \
+ and KeyBindingFrameBinding1Header:GetText() == BINDING_HEADER_MOVEMENT"
+ )
+ .unwrap());
+ assert_eq!(
+ s.eval::("return KeyBindingFrameBinding2Key1Button:GetText()")
+ .unwrap(),
+ s.eval::(r#"return GetBindingText("BUTTON3", "KEY_")"#)
+ .unwrap(),
+ "Move and Steer's first key in the Key 1 column"
+ );
+ assert_eq!(
+ s.eval::("return KeyBindingFrameBinding3Key2Button:GetText()")
+ .unwrap(),
+ s.eval::(r#"return GetBindingText("UP", "KEY_")"#)
+ .unwrap(),
+ "Move Forward's second key in the Key 2 column"
+ );
assert!(s.errors().is_empty(), "{:?}", s.errors());
}
@@ -283,7 +475,7 @@ fn unbind_reset_and_the_live_commit_replace_okay_cancel() {
// Deviation: a bind saves at once, as the era panel commits, where 1.12 had Okay and Cancel;
// closing the window keeps it.
s.run(&format!("{ROW}9Key1Button:Click()")).unwrap();
- s.run(r#"KeyBindings_OnHostKey("G")"#).unwrap();
+ press(&mut s, "G");
assert_eq!(s.take_keybind_requests(), vec![KeybindRequest::Save(1)]);
s.run("BenillaOptionsFrameCloseButton:Click()").unwrap();
assert_eq!(
@@ -384,8 +576,8 @@ fn search_surfaces_bindings_as_live_rows_under_the_redirect_head() {
.unwrap());
s.run("BenillaOptionsFrameContainerBodyKeybindSearch1Key1Button:Click()")
.unwrap();
- assert!(s.bind_capture_armed());
- s.run(r#"KeyBindings_OnHostKey("H")"#).unwrap();
+ assert!(armed(&s));
+ press(&mut s, "H");
assert_eq!(
s.eval::(r#"return GetBindingAction("H")"#).unwrap(),
"JUMP"
@@ -474,8 +666,8 @@ fn the_wheel_bubbles_from_the_rows_and_the_bar_rides_the_gutter() {
on_page(&mut s);
// Every section open: over 100 rows overflow the list's 19 slots.
s.run(
- r#"for i = 1, table.getn(KeyBindingsPage.sections) do KeyBindings_ExpandSection(i, true) end
- KeyBindingsPage_Update()"#,
+ r#"for i = 1, table.getn(BenillaKeyBindingsPage.sections) do BenillaKeyBindings_ExpandSection(i, true) end
+ BenillaKeyBindingsPage_Update()"#,
)
.unwrap();
const SF: &str = "BenillaOptionsFrameContainerBodyKeybindingsScrollFrame";
@@ -536,8 +728,8 @@ fn the_wheel_bubbles_from_the_rows_and_the_bar_rides_the_gutter() {
);
let set_all = |s: &UiScript, open: bool| {
s.run(&format!(
- "for i = 1, table.getn(KeyBindingsPage.sections) do KeyBindings_ExpandSection(i, {}) end
- KeyBindingsPage_Update()",
+ "for i = 1, table.getn(BenillaKeyBindingsPage.sections) do BenillaKeyBindings_ExpandSection(i, {}) end
+ BenillaKeyBindingsPage_Update()",
if open { "true" } else { "nil" }
))
.unwrap();
@@ -603,9 +795,11 @@ fn the_wheel_bubbles_from_the_rows_and_the_bar_rides_the_gutter() {
after_capsule > after_name,
"a spin over a CAPSULE bubbles to the page too: {after_name} -> {after_capsule}"
);
- // While a capsule is armed a spin binds, never scrolls; the page's guard catches a spin the
- // host lets through.
+ // While a capsule is armed a spin binds, never scrolls.
s.run(&format!("{ROW}2Key1Button:Click()")).unwrap();
+ let armed_command = s
+ .eval::(&format!("return {ROW}2Key1Button.commandName"))
+ .unwrap();
s.mouse_wheel(wx, wy, -1.0);
assert_eq!(
s.eval::(&format!("return FauxScrollFrame_GetOffset({SF})"))
@@ -613,7 +807,12 @@ fn the_wheel_bubbles_from_the_rows_and_the_bar_rides_the_gutter() {
after_capsule,
"armed: the wheel must not scroll"
);
- s.run("KeyBindings_SetSelected(nil)").unwrap();
+ assert_eq!(
+ s.eval::(r#"return GetBindingAction("MOUSEWHEELDOWN")"#)
+ .unwrap(),
+ armed_command,
+ "armed: the spin binds"
+ );
assert!(s.errors().is_empty(), "{:?}", s.errors());
}
@@ -626,11 +825,14 @@ fn the_pet_lane_is_registered_under_the_action_bar_header() {
let mut s = harness();
on_page(&mut s);
+ // The header row a command sits under: the last `HEADER_*` row above it.
let category = |s: &mut UiScript, name: &str| {
s.eval::(&format!(
- r#"for i = 1, GetNumBindings() do
- local n, c = GetBinding(i)
- if n == "{name}" then return c end
+ r#"local header
+ for i = 1, GetNumBindings() do
+ local n = GetBinding(i)
+ if strsub(n, 1, 6) == "HEADER" then header = n end
+ if n == "{name}" then return header end
end"#
))
.unwrap()
@@ -639,7 +841,7 @@ fn the_pet_lane_is_registered_under_the_action_bar_header() {
let name = format!("BONUSACTIONBUTTON{i}");
assert_eq!(
category(&mut s, &name),
- "BINDING_HEADER_ACTIONBAR",
+ "HEADER_ACTIONBAR",
"{name} files under the action bar, as 1.12 does"
);
let key = s
diff --git a/crates/benilla-app/src/ui_script/layer_tests.rs b/crates/benilla-app/src/ui_script/layer_tests.rs
index f0576e65..f50e2be9 100644
--- a/crates/benilla-app/src/ui_script/layer_tests.rs
+++ b/crates/benilla-app/src/ui_script/layer_tests.rs
@@ -236,8 +236,8 @@ fn the_stock_ui_switch_is_a_dev_builds_alone() {
assert!(layer_enabled_by(true, None) && layer_enabled_by(true, Some("0")));
}
-/// Booted without the layer, as `WOW_STOCK_UI=1` does: no layer file loads, the stock files raise
-/// nothing, and the host's calls into the layer are no-ops.
+/// Booted without the layer, as `WOW_STOCK_UI=1` does: no layer file loads and the stock files
+/// raise nothing.
#[test]
fn the_stock_ui_switch_boots_the_core_without_the_layer() {
benilla_formats::wow_data_or_skip!();
@@ -250,7 +250,7 @@ fn the_stock_ui_switch_boots_the_core_without_the_layer() {
// One global from each layer file that defines a new one.
for name in [
"BenillaScrollBar_Step",
- "KeyBindings_OnHostKey",
+ "BenillaKeyBindings_OnKeyDown",
"BenillaOptionsFrame_SelectCategory",
"BenillaGameMenuButtonEditMode",
"BENILLA_BAG_WAS_OPEN",
@@ -263,13 +263,233 @@ fn the_stock_ui_switch_boots_the_core_without_the_layer() {
}
assert!(!s.eval::("return BenillaOptionsFrame ~= nil").unwrap());
assert!(!s.eval::("return ZZORDER_SAW_LAYER").unwrap());
- // 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"),
- ] {
- s.run(&body).unwrap_or_else(|e| panic!("{body}: {e}"));
+ assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
+}
+
+/// Types `line` into the chat box and sends it through the stock `ChatEdit_ParseText`
+/// (`ChatFrame.lua:2087`), counting the `HELP_TEXT_SIMPLE` lines it prints.
+fn type_line(s: &UiScript, line: &str) -> i64 {
+ s.run(&format!(
+ r#"HELPS = 0
+ local box = ChatFrameEditBox
+ box.chatFrame = box.chatFrame or DEFAULT_CHAT_FRAME
+ local frame = box.chatFrame
+ local add = frame.AddMessage
+ frame.AddMessage = function(f, text, r, g, b, id)
+ if text == HELP_TEXT_SIMPLE then HELPS = HELPS + 1 end
+ return add(f, text, r, g, b, id)
+ end
+ box:SetText({line:?})
+ ChatEdit_ParseText(box, 1)
+ frame.AddMessage = add"#
+ ))
+ .unwrap_or_else(|e| panic!("{line}: {e}"));
+ s.eval("return HELPS").unwrap()
+}
+
+/// The layer's player commands are plain `SlashCmdList` rows: `/reload` runs `ReloadUI()`,
+/// `/errors` (`/err`) toggles the log and `/errors clear` empties it. 1.12 has no `/convertraid`
+/// (the Raid tab's button converts, `RaidFrame.lua:63-71`), so it answers `HELP_TEXT_SIMPLE`, and
+/// the forwarding hook `SubmitChatInput` is gone.
+#[test]
+fn the_layers_commands_are_slash_rows_and_convertraid_is_unknown() {
+ benilla_formats::wow_data_or_skip!();
+ let _l = ENV_LOCK
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner);
+ let (mut s, failures) = production_load("slash", false);
+ assert!(failures.is_empty(), "load failures: {failures:#?}");
+ assert!(s.eval::("return SubmitChatInput == nil").unwrap());
+
+ s.take_session_requests();
+ assert_eq!(type_line(&s, "/reload"), 0);
+ assert_eq!(
+ s.take_session_requests(),
+ [benilla_ui::script::SessionRequest::ReloadUi],
+ "/reload is ReloadUI()"
+ );
+ assert!(
+ s.take_chat_input().is_empty(),
+ "nothing is handed to the host"
+ );
+
+ assert_eq!(type_line(&s, "/errors"), 0);
+ assert!(s
+ .eval::("return BenillaScriptLogFrame:IsVisible()")
+ .unwrap());
+ assert_eq!(type_line(&s, "/err"), 0);
+ assert!(!s
+ .eval::("return BenillaScriptLogFrame:IsVisible()")
+ .unwrap());
+ s.run("BenillaScriptLog_Record('kept')").unwrap();
+ assert_eq!(type_line(&s, "/errors clear "), 0);
+ assert_eq!(
+ s.eval::("return table.getn(BenillaScriptLog.rows)")
+ .unwrap(),
+ 0,
+ "/errors clear empties the log"
+ );
+
+ assert_eq!(type_line(&s, "/convertraid"), 1, "the unknown-command line");
+ assert!(s.take_chat_input().is_empty());
+ assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
+}
+
+/// In a player build the dev instruments are no `SlashCmdList` row, so a typed one answers
+/// `HELP_TEXT_SIMPLE` once and hands the host nothing to re-run; in a dev build each is a host row
+/// that hands the host its line.
+#[test]
+fn the_dev_commands_are_host_rows_in_a_dev_build_alone() {
+ benilla_formats::wow_data_or_skip!();
+ let _l = ENV_LOCK
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner);
+ let lines = [
+ "/chattest",
+ "/shot",
+ "/liquid",
+ "/castvis",
+ "/partytest ping",
+ "/reaction",
+ "/react Probe",
+ ];
+
+ let (mut s, failures) = production_load("devcmds-player", false);
+ assert!(failures.is_empty(), "load failures: {failures:#?}");
+ crate::ui_chat::commands::register_dev_commands(&s, false);
+ for line in lines {
+ assert_eq!(
+ type_line(&s, line),
+ 1,
+ "{line}: the unknown-command line, once"
+ );
+ assert!(
+ s.take_chat_input().is_empty(),
+ "{line}: nothing queued to loop"
+ );
+ let cmd = line[1..].split(' ').next().unwrap();
+ assert!(!s.has_slash_command(cmd), "{cmd} is no SlashCmdList row");
+ }
+ drop(s);
+
+ let (mut s, failures) = production_load("devcmds-dev", false);
+ assert!(failures.is_empty(), "load failures: {failures:#?}");
+ crate::ui_chat::commands::register_dev_commands(&s, true);
+ for line in lines {
+ assert_eq!(type_line(&s, line), 0, "{line}");
+ let queued = s.take_chat_input();
+ assert_eq!(queued.len(), 1, "{line}: {queued:?}");
+ // An alias queues under the command's first one, as the row's handler knows only that.
+ let want = line.replacen("/react ", "/reaction ", 1);
+ assert_eq!(queued[0].trim(), want, "{line}");
}
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
+
+/// The `/errors` log collects off the Lua error handler, ahead of the stock `_ERRORMESSAGE`
+/// (`BasicControls.xml:16`), which still shows the `ScriptErrors` dialog: an addon's runtime error,
+/// an error in one of the layer's own handlers, and one raised in the stock load before the layer
+/// loaded (the engine hands caught errors to the handler after the load).
+#[test]
+fn the_error_log_collects_off_the_error_handler() {
+ benilla_formats::wow_data_or_skip!();
+ let _l = ENV_LOCK
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner);
+ let before = r#"
+ local pre = CreateFrame("Button", "ZZPreLayer")
+ pre:SetScript("OnClick", function() error("before the layer") end)
+ pre:Click()
+ "#;
+ let (mut s, failures) = production_load_with("errlog", false, before, |root| {
+ let dir = root.join("ZZErr");
+ std::fs::create_dir_all(&dir).unwrap();
+ std::fs::write(dir.join("ZZErr.toc"), "## Interface: 11200\nerr.lua\n").unwrap();
+ std::fs::write(
+ dir.join("err.lua"),
+ "ZZErrButton = CreateFrame('Button', 'ZZErrButton')\n\
+ ZZErrButton:SetScript('OnClick', function() error('an addon boom') end)\n",
+ )
+ .unwrap();
+ });
+ assert!(
+ failures.iter().all(|f| f.contains("before the layer")),
+ "load failures: {failures:#?}"
+ );
+ s.run("ZZErrButton:Click()").unwrap();
+ // A layer handler raising: the window's OnShow calls this.
+ s.run("BenillaOptionsFrame_UpdateScale = function() error('a layer boom') end")
+ .unwrap();
+ s.run("ShowUIPanel(BenillaOptionsFrame)").unwrap();
+ s.dispatch_script_errors_to_handler();
+
+ let rows: Vec = s
+ .eval(
+ "local out = {} \
+ for _, row in ipairs(BenillaScriptLog.rows) do table.insert(out, row.message) end \
+ return out",
+ )
+ .unwrap();
+ for want in ["before the layer", "an addon boom", "a layer boom"] {
+ assert!(
+ rows.iter().any(|r| r.contains(want)),
+ "{want:?} is in the log: {rows:#?}"
+ );
+ }
+ assert!(
+ s.eval::("return ScriptErrors:IsVisible()").unwrap(),
+ "the stock handler still runs after the log"
+ );
+ let shown: String = s.eval("return ScriptErrors_Message:GetText()").unwrap();
+ assert!(
+ shown.contains("before the layer"),
+ "the first error shows: {shown}"
+ );
+ // A repeat is one row with a count.
+ s.run("ZZErrButton:Click() ZZErrButton:Click()").unwrap();
+ s.dispatch_script_errors_to_handler();
+ assert_eq!(
+ s.eval::(
+ "for _, row in ipairs(BenillaScriptLog.rows) do \
+ if strfind(row.message, 'an addon boom', 1, 1) then return row.count end \
+ end"
+ )
+ .unwrap(),
+ 3
+ );
+}
+
+/// Every global the layer defines takes the `Benilla` prefix (a slash alias its `SLASH_BENILLA_`
+/// form, as `ChatEdit_ParseText` reads it), so no layer name meets a stock or an addon one; a stock
+/// global the layer redefines keeps its name and is not a new definition here.
+#[test]
+fn every_global_the_layer_defines_takes_the_benilla_prefix() {
+ benilla_formats::wow_data_or_skip!();
+ let _l = ENV_LOCK
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner);
+ let (s, failures) = production_load("prefix", false);
+ assert!(failures.is_empty(), "load failures: {failures:#?}");
+ let log: Vec = s.eval("return DEFINE_LOG").unwrap();
+ let start = defined_at(&log, "BenillaScrollBar_Step");
+ let end = defined_at(&log, "ZZORDER_SAW_LAYER");
+ let dispatch = |n: &str| {
+ n == "this"
+ || n == "event"
+ || n.strip_prefix("arg")
+ .is_some_and(|d| !d.is_empty() && d.bytes().all(|b| b.is_ascii_digit()))
+ };
+ let unprefixed: Vec<&String> = log[start..end]
+ .iter()
+ .filter(|n| {
+ !(n.starts_with("Benilla")
+ || n.starts_with("BENILLA_")
+ || n.starts_with("SLASH_BENILLA_"))
+ && !dispatch(n)
+ })
+ .collect();
+ assert!(
+ unprefixed.is_empty(),
+ "layer globals without the Benilla prefix: {unprefixed:?}"
+ );
+}
diff --git a/crates/benilla-app/src/ui_script/lifecycle.rs b/crates/benilla-app/src/ui_script/lifecycle.rs
index 31c48d8d..58e1841f 100644
--- a/crates/benilla-app/src/ui_script/lifecycle.rs
+++ b/crates/benilla-app/src/ui_script/lifecycle.rs
@@ -367,6 +367,7 @@ pub(crate) fn load_ingame_ui_on_world_entry(world: &mut World) {
// No sound during the load edge, as in the reference's `UI_Init` (`0x48fbfa` → `0x49016d`).
silenced_ui_load(&mut script, |script| {
let _ = load_ingame_ui(script, identity.as_ref(), &roster, version_check);
+ crate::ui_chat::commands::register_dev_commands(script, crate::run_mode::dev_affordances());
// The new Minimap's zoom indices from the persisted CVars, as the reference's minimap reset
// copies each CVar into its live index; `Minimap:SetZoom` owns them from here.
script.set_minimap_zoom(zoom.0, zoom.1);
@@ -385,25 +386,22 @@ pub(crate) fn load_ingame_ui_on_world_entry(world: &mut World) {
},
);
});
- // Deviation: failed addons are reported in chat, because otherwise an addon that does not
- // load says nothing on screen. Counted from the deduplicated diagnostics log as `/errors`
- // lists them, after the whole load edge (handlers included); the VM is fresh, so every `Load`
- // row is this load's.
- let failed = script
- .diagnostics()
- .iter()
- .filter(|d| d.kind == benilla_ui::script::diagnostics::DiagnosticKind::Load)
- .count();
- if failed > 0 {
- if let Some(mut chat) = world.get_resource_mut::() {
+ // Deviation: each failed addon file is reported in chat, one line apiece, because otherwise
+ // an addon that does not load says nothing on screen: a failure that never raised reaches no
+ // error handler, so the `/errors` log cannot hold it. Read from the engine's deduplicated
+ // record after the whole load edge (handlers included); the VM is fresh, so every `Load` row
+ // is this load's.
+ if let Some(mut chat) = world.get_resource_mut::() {
+ for row in script
+ .diagnostics()
+ .iter()
+ .filter(|d| d.kind == benilla_ui::script::diagnostics::DiagnosticKind::Load)
+ {
+ let head = row.message.lines().next().unwrap_or_default();
// Queued: the chat feed drains `ChatLog` once the UI is up.
chat.push_event(crate::ui_chat::ChatEvent::text_only(
crate::ui_chat::ChatEventKind::System,
- format!(
- "{failed} addon load {} — type /errors to see {}.",
- if failed == 1 { "failure" } else { "failures" },
- if failed == 1 { "it" } else { "them" }
- ),
+ format!("Addon load failure: {head}"),
));
}
}
diff --git a/crates/benilla-app/src/ui_script/manifest.rs b/crates/benilla-app/src/ui_script/manifest.rs
index f27aed0a..2913cb14 100644
--- a/crates/benilla-app/src/ui_script/manifest.rs
+++ b/crates/benilla-app/src/ui_script/manifest.rs
@@ -26,19 +26,6 @@ pub(super) fn layer_enabled_by(dev_build: bool, stock_ui: Option<&str>) -> bool
!(dev_build && stock_ui == Some("1"))
}
-/// `/errors`: toggles the layer's script error log; a no-op when the run boots without the layer.
-pub(crate) const ERRORS_TOGGLE: &str =
- "if BenillaScriptLog_Toggle then BenillaScriptLog_Toggle() end";
-
-/// `/errors clear`: empties the layer's script error log, or does nothing without the layer.
-pub(crate) const ERRORS_CLEAR: &str = "if BenillaScriptLog_Clear then BenillaScriptLog_Clear() end";
-
-/// Hands a chord the layer's Keybindings page is capturing (`BenillaBindCapture`), a bind string
-/// such as `CTRL-J`, to its handler; a no-op without the layer.
-pub(crate) fn host_key_capture(chord: &str) -> String {
- format!("if KeyBindings_OnHostKey then KeyBindings_OnHostKey(\"{chord}\") end")
-}
-
/// Every entry in load order: the core's rows, none without an install, then the layer's.
#[cfg(test)]
pub(super) fn manifest_files() -> Vec {
diff --git a/crates/benilla-app/src/ui_script/mod.rs b/crates/benilla-app/src/ui_script/mod.rs
index fcbfa2bb..7d9f5909 100644
--- a/crates/benilla-app/src/ui_script/mod.rs
+++ b/crates/benilla-app/src/ui_script/mod.rs
@@ -34,7 +34,6 @@ pub(crate) use session::VmMemo;
// Not test-only: the addon harness loads the whole shipped interface under each addon.
pub(crate) use manifest::load_default_ui;
pub(crate) use manifest::load_ingame_ui;
-pub(crate) use manifest::{host_key_capture, ERRORS_CLEAR, ERRORS_TOGGLE};
/// Whether the pointer is over any UI (the egui dev overlay or a player-UI frame), combined by
/// [`arbitrate_pointer_over_ui`]; gameplay reads it, so it is not the dev plugin's.
diff --git a/crates/benilla-app/src/ui_script/options_tests.rs b/crates/benilla-app/src/ui_script/options_tests.rs
index c366fa35..29028eee 100644
--- a/crates/benilla-app/src/ui_script/options_tests.rs
+++ b/crates/benilla-app/src/ui_script/options_tests.rs
@@ -173,7 +173,7 @@ fn the_selected_row_wears_the_gold_wash_and_hover_runs_blue() {
benilla_formats::wow_data_or_skip!();
let mut s = harness();
// Scale 1, so rects and the pointer share coordinates; at 1024x768 the fit clamp stays above 1.
- s.run("ERA_WINDOW_SCALE = 1").unwrap();
+ s.run("BENILLA_ERA_WINDOW_SCALE = 1").unwrap();
s.run("ShowUIPanel(BenillaOptionsFrame)").unwrap();
assert_eq!(
@@ -275,7 +275,7 @@ fn the_selected_row_wears_the_gold_wash_and_hover_runs_blue() {
fn the_ground_dim_draws_over_the_tile_and_clear_of_the_rope() {
benilla_formats::wow_data_or_skip!();
let mut s = harness();
- s.run("ERA_WINDOW_SCALE = 1").unwrap();
+ s.run("BENILLA_ERA_WINDOW_SCALE = 1").unwrap();
s.run("ShowUIPanel(BenillaOptionsFrame)").unwrap();
s.resolve();
@@ -514,7 +514,7 @@ fn a_track_press_seats_the_thumb_and_keeps_dragging() {
benilla_formats::wow_data_or_skip!();
let mut s = harness_on(audio_harness());
s.set_cvar_host("MasterVolume", "0.1");
- s.run("ERA_WINDOW_SCALE = 1").unwrap(); // pointer and rects share coordinates
+ s.run("BENILLA_ERA_WINDOW_SCALE = 1").unwrap(); // pointer and rects share coordinates
s.run("ShowUIPanel(BenillaOptionsFrame)").unwrap();
s.run("BenillaOptionsFrameCategoryListRowAudio:Click()")
.unwrap();
@@ -1839,7 +1839,7 @@ fn defaults_resets_the_controls_page_to_registered_defaults() {
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
-/// Run a few frames so the page body's fit converges: `OptionsScroll_Fit` reads the previous
+/// Run a few frames so the page body's fit converges: `BenillaOptionsScroll_Fit` reads the previous
/// frame's rects.
fn settle(s: &mut UiScript) {
for _ in 0..4 {
@@ -1942,11 +1942,14 @@ fn a_broad_search_scrolls_the_page_instead_of_overflowing_it() {
clip.bottom
);
- // Widget coordinates here, not the extract's pixels: the window carries `ERA_WINDOW_SCALE`.
+ // Widget coordinates here, not the extract's pixels: the window carries `BENILLA_ERA_WINDOW_SCALE`.
let sf_bottom: f32 = s
.eval("return BenillaOptionsFrameContainerScroll:GetBottom()")
.unwrap();
- let tail = |s: &UiScript| -> f32 { s.eval("return OptionsScroll_ContentBottom()").unwrap() };
+ let tail = |s: &UiScript| -> f32 {
+ s.eval("return BenillaOptionsScroll_ContentBottom()")
+ .unwrap()
+ };
assert!(
tail(&s) < sf_bottom,
"the tail starts below the fold ({} vs {sf_bottom})",
@@ -2068,7 +2071,7 @@ fn the_page_scroll_bar_wears_the_trough_with_its_arrows_in_the_sockets() {
// ── The row tooltips ─────────────────────────────────────────────────────────────
-/// Hover a row's label half, 60 units in at mid-height. Callers pin `ERA_WINDOW_SCALE = 1` first,
+/// Hover a row's label half, 60 units in at mid-height. Callers pin `BENILLA_ERA_WINDOW_SCALE = 1` first,
/// so the row's rect and the pointer share coordinates.
fn hover_label(s: &mut UiScript, frame: &str) {
s.resolve();
@@ -2112,7 +2115,7 @@ fn a_hovered_row_raises_its_1_12_description_on_the_era_seat() {
s.set_text_measurer(Box::new(super::FixedWidthFont(6.0)));
s.run("OPTION_TOOLTIP_GAMEFIELD_DESELECT = \"Checking this will prevent the deselection.\"")
.unwrap();
- s.run("ERA_WINDOW_SCALE = 1").unwrap();
+ s.run("BENILLA_ERA_WINDOW_SCALE = 1").unwrap();
s.run("ShowUIPanel(BenillaOptionsFrame)").unwrap();
let row = "BenillaOptionsFrameContainerBodyControlsRowStickyTarget";
@@ -2181,7 +2184,7 @@ fn a_row_with_no_1_12_string_raises_no_plate() {
let mut s = harness_on(audio_harness());
s.run("OPTION_TOOLTIP_GAMEFIELD_DESELECT = \"Sticky's own description.\"")
.unwrap();
- s.run("ERA_WINDOW_SCALE = 1").unwrap();
+ s.run("BENILLA_ERA_WINDOW_SCALE = 1").unwrap();
s.run("ShowUIPanel(BenillaOptionsFrame)").unwrap();
hover_label(
&mut s,
@@ -2201,10 +2204,10 @@ fn a_row_with_no_1_12_string_raises_no_plate() {
);
// Sticky's plate up, then straight into the mute row with no OnLeave in between.
- s.run("OptionsRow_Hover(BenillaOptionsFrameContainerBodyControlsRowStickyTarget, 1)")
+ s.run("BenillaOptionsRow_Hover(BenillaOptionsFrameContainerBodyControlsRowStickyTarget, 1)")
.unwrap();
assert!(s.eval::("return GameTooltip:IsVisible()").unwrap());
- s.run("OptionsRow_Hover(BenillaOptionsFrameContainerBodyControlsRowAutoLoot, 1)")
+ s.run("BenillaOptionsRow_Hover(BenillaOptionsFrameContainerBodyControlsRowAutoLoot, 1)")
.unwrap();
assert!(
!s.eval::("return GameTooltip:IsVisible()").unwrap(),
@@ -2232,7 +2235,7 @@ fn every_row_tooltip_key_resolves_in_the_real_global_strings() {
let listing: String = s
.eval(
"local out = {} \
- for page, rows in pairs(OPTIONS_PAGE_ROWS) do \
+ for page, rows in pairs(BENILLA_OPTIONS_PAGE_ROWS) do \
for _, rkey in ipairs(rows) do \
local row = getglobal(\"BenillaOptionsFrameContainerBody\" .. page .. rkey) \
table.insert(out, page .. rkey .. \"=\" .. (row.tip or \"\")) \
@@ -2320,7 +2323,7 @@ fn every_flavor_of_row_raises_its_plate_from_the_page_it_lives_on() {
let mut s = harness_on(audio_harness());
// Stand-in strings for every key the rows name.
s.run(
- "for page, rows in pairs(OPTIONS_PAGE_ROWS) do \
+ "for page, rows in pairs(BENILLA_OPTIONS_PAGE_ROWS) do \
for _, rkey in ipairs(rows) do \
local row = getglobal(\"BenillaOptionsFrameContainerBody\" .. page .. rkey) \
if row.tip then setglobal(row.tip, \"described: \" .. rkey) end \
@@ -2328,7 +2331,7 @@ fn every_flavor_of_row_raises_its_plate_from_the_page_it_lives_on() {
end",
)
.unwrap();
- s.run("ERA_WINDOW_SCALE = 1").unwrap();
+ s.run("BENILLA_ERA_WINDOW_SCALE = 1").unwrap();
s.run("ShowUIPanel(BenillaOptionsFrame)").unwrap();
let mut raised = 0;
@@ -2346,7 +2349,7 @@ fn every_flavor_of_row_raises_its_plate_from_the_page_it_lives_on() {
.unwrap();
let rows: String = s
.eval(&format!(
- "return table.concat(OPTIONS_PAGE_ROWS.{page}, \",\")"
+ "return table.concat(BENILLA_OPTIONS_PAGE_ROWS.{page}, \",\")"
))
.unwrap();
for rkey in rows.split(',') {
@@ -2387,7 +2390,7 @@ fn every_flavor_of_row_raises_its_plate_from_the_page_it_lives_on() {
}
/// 1.12's AdvancedOptionsCombatText box as saved-global rows: a click writes the global, never a
-/// CVar, and runs the family's apply, `CombatText_UpdateOrLoad`.
+/// CVar, and runs the family's apply, `BenillaCombatText_UpdateOrLoad`.
#[test]
fn the_combat_page_writes_saved_variable_globals_and_applies_them() {
benilla_formats::wow_data_or_skip!();
@@ -3236,7 +3239,7 @@ fn the_defaults_button_is_armed_by_rows_not_by_a_category() {
s.run("ShowUIPanel(BenillaOptionsFrame)").unwrap();
let keys: Vec = s
- .eval::("return table.concat(OPTIONS_CATEGORY_KEYS, \",\")")
+ .eval::("return table.concat(BENILLA_OPTIONS_CATEGORY_KEYS, \",\")")
.unwrap()
.split(',')
.map(str::to_string)
@@ -3245,8 +3248,8 @@ fn the_defaults_button_is_armed_by_rows_not_by_a_category() {
for key in &keys {
let has_rows = s
.eval::(&format!(
- "return OPTIONS_PAGE_ROWS[\"{key}\"] ~= nil and \
- getn(OPTIONS_PAGE_ROWS[\"{key}\"]) > 0"
+ "return BENILLA_OPTIONS_PAGE_ROWS[\"{key}\"] ~= nil and \
+ getn(BENILLA_OPTIONS_PAGE_ROWS[\"{key}\"]) > 0"
))
.unwrap();
assert!(
@@ -3966,7 +3969,7 @@ fn the_two_option_tabs_fit_their_labels_at_the_drawn_scale() {
assert_eq!(
num(&mut s, "BenillaOptionsFrame:GetScale()"),
0.78,
- "ERA_WINDOW_SCALE"
+ "BENILLA_ERA_WINDOW_SCALE"
);
for tab in ["BenillaOptionsFrameGameTab", "BenillaOptionsFrameAddOnsTab"] {
@@ -4156,7 +4159,7 @@ fn every_registered_reference_cvar_has_a_row_on_our_own_window() {
let ours: std::collections::HashSet = s
.eval::>(
"local out = {} \
- for page, rows in pairs(OPTIONS_PAGE_ROWS) do \
+ for page, rows in pairs(BENILLA_OPTIONS_PAGE_ROWS) do \
for _, rkey in ipairs(rows) do \
local row = getglobal(\"BenillaOptionsFrameContainerBody\" .. page .. rkey) \
if row.cvar then table.insert(out, row.cvar) end \
@@ -4396,7 +4399,7 @@ fn the_guild_line_greys_with_player_names_and_the_follow_speed_with_the_style()
.unwrap()
};
assert!(speed_thumb(&mut s));
- s.run("SetCVar(\"cameraSmoothStyle\", \"0\") OptionsPage_Refresh(\"Controls\")")
+ s.run("SetCVar(\"cameraSmoothStyle\", \"0\") BenillaOptionsPage_Refresh(\"Controls\")")
.unwrap();
assert!(
!speed_thumb(&mut s),
@@ -4410,7 +4413,7 @@ fn the_guild_line_greys_with_player_names_and_the_follow_speed_with_the_style()
:IsMouseEnabled()"
)
.unwrap());
- s.run("SetCVar(\"cameraSmoothStyle\", \"1\") OptionsPage_Refresh(\"Controls\")")
+ s.run("SetCVar(\"cameraSmoothStyle\", \"1\") BenillaOptionsPage_Refresh(\"Controls\")")
.unwrap();
assert!(speed_thumb(&mut s));
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
diff --git a/crates/benilla-app/src/ui_script/shipped_xml_tests.rs b/crates/benilla-app/src/ui_script/shipped_xml_tests.rs
index ebd21a28..800d1b74 100644
--- a/crates/benilla-app/src/ui_script/shipped_xml_tests.rs
+++ b/crates/benilla-app/src/ui_script/shipped_xml_tests.rs
@@ -1964,3 +1964,75 @@ fn the_layer_does_not_grow() {
"assets/ui holds the layer's manifest and its files, nothing else"
);
}
+
+/// The layer speaks the 1.12 dialect: a handler body is the chunk itself (`0x704c70`), so it reads
+/// its frame from `this`, never `self`; the loader reads no `function=`; the strata table
+/// (`0x8119f8`) has no `BLIZZARD`; `SetDrawLayer` (`0x79a780`) takes a layer name alone; and a
+/// button's state textures take a texture, never a colour (`0x781970`).
+#[test]
+fn the_layer_speaks_the_1_12_dialect() {
+ fn handlers(el: &benilla_ui::framexml::Element, out: &mut Vec<(String, String)>) {
+ if el.tag == "Scripts" {
+ for h in &el.children {
+ out.push((h.tag.clone(), h.body.clone()));
+ assert!(
+ h.attr("function").is_none(),
+ "<{} function=…> is no 1.12 form",
+ h.tag
+ );
+ }
+ }
+ for c in &el.children {
+ handlers(c, out);
+ }
+ }
+ let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("assets/ui");
+ let files = super::addons::Addon::layer().toc.files;
+ assert!(files.len() >= 8, "the layer's toc lists {files:?}");
+ let word = |text: &str, w: &str| {
+ text.match_indices(w).any(|(i, _)| {
+ let before = text[..i].chars().next_back();
+ let after = text[i + w.len()..].chars().next();
+ !before.is_some_and(|c| c.is_alphanumeric() || c == '_')
+ && !after.is_some_and(|c| c.is_alphanumeric() || c == '_')
+ })
+ };
+ for file in files {
+ let text = std::fs::read_to_string(dir.join(&file)).expect("read");
+ let doc = benilla_ui::framexml::parse(&text).expect("parses");
+ let mut found = Vec::new();
+ for item in &doc.items {
+ use benilla_ui::framexml::TopLevel;
+ if let TopLevel::Template(el) | TopLevel::Instance(el) = item {
+ handlers(el, &mut found);
+ }
+ }
+ for (tag, body) in found {
+ assert!(!word(&body, "self"), "{file}: <{tag}> reads `self`: {body}");
+ }
+ assert!(!text.contains("BLIZZARD"), "{file}: BLIZZARD strata");
+ for line in text.lines().filter(|l| l.contains("SetDrawLayer(")) {
+ assert_eq!(
+ line.matches(',').count(),
+ 0,
+ "{file}: SetDrawLayer takes the layer alone: {line}"
+ );
+ }
+ for verb in [
+ "SetNormalTexture(",
+ "SetPushedTexture(",
+ "SetHighlightTexture(",
+ "SetDisabledTexture(",
+ "SetCheckedTexture(",
+ "SetDisabledCheckedTexture(",
+ ] {
+ for line in text.lines().filter(|l| l.contains(verb)) {
+ let args = line.split(verb).nth(1).unwrap_or_default();
+ assert!(
+ !args.trim_start().starts_with(|c: char| c.is_ascii_digit()),
+ "{file}: {verb} takes a texture, never a colour: {line}"
+ );
+ }
+ }
+ }
+}
diff --git a/crates/benilla-app/src/ui_script/world_entry_tests.rs b/crates/benilla-app/src/ui_script/world_entry_tests.rs
index 418c0aca..a5761fad 100644
--- a/crates/benilla-app/src/ui_script/world_entry_tests.rs
+++ b/crates/benilla-app/src/ui_script/world_entry_tests.rs
@@ -865,8 +865,9 @@ fn the_login_one_shots_wait_for_the_in_game_ui() {
let _ = std::fs::remove_dir_all(&tmp);
}
-/// An addon that fails to load without raising (a `.toc` naming a file the package lacks) is kept
-/// in the log, readable from Lua by the error window, and announced in chat.
+/// An addon that fails to load without raising (a `.toc` naming a file the package lacks) reaches
+/// no Lua error handler, so the `/errors` log (which collects off the handler) does not hold it:
+/// the engine's record keeps it, and a chat line names it.
#[test]
fn an_addon_that_fails_to_load_without_raising_is_readable_in_the_error_log() {
benilla_formats::wow_data_or_skip!();
@@ -914,57 +915,20 @@ fn an_addon_that_fails_to_load_without_raising_is_readable_in_the_error_log() {
row.message
);
- // 2. Readable from Lua through the window's own reads.
- let count: i64 = script
- .eval("local shown = BenillaGetNumScriptErrors() return shown")
- .expect("BenillaGetNumScriptErrors is installed");
- assert!(count >= 1, "the window's own read sees it");
- let seen: String = script
- .eval(
- "local text = '' \
- for i = 1, BenillaGetNumScriptErrors() do \
- local seq, kind, message = BenillaGetScriptErrorInfo(i) \
- if kind == 'load' then text = message end \
- end \
- return text",
- )
- .expect("BenillaGetScriptErrorInfo is installed");
- assert!(
- seen.contains("AaMissing"),
- "the window walks the log and finds it: {seen:?}"
- );
-
- assert!(
- script
- .eval::("return BenillaScriptLogFrame ~= nil")
- .expect("eval"),
- "ScriptLogFrame.xml loaded and built the window"
- );
-
- // The repaint runs over a real row, so a nil global anywhere on its path raises here.
+ // 2. Not in the `/errors` log: it never reached the error handler.
script
.eval::<()>("BenillaScriptLog_Update() return nil")
- .expect("the window repaints over a real log without raising");
- // Some row shows it, not necessarily row 1: the log also holds the warnings a world entry
- // raises before any addon loads.
- let row_labels: Vec = (1..=13)
- .filter_map(|i| {
- script
- .eval::