Skip to content

Commit 2ac0f89

Browse files
committed
Feature: Visbility | Custom Viewers
1 parent 57adb6d commit 2ac0f89

12 files changed

Lines changed: 1268 additions & 318 deletions

CHANGELOG.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
TavernUI Changelog
22
==================
3+
v1.0.0-beta-9 (2026-02-20)
4+
- Custom viewers
5+
- Visibility configuration
6+
- KNOWN issues
7+
- Saturation on custom spells even when they are availble is broken
8+
39
v1.0.0-beta-8a (2026-02-20)
410
- Fix some more scaling issues, pixel perfect?
511
- If you have issues try playing with your CDM scale

Localization/enUS.lua

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,8 @@ L["PICK_ACTION_SLOT_PROMPT"] = "Click an action bar slot to add it to the cooldo
371371
L["ACTION_SLOT_ADDED"] = "Added action bar slot %d."
372372
L["ACTION_SLOT_N"] = "Slot %d"
373373
L["ACTION_BAR_SLOT_FORMAT"] = "Bar %d - Slot %d"
374+
L["ACTION_BAR_N_SLOT_M"] = "Action Bar %d - Slot %d"
375+
L["ACTION_BAR_SLOT_WITH_NAME"] = "Action Bar %d - Slot %d [%s]"
374376
L["DEFAULT_VIEWER"] = "Default Viewer"
375377
L["DEFAULT_VIEWER_DESC"] = "Default viewer for new custom entries"
376378
L["ESSENTIAL_VIEWER"] = "Essential Viewer"
@@ -389,6 +391,60 @@ L["DISPLAY_ORDER_DESC"] = "Display order (lower = earlier)"
389391
L["VIEWER"] = "Viewer"
390392
L["Viewer"] = "Viewer"
391393
L["ASSIGN_ENTRY_TO_VIEWER_DESC"] = "Assign entry to viewer"
394+
395+
L["VISIBILITY"] = "Visibility"
396+
L["VISIBILITY_DESC"] = "When to show all CDM viewers. Tick the states where the bar should be visible; untick to hide in that state. Default: shown everywhere."
397+
L["VISIBILITY_COMBAT"] = "Combat"
398+
L["VISIBILITY_COMBAT_DESC"] = "Show only in combat, only out of combat, or both (leave both unchecked)."
399+
L["SHOW_IN_COMBAT"] = "Show in combat"
400+
L["SHOW_OUT_OF_COMBAT"] = "Show out of combat"
401+
L["VISIBILITY_TARGET"] = "Target"
402+
L["VISIBILITY_TARGET_DESC"] = "Show only when you have a target."
403+
L["SHOW_WHEN_TARGET_EXISTS"] = "Show when target exists"
404+
L["VISIBILITY_GROUP"] = "Group"
405+
L["VISIBILITY_GROUP_DESC"] = "Show only when in this group type. Leave all unchecked to show in any group."
406+
L["SHOW_WHEN_SOLO"] = "Show when solo"
407+
L["SHOW_WHEN_IN_PARTY"] = "Show when in party"
408+
L["SHOW_WHEN_IN_RAID"] = "Show when in raid"
409+
L["VISIBILITY_INSTANCE"] = "Instance type"
410+
L["VISIBILITY_INSTANCE_DESC"] = "Show only in this instance type. Leave all unchecked to show anywhere."
411+
L["SHOW_IN_OPEN_WORLD"] = "Show in open world"
412+
L["SHOW_IN_PARTY_INSTANCE"] = "Show in dungeon (party)"
413+
L["SHOW_IN_RAID_INSTANCE"] = "Show in raid"
414+
L["SHOW_IN_ARENA"] = "Show in arena"
415+
L["SHOW_IN_PVP"] = "Show in battleground"
416+
L["SHOW_IN_SCENARIO"] = "Show in scenario"
417+
L["VISIBILITY_ROLE"] = "Role"
418+
L["VISIBILITY_ROLE_DESC"] = "Show only when you have this role. Leave all unchecked to show in any role."
419+
L["SHOW_WHEN_TANK"] = "Show when tanking"
420+
L["SHOW_WHEN_HEALER"] = "Show when healing"
421+
L["SHOW_WHEN_DPS"] = "Show when DPS"
422+
L["VISIBILITY_HIDE_WHEN"] = "Hide when"
423+
L["VISIBILITY_HIDE_WHEN_DESC"] = "Hide all CDM viewers when these conditions are met."
424+
L["HIDE_WHEN_IN_VEHICLE"] = "Hide when in vehicle"
425+
L["HIDE_WHEN_MOUNTED"] = "Hide when mounted"
426+
L["HIDE_WHEN_MOUNTED_WHEN"] = "When (mounted)"
427+
L["HIDE_WHEN_MOUNTED_WHEN_DESC"] = "Both: hide whenever mounted (ground or air). Grounded: hide only when mounted on the ground. Flying: hide only when mounted and flying."
428+
L["VISIBILITY_WHEN_BOTH"] = "Both"
429+
L["VISIBILITY_WHEN_ALWAYS"] = "All"
430+
L["VISIBILITY_WHEN_GROUNDED"] = "Grounded"
431+
L["VISIBILITY_WHEN_FLYING"] = "Flying"
432+
L["VISIBILITY_WHEN_MOUNTED"] = "Only when mounted"
433+
L["VISIBILITY_WHEN_NOT_MOUNTED"] = "Only when not mounted"
434+
435+
L["PREVIEW"] = "Preview"
436+
L["SHOW_PREVIEW"] = "Show preview"
437+
L["SHOW_PREVIEW_DESC"] = "Show a row of placeholder icons so you can see the layout when no buffs are active."
438+
L["PREVIEW_ICON_COUNT"] = "Preview icon count"
439+
L["PREVIEW_ICON_COUNT_DESC"] = "Number of placeholder icons to show in the preview row."
440+
441+
L["MY_VIEWERS"] = "My Viewers"
442+
L["MY_VIEWERS_DESC"] = "Create custom viewers, move them in Edit Mode, and add spells or items to them."
443+
L["ADD_VIEWER"] = "Add viewer"
444+
L["VIEWER_NAME"] = "Viewer name"
445+
L["REMOVE_VIEWER"] = "Remove viewer"
446+
L["UNKNOWN"] = "Unknown"
447+
392448
L["VIEWER_SCALE_DESC"] = "Scale the entire viewer and its contents"
393449

