mirror of
https://github.com/fuffc/WeakestAuras.git
synced 2026-11-10 20:07:35 +00:00
ClassicAPI 1.18 is now the only requirement Requirements - Needs ClassicAPI 1.18.0 or newer. SuperWoW is no longer used, and Nampower is optional. - On an older ClassicAPI the addon stays off, and a popup links the download. - Custom triggers on SuperWoW or Nampower events only fire while that mod is installed. Triggers - New Combat Log trigger, with lists of spell IDs, spell names and NPC IDs. WeakAuras2 auras that use one now import. - Custom triggers can listen to the combat log with CLEU or CLEU:SUBEVENT. - Crowd Control matches a specific type (stun, fear, root, silence, disarm and more) or a school lockout, with the effect's real timer. - Saved Crowd Control auras: Root, Silence and Disarm filters carry over; CC and Snare become "any type", and snares are no longer detected. - Cast bars follow pushback and channel updates. Displays - Icon cooldown swipes are drawn by the client's own cooldown frame. - Sound actions play on a chosen channel, and a new Stop sound type ends a looping sound. Fixes - Cooldown auras no longer sweep on every wand shot. - The Global Cooldown trigger ignores wand and Auto Shot shots.
344 lines
15 KiB
Lua
344 lines
15 KiB
Lua
-- WeakestAuras -- the shared font block: the curated font list, the addon's one
|
|
-- SetFont call site, and the BuildOptions rows that drive them. Mirrors the font
|
|
-- half of WA2's Text region (§7).
|
|
-- Upstream section refs (§N) point at design/architecture/weakauras2-reference.md
|
|
--
|
|
-- A consumer keeps its settings as flat keys under its own schema and hands the
|
|
-- table plus a prefix and alias map to Apply; every key is optional, and the
|
|
-- defaults below are what a FontString does with no font block at all.
|
|
|
|
if WeakestAuras.disabled then return end
|
|
|
|
local WA = WeakestAuras
|
|
|
|
local textCore = {}
|
|
WA.textCore = textCore
|
|
|
|
-- No LibSharedMedia here, so the picker is a fixed list and what gets *stored*
|
|
-- is the path, not the name: there is no registry to resolve a name against
|
|
-- later, so renaming an entry would leave every aura holding it with no font.
|
|
--
|
|
-- The bundled faces carry WA2's LibSharedMedia names verbatim, because that is
|
|
-- what a WeakAuras export stores in text_font and WA2Import resolves an incoming
|
|
-- name against this list -- renaming one here silently strips the font off every
|
|
-- aura imported afterwards.
|
|
textCore.FONTS = {
|
|
{ name = "Friz Quadrata", path = "Fonts\\FRIZQT__.TTF" },
|
|
{ name = "Arial Narrow", path = "Fonts\\ARIALN.TTF" },
|
|
{ name = "Morpheus", path = "Fonts\\MORPHEUS.TTF" },
|
|
{ name = "Skurri", path = "Fonts\\SKURRI.TTF" },
|
|
{ name = "Roboto Mono", path = "Interface\\AddOns\\WeakestAuras\\fonts\\RobotoMono.ttf" },
|
|
{ name = "Fira Mono Medium", path = "Interface\\AddOns\\WeakestAuras\\fonts\\FiraMono-Medium.ttf" },
|
|
{ name = "Fira Sans Black", path = "Interface\\AddOns\\WeakestAuras\\fonts\\FiraSans-Heavy.ttf" },
|
|
{ name = "Fira Sans Medium", path = "Interface\\AddOns\\WeakestAuras\\fonts\\FiraSans-Medium.ttf" },
|
|
{ name = "Fira Sans Condensed Black", path = "Interface\\AddOns\\WeakestAuras\\fonts\\FiraSansCondensed-Heavy.ttf" },
|
|
{ name = "Fira Sans Condensed Medium", path = "Interface\\AddOns\\WeakestAuras\\fonts\\FiraSansCondensed-Medium.ttf" },
|
|
{ name = "PT Sans Narrow Regular", path = "Interface\\AddOns\\WeakestAuras\\fonts\\PTSansNarrow-Regular.ttf" },
|
|
{ name = "PT Sans Narrow Bold", path = "Interface\\AddOns\\WeakestAuras\\fonts\\PTSansNarrow-Bold.ttf" },
|
|
{ name = "Oswald", path = "Interface\\AddOns\\WeakestAuras\\fonts\\Oswald-Regular.ttf" },
|
|
{ name = "Accidental Presidency", path = "Interface\\AddOns\\WeakestAuras\\fonts\\Accidental Presidency.ttf" },
|
|
{ name = "Expressway", path = "Interface\\AddOns\\WeakestAuras\\fonts\\Expressway.ttf" },
|
|
{ name = "Bazooka", path = "Interface\\AddOns\\WeakestAuras\\fonts\\Bazooka.ttf" },
|
|
{ name = "FORCED SQUARE", path = "Interface\\AddOns\\WeakestAuras\\fonts\\FORCED SQUARE.ttf" },
|
|
{ name = "Cabin", path = "Interface\\AddOns\\WeakestAuras\\fonts\\Cabin.ttf" },
|
|
{ name = "Celestia Medium Redux", path = "Interface\\AddOns\\WeakestAuras\\fonts\\CelestiaMediumRedux1.55.ttf" },
|
|
{ name = "DejaVu Sans", path = "Interface\\AddOns\\WeakestAuras\\fonts\\DejaVuLGCSans.ttf" },
|
|
{ name = "DejaVu Serif", path = "Interface\\AddOns\\WeakestAuras\\fonts\\DejaVuLGCSerif.ttf" },
|
|
{ name = "Gentium Plus", path = "Interface\\AddOns\\WeakestAuras\\fonts\\GentiumPlus-Regular.ttf" },
|
|
{ name = "Hack", path = "Interface\\AddOns\\WeakestAuras\\fonts\\Hack-Regular.ttf" },
|
|
{ name = "Liberation Mono", path = "Interface\\AddOns\\WeakestAuras\\fonts\\LiberationMono-Regular.ttf" },
|
|
{ name = "Liberation Sans", path = "Interface\\AddOns\\WeakestAuras\\fonts\\LiberationSans-Regular.ttf" },
|
|
{ name = "Liberation Serif", path = "Interface\\AddOns\\WeakestAuras\\fonts\\LiberationSerif-Regular.ttf" },
|
|
{ name = "swf!t", path = "Interface\\AddOns\\WeakestAuras\\fonts\\SWF!T___.TTF" },
|
|
{ name = "Yellowjacket", path = "Interface\\AddOns\\WeakestAuras\\fonts\\yellow.ttf" },
|
|
}
|
|
|
|
-- Other registrars' LibSharedMedia names for a face FONTS already carries, so an
|
|
-- import resolves it instead of falling back to the default. Not a FONTS entry:
|
|
-- two rows sharing a path would put the same face in the picker twice and leave
|
|
-- FONT_LABELS holding whichever name was registered last.
|
|
--
|
|
-- "PT Sans Narrow" is ElvUI's name for the face WeakAuras registers as
|
|
-- "PT Sans Narrow Regular". Both spellings travel in exports, because what a
|
|
-- pack stores is whatever its author's registry called it -- so the FONTS name
|
|
-- has to stay upstream's and the other spelling has to land here.
|
|
textCore.FONT_ALIASES = {
|
|
["PT Sans Narrow"] = "PT Sans Narrow Regular",
|
|
}
|
|
|
|
-- Stored as "None" rather than "" so the picker has no empty value; Apply maps
|
|
-- it back. The two MONOCHROME combos are comma-joined, which is the separator
|
|
-- this client's SetFont takes.
|
|
textCore.FLAGS = {
|
|
"None", "OUTLINE", "THICKOUTLINE",
|
|
"MONOCHROME", "MONOCHROME,OUTLINE", "MONOCHROME,THICKOUTLINE",
|
|
}
|
|
textCore.FLAG_LABELS = {
|
|
None = "None",
|
|
OUTLINE = "Outline",
|
|
THICKOUTLINE = "Thick Outline",
|
|
MONOCHROME = "Monochrome",
|
|
["MONOCHROME,OUTLINE"] = "Monochrome Outline",
|
|
["MONOCHROME,THICKOUTLINE"] = "Monochrome Thick Outline",
|
|
}
|
|
|
|
textCore.JUSTIFY_H = { "LEFT", "CENTER", "RIGHT" }
|
|
textCore.JUSTIFY_V = { "TOP", "MIDDLE", "BOTTOM" }
|
|
local JUSTIFY_LABELS = {
|
|
LEFT = "Left", CENTER = "Center", RIGHT = "Right",
|
|
TOP = "Top", MIDDLE = "Middle", BOTTOM = "Bottom",
|
|
}
|
|
|
|
-- Every key the block reads, with the value it takes when absent. These are what
|
|
-- a bare FontString already does, which is why no saved aura needs migrating to
|
|
-- pick the block up.
|
|
local DEFAULTS = {
|
|
font = "Fonts\\FRIZQT__.TTF",
|
|
size = 12,
|
|
flags = "OUTLINE",
|
|
color = { 1, 1, 1, 1 },
|
|
justifyH = "CENTER",
|
|
justifyV = "MIDDLE",
|
|
spacing = 0,
|
|
shadowColor = { 0, 0, 0, 1 },
|
|
shadowX = 0,
|
|
shadowY = 0,
|
|
}
|
|
textCore.DEFAULTS = DEFAULTS
|
|
|
|
textCore.REGION_KEYS = {
|
|
font = "font", size = "fontSize", flags = "outline", color = "color",
|
|
justifyH = "justify", justifyV = "justifyV", spacing = "spacing",
|
|
shadowColor = "shadowColor", shadowX = "shadowXOffset", shadowY = "shadowYOffset",
|
|
}
|
|
textCore.SUBTEXT_KEYS = {
|
|
font = "font", size = "fontSize", flags = "fontType", color = "color",
|
|
justifyH = "justify", justifyV = "justifyV", spacing = "spacing",
|
|
shadowColor = "shadowColor", shadowX = "shadowXOffset", shadowY = "shadowYOffset",
|
|
}
|
|
|
|
local FONT_VALUES, FONT_LABELS = {}, {}
|
|
for i = 1, #textCore.FONTS do
|
|
local f = textCore.FONTS[i]
|
|
table.insert(FONT_VALUES, f.path)
|
|
FONT_LABELS[f.path] = f.name
|
|
end
|
|
|
|
local function flagString(flags)
|
|
if not flags or flags == "None" then return "" end
|
|
return flags
|
|
end
|
|
|
|
-- SetFont reports falsy and leaves the string *unrendered* when it rejects the
|
|
-- path or the flag combination, and GetFont then reads back nil -- so a bad pick
|
|
-- is not a wrong-looking region but an invisible one, with nothing on screen to
|
|
-- say why. Both readings are taken, since only the second survives a client that
|
|
-- returns nothing from SetFont on success.
|
|
--
|
|
-- The flag string is dropped before the face because it is the likelier half to
|
|
-- be refused: MONOCHROME and the two combos are not confirmed on this client.
|
|
local function setFontFace(fs, path, size, flags)
|
|
local ok = fs:SetFont(path, size, flags)
|
|
if ok and fs:GetFont() then return true end
|
|
ok = fs:SetFont(path, size, "")
|
|
if ok and fs:GetFont() then return true end
|
|
ok = fs:SetFont(DEFAULTS.font, size, "")
|
|
if ok and fs:GetFont() then return true end
|
|
fs:SetFontObject(GameFontHighlight)
|
|
return fs:GetFont() ~= nil
|
|
end
|
|
|
|
-- Where this client stops honouring SetFont's size. Nothing announces it: past
|
|
-- the ceiling SetFont returns normally, GetFont reads back the size that was
|
|
-- asked for, and the glyphs are rasterised at the ceiling regardless -- so a
|
|
-- 40pt aura draws the same height as a 20pt one.
|
|
--
|
|
-- It has to be MEASURED, and by width, because a clamped size lays out exactly
|
|
-- like the ceiling's: the sweep looks for where the string stops widening. A
|
|
-- sweep that widens the whole way (or that measures nothing at all, which is
|
|
-- what the headless harness does) latches `false` and turns the scaling below
|
|
-- off entirely, so a client without a ceiling pays nothing for this.
|
|
--
|
|
-- One measurement serves every face: the ceiling belongs to the rasteriser, not
|
|
-- to the file.
|
|
local sizeCeiling
|
|
local CEILING_SWEEP_TOP = 80
|
|
local function fontSizeCeiling()
|
|
if sizeCeiling ~= nil then return sizeCeiling end
|
|
sizeCeiling = false
|
|
local host = CreateFrame("Frame", nil, UIParent)
|
|
host:Hide()
|
|
local probe = host:CreateFontString(nil, "BACKGROUND")
|
|
local grew, last = nil, nil
|
|
for size = 8, CEILING_SWEEP_TOP do
|
|
probe:SetFont(DEFAULTS.font, size, "")
|
|
probe:SetText("HHHHHH")
|
|
local w = probe:GetStringWidth() or 0
|
|
if w <= 0 then return sizeCeiling end
|
|
if last and w > last + 0.5 then grew = size end
|
|
last = w
|
|
end
|
|
if grew and grew < CEILING_SWEEP_TOP then sizeCeiling = grew end
|
|
return sizeCeiling
|
|
end
|
|
|
|
-- The measurement itself, for /wa textprobe to report.
|
|
textCore.SizeCeiling = fontSizeCeiling
|
|
|
|
-- SetTextHeight scales what SetFont rasterised, and is the only way to draw
|
|
-- above the ceiling -- softer edges up there, which is the whole price. Two
|
|
-- things about it are load-bearing, and the fix does not work without either:
|
|
--
|
|
-- The two sizes must DIFFER. SetTextHeight does nothing when it would not change
|
|
-- the size SetFont was given, and above the ceiling that size is the one the
|
|
-- user asked for -- SetFont was told 40 and reports 40, it merely drew 20. So
|
|
-- the face is deliberately rasterised at the ceiling and scaled from there;
|
|
-- asking for 40 twice changes nothing. MikScrollingBattleText reaches the same
|
|
-- shape from the other side, rasterising one point below whatever it wants.
|
|
--
|
|
-- And it is re-asserted after the string, which is why the size is kept on the
|
|
-- FontString for SetText below. This pair applied at SetFont time alone drew at
|
|
-- the ceiling on screen even though it measures correctly on a bare FontString,
|
|
-- so something between the two puts the height back -- the colour/justify/
|
|
-- spacing/shadow calls in Apply, or the SetParent round trip Auto-mode measuring
|
|
-- makes. Which one is unidentified; the re-assert costs one call per string and
|
|
-- only on a string that is actually scaled.
|
|
local function setFont(fs, path, size, flags)
|
|
local ceiling = fontSizeCeiling()
|
|
local raster = size
|
|
if ceiling and size > ceiling then raster = ceiling end
|
|
if not setFontFace(fs, path, raster, flags) then return false end
|
|
if raster ~= size then
|
|
fs.waTextHeight = size
|
|
fs:SetTextHeight(size)
|
|
else
|
|
fs.waTextHeight = nil
|
|
end
|
|
-- A font or size change must reach the next SetText below even when the
|
|
-- string itself is unchanged -- the scaled-size re-assert rides that call.
|
|
fs.waLastText = nil
|
|
return true
|
|
end
|
|
|
|
local function keyName(aliases, key)
|
|
return aliases and aliases[key] or key
|
|
end
|
|
|
|
local function val(data, prefix, key, aliases)
|
|
local v = data[prefix .. keyName(aliases, key)]
|
|
if v == nil then return DEFAULTS[key] end
|
|
return v
|
|
end
|
|
|
|
-- Applies the whole block to `fs`, reading `data[prefix .. key]` for each key.
|
|
-- Returns whether a font actually loaded, which is what gates SetText below.
|
|
--
|
|
-- `size` overrides the stored one without writing it back: a condition changing
|
|
-- font size has to re-enter SetFont -- and therefore the read-back above -- not
|
|
-- merely call SetTextHeight, but the size it picked is not the user's setting.
|
|
function textCore.Apply(fs, data, prefix, size, aliases)
|
|
local ok = setFont(fs, val(data, prefix, "font", aliases), size or val(data, prefix, "size", aliases),
|
|
flagString(val(data, prefix, "flags", aliases)))
|
|
|
|
local c = val(data, prefix, "color", aliases)
|
|
fs:SetTextColor(c[1] or 1, c[2] or 1, c[3] or 1, c[4] or 1)
|
|
|
|
fs:SetJustifyH(val(data, prefix, "justifyH", aliases))
|
|
fs:SetJustifyV(val(data, prefix, "justifyV", aliases))
|
|
fs:SetSpacing(val(data, prefix, "spacing", aliases))
|
|
|
|
local s = val(data, prefix, "shadowColor", aliases)
|
|
fs:SetShadowColor(s[1] or 0, s[2] or 0, s[3] or 0, s[4] or 1)
|
|
fs:SetShadowOffset(val(data, prefix, "shadowX", aliases), val(data, prefix, "shadowY", aliases))
|
|
|
|
return ok
|
|
end
|
|
|
|
-- The other half of the read-back: a FontString whose font failed to load draws
|
|
-- nothing at all, so replacing its string blanks the region outright where
|
|
-- leaving the previous one at least keeps something on screen.
|
|
function textCore.SetText(fs, str)
|
|
if not fs:GetFont() then return false end
|
|
-- A countdown formatted to whole seconds hands over the same string dozens
|
|
-- of frames in a row; re-rendering it is pure engine layout cost. setFont
|
|
-- clears the cache, so a font/size change still reaches the calls below.
|
|
if fs.waLastText == str then return true end
|
|
fs.waLastText = str
|
|
fs:SetText(str)
|
|
-- Only a string scaled past the size ceiling carries one, and only that string
|
|
-- pays for the second call.
|
|
if fs.waTextHeight then fs:SetTextHeight(fs.waTextHeight) end
|
|
return true
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Options
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
local function summary(get, aliases)
|
|
local font = get(keyName(aliases, "font")) or DEFAULTS.font
|
|
local flags = get(keyName(aliases, "flags")) or DEFAULTS.flags
|
|
return (FONT_LABELS[font] or font) .. ", "
|
|
.. (textCore.FLAG_LABELS[flags] or flags)
|
|
end
|
|
|
|
-- The block's BuildOptions rows, behind a fold keyed on `foldKey` within `data`.
|
|
-- `get(key)`/`set(key, v)` address the bare key names above; the consumer owns
|
|
-- the prefix and the WA.Add call.
|
|
--
|
|
-- Size and colour are deliberately not here. They belong wherever the consumer
|
|
-- already puts them: they are the two rows anyone actually edits, and burying
|
|
-- them under a fold to keep the other eight company is a worse tab.
|
|
function textCore.OptionFields(data, foldKey, get, set, aliases)
|
|
local S = WA.OptionsState
|
|
local collapsed = S.isCollapsed(data, foldKey, true)
|
|
local fields = { {
|
|
type = "disclosure", name = "Font Options",
|
|
summary = summary(get, aliases),
|
|
collapsed = collapsed,
|
|
onToggle = function()
|
|
S.setCollapsed(data, foldKey, not collapsed)
|
|
WA.RefreshOptions()
|
|
end,
|
|
} }
|
|
if collapsed then return fields end
|
|
|
|
local function row(f) table.insert(fields, f) end
|
|
local function pick(key, name, values, labels, half)
|
|
local storedKey = keyName(aliases, key)
|
|
row({
|
|
type = "select", name = name, key = storedKey, values = values, labels = labels, half = half,
|
|
get = function() return get(storedKey) or DEFAULTS[key] end,
|
|
set = function(v) set(storedKey, v) end,
|
|
})
|
|
end
|
|
local function slide(key, name, min, max, half)
|
|
local storedKey = keyName(aliases, key)
|
|
row({
|
|
type = "range", name = name, key = storedKey, min = min, max = max, step = 1, half = half,
|
|
get = function()
|
|
local v = get(storedKey)
|
|
if v == nil then return DEFAULTS[key] end
|
|
return v
|
|
end,
|
|
set = function(v) set(storedKey, v) end,
|
|
})
|
|
end
|
|
|
|
pick("font", "Font", FONT_VALUES, FONT_LABELS)
|
|
pick("flags", "Outline", textCore.FLAGS, textCore.FLAG_LABELS)
|
|
pick("justifyH", "Justify", textCore.JUSTIFY_H, JUSTIFY_LABELS, true)
|
|
pick("justifyV", "Vertical Justify", textCore.JUSTIFY_V, JUSTIFY_LABELS, true)
|
|
slide("spacing", "Line Spacing", 0, 20)
|
|
local shadowColorKey = keyName(aliases, "shadowColor")
|
|
row({
|
|
type = "color", name = "Shadow Color", key = shadowColorKey,
|
|
get = function() return get(shadowColorKey) or DEFAULTS.shadowColor end,
|
|
set = function(v) set(shadowColorKey, v) end,
|
|
})
|
|
slide("shadowX", "Shadow X", -10, 10, true)
|
|
slide("shadowY", "Shadow Y", -10, 10, true)
|
|
|
|
return fields
|
|
end
|