7 Commits
Author SHA1 Message Date
AbyssandBrues e609809fff Raid unitframes: add option to reverse group order (#75)
* Add raidreverse default to unitframes config

Defaults to off, so existing layouts are unchanged.

* Raid frames: add option to reverse group order

Read the new raidreverse setting and mirror the column index when placing raid frames, so group 1 sits on the far right and the following groups grow to the left.

* Add "Reverse Group Order" checkbox to raid settings

Placed under "Raid Fill Direction" in the raid frame options.
/pfui > Settings > Unit Frames > Raid > Layout

* Raid frames: rename reverse option to "Grow Right To Left"

The option mirrors columns in both fill directions, so name it for what
it does. Mirror across the columns maxraid can fill instead of the whole
layout, so smaller raids stay next to the anchor. Lay the raid grid out
through SlotToCoord like the pet grid, move the default next to the other
raid layout keys, and add the label to the locale tables.

---------

Co-authored-by: Brues <5278969+brues-code@users.noreply.github.com>
2026-11-01 18:02:31 -06:00
Brues 3651341697 Scroll every chat copy box to the newest line when it opens
The caret at the end scrolled only the box with the focus, and each
visible chat frame's box takes the focus as it opens, so only the last
one shown reached the bottom. Even that one stopped short: 1.12's
OnCursorChanged has no input context, so ScrollCursorIntoView lets the
last line clip.

Each box now measures its scroll box and scrolls it to the end on the
next frame, after the edit box has laid the new text out.
2026-11-01 17:22:06 -06:00
Antoine ChedinandBrues 77be27094a Add Offsets to unit frame buff/debuff (#74)
* Add anchor offset for unitframe buff or defuff

* Fix default values

* Stack a shared-corner aura block past the other block's Y offset, read the offsets without a table per icon

---------

Co-authored-by: Brues <5278969+brues-code@users.noreply.github.com>
2026-11-01 16:39:48 -06:00
Brues 3e50a0b34f Turn on countdownForCooldowns when migrating the old cooldown settings
pfUI drew its own cooldown numbers before; the native countdown shows
them only with the countdownForCooldowns cvar on, and only the first-run
setup set it. Upgraded installs showed no numbers at all, so the
cooldown settings migration turns the cvar on as it removes
appearance.cd.
2026-11-01 00:23:39 -05:00
Brues 3faeafdfc5 Migrate the old cooldown settings into C.cooldown
The shared appearance.cd values (font, size, threshold, tenths, colors)
move to the cooldown table. "Enable Durations On Foreign Frames" and
"Hide Foreign Cooldown Animations" become the Other Addons numbers and
sweep; the spellbook and character slots take that sweep, and the
foreign numbers when "Enable Durations On Blizzard Frames" was off.

Each module's old options move to its kind: the bars, bags and totems
reverse, the buffs sweep and reverse, and the nameplate debuff text,
sweep and reverse, with "Enable Debuff Timers" off turning off both
numbers and sweep. Unit frames had one set per frame and now share one:
the target frame's timer options and the raid frames' "Show Time Left"
stand for the rest. The per-frame keys, appearance.cd and bars.cd_size
are removed.

The migration is keyed on the old keys rather than the version, so it
also runs on dev installs, and runs once.
2026-10-31 23:52:28 -05:00
Brues b2cffcb3c2 Load each module and skin through securecallfunction so one error no longer stops the rest from loading 2026-10-31 21:17:42 -05:00
Brues 5476a28982 Skin menu dividers: CreateMenuDivider draws a 1px border-colored line; use it in the radar menu 2026-10-31 21:04:11 -05:00
16 changed files with 148 additions and 36 deletions
+71 -1
View File
@@ -460,6 +460,7 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("unitframes", "raid", "raidlayout", "8x5")
pfUI:UpdateConfig("unitframes", "raid", "raidpadding", "3")
pfUI:UpdateConfig("unitframes", "raid", "raidfill", "VERTICAL")
pfUI:UpdateConfig("unitframes", "raid", "raidgrowleft", "0")
pfUI:UpdateConfig("unitframes", "raid", "collapse", "0")
pfUI:UpdateConfig("unitframes", "raid", "raidgrouplabel", "0")
pfUI:UpdateConfig("unitframes", "raid", "grouplabelxoff", "0")
@@ -550,6 +551,8 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("unitframes", unit, "bufflimit", "32")
pfUI:UpdateConfig("unitframes", unit, "buffperrow", "8")
pfUI:UpdateConfig("unitframes", unit, "ownbuffsize", "-1")
pfUI:UpdateConfig("unitframes", unit, "buffoffx", "0")
pfUI:UpdateConfig("unitframes", unit, "buffoffy", "0")
pfUI:UpdateConfig("unitframes", unit, "buffspacingx", "1")
pfUI:UpdateConfig("unitframes", unit, "buffspacingy", "1")
pfUI:UpdateConfig("unitframes", unit, "debuffs", "TOPLEFT")
@@ -557,6 +560,8 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("unitframes", unit, "debufflimit", "32")
pfUI:UpdateConfig("unitframes", unit, "debuffperrow", "8")
pfUI:UpdateConfig("unitframes", unit, "owndebuffsize", "-1")
pfUI:UpdateConfig("unitframes", unit, "debuffoffx", "0")
pfUI:UpdateConfig("unitframes", unit, "debuffoffy", "0")
pfUI:UpdateConfig("unitframes", unit, "debuffspacingx", "1")
pfUI:UpdateConfig("unitframes", unit, "debuffspacingy", "1")
pfUI:UpdateConfig("unitframes", unit, "invert_healthbar", "0")
@@ -1431,6 +1436,71 @@ function pfUI:MigrateConfig()
pfUI_config.unitframes.always2dportrait = nil
end
-- migrate the scattered cooldown settings into the cooldown table; keyed
-- on the old settings rather than the version, like the portrait style
local cooldown = pfUI_config.cooldown
local cd = pfUI_config.appearance.cd
if cd then
for _, entry in pairs({ "font", "font_size", "dynamicsize", "threshold", "milliseconds",
"lowcolor", "normalcolor", "minutecolor", "hourcolor", "daycolor" }) do
cooldown[entry] = cd[entry] or cooldown[entry]
end
cooldown.foreign.text = cd.foreign or cooldown.foreign.text
if cd.hideanim then
cooldown.foreign.animation = cd.hideanim == "1" and "0" or "1"
end
-- spellbook and character slots took the foreign sweep, and with the
-- Blizzard toggle off the foreign numbers too
cooldown.blizzard.animation = cooldown.foreign.animation
if cd.blizzard == "0" then
cooldown.blizzard.text = cooldown.foreign.text
end
-- pfUI drew its own numbers before; the native countdown shows them
-- only with this cvar on, which only the first-run setup sets
SetCVar("countdownForCooldowns", "1")
pfUI_config.appearance.cd = nil
end
local function move(config, old, kind, entry)
if config[old] then
cooldown[kind][entry] = config[old]
config[old] = nil
end
end
move(pfUI_config.bars, "cooldown_reverse", "actionbar", "reverse")
move(pfUI_config.appearance.bags, "cooldown_reverse", "bags", "reverse")
move(pfUI_config.buffs, "cooldown_anim", "buff", "animation")
move(pfUI_config.buffs, "cooldown_reverse", "buff", "reverse")
move(pfUI_config.totems, "cooldown_reverse", "totem", "reverse")
move(pfUI_config.nameplates, "debufftext", "nameplate", "text")
move(pfUI_config.nameplates, "debuffanim", "nameplate", "animation")
move(pfUI_config.nameplates, "debreverse", "nameplate", "reverse")
if pfUI_config.nameplates.debufftimers == "0" then
cooldown.nameplate.text, cooldown.nameplate.animation = "0", "0"
end
-- one setting now covers every unit frame: the target frame's auras and
-- the raid frames' indicators stand for the rest
move(pfUI_config.unitframes.target, "cooldown_text", "unitframe", "text")
move(pfUI_config.unitframes.target, "cooldown_anim", "unitframe", "animation")
move(pfUI_config.unitframes.target, "cooldown_reverse", "unitframe", "reverse")
move(pfUI_config.unitframes.raid, "indicator_time", "indicator", "text")
for _, unit in pairs(pfUI_config.unitframes) do
if type(unit) == "table" then
unit.cooldown_text, unit.cooldown_anim, unit.cooldown_reverse, unit.indicator_time = nil, nil, nil, nil
end
end
pfUI_config.nameplates.debufftimers = nil
pfUI_config.bars.cd_size = nil
-- Remove "Show only own debuffs" from unitframes and nameplates
-- (feature was removed; only Target Debuff Bar in buffwatch keeps it)
if pfUI_config.nameplates then
@@ -1459,4 +1529,4 @@ function pfUI:MigrateConfig()
else
pfUI_config.version = pfUI.version.string
end
end
end
+19
View File
@@ -86,6 +86,25 @@ do
end
end
-- [ CreateMenuDivider ]
-- Adds a divider to a pfUIMenuStyleMixin menu: a 1px line in the border
-- color in place of the stock divider art. The style itself cannot do this,
-- as menu entries draw their own art.
-- 'description' [table] the menu description to add the divider to
-- returns: [table] the divider's element description
do
local function SkinDivider(frame)
frame.divider:SetHeight(1)
frame.divider:SetColorTexture(GetStringColor(pfUI_config.appearance.border.color))
end
function pfUI.api.CreateMenuDivider(description)
local divider = description:CreateDivider()
divider:AddInitializer(SkinDivider)
return divider
end
end
-- [ pfUIDropdownTemplate ]
-- Mixin of the template in ui-widgets.xml: a dropdown that shows its selection
-- as text, with pfUI's arrow box at the right.
+11 -7
View File
@@ -19,11 +19,11 @@ local auraCorners = {
}
-- Flows the shown icons of an aura block outward from the frame edge, starting
-- `offset` away from it (its own row gap when nil): `perrow` icons per row,
-- each row as tall as its largest icon. The first `icons.own` icons are the
-- player's (C_UnitAuras lists them first) and use `icons.ownsize`. Returns the
-- offset just past the block's last row, where a block sharing the same corner
-- continues.
-- `offset` away from it (its own row gap when nil) and shifted on screen by
-- `offx`/`offy`: `perrow` icons per row, each row as tall as its largest icon.
-- The first `icons.own` icons are the player's (C_UnitAuras lists them first)
-- and use `icons.ownsize`. Returns the offset just past the block's last
-- shifted row, where a block sharing the same corner continues.
local function LayoutAuraBlock(unit, icons, offset)
local corner = auraCorners[icons.pos]
local anchor = unit.config.portraitheight ~= "-1" and unit.hp or unit
@@ -42,7 +42,7 @@ local function LayoutAuraBlock(unit, icons, offset)
local size = i <= icons.own and icons.ownsize or icons.size
icons[i]:SetSize(size, size)
icons[i]:ClearAllPoints()
icons[i]:SetPoint(corner[1], anchor, icons.pos, corner[2] * x, corner[3] * offset)
icons[i]:SetPoint(corner[1], anchor, icons.pos, icons.offx + corner[2] * x, icons.offy + corner[3] * offset)
x = x + size + icons.gapx
if size > rowsize then rowsize = size end
@@ -50,7 +50,7 @@ local function LayoutAuraBlock(unit, icons, offset)
i = i + 1
end
if col > 0 then offset = offset + rowsize + icons.gapy end
if col > 0 then offset = offset + rowsize + icons.gapy + corner[3] * icons.offy end
return offset
end
@@ -877,6 +877,8 @@ function pfUI.uf:UpdateConfig()
f.buffs.size = tonumber(f.config.buffsize)
f.buffs.ownsize = f.config.ownbuffsize == "-1" and f.buffs.size or tonumber(f.config.ownbuffsize)
f.buffs.perrow = tonumber(f.config.buffperrow)
f.buffs.offx = tonumber(f.config.buffoffx)
f.buffs.offy = tonumber(f.config.buffoffy)
f.buffs.gapx = aura_border + tonumber(f.config.buffspacingx)
f.buffs.gapy = aura_border + tonumber(f.config.buffspacingy)
f.buffs.own = 0
@@ -927,6 +929,8 @@ function pfUI.uf:UpdateConfig()
f.debuffs.size = tonumber(f.config.debuffsize)
f.debuffs.ownsize = f.config.owndebuffsize == "-1" and f.debuffs.size or tonumber(f.config.owndebuffsize)
f.debuffs.perrow = tonumber(f.config.debuffperrow)
f.debuffs.offx = tonumber(f.config.debuffoffx)
f.debuffs.offy = tonumber(f.config.debuffoffy)
f.debuffs.gapx = aura_border + tonumber(f.config.debuffspacingx)
f.debuffs.gapy = aura_border + tonumber(f.config.debuffspacingy)
f.debuffs.own = 0
+1
View File
@@ -359,6 +359,7 @@ pfUI_translation["deDE"] = {
["Group Options"] = nil,
["Group-Pet"] = nil,
["Group-Target"] = nil,
["Grow Right To Left"] = nil,
["Gryphon"] = nil,
["Gryphons"] = nil,
["Guild Online"] = nil,
+5
View File
@@ -90,6 +90,8 @@ pfUI_translation["enUS"] = {
["Buffs"] = nil,
["Buff Size"] = nil,
["Buff Spacing"] = nil,
["Buff X-Offset"] = nil,
["Buff Y-Offset"] = nil,
["Buff X-Spacing"] = nil,
["Buff Y-Spacing"] = nil,
["Buffs Per Row"] = nil,
@@ -196,6 +198,8 @@ pfUI_translation["enUS"] = {
["Debuff Position"] = nil,
["Debuffs"] = nil,
["Debuff Size"] = nil,
["Debuff X-Offset"] = nil,
["Debuff Y-Offset"] = nil,
["Debuff X-Spacing"] = nil,
["Debuff Y-Spacing"] = nil,
["Debuffs Per Row"] = nil,
@@ -369,6 +373,7 @@ pfUI_translation["enUS"] = {
["Group Options"] = nil,
["Group-Pet"] = nil,
["Group-Target"] = nil,
["Grow Right To Left"] = nil,
["Gryphon"] = nil,
["Gryphons"] = nil,
["Guild Online"] = nil,
+1
View File
@@ -359,6 +359,7 @@ pfUI_translation["esES"] = {
["Group Options"] = "Opciones de grupo",
["Group-Pet"] = "Mascota-Grupo",
["Group-Target"] = "Objetivo-Grupo",
["Grow Right To Left"] = nil,
["Gryphon"] = nil,
["Gryphons"] = nil,
["Guild Online"] = "Conectados en hermandad",
+1
View File
@@ -359,6 +359,7 @@ pfUI_translation["frFR"] = {
["Group Options"] = "Options de groupe",
["Group-Pet"] = "Groupe des familiers",
["Group-Target"] = "Groupe des cibles",
["Grow Right To Left"] = nil,
["Gryphon"] = nil,
["Gryphons"] = nil,
["Guild Online"] = "Membres de guilde connectés",
+1
View File
@@ -359,6 +359,7 @@ pfUI_translation["koKR"] = {
["Group Options"] = nil,
["Group-Pet"] = "파티-팻",
["Group-Target"] = "파티-타켓",
["Grow Right To Left"] = nil,
["Gryphon"] = nil,
["Gryphons"] = nil,
["Guild Online"] = "접속중인 길드원",
+1
View File
@@ -359,6 +359,7 @@ pfUI_translation["ruRU"] = {
["Group Options"] = "Настройки группы",
["Group-Pet"] = "Группа-питомец",
["Group-Target"] = "Группа-цель",
["Grow Right To Left"] = nil,
["Gryphon"] = nil,
["Gryphons"] = nil,
["Guild Online"] = "Члены гильдии в сети",
+1
View File
@@ -359,6 +359,7 @@ pfUI_translation["zhCN"] = {
["Group Options"] = "队伍选项",
["Group-Pet"] = "小队宠物",
["Group-Target"] = "小队目标",
["Grow Right To Left"] = nil,
["Gryphon"] = "狮鹫",
["Gryphons"] = "狮鹫(多个)",
["Guild Online"] = "公会在线",
+1
View File
@@ -359,6 +359,7 @@ pfUI_translation["zhTW"] = {
["Group Options"] = nil,
["Group-Pet"] = "小隊寵物",
["Group-Target"] = "小隊目標",
["Grow Right To Left"] = nil,
["Gryphon"] = nil,
["Gryphons"] = nil,
["Guild Online"] = "公會線上",
+8 -2
View File
@@ -85,10 +85,16 @@ pfUI:RegisterModule("chatcopy", function ()
scroll:SetFont(frame:GetFont())
scroll:SetText(text)
-- the cursor at the end scrolls the box to the newest line
scroll:SetFocus()
scroll:SetCursorPosition(#text)
-- the edit box lays the text out in this frame's update, so scroll to
-- the newest line after it; the caret only scrolls the focused box
RunNextFrame(function()
local scrollBox = scroll:GetScrollBox()
scrollBox:FullUpdate(ScrollBoxConstants.UpdateImmediately)
scrollBox:ScrollToEnd(ScrollBoxConstants.NoScrollInterpolation)
end)
end)
frame.scroll = scroll
+6 -1
View File
@@ -2294,6 +2294,7 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(U["raid"], T["Raid Padding"], C.unitframes[c], "raidpadding")
CreateConfig(U["raid"], T["Raid Layout"], C.unitframes[c], "raidlayout", "dropdown", pfUI.gui.dropdowns.uf_raidlayout)
CreateConfig(U["raid"], T["Raid Fill Direction"], C.unitframes[c], "raidfill", "dropdown", pfUI.gui.dropdowns.orientation)
CreateConfig(U["raid"], T["Grow Right To Left"], C.unitframes[c], "raidgrowleft", "checkbox")
CreateConfig(U["raid"], T["Collapse Empty Slots"], C.unitframes[c], "collapse", "checkbox")
elseif c == "raidpet" then
CreateConfig(U[c], T["Layout"], nil, nil, "header")
@@ -2360,6 +2361,8 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(U[c], T["Buff Limit"], C.unitframes[c], "bufflimit")
CreateConfig(U[c], T["Buffs Per Row"], C.unitframes[c], "buffperrow")
CreateConfig(U[c], T["Own Buff Size"], C.unitframes[c], "ownbuffsize")
CreateConfig(U[c], T["Buff X-Offset"], C.unitframes[c], "buffoffx")
CreateConfig(U[c], T["Buff Y-Offset"], C.unitframes[c], "buffoffy")
CreateConfig(U[c], T["Buff X-Spacing"], C.unitframes[c], "buffspacingx")
CreateConfig(U[c], T["Buff Y-Spacing"], C.unitframes[c], "buffspacingy")
@@ -2369,6 +2372,8 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(U[c], T["Debuff Limit"], C.unitframes[c], "debufflimit")
CreateConfig(U[c], T["Debuffs Per Row"], C.unitframes[c], "debuffperrow")
CreateConfig(U[c], T["Own Debuff Size"], C.unitframes[c], "owndebuffsize")
CreateConfig(U[c], T["Debuff X-Offset"], C.unitframes[c], "debuffoffx")
CreateConfig(U[c], T["Debuff Y-Offset"], C.unitframes[c], "debuffoffy")
CreateConfig(U[c], T["Debuff X-Spacing"], C.unitframes[c], "debuffspacingx")
CreateConfig(U[c], T["Debuff Y-Spacing"], C.unitframes[c], "debuffspacingy")
@@ -3084,4 +3089,4 @@ pfUI:RegisterModule("gui", function ()
end
end)
end
end)
end)
+1 -1
View File
@@ -85,7 +85,7 @@ pfUI:RegisterModule("radar", function ()
for _, yards in ipairs(RANGES) do
rootDescription:CreateRadio(yards .. " yd", IsRange, SetRange, yards)
end
rootDescription:CreateDivider()
CreateMenuDivider(rootDescription)
rootDescription:CreateButton(HIDE, function() radar:Hide() end)
end
+16 -22
View File
@@ -30,8 +30,8 @@ pfUI:RegisterModule("raid", function ()
-- flat pool of pet frames, laid out by LayoutPets (mirror or collapsed)
pfUI.uf.raid.pets = {}
-- 1-based grid slot -> (row, col) for the current fill direction, matching
-- the raid grid's own fill loops.
-- 1-based grid slot -> (col, row) for the current fill direction, shared by
-- the raid and pet grids.
local function SlotToCoord(slot, fill, x, y)
slot = slot - 1
if fill == "VERTICAL" then
@@ -75,12 +75,12 @@ pfUI:RegisterModule("raid", function ()
end
end
local i = 1
local width = pfUI.uf.raid[1]:GetWidth()+2*default_border
local height = pfUI.uf.raid[1]:GetHeight()+2*default_border
local layout = pfUI.uf.raid[1].config.raidlayout
local padding = tonumber(pfUI.uf.raid[1].config.raidpadding)*GetPerfectPixel()
local fill = pfUI.uf.raid[1].config.raidfill
local growleft = pfUI.uf.raid[1].config.raidgrowleft == "1"
local _, _, x, y = string.find(layout,"(.+)x(.+)")
x, y = tonumber(x), tonumber(y)
@@ -95,24 +95,18 @@ pfUI:RegisterModule("raid", function ()
}
end
if fill == "VERTICAL" then
for r=1, x do for g=1, y do
if pfUI.uf.raid[i] then
pfUI.uf.raid[i]:ClearAllPoints()
pfUI.uf.raid[i]:SetPoint("BOTTOMLEFT", (r-1)*(padding+width), (g-1)*(padding+height))
UpdateMovable(pfUI.uf.raid[i], true)
end
i = i + 1
end end
else
for g=1, y do for r=1, x do
if pfUI.uf.raid[i] then
pfUI.uf.raid[i]:ClearAllPoints()
pfUI.uf.raid[i]:SetPoint("BOTTOMLEFT", (r-1)*(padding+width), (g-1)*(padding+height))
UpdateMovable(pfUI.uf.raid[i], true)
end
i = i + 1
end end
-- right to left mirrors across the columns maxraid can fill, so a smaller
-- raid stays next to the anchor instead of the far edge of the layout
local cols = min(x, fill == "VERTICAL" and ceil(maxraid / y) or maxraid)
for i=1, x*y do
if pfUI.uf.raid[i] then
local r, g = SlotToCoord(i, fill, x, y)
if growleft then r = cols - r + 1 end
pfUI.uf.raid[i]:ClearAllPoints()
pfUI.uf.raid[i]:SetPoint("BOTTOMLEFT", (r-1)*(padding+width), (g-1)*(padding+height))
UpdateMovable(pfUI.uf.raid[i], true)
end
end
self:LayoutPets()
@@ -303,4 +297,4 @@ pfUI:RegisterModule("raid", function ()
pfUI.uf.raid:QueueUpdate(0)
end
end)
end)
end)
+4 -2
View File
@@ -345,14 +345,16 @@ function pfUI:RegisterSkin(name, func, arg3)
end
end
-- an error in one module or skin goes to the error handler without
-- stopping the ones that load after it
function pfUI:LoadModule(m)
setfenv(pfUI.module[m], pfUI:GetEnvironment())
pfUI.module[m]()
securecallfunction(pfUI.module[m])
end
function pfUI:LoadSkin(s)
setfenv(pfUI.skin[s], pfUI:GetEnvironment())
pfUI.skin[s]()
securecallfunction(pfUI.skin[s])
end
pfUI:SetScript("OnEvent", function()