394450
L["Anchoring"] = "Anchoring"

Modules/uCDM/uCDM.lua

Lines changed: 175 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ local defaults = {
3939
combat = 0.3,
4040
initial = 0.05,
4141
},
42+
visibility = {
43+
combat = { showInCombat = true, showOutOfCombat = true },
44+
target = { showWhenTargetExists = false },
45+
group = { showSolo = true, showParty = true, showRaid = true },
46+
hideWhenInVehicle = false,
47+
hideWhenMounted = false,
48+
hideWhenMountedWhen = "both",
49+
},
4250
},
4351
viewers = {
4452
essential = {
@@ -135,6 +143,8 @@ local defaults = {
135143
buff = {
136144
enabled = true,
137145
scale = 1.0,
146+
showPreview = false,
147+
previewIconCount = 6,
138148
anchorConfig = {
139149
target = "TavernUI.uCDM.essential",
140150
point = "BOTTOM",
@@ -191,12 +201,12 @@ local defaults = {
191201
rows = {
192202
{
193203
name = "Default",
194-
iconCount = 4,
195-
iconSize = 40,
204+
iconCount = 8,
205+
iconSize = 38,
196206
padding = 4,
197207
yOffset = 0,
198208
aspectRatioCrop = 1.0,
199-
zoom = 0,
209+
zoom = 0.08,
200210
iconBorderSize = 1,
201211
iconBorderColor = {r = 0, g = 0, b = 0, a = 1},
202212
rowBorderSize = 0,
@@ -214,14 +224,67 @@ local defaults = {
214224
},
215225
},
216226
customEntries = {},
227+
customViewers = {},
217228
positions = {},
218229
}
219230

220231
TavernUI:RegisterModuleDefaults("uCDM", defaults, true)
221232

233+
local function CopyTableShallow(src)
234+
if type(src) ~= "table" then return src end
235+
local t = {}
236+
for k, v in pairs(src) do
237+
t[k] = (type(v) == "table" and v ~= src) and CopyTableShallow(v) or v
238+
end
239+
return t
240+
end
241+
242+
local DEFAULT_CUSTOM_VIEWER_SETTINGS = {
243+
enabled = true,
244+
scale = 1.0,
245+
anchorConfig = nil,
246+
rowGrowDirection = "down",
247+
rowSpacing = 5,
248+
showKeybinds = false,
249+
keybindSize = 10,
250+
keybindPoint = "TOPLEFT",
251+
keybindOffsetX = 2,
252+
keybindOffsetY = -2,
253+
keybindColor = {r = 1, g = 1, b = 1, a = 1},
254+
disableTooltips = false,
255+
rows = {
256+
{
257+
name = "Default",
258+
iconCount = 8,
259+
iconSize = 38,
260+
padding = 4,
261+
yOffset = 0,
262+
aspectRatioCrop = 1.0,
263+
zoom = 0.08,
264+
iconBorderSize = 1,
265+
iconBorderColor = {r = 0, g = 0, b = 0, a = 1},
266+
rowBorderSize = 0,
267+
rowBorderColor = {r = 0, g = 0, b = 0, a = 1},
268+
durationSize = 18,
269+
durationPoint = "CENTER",
270+
durationOffsetX = 0,
271+
durationOffsetY = 0,
272+
stackSize = 16,
273+
stackPoint = "BOTTOMRIGHT",
274+
stackOffsetX = 0,
275+
stackOffsetY = 0,
276+
},
277+
},
278+
}
279+
280+
function module:GetDefaultCustomViewerSettings()
281+
return CopyTableShallow(DEFAULT_CUSTOM_VIEWER_SETTINGS)
282+
end
283+
222284
function module:OnInitialize()
223285
pcall(function() SetCVar("cooldownViewerEnabled", 1) end)
224286
self:RegisterMessage("TavernUI_ProfileChanged", "OnProfileChanged")
287+
self.CustomViewerFrames = {}
225288

226289
-- Initialize subsystems in order
227290
if self.ItemRegistry then self.ItemRegistry.Initialize() end
@@ -271,9 +334,13 @@ end
271334

272335
function module:OnDisable()
273336
self.__onEnableCalled = nil
337+
if self._visibilityCallbackId and TavernUI.Visibility and TavernUI.Visibility.UnregisterCallback then
338+
TavernUI.Visibility.UnregisterCallback(self._visibilityCallbackId)
339+
self._visibilityCallbackId = nil
340+
end
274341
self:UnregisterAllEvents()
275342
self:StopUpdateLoop()
276-
343+
277344
if self.ItemRegistry then
278345
self.ItemRegistry.Reset()
279346
end
@@ -328,11 +395,18 @@ end
328395

329396
function module:Update()
330397
if not self:IsEnabled() then return end
331-
332-
-- Update ALL items (we now control all frames)
398+
333399
if self.ItemRegistry then
334400
for _, viewerKey in ipairs(CONSTANTS.VIEWER_KEYS) do
335-
local items = self.ItemRegistry.GetItemsForViewer(viewerKey)
401+
if viewerKey ~= "custom" then
402+
local items = self.ItemRegistry.GetItemsForViewer(viewerKey)
403+
for _, item in ipairs(items) do
404+
item:update()
405+
end
406+
end
407+
end
408+
for _, id in ipairs(self:GetCustomViewerIds()) do
409+
local items = self.ItemRegistry.GetItemsForViewer(id)
336410
for _, item in ipairs(items) do
337411
item:update()
338412
end
@@ -343,16 +417,32 @@ end
343417
function module:OnPlayerEnteringWorld()
344418
if not self:IsEnabled() then return end
345419
self:StartUpdateLoop()
420+
if TavernUI.Visibility and TavernUI.Visibility.RegisterCallback and not self._visibilityCallbackId then
421+
self._visibilityCallbackId = TavernUI.Visibility.RegisterCallback(function()
422+
if self:IsEnabled() then self:RefreshAllViewers() end
423+
end)
424+
end
346425

347-
local reg = self.ItemRegistry
348-
if reg then
349-
reg.HookBlizzardViewers()
350-
reg.LoadCustomEntries()
351-
for _, viewerKey in ipairs({"essential", "utility", "buff"}) do
352-
reg.CollectBlizzardItems(viewerKey)
426+
C_Timer.After(0, function()
427+
if not self:IsEnabled() then return end
428+
for _, entry in ipairs(self:GetSetting("customViewers", {})) do
429+
if entry and entry.id and not self.CustomViewerFrames[entry.id] then
430+
self:CreateCustomViewerFrame(entry.id, entry.name)
431+
end
353432
end
354-
end
355-
self:RefreshAllViewers()
433+
if self.Anchoring and self.Anchoring.RegisterAnchors then
434+
self.Anchoring.RegisterAnchors()
435+
end
436+
local reg = self.ItemRegistry
437+
if reg then
438+
reg.HookBlizzardViewers()
439+
reg.LoadCustomEntries()
440+
for _, viewerKey in ipairs({"essential", "utility", "buff"}) do
441+
reg.CollectBlizzardItems(viewerKey)
442+
end
443+
end
444+
self:RefreshAllViewers()
445+
end)
356446
end
357447

358448
function module:OnEquipmentChanged()
@@ -402,16 +492,31 @@ function module:HandleEnabledChange(newValue, oldValue)
402492
if newValue then
403493
if self:IsEnabled() then
404494
self:StartUpdateLoop()
495+
if TavernUI.Visibility and TavernUI.Visibility.RegisterCallback and not self._visibilityCallbackId then
496+
self._visibilityCallbackId = TavernUI.Visibility.RegisterCallback(function()
497+
if self:IsEnabled() then self:RefreshAllViewers() end
498+
end)
499+
end
405500
self:RefreshAllViewers()
406501
end
407502
else
503+
if self._visibilityCallbackId and TavernUI.Visibility and TavernUI.Visibility.UnregisterCallback then
504+
TavernUI.Visibility.UnregisterCallback(self._visibilityCallbackId)
505+
self._visibilityCallbackId = nil
506+
end
408507
self:StopUpdateLoop()
409508
for _, viewerKey in ipairs(CONSTANTS.VIEWER_KEYS) do
410509
local viewer = self:GetViewerFrame(viewerKey)
411510
if viewer then
412511
viewer:Hide()
413512
end
414513
end
514+
for _, id in ipairs(self:GetCustomViewerIds()) do
515+
local viewer = self:GetViewerFrame(id)
516+
if viewer then
517+
viewer:Hide()
518+
end
519+
end
415520
end
416521
end
417522

@@ -421,7 +526,12 @@ local REFRESH_DEBOUNCE_SEC = 0.15
421526

422527
function module:RefreshAllViewers()
423528
for _, viewerKey in ipairs(CONSTANTS.VIEWER_KEYS) do
424-
self:RefreshViewer(viewerKey)
529+
if viewerKey ~= "custom" then
530+
self:RefreshViewer(viewerKey)
531+
end
532+
end
533+
for _, id in ipairs(self:GetCustomViewerIds()) do
534+
self:RefreshViewer(id)
425535
end
426536
end
427537

@@ -438,10 +548,11 @@ function module:RefreshViewer(viewerKey)
438548
refreshTimers[viewerKey] = nil
439549
if not self:IsEnabled() then return end
440550

441-
if viewerKey ~= "custom" and self.ItemRegistry then
551+
local skipBlizzard = (viewerKey == "custom") or self:IsCustomViewerId(viewerKey)
552+
if not skipBlizzard and self.ItemRegistry then
442553
self.ItemRegistry.CollectBlizzardItems(viewerKey)
443554
end
444-
555+
445556
if self.LayoutEngine then
446557
self.LayoutEngine.RefreshViewer(viewerKey)
447558
end
@@ -488,7 +599,52 @@ function module:GetViewerSettings(viewerKey)
488599
end
489600

490601
function module:GetViewerFrame(viewerKey)
491-
return _G[CONSTANTS.VIEWER_NAMES[viewerKey]]
602+
if CONSTANTS.VIEWER_NAMES[viewerKey] then
603+
return _G[CONSTANTS.VIEWER_NAMES[viewerKey]]
604+
end
605+
if self:IsCustomViewerId(viewerKey) and self.CustomViewerFrames then
606+
return self.CustomViewerFrames[viewerKey]
607+
end
608+
return nil
609+
end
610+
611+
function module:CreateCustomViewerFrame(id, name)
612+
if self.CustomViewerManager and self.CustomViewerManager.CreateCustomViewerFrame then
613+
return self.CustomViewerManager.CreateCustomViewerFrame(self, id, name)
614+
end
615+
end
616+
617+
function module:RemoveCustomViewer(id)
618+
if self.CustomViewerManager and self.CustomViewerManager.RemoveCustomViewer then
619+
self.CustomViewerManager.RemoveCustomViewer(self, id)
620+
end
621+
end
622+
623+
function module:SetCustomViewerName(id, name)
624+
if self.CustomViewerManager and self.CustomViewerManager.SetCustomViewerName then
625+
self.CustomViewerManager.SetCustomViewerName(self, id, name)
626+
end
627+
end
628+
629+
function module:GetCustomViewerIds()
630+
if self.CustomViewerManager and self.CustomViewerManager.GetCustomViewerIds then
631+
return self.CustomViewerManager.GetCustomViewerIds(self)
632+
end
633+
return {}
634+
end
635+
636+
function module:IsCustomViewerId(viewerKey)
637+
if self.CustomViewerManager and self.CustomViewerManager.IsCustomViewerId then
638+
return self.CustomViewerManager.IsCustomViewerId(self, viewerKey)
639+
end
640+
return false
641+
end
642+
643+
function module:GetCustomViewerDisplayName(viewerKey)
644+
if self.CustomViewerManager and self.CustomViewerManager.GetCustomViewerDisplayName then
645+
return self.CustomViewerManager.GetCustomViewerDisplayName(self, viewerKey)
646+
end
647+
return viewerKey
492648
end
493649

494650
function module:IsEnabled()

Modules/uCDM/uCDM.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
22
<!-- Core module definition -->
33
<Script file="uCDM.lua"/>
4+
<Script file="uCDM_CustomViewerManager.lua"/>
45

56
<!-- Cooldown tracking (helpers first, then tracker) -->
67
<Script file="uCDM_CooldownTrackerHelpers.lua"/>
@@ -12,6 +13,9 @@
1213
<!-- Item management (uses CooldownItem) -->
1314
<Script file="uCDM_ItemRegistry.lua"/>
1415

16+
<!-- Buff preview (placeholder icons when no buffs) -->
17+
<Script file="uCDM_Preview.lua"/>
18+
1519
<!-- Layout and positioning (uses ItemRegistry) -->
1620
<Script file="uCDM_LayoutEngine.lua"/>
1721

0 commit comments

Comments
 (0)