Blizzard Damage Meter — Frame Reference
Captured 2026-05-02 from WoW Midnight 12.0.x source (
Interface/AddOns/Blizzard_DamageMeter/) via in-game framestack capture and cross-referenced against the Gethe/wow-ui-source mirror. Update this file if Blizzard restructures the meter in a future patch.
TOC + load order
## Title: Blizzard_DamageMeter
## SavedVariablesPerCharacter: DamageMeterPerCharacterSettings
## Dependencies: Blizzard_EditMode
DamageMeterConstants.lua
DamageMeterEntry.lua / DamageMeterEntry.xml
DamageMeterSettingsDropdownButton.lua / .xml
DamageMeterSourceWindow.lua / .xml
DamageMeterSessionWindow.lua / .xml
DamageMeter.lua / DamageMeter.xml
ADDON_LOADED arg matches Blizzard_DamageMeter. Hard-depends on Blizzard_EditMode (the meter is an Edit Mode “system” frame).
Activation CVar
damageMeterEnabled — boolean cached in CVarCallbackRegistry. Toggling Options → Gameplay Enhancements → Damage Meter flips it. Sample:
CVarCallbackRegistry:GetCVarValueBool("damageMeterEnabled") -- true if user enabled the meter
DamageMeterMixin:ShouldBeShown() reads this CVar and C_DamageMeter.IsDamageMeterAvailable() to decide visibility. Returns true unconditionally when IsEditing is set, so the meter forcibly appears during Blizzard Edit Mode.
Frame hierarchy
Manager (singleton)
- Global:
_G.DamageMeter - Mixin:
DamageMeterMixin - Inherits:
EditModeDamageMeterSystemTemplate(Edit Mode system frame — hasOnSystemLoad,IsEditing,SetIsEditing, etc.) - Parent:
UIParent - Holds:
windowDataListarray[{ damageMeterType, sessionType, sessionID, sessionWindow, locked, nonInteractive, minimized }] - Created at addon load via
<Frame name="DamageMeter" inherits="DamageMeterTemplate" parent="UIParent"> - Max session windows: 3 (
MAX_DAMAGE_METER_SESSION_WINDOWS = 3) - Primary window index: 1 (
PRIMARY_SESSION_WINDOW_INDEX = 1)
Session windows
- Globals:
_G.DamageMeterSessionWindow1,…Window2,…Window3(created on demand byDamageMeterMixin:SetupSessionWindow). - Template:
DamageMeterSessionWindowTemplate - Mixin:
DamageMeterSessionWindowMixin - Parent:
_G.DamageMeter - Resizable: yes (300×120 to 600×400)
- Primary window: anchored TOPLEFT/BOTTOMRIGHT to
_G.DamageMeter; positioned via Edit Mode only. - Secondary windows: mouse-movable / mouse-resizable when not locked.
Session window children (read paths)
DamageMeterSessionWindow<N>
├─ Header -- BACKGROUND layer texture, atlas "ui-damagemeters-header-bar"
├─ SessionTimer -- OVERLAY layer fontstring (GameFontNormalMed1)
├─ MinimizeButton -- Button (atlas "ui-questtrackerbutton-collapse-all")
├─ SettingsDropdown -- DropdownButton (DamageMeterSettingsDropdownButtonTemplate)
├─ SessionDropdown -- DropdownButton (WowStyle2DropdownTemplate)
│ └─ SessionName -- OVERLAY fontstring; the "O"/"C"/prior pill text
├─ DamageMeterTypeDropdown -- DropdownButton (WowStyle1ArrowDropdownTemplate)
│ └─ TypeName -- OVERLAY fontstring (e.g. "Damage Done", "DPS")
└─ MinimizeContainer -- Frame (setAllPoints, useParentLevel)
├─ Background -- BACKGROUND atlas "damagemeters-background", initial alpha=0
├─ NotActive -- OVERLAY fontstring (shown when no data)
├─ ScrollBox -- WowScrollBoxList (the meter's row list)
├─ ScrollBar -- EventFrame, MinimalScrollBar, alpha 0.3 default
├─ ResizeButton -- Button, atlas "damagemeters-scalehandle"
├─ LocalPlayerEntry -- Button (DamageMeterSourceEntryTemplate), shown when local player scrolls out of view
└─ SourceWindow -- Frame (DamageMeterSourceWindowTemplate) — the BREAKDOWN POPUP
Convenience accessors on DamageMeterSessionWindowMixin
sessionWindow:GetMinimizeContainer() -- the MinimizeContainer frame
sessionWindow:GetScrollBox() -- WowScrollBoxList for entry frames
sessionWindow:GetScrollBar()
sessionWindow:GetSourceWindow() -- the breakdown popup frame
sessionWindow:GetHeader() -- the Header texture
sessionWindow:GetMinimizeButton()
sessionWindow:GetSettingsDropdown()
sessionWindow:GetSessionDropdown()
sessionWindow:GetDamageMeterTypeDropdown()
sessionWindow:GetLocalPlayerEntry()
sessionWindow:GetResizeButton()
sessionWindow:GetBackground() -- MinimizeContainer.Background
sessionWindow:GetNotActiveFontString()
sessionWindow:GetSessionTimerFontString()
sessionWindow:EnumerateEntryFrames() -- iterator over visible row frames
sessionWindow:ForEachEntryFrame(func) -- callback invoked per row
sessionWindow:GetEntryFrameCount()
Source window (breakdown popup)
- Path:
sessionWindow.MinimizeContainer.SourceWindow(NOT a separate global) - Template:
DamageMeterSourceWindowTemplate - Mixin:
DamageMeterSourceWindowMixin - Frame strata:
HIGH - Created as a child of
MinimizeContainerper session window. Hidden by default; shown when user clicks a row →DamageMeterSessionWindowMixin:ShowSourceWindow(source). - Children:
SourceWindow ├─ Background -- BACKGROUND atlas "common-dropdown-bg" ├─ ScrollBox -- WowScrollBoxList, rows use DamageMeterSpellEntryTemplate ├─ ScrollBar -- EventFrame, MinimalScrollBar └─ ResizeButton -- Button, atlas "damagemeters-scalehandle" - Auto-closes on
GLOBAL_MOUSE_DOWNoutside the popup.
Row entries
Three templates, all inheriting DamageMeterEntryTemplate:
| Template | Mixin | Used for |
|---|---|---|
DamageMeterEntryTemplate |
DamageMeterEntryMixin |
base |
DamageMeterSourceEntryTemplate |
DamageMeterSourceEntryMixin |
session window rows (one per source) |
DamageMeterSpellEntryTemplate |
DamageMeterSpellEntryMixin |
breakdown popup rows (one per ability) |
Row child structure
<row Button> -- frame name auto-generated (e.g. "IRB0347abf0")
├─ Icon -- Frame, 24×24
│ └─ Icon -- ARTWORK Texture, parent's `.Icon` atlas/texture (class or spec icon)
└─ StatusBar -- StatusBar
│ <BarTexture atlas="UI-HUD-CoolDownManager-Bar" setAllPoints="true"/>
├─ Background -- BACKGROUND Texture, atlas "ui-damagemeters-bar-shadowbg"
├─ BackgroundEdge -- OVERLAY Texture, atlas "ui-damagemeters-bar-shadowedge"
├─ Value -- OVERLAY FontString (NumberFontNormal, left-justify)
└─ Name -- OVERLAY FontString (NumberFontNormal, left-justify)
StatusBar.Background and StatusBar.BackgroundEdge are tracked together via StatusBar.BackgroundRegions = { Background, BackgroundEdge }.
Row mixin accessors
row:GetIcon() -- the .Icon.Icon texture
row:GetStatusBar() -- the StatusBar frame
row:GetStatusBarTexture() -- StatusBar:GetStatusBarTexture() (the BarTexture)
row:GetName() -- StatusBar.Name fontstring
row:GetValue() -- StatusBar.Value fontstring
row:GetBackground() -- StatusBar.Background texture
row:GetBackgroundEdge() -- StatusBar.BackgroundEdge texture
row:GetBackgroundRegions() -- { Background, BackgroundEdge }
row:GetBackgroundAlpha() -- value Blizzard wants applied
row:GetBackgroundRegionAlpha() -- alpha currently applied to background regions
Row state writes (hook-target candidates)
DamageMeterEntryMixin:Init(source) -- new data assigned, all updaters fire
DamageMeterEntryMixin:UpdateIcon()
DamageMeterEntryMixin:UpdateName()
DamageMeterEntryMixin:UpdateValue()
DamageMeterEntryMixin:UpdateStatusBar()
DamageMeterEntryMixin:UpdateStatusBarColor() -- sets vertex color on bar texture (CLASS COLOR PATH)
DamageMeterEntryMixin:UpdateStyle() -- re-anchors based on Enum.DamageMeterStyle
DamageMeterEntryMixin:UpdateBackground() -- re-asserts alpha on BackgroundRegions
DamageMeterEntryMixin:SetUseClassColor(bool)
DamageMeterEntryMixin:SetStyle(style)
DamageMeterEntryMixin:SetBackgroundAlpha(alpha)
Class colors — IMPORTANT GOTCHA
Class color is applied as a vertex color on the bar texture, not via SetStatusBarColor. From DamageMeterEntryMixin:SetStatusBarColor:
function DamageMeterEntryMixin:SetStatusBarColor(color)
if color ~= self.statusBarColor then
self.statusBarColor = color;
self:GetStatusBarTexture():SetVertexColor(color:GetRGB());
end
end
Implication for our skin: if we replace the StatusBar’s texture with Helpers.GetGeneralTexture(), the new texture inherits the previous vertex color until Blizzard’s next UpdateStatusBarColor call. Two safe patterns:
- Don’t swap the bar texture — the stock atlas
UI-HUD-CoolDownManager-Baris essentially flat, so leaving it alone preserves class colors automatically. Just hideStatusBar.BackgroundandStatusBar.BackgroundEdgeto remove the chrome around it. - Swap the bar texture and re-tint — call
:SetStatusBarTexture(QUI_TEXTURE)then immediately:SetVertexColor(currentR, currentG, currentB, currentA)reading from:GetStatusBarColor(). Re-hookUpdateStatusBarColorto keep the tint in sync when class color changes (e.g. user toggles class colors off via Edit Mode).
The simpler choice is option 1 (leave the texture alone). The Blizzard atlas is a flat rectangle, not gradient; visually it matches QUI’s WHITE8x8 once the chrome is stripped.
Style enum (4 layouts)
Enum.DamageMeterStyle:
| Value | Setup function | Background atlas |
|---|---|---|
Default |
SetupDefaultStyle |
ui-damagemeters-bar-shadowbg |
Bordered |
SetupBorderedStyle (alias of Default w/ different atlas) |
UI-HUD-CoolDownManager-Bar-BG |
FullBackground |
SetupFullBackgroundStyle (alias of Default) |
ui-damagemeters-bar-shadowbg |
Thin |
SetupThinStyle (text above bar) |
ui-damagemeters-bar-shadowbg |
User picks via Edit Mode. Our skin must work across all four. UpdateStyle() re-anchors and re-applies the style; it’s called by SetStyle(). Hooking UpdateStyle() post-call is the right point to re-strip our chrome and re-apply our row backdrop sizes.
UpdateBackground() re-asserts :SetAlpha(alpha) on every region in BackgroundRegions. To keep the stock backgrounds hidden we hook UpdateBackground and immediately set them back to alpha 0.
Atlases used (chrome to strip)
| Region | Atlas |
|---|---|
| Header (per session window) | ui-damagemeters-header-bar |
| MinimizeContainer.Background | damagemeters-background (initial alpha=0 in XML, can be raised by SetBackgroundAlpha) |
| StatusBar.Background | ui-damagemeters-bar-shadowbg (Default/FullBg/Thin) or UI-HUD-CoolDownManager-Bar-BG (Bordered) |
| StatusBar.BackgroundEdge | ui-damagemeters-bar-shadowedge |
| MinimizeContainer.ResizeButton | damagemeters-scalehandle (+ -hover, -pressed) |
| Source window Background | common-dropdown-bg |
| MinimizeButton | ui-questtrackerbutton-collapse-all (toggles to expand-all when minimized) |
Edit Mode integration
The meter inherits EditModeDamageMeterSystemTemplate and registers with the Edit Mode framework via EditModeDamageMeterSystemMixin.OnSystemLoad(self) (called from DamageMeterMixin:OnLoad).
Edit Mode controls these meter properties via the system framework:
- Bar height (
SetBarHeight) - Text scale (
SetTextScale) - Window alpha (
SetWindowAlpha) - Background alpha (
SetBackgroundAlpha) - Bar spacing (
SetBarSpacing) - Class colors on/off (
SetUseClassColor) - Show bar icons (
SetShowBarIcons) - Style (
SetStyle) - Number display type (
SetNumberDisplayType) - Position / size of the primary window only
DamageMeterMixin:SetIsEditing(true) forces the meter visible (ShouldBeShown returns true when editing). This means our suppression-during-Blizzard-Edit-Mode approach must override Blizzard’s force-show. :Hide() on each session window after Blizzard’s Edit Mode handler runs is the cleanest path. Order matters: hook EnterEditMode and call :Hide() after Blizzard finishes its work.
API for the future “Take Over” phase (C_DamageMeter)
Blizzard exposes these — useful for Stage 3 when QUI builds its own meter window backed by Blizzard’s data feed:
C_DamageMeter.IsDamageMeterAvailable() -- (isAvailable, failureReason)
C_DamageMeter.GetSessionDurationSeconds(sessionType) -- elapsed seconds for current/overall session
C_DamageMeter.GetCombatSessionFromType(sessionType, type) -- {combatSources, maxAmount, totalAmount, durationSeconds, ...}
C_DamageMeter.GetCombatSessionFromID(sessionID, type)
C_DamageMeter.GetAvailableCombatSessions() -- { {sessionID, name, durationSeconds}, ... }
C_DamageMeter.ResetAllCombatSessions()
Events:
DAMAGE_METER_COMBAT_SESSION_UPDATED(args:damageMeterType,sessionID)DAMAGE_METER_RESETDAMAGE_METER_CURRENT_SESSION_UPDATED
Enums:
Enum.DamageMeterType.{DamageDone, Dps, DamageTaken, AvoidableDamageTaken, EnemyDamageTaken, HealingDone, Hps, Absorbs, Interrupts, Dispels, Deaths}Enum.DamageMeterSessionType.{Overall, Current, Expired}Enum.DamageMeterStyle.{Default, Bordered, FullBackground, Thin}Enum.DamageMeterNumbers.{Minimal, Compact, Complete}Enum.DamageMeterVisibility.{Always, InCombat, Hidden}
Saved variables
DamageMeterPerCharacterSettings:
{
windowDataList = {
[1] = { damageMeterType, sessionType, shown, locked, nonInteractive, minimized },
[2] = { ... },
[3] = { ... },
},
}
Hook targets summary
For QUI’s skin module, these are the recommended hooksecurefunc targets:
| Target | Purpose |
|---|---|
_G.DamageMeter:CreateWindowData(idx) (post-call) |
Discover newly-created session windows |
_G.DamageMeter:SetupSessionWindow(idx, data) |
Discover when an existing window slot gets a window assigned |
_G.DamageMeter:OnUseClassColorChanged(useClassColor) |
Notice when user toggles class colors via Edit Mode |
_G.DamageMeter:OnStyleChanged(style) |
Notice when user changes layout style |
_G.DamageMeter:OnShowBarIconsChanged(showBarIcons) |
Notice icon-visibility changes |
DamageMeterSessionWindowMixin.SetupEntry |
Per-row first-time setup; ideal point to apply our row skin |
DamageMeterSessionWindowMixin.InitEntry |
Per-row data refresh; cheap re-skin point |
DamageMeterSessionWindowMixin.OnUseClassColorChanged |
Per-window class-color refresh |
DamageMeterSessionWindowMixin.UpdateBackground (via OnBackgroundAlphaChanged) |
Re-apply our stripped backgrounds |
DamageMeterSessionWindowMixin.ShowSourceWindow |
Detect and skin breakdown popup on first show |
DamageMeterEntryMixin.UpdateStyle |
Per-row style change; re-strip backgrounds + re-anchor our row backdrop |
DamageMeterEntryMixin.UpdateBackground |
Per-row background-alpha re-assertion; re-strip |
DamageMeterEntryMixin.UpdateStatusBarColor |
Class-color writes; only need if we swap the bar texture |
EditModeManagerFrame.EnterEditMode (post-call) |
Hide all session windows |
EditModeManagerFrame.ExitEditMode (post-call) |
Show all session windows that should be shown |
Mixin-level hooksecurefunc(SomeMixin, "Method", fn) works because session windows / row buttons are created from these mixins; our hook fires on every instance.
Confirmed working assumptions
_G.DamageMeteris the singleton manager,parent=UIParent,frameLevel1 by default.- Session windows are
_G.DamageMeterSessionWindow1..3— at most 3 ever exist. - Primary window is always
DamageMeterSessionWindow1; it can never be hidden by user action. - Secondary windows are mouse-movable / mouse-resizable when not locked.
- Every session window includes a SourceWindow (breakdown popup) as a child of MinimizeContainer.
- Rows are pooled WowScrollBoxList children; access via
sessionWindow:ForEachEntryFrame(fn)orsessionWindow:EnumerateEntryFrames(). - Class-color path: bar texture vertex color, NOT
SetStatusBarColor. - Edit Mode integration is mandatory (
Dependencies: Blizzard_EditMode).
Open questions / surprises
- Tooltip type:
DamageMeterSpellEntryMixin:InitusesGetAppropriateTooltip()and callstooltip:SetSpellByID. This returns the standardGameTooltipin normal context, so our existing tooltip skin inskinning/system/tooltips.luacovers it. - Animations:
MinimizeContainer.ShowResizeButtonandEmphasizeScrollBarare AnimationGroups that play on mouse-over. We don’t need to interact with these; they don’t affect skin. - The meter forces visibility during Edit Mode even with our skin off. Our skin’s
:Hide()afterEnterEditModewill fight this — but since QUI takes positioning, that’s the desired outcome. damageMeterEnabledCVar is the only signal for “is the meter on right now”.ADDON_LOADED("Blizzard_DamageMeter")fires once when the meter addon is enabled at character creation/character switch — but the meter addon may load even when the user has the toggle off. UseIsBlizzardMeterEnabled = function() return CVarCallbackRegistry:GetCVarValueBool("damageMeterEnabled") endinstead of frame existence.
How to update this doc
- After any patch where the meter visibly changes, re-run
tools/dump_damage_meter.luain-game. - Pull current source via
gh api "repos/Gethe/wow-ui-source/contents/Interface/AddOns/Blizzard_DamageMeter?ref=live". - Diff against the captured frame names and accessor methods listed above.
- Update the “Hook targets summary” if Blizzard added or renamed mixin methods.