Add and optimize requested TokensWorth modules

Merge the audited TokensWorth-derived modules and ClassicAPI-focused optimizations into the default branch.
This commit is contained in:
2026-08-31 10:11:49 +02:00
committed by GitHub
parent e7d01c8aa0
commit 0e1c3385e4
12 changed files with 1151 additions and 1 deletions
+215
View File
@@ -670,3 +670,218 @@ Static audit is complete. Before merging to the stable branch, test:
Do not merge this audit branch solely on static analysis. Treat the branch as a
test candidate until the in-game matrix has passed without Lua errors or UI
regressions.
## TokensWorth requested modules — test branch audit
Branch: `tokensworth-mods-classicapi-test`
Seven requested modules were added for runtime testing. **Improved Roll Frames was
not duplicated** because an optimized version already exists in
ShaguTweaks-ClassicAPI.
### Mouseover Right / Mouseover Right 2
The original implementation created one independent mouse-catching overlay for
every action button plus the bar itself. The test version uses one invisible
reveal area per bar and one 100 ms controller that only runs while the bar is
visible. When hidden, there is no polling.
The helper preserves existing bar OnShow/OnHide scripts and tracks the native
`SHOW_MULTI_ACTIONBAR_3` / `SHOW_MULTI_ACTIONBAR_4` state through
`CVAR_UPDATE`.
### Hide Macro Text
One-time UI update only. No ClassicAPI replacement is useful for this FrameXML
operation and no periodic work is installed.
### Unit Frame Abbreviated Names
The original target-of-target implementation recalculated the name on every
rendered frame. The test version uses ClassicAPI event validation and
`UNIT_TARGET` when available. Only environments without that event use a
250 ms fallback ticker.
### Movable Unit Frames Extended
The permanent per-frame Ctrl+Shift check was removed. The module uses
`ShaguTweaks.API.IsShiftKeyDown`, `API.IsControlKeyDown` and
`MODIFIER_STATE_CHANGED` when ClassicAPI exposes modifier-state events.
The grid is created lazily on the first unlock. Positions are stored in the
existing `ShaguTweaks_config["MoveUnitframesExtended"]` table.
For the requested Turtle layout, the debuff anchor is **BuffButton32** instead
of the upstream BuffButton16.
### Cursor Tooltip
The original global `GameTooltip_SetDefaultAnchor` replacement was removed.
The test version keeps the native function intact through the ShaguTweaks safe
post-hook helper. Cursor tracking runs only while a default-anchored tooltip is
shown.
### Hide Combat Tooltip
The original combat-long per-frame Shift polling was removed. ClassicAPI's
modifier event updates the tooltip only when modifier state actually changes.
A 50 ms combat-only fallback exists for environments without that event.
The GameTooltip Show method is post-hooked so tooltips opened after entering
combat immediately inherit the correct hidden/Shift-visible state.
### Runtime validation required
Before merging this branch, test:
- login and `/reload` with the seven new modules disabled
- each module individually enabled
- both Mouseover Right modules together
- toggle the two right actionbars in Interface Options while mouseover modules are enabled
- stance/bonus actionbar changes with Hide Macro Text enabled
- long NPC target names and target-of-target changes
- Ctrl+Shift dragging of party frames, minimap, BuffButton0, BuffButton32 and TempEnchant1
- relog/reload persistence of moved positions
- Cursor Tooltip alone, Hide Combat Tooltip alone, and both enabled together
- combat tooltip Shift reveal/re-hide behavior
- interaction with DragonflightUI-Reforged's injected ShaguTweaks Extras list
- Lua errors and visible FPS regressions
Static code review is complete; this branch remains a runtime test candidate.
### Cursor Tooltip — focused audit / optimization
A focused audit compared the test implementation with the TokensWorth upstream
module.
Findings:
- the feature genuinely needs frame-by-frame cursor coordinates while the
tooltip is visible; there is no ClassicAPI event that can replace pointer
tracking
- the previous test implementation showed its cursor tracker as soon as
`GameTooltip_SetDefaultAnchor` ran, even though default anchoring normally
happens before `GameTooltip:Show()`
- `ClearAllPoints()` on the cursor tracker every rendered frame was
unnecessary
- moving the tracker while the mouse coordinates were unchanged caused
avoidable layout work
- replacing `GameTooltip_SetDefaultAnchor` for every possible tooltip caller
could affect non-`GameTooltip` frames unnecessarily
The optimized test version now:
- starts its `OnUpdate` work only while the actual `GameTooltip` is shown
- performs one immediate cursor-position update before Show to avoid a first
frame jump
- skips `SetPoint` when cursor coordinates and UI scale have not changed
- reuses the same CENTER anchor without per-frame `ClearAllPoints`
- keeps the original default-anchor helper for non-`GameTooltip` callers
- keeps `SetClampedToScreen(true)` so the cursor tooltip remains inside the
visible UI area
- retains the intentional default-anchor replacement because a post-hook alone
was not reliable on Vanilla/Turtle layout code
No ClassicAPI-specific replacement is useful here. `GetCursorPosition`,
tooltip ownership and frame anchoring are native UI operations. The optimized
design therefore keeps the unavoidable cursor-following `OnUpdate`, but only
for the time where that work is actually needed.
## Remaining TokensWorth modules — focused audit
The remaining requested TokensWorth-derived modules were reviewed against their
upstream implementations and the current ClassicAPI/ShaguTweaks architecture.
### Mouseover Right / Mouseover Right 2 — optimized
Both options continue to use one shared helper instead of duplicating the
upstream implementation.
Upstream creates one mouse-catching overlay for each of the 12 buttons plus an
additional bar overlay for each actionbar. The ClassicAPI test version keeps one
hotspot and one controller per bar.
The focused audit additionally:
- caches the native actionbar visibility flag from `CVAR_UPDATE` /
`PLAYER_ENTERING_WORLD` instead of looking it up on every watcher tick
- replaces repeated `GetTime()` deadline checks with a simple accumulated
two-second idle timer
- removes a duplicate watcher restart when the hidden bar is revealed
- leaves the watcher disabled while the bar is hidden
- preserves any pre-existing bar `OnShow` / `OnHide` scripts
The remaining 100 ms watcher only exists while the relevant actionbar is
actually visible and waiting to auto-hide. No ClassicAPI API can replace the
required mouse-over state check.
### Hide Macro Text — retained as-is
No hot-path issue was found.
The module performs a single pass over the native action-button FontStrings and
sets their alpha to zero. It installs no event handlers, no hooks and no
`OnUpdate`.
Using ClassicAPI would add abstraction without benefit because this is a
one-time FrameXML presentation change. The current implementation is already
effectively zero-cost after enable.
### Unit Frame Abbreviated Names — further optimized
The upstream module recalculates target-of-target text on every rendered frame.
The ClassicAPI version already prefers validated `UNIT_TARGET` and
`UNIT_NAME_UPDATE` events, with a 250 ms fallback only where `UNIT_TARGET`
is unavailable.
The focused audit additionally:
- caches the raw unit name and abbreviated result
- skips repeated abbreviation/string work while the underlying unit name is
unchanged
- skips `FontString:SetText` when the displayed text is already correct
- limits the legacy fallback to cases where `targettarget` actually exists
### Movable Unit Frames Extended — hardened
The upstream module polls Ctrl+Shift every rendered frame. The ClassicAPI
version uses `MODIFIER_STATE_CHANGED` plus the shared modifier helpers, with a
100 ms fallback only for environments without ClassicAPI modifier events.
The focused audit found a correctness issue in the first test conversion:
merely pressing and releasing Ctrl+Shift saved absolute positions for every
managed frame, even if the user had not dragged them. On later logins this
could turn untouched Blizzard-managed frames into explicit absolute-position
frames.
The hardened version now:
- saves a position only after that specific frame was actually dragged
- preserves and restores the original drag scripts
- preserves and restores the original mouse-enabled state
- preserves the original movable state
- restores the original user-placed state for untouched frames
- only marks a frame user-placed when a real drag starts
- keeps the grid lazily created on first unlock
- retains the requested Turtle WoW `BuffButton32` debuff anchor
### Current status
The focused static audit found no additional change worth making to
`actionbar-mouseover-bar-right.lua`,
`actionbar-mouseover-bar-right2.lua` or `actionbar-hide-macro.lua` beyond
their shared/helper behavior.
Runtime validation is still required before merge, especially for:
- enabling/disabling the two native right actionbars while mouseover hiding is
active
- repeated reveal/hide cycles with action buttons clicked normally
- abbreviated target-of-target names during rapid target switching
- Ctrl+Shift without dragging anything, followed by relog/reload
- dragging each supported frame individually, followed by relog/reload
- default party/buff/minimap layout remaining unchanged for frames never moved
+19 -1
View File
@@ -43,6 +43,8 @@ Settings: **Esc → Advanced Options**.
- Buy Em All uses ClassicAPI merchant, item count and bag-family data, including specialty bags.
- Added modifier-aware Key-Down Casting with Shift/Ctrl/Alt binding support and an independent Alt Self-Cast option.
- Added Metric Range for metre labels in range tooltips without changing numeric values.
- Added optimized modules adapted from TokensWorth/ShaguTweaks-mods: Mouseover Right bars, Hide Macro Text, Unit Frame Abbreviated Names, Movable Unit Frames Extended, Cursor Tooltip and Hide Combat Tooltip.
- Movable Unit Frames Extended and Hide Combat Tooltip use ClassicAPI modifier-state events instead of permanent per-frame modifier polling.
- Various stability fixes across legacy ShaguTweaks Extras modules.
## 🔷 Mods using ClassicAPI integration
@@ -55,12 +57,15 @@ Settings: **Esc → Advanced Options**.
- Alt Self-Cast
- Macro Icons
- Macro Tweaks
- Movable Unit Frames Extended
- Raid Frames
- Reagent Counter
- Reveal World Map
- Show Dispel Indicators
- Show Bags
- Show Micro Menu
- Hide Combat Tooltip
- Unit Frame Abbreviated Names
Other modules can also benefit indirectly from ClassicAPI through shared ShaguTweaks libraries and helpers.
@@ -76,6 +81,9 @@ Other modules can also benefit indirectly from ClassicAPI through shared ShaguTw
- Show Micro Menu
- Key-Down Casting
- Alt Self-Cast
- Mouseover Right
- Mouseover Right 2
- Hide Macro Text
### Bags & Inventory
@@ -90,6 +98,13 @@ Other modules can also benefit indirectly from ClassicAPI through shared ShaguTw
### Tooltip & Items
- Metric Range
- Cursor Tooltip
- Hide Combat Tooltip
### Unit Frames
- Unit Frame Abbreviated Names
- Movable Unit Frames Extended
### World Map
@@ -127,10 +142,11 @@ Macro Tweaks adds convenient ClassicAPI-backed aliases:
## 🧹 Removed duplicates
The following original Extras modules are not included because improved versions already exist in ShaguTweaks-ClassicAPI:
The following modules are not included because improved versions already exist in ShaguTweaks-ClassicAPI:
- **Chat History** → integrated into **Chat Tweaks**
- **Show Energy Ticks** → integrated as **Energy & Mana Tick**
- **Improved Roll Frames** → already integrated and optimized in **ShaguTweaks-ClassicAPI**
## 🔧 Compatibility
@@ -147,4 +163,6 @@ Additional maintenance and Turtle WoW work by **paokkerkir**.
ClassicAPI compatibility fork maintained by **Dusk-92**.
Additional requested modules adapted from **TokensWorth/ShaguTweaks-mods**, originally released under MIT by **GryllsAddons**. See `THIRD_PARTY_NOTICES.md`.
Released under the original **MIT License**.
+10
View File
@@ -26,6 +26,10 @@ mods\actionbar-float.lua
mods\classic-snowfall.lua
mods\reduced-actionbar-bags.lua
mods\reduced-actionbar-micromenu.lua
mods\actionbar-mouseover-common.lua
mods\actionbar-mouseover-bar-right.lua
mods\actionbar-mouseover-bar-right2.lua
mods\actionbar-hide-macro.lua
# bags & inventory
mods\bag-item-click.lua
@@ -37,6 +41,12 @@ mods\buy-em-all.lua
# tooltip & items
mods\metric-range.lua
mods\cursor-tooltip.lua
mods\hide-combat-tooltip.lua
# unit frames
mods\unitframes-abbrev-names.lua
mods\move-unitframes-extended.lua
# world map
mods\worldmap-reveal.lua
+40
View File
@@ -0,0 +1,40 @@
# Third-Party Notices
## TokensWorth/ShaguTweaks-mods
The following modules in this fork contain code adapted from
`TokensWorth/ShaguTweaks-mods`:
- `mods/actionbar-mouseover-common.lua`
- `mods/actionbar-mouseover-bar-right.lua`
- `mods/actionbar-mouseover-bar-right2.lua`
- `mods/actionbar-hide-macro.lua`
- `mods/unitframes-abbrev-names.lua`
- `mods/move-unitframes-extended.lua`
- `mods/cursor-tooltip.lua`
- `mods/hide-combat-tooltip.lua`
Source:
https://github.com/TokensWorth/ShaguTweaks-mods
MIT License
Copyright (c) 2022 GryllsAddons
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+34
View File
@@ -0,0 +1,34 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local module = ShaguTweaks:register({
title = T["Hide Macro Text"],
description = T["Hides macro names on action buttons."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Action Bar"],
enabled = nil,
})
module.enable = function(self)
local function HideMacroText(button)
if not button or not button.GetName then return end
local name = button:GetName()
local text = name and _G[name .. "Name"]
if text then text:SetAlpha(0) end
end
for i = 1, 24 do
HideMacroText(_G["BonusActionButton" .. i])
end
for i = 1, 12 do
HideMacroText(_G["ActionButton" .. i])
HideMacroText(_G["MultiBarRightButton" .. i])
HideMacroText(_G["MultiBarLeftButton" .. i])
HideMacroText(_G["MultiBarBottomLeftButton" .. i])
HideMacroText(_G["MultiBarBottomRightButton" .. i])
end
end
+15
View File
@@ -0,0 +1,15 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local T = ShaguTweaks.T
local module = ShaguTweaks:register({
title = T["Mouseover Right"],
description = T["Hide the right actionbar and show it on mouseover."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Action Bar"],
enabled = nil,
})
module.enable = function(self)
ShaguTweaks.CreateMouseoverActionBar(MultiBarRight, "SHOW_MULTI_ACTIONBAR_3")
end
+15
View File
@@ -0,0 +1,15 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local T = ShaguTweaks.T
local module = ShaguTweaks:register({
title = T["Mouseover Right 2"],
description = T["Hide the second right actionbar and show it on mouseover."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Action Bar"],
enabled = nil,
})
module.enable = function(self)
ShaguTweaks.CreateMouseoverActionBar(MultiBarLeft, "SHOW_MULTI_ACTIONBAR_4")
end
+138
View File
@@ -0,0 +1,138 @@
-- Shared mouseover actionbar helper.
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local _G = ShaguTweaks.GetGlobalEnv()
if not ShaguTweaks.CreateMouseoverActionBar then
ShaguTweaks.CreateMouseoverActionBar = function(bar, visibilityFlag)
if not bar or not visibilityFlag then return end
if bar.ShaguTweaksMouseoverController then return end
local controller = CreateFrame("Frame", nil, UIParent)
local hotspot = CreateFrame("Frame", nil, UIParent)
controller.bar = bar
controller.hotspot = hotspot
controller.visibilityFlag = visibilityFlag
controller.enabled = false
controller.elapsed = 0
controller.idle = 0
bar.ShaguTweaksMouseoverController = controller
-- One invisible reveal area replaces the original per-button overlay
-- frames. It only accepts mouse input while the actionbar is hidden.
hotspot:SetAllPoints(bar)
hotspot:SetFrameStrata("DIALOG")
hotspot:EnableMouse(false)
hotspot:Hide()
local function ReadEnabled()
return _G[visibilityFlag] and true or false
end
local function StopWatching()
controller:SetScript("OnUpdate", nil)
controller.elapsed = 0
controller.idle = 0
end
local function StartWatching()
if not controller.enabled or not bar:IsShown() then return end
hotspot:EnableMouse(false)
controller.elapsed = 0
controller.idle = 0
controller:SetScript("OnUpdate", function()
this.elapsed = this.elapsed + (arg1 or 0)
if this.elapsed < .10 then return end
local step = this.elapsed
this.elapsed = 0
if not this.enabled then
StopWatching()
hotspot:EnableMouse(false)
hotspot:Hide()
return
end
if not bar:IsShown() then
StopWatching()
hotspot:Show()
hotspot:EnableMouse(true)
return
end
if MouseIsOver(bar) then
this.idle = 0
return
end
this.idle = this.idle + step
if this.idle >= 2 then
StopWatching()
bar:Hide()
end
end)
end
local function Sync()
controller.enabled = ReadEnabled()
if not controller.enabled then
StopWatching()
hotspot:EnableMouse(false)
hotspot:Hide()
return
end
hotspot:Show()
if bar:IsShown() then
StartWatching()
else
hotspot:EnableMouse(true)
end
end
hotspot:SetScript("OnEnter", function()
if not controller.enabled then return end
hotspot:EnableMouse(false)
bar:Show()
-- The bar's OnShow script starts the watcher. Avoid a second redundant
-- StartWatching() call here.
end)
-- Keep existing scripts intact while observing native/UI-addon visibility
-- changes. These wrappers are installed once per bar.
local oldOnShow = bar:GetScript("OnShow")
bar:SetScript("OnShow", function()
if oldOnShow then oldOnShow() end
if controller.enabled then StartWatching() end
end)
local oldOnHide = bar:GetScript("OnHide")
bar:SetScript("OnHide", function()
if oldOnHide then oldOnHide() end
StopWatching()
if controller.enabled then
hotspot:Show()
hotspot:EnableMouse(true)
else
hotspot:EnableMouse(false)
hotspot:Hide()
end
end)
controller:RegisterEvent("PLAYER_ENTERING_WORLD")
controller:RegisterEvent("CVAR_UPDATE")
controller:SetScript("OnEvent", Sync)
Sync()
end
end
+96
View File
@@ -0,0 +1,96 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local hooksecurefunc = ShaguTweaks.hooksecurefunc
local module = ShaguTweaks:register({
title = T["Cursor Tooltip"],
description = T["Attaches default tooltips to the cursor."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Tooltip & Items"],
enabled = nil,
})
module.enable = function(self)
if ShaguTweaks.CursorTooltipInstalled then return end
ShaguTweaks.CursorTooltipInstalled = true
-- Keep the native helper for non-GameTooltip callers. The feature only
-- needs to move the normal world/UI GameTooltip and should not unexpectedly
-- change other tooltip frames that may reuse the same helper.
local originalDefaultAnchor = ShaguTweaks.CursorTooltipOriginalDefaultAnchor
or _G.GameTooltip_SetDefaultAnchor
ShaguTweaks.CursorTooltipOriginalDefaultAnchor = originalDefaultAnchor
local cursor = CreateFrame("Frame", nil, UIParent)
cursor:SetWidth(1)
cursor:SetHeight(1)
cursor:Hide()
local following = false
local lastX, lastY, lastScale
local function UpdateCursor(force)
local scale = UIParent:GetEffectiveScale()
if not scale or scale == 0 then scale = UIParent:GetScale() end
if not scale or scale == 0 then scale = 1 end
local x, y = GetCursorPosition()
-- GetCursorPosition is cheap and required for following the pointer, but
-- avoid a frame-layout operation while the mouse is completely still.
if not force and x == lastX and y == lastY and scale == lastScale then
return
end
lastX, lastY, lastScale = x, y, scale
-- Reusing the same CENTER anchor updates it in place; ClearAllPoints every
-- rendered frame is unnecessary.
cursor:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x / scale, y / scale)
end
cursor:SetScript("OnUpdate", function()
if not following or not GameTooltip:IsShown() then
this:Hide()
return
end
UpdateCursor(false)
end)
function _G.GameTooltip_SetDefaultAnchor(tooltip, parent)
if tooltip ~= GameTooltip then
return originalDefaultAnchor(tooltip, parent)
end
tooltip:SetOwner(parent or UIParent, "ANCHOR_NONE")
tooltip:ClearAllPoints()
-- Position once before Show() so there is no first-frame jump from the
-- cursor frame's default origin.
UpdateCursor(true)
tooltip:SetPoint("BOTTOMLEFT", cursor, "TOPRIGHT", 12, 12)
tooltip:SetClampedToScreen(true)
tooltip.default = 1
following = true
end
-- Default anchoring normally happens before GameTooltip:Show(). Start the
-- per-frame cursor tracker only once the tooltip is actually visible.
hooksecurefunc(GameTooltip, "Show", function()
if not following then return end
UpdateCursor(true)
cursor:Show()
end)
hooksecurefunc(GameTooltip, "Hide", function()
following = false
cursor:Hide()
end)
end
+146
View File
@@ -0,0 +1,146 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local T = ShaguTweaks.T
local API = ShaguTweaks.API
local module = ShaguTweaks:register({
title = T["Hide Combat Tooltip"],
description = T["Hides the tooltip in combat. Hold Shift to show it temporarily."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Tooltip & Items"],
enabled = nil,
})
module.enable = function(self)
if ShaguTweaks.HideCombatTooltipInstalled then return end
ShaguTweaks.HideCombatTooltipInstalled = true
local controller = CreateFrame("Frame", nil, UIParent)
local inCombat = UnitAffectingCombat("player") and true or false
local shiftDown = API.IsShiftKeyDown() and true or false
local guardActive = false
local elapsed = 0
local function Apply()
if not inCombat then
if GameTooltip:GetAlpha() ~= 1 then
GameTooltip:SetAlpha(1)
end
return
end
local alpha = shiftDown and 1 or 0
if GameTooltip:GetAlpha() ~= alpha then
GameTooltip:SetAlpha(alpha)
end
end
local function GuardTick()
elapsed = elapsed + (arg1 or 0)
if elapsed < .05 then return end
elapsed = 0
-- ClassicAPI provides modifier events, so normal clients don't need to
-- query Shift here. This fallback is only for old environments.
if not API.modifierstate then
shiftDown = API.IsShiftKeyDown() and true or false
end
Apply()
end
local function StartGuard()
if guardActive or not inCombat or not GameTooltip:IsShown() then return end
guardActive = true
elapsed = 0
controller:SetScript("OnUpdate", GuardTick)
end
local function StopGuard()
if not guardActive then return end
guardActive = false
elapsed = 0
controller:SetScript("OnUpdate", nil)
end
local function RefreshShift()
shiftDown = API.IsShiftKeyDown() and true or false
if inCombat and GameTooltip:IsShown() then
Apply()
StartGuard()
end
end
local function EnterCombat()
inCombat = true
shiftDown = API.IsShiftKeyDown() and true or false
if GameTooltip:IsShown() then
Apply()
StartGuard()
end
end
local function LeaveCombat()
inCombat = false
StopGuard()
Apply()
end
-- Vanilla/Turtle can restore GameTooltip alpha internally while updating a
-- visible unit tooltip. A purely event-driven implementation therefore does
-- not stay hidden reliably. The guard below exists only while BOTH combat
-- and an actual GameTooltip are active, and is throttled to 20 Hz instead of
-- running the full upstream logic every rendered frame.
local oldOnShow = GameTooltip:GetScript("OnShow")
GameTooltip:SetScript("OnShow", function()
if oldOnShow then oldOnShow() end
if inCombat then
shiftDown = API.IsShiftKeyDown() and true or false
Apply()
StartGuard()
else
Apply()
end
end)
local oldOnHide = GameTooltip:GetScript("OnHide")
GameTooltip:SetScript("OnHide", function()
if oldOnHide then oldOnHide() end
StopGuard()
end)
controller:RegisterEvent("PLAYER_ENTERING_WORLD")
controller:RegisterEvent("PLAYER_REGEN_DISABLED")
controller:RegisterEvent("PLAYER_REGEN_ENABLED")
if API.modifierstate then
controller:RegisterEvent("MODIFIER_STATE_CHANGED")
end
controller:SetScript("OnEvent", function()
if event == "PLAYER_REGEN_DISABLED" then
EnterCombat()
elseif event == "PLAYER_REGEN_ENABLED" then
LeaveCombat()
elseif event == "MODIFIER_STATE_CHANGED" then
RefreshShift()
elseif event == "PLAYER_ENTERING_WORLD" then
if UnitAffectingCombat("player") then
EnterCombat()
else
LeaveCombat()
end
end
end)
if inCombat then
EnterCombat()
else
LeaveCombat()
end
end
+283
View File
@@ -0,0 +1,283 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local API = ShaguTweaks.API
local module = ShaguTweaks:register({
title = T["Movable Unit Frames Extended"],
description = T["Party frames, minimap, buffs, weapon buffs and debuffs can be moved while Ctrl+Shift are held."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Unit Frames"],
enabled = nil,
})
module.enable = function(self)
ShaguTweaks_config = ShaguTweaks_config or {}
ShaguTweaks_config["MoveUnitframesExtended"] = ShaguTweaks_config["MoveUnitframesExtended"] or {}
local movedb = ShaguTweaks_config["MoveUnitframesExtended"]
local unlocked = false
local states = {}
-- Turtle WoW places the first debuff at BuffButton32 in the layout this
-- module targets. BuffButton16 from the original mod is intentionally not
-- used here.
local targets = {
{ name = "PartyMemberFrame1" },
{ name = "PartyMemberFrame2" },
{ name = "PartyMemberFrame3" },
{ name = "PartyMemberFrame4" },
{ name = "Minimap", moveParent = true },
{ name = "BuffButton0" },
{ name = "BuffButton32" },
{ name = "TempEnchant1" },
}
local function Resolve(target)
local handle = _G[target.name]
if not handle then return end
local moveFrame = target.moveParent and handle:GetParent() or handle
if not moveFrame then return end
return handle, moveFrame
end
local function PositionKey(target, moveFrame)
return (moveFrame.GetName and moveFrame:GetName()) or target.name
end
local function SavePosition(target, moveFrame)
if not moveFrame then
local _, resolved = Resolve(target)
moveFrame = resolved
end
if not moveFrame then return end
local left = moveFrame:GetLeft()
local top = moveFrame:GetTop()
if not left or not top then return end
movedb[PositionKey(target, moveFrame)] = { left, top }
end
local function RestorePosition(target)
local _, moveFrame = Resolve(target)
if not moveFrame then return end
local pos = movedb[PositionKey(target, moveFrame)]
if not pos or not pos[1] or not pos[2] then return end
moveFrame:SetMovable(true)
if moveFrame.SetUserPlaced then moveFrame:SetUserPlaced(true) end
moveFrame:ClearAllPoints()
moveFrame:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", pos[1], pos[2])
end
local grid
local function CreateGrid()
if grid then return grid end
grid = CreateFrame("Frame", nil, WorldFrame)
grid:SetAllPoints(WorldFrame)
grid:Hide()
local size = 1
local width = GetScreenWidth()
local height = GetScreenHeight()
local ratio = width / height
local adjustedHeight = height * ratio
local wStep = width / 64
local hStep = adjustedHeight / 64
for i = 0, 64 do
local line = grid:CreateTexture(nil, i == 32 and "BORDER" or "BACKGROUND")
if i == 32 then
line:SetTexture(.8, .6, 0)
else
line:SetTexture(0, 0, 0, .2)
end
line:SetPoint("TOPLEFT", grid, "TOPLEFT", i * wStep - (size / 2), 0)
line:SetPoint("BOTTOMRIGHT", grid, "BOTTOMLEFT", i * wStep + (size / 2), 0)
end
local rows = floor(height / hStep)
local middle = floor(rows / 2)
for i = 1, rows do
local line = grid:CreateTexture(nil, i == middle and "BORDER" or "BACKGROUND")
if i == middle then
line:SetTexture(.8, .6, 0)
else
line:SetTexture(0, 0, 0, .2)
end
line:SetPoint("TOPLEFT", grid, "TOPLEFT", 0, -(i * hStep) + (size / 2))
line:SetPoint("BOTTOMRIGHT", grid, "TOPRIGHT", 0, -(i * hStep + size / 2))
end
return grid
end
local function UnlockTarget(index, target)
local handle, moveFrame = Resolve(target)
if not handle or not moveFrame then return end
states[index] = states[index] or {}
local state = states[index]
if state.active then return end
state.active = true
state.dragged = false
state.handle = handle
state.moveFrame = moveFrame
state.onDragStart = handle:GetScript("OnDragStart")
state.onDragStop = handle:GetScript("OnDragStop")
if handle.IsMouseEnabled then
state.mouseEnabled = handle:IsMouseEnabled() and true or false
else
state.mouseEnabled = nil
end
if moveFrame.IsMovable then
state.movable = moveFrame:IsMovable() and true or false
else
state.movable = nil
end
if moveFrame.IsUserPlaced then
state.userPlaced = moveFrame:IsUserPlaced() and true or false
else
state.userPlaced = nil
end
moveFrame:SetMovable(true)
handle:EnableMouse(true)
handle:RegisterForDrag("LeftButton")
handle:SetScript("OnDragStart", function()
state.dragged = true
if moveFrame.SetUserPlaced then
moveFrame:SetUserPlaced(true)
end
moveFrame:StartMoving()
end)
handle:SetScript("OnDragStop", function()
moveFrame:StopMovingOrSizing()
if state.dragged then
SavePosition(target, moveFrame)
end
end)
end
local function LockTarget(index, target)
local state = states[index]
if not state or not state.active then return end
local handle = state.handle
local moveFrame = state.moveFrame
if moveFrame then
moveFrame:StopMovingOrSizing()
-- Only persist an anchor if the user actually dragged this frame. The
-- previous test version saved every frame whenever Ctrl+Shift was
-- released, which could turn untouched default-managed UI elements into
-- permanently absolute-positioned frames.
if state.dragged then
SavePosition(target, moveFrame)
end
if state.movable ~= nil then
moveFrame:SetMovable(state.movable)
end
if not state.dragged
and state.userPlaced ~= nil
and moveFrame.SetUserPlaced then
moveFrame:SetUserPlaced(state.userPlaced)
end
end
if handle then
handle:SetScript("OnDragStart", state.onDragStart)
handle:SetScript("OnDragStop", state.onDragStop)
if state.mouseEnabled ~= nil then
handle:EnableMouse(state.mouseEnabled)
end
end
state.active = false
end
local function UnlockAll()
if unlocked then return end
unlocked = true
for i, target in ipairs(targets) do
UnlockTarget(i, target)
end
CreateGrid():Show()
end
local function LockAll()
if not unlocked then return end
for i, target in ipairs(targets) do
LockTarget(i, target)
end
if grid then grid:Hide() end
unlocked = false
end
local function UpdateLockState()
if API.IsShiftKeyDown() and API.IsControlKeyDown() then
UnlockAll()
else
LockAll()
end
end
local events = CreateFrame("Frame")
events:RegisterEvent("PLAYER_ENTERING_WORLD")
if API.modifierstate then
events:RegisterEvent("MODIFIER_STATE_CHANGED")
end
events:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" then
for _, target in ipairs(targets) do
RestorePosition(target)
end
end
UpdateLockState()
end)
-- ClassicAPI supplies MODIFIER_STATE_CHANGED. Only old/fallback environments
-- use a throttled state check.
if not API.modifierstate then
events.elapsed = 0
events:SetScript("OnUpdate", function()
this.elapsed = this.elapsed + (arg1 or 0)
if this.elapsed < .10 then return end
this.elapsed = 0
UpdateLockState()
end)
end
for _, target in ipairs(targets) do
RestorePosition(target)
end
UpdateLockState()
end
+140
View File
@@ -0,0 +1,140 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local API = ShaguTweaks.API
local module = ShaguTweaks:register({
title = T["Unit Frame Abbreviated Names"],
description = T["Abbreviates long target and target-of-target names."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Unit Frames"],
enabled = nil,
})
module.enable = function(self)
local maxLength = 15
local cache = {}
local function AbbrevWord(word)
return string.sub(word, 1, 1) .. ". "
end
local function GetShortName(unit)
local name = UnitName(unit)
if not name then
cache[unit] = nil
return
end
local cached = cache[unit]
if cached and cached.raw == name then
return cached.short
end
local short = name
if strlen(short) > maxLength then
short = string.gsub(short, "^(%S+) ", AbbrevWord)
end
if strlen(short) > maxLength then
short = string.gsub(short, "(%S+) ", AbbrevWord)
end
cache[unit] = {
raw = name,
short = short,
}
return short
end
local function GetNameText(frame)
if not frame then return end
if frame.name then return frame.name end
if frame.GetName then
local frameName = frame:GetName()
if frameName then
return _G[frameName .. "Name"]
end
end
end
local function UpdateFrame(frame, unit)
local text = GetNameText(frame)
local name = GetShortName(unit)
if not text or not name then return end
-- Avoid forcing a FontString update when the displayed value is already
-- correct. This matters most for the legacy 250 ms target-of-target
-- fallback.
if not text.GetText or text:GetText() ~= name then
text:SetText(name)
end
end
local function UpdateTarget()
UpdateFrame(TargetFrame, "target")
end
local function UpdateTargetTarget()
if TargetofTargetFrame then
UpdateFrame(TargetofTargetFrame, "targettarget")
end
end
local function EventValid(name)
return API.eventutils
and _G.C_EventUtils
and _G.C_EventUtils.IsEventValid
and _G.C_EventUtils.IsEventValid(name)
end
local events = CreateFrame("Frame")
events:RegisterEvent("PLAYER_ENTERING_WORLD")
events:RegisterEvent("PLAYER_TARGET_CHANGED")
local hasUnitTarget = EventValid("UNIT_TARGET")
local hasUnitName = EventValid("UNIT_NAME_UPDATE")
if hasUnitTarget then events:RegisterEvent("UNIT_TARGET") end
if hasUnitName then events:RegisterEvent("UNIT_NAME_UPDATE") end
events:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_TARGET_CHANGED" then
UpdateTarget()
UpdateTargetTarget()
elseif event == "UNIT_TARGET" then
if arg1 == "target" then UpdateTargetTarget() end
elseif event == "UNIT_NAME_UPDATE" then
if arg1 == "target" then
UpdateTarget()
elseif arg1 == "targettarget" then
UpdateTargetTarget()
end
end
end)
-- ClassicAPI normally provides UNIT_TARGET. Keep a lightweight Vanilla
-- fallback only when that event isn't available instead of running every
-- rendered frame like the original module.
if not hasUnitTarget then
local fallback = CreateFrame("Frame")
fallback.elapsed = 0
fallback:SetScript("OnUpdate", function()
this.elapsed = this.elapsed + (arg1 or 0)
if this.elapsed < .25 then return end
this.elapsed = 0
if UnitName("targettarget") then
UpdateTargetTarget()
end
end)
end
UpdateTarget()
UpdateTargetTarget()
end