From 8240256726adc63c09bb3ddd70d1f0699572181e Mon Sep 17 00:00:00 2001 From: OEVG <950+oevg@noreply.octowow.st> Date: Tue, 3 Nov 2026 19:55:47 -0400 Subject: [PATCH] Release OEVG Cursive 1.6 with ClassicAPI tracking and appearance options --- .gitattributes | 1 + .gitignore | 1 + CHANGELOG.md | 30 ++++ Cursive.toc | 7 +- README.md | 61 +++++--- classicapi.lua | 59 ++++++++ img/BlizzardRetailBar.tga | Bin 0 -> 12134 bytes img/EliteDragon.tga | Bin 0 -> 27236 bytes settings.lua | 42 ++++++ tests/appearance.lua | 68 +++++++++ tests/classicapi.lua | 41 ++++++ tests/testmode.lua | 48 ++++++ ui.lua | 299 +++++++++++++++++++++++++++++++++++--- 13 files changed, 611 insertions(+), 46 deletions(-) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 classicapi.lua create mode 100644 img/BlizzardRetailBar.tga create mode 100644 img/EliteDragon.tga create mode 100644 tests/appearance.lua create mode 100644 tests/classicapi.lua create mode 100644 tests/testmode.lua diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df7d134 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.luac \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7fbdf95 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +## 1.6 — 2026-11-03 + +First OEVG release, based on Wayoff333/Cursive 1.5. + +### Tracking +- Add optional ClassicAPI harmful-slot reconciliation for confirmed player effects, refreshes and removals, retaining Nampower casting/GUID tracking. +- Use ClassicAPI harmful aura indices for target ghost icons and Shadow Vulnerability when available. +- Preserve pending casts while discovering confirmed auras; do not erase optimistic casts before their first confirmation. + +### Appearance +- Add a Classic WoW settings style with a centered title plaque, ShaguTweaks dark/gold tinting, and an opacity slider with an opaque 100% background. +- Keep the original menu's neutral border when Classic style is off. +- Bundle a Modern Blizzard health-bar texture. +- Add independent tooltip-border options for health bars and aura icons, matching the active ShaguTweaks palette. +- Add a trimmed elite dragon above the bar's upper-right corner for elites, rare elites and world bosses; desaturate rare elites and leave normal mobs/ordinary rares unmarked. +- Add space before aura icons when the dragon option is enabled in the normal layout. +- Add session-only Test Mode using the real row geometry, sample health, raid markers, timers and GCD. +- Reduce the row-rebuild debounce from three seconds to 0.1 seconds, snap sliders to configured steps, and reflect selected text sizes in the preview. +- Fix health-border anchors that referenced a nonexistent healthBar.bar, and clear retired icons from the flash registry. + +### Maintenance and validation +- Add Lua 5.0 mocked tests for ClassicAPI reconciliation, menu styling/opacity, elite classification and test-mode layout. +- Add LF checkout attributes and ignore compiled Lua files. +- OEVG tested the appearance changes in game. Broader ClassicAPI combat validation and in-game CPU/memory measurements remain outstanding. + +## 1.5 — upstream + +Fonts and settings preview. Earlier releases and command behavior are documented in the [upstream repository](https://github.com/Wayoff333/Cursive). \ No newline at end of file diff --git a/Cursive.toc b/Cursive.toc index d68bed8..7939e37 100644 --- a/Cursive.toc +++ b/Cursive.toc @@ -1,10 +1,10 @@ ## Interface: 11200 -## Title: Cursive 1.5 -## Title-zhCN: Cursive 1.5 +## Title: Cursive 1.6 +## Title-zhCN: Cursive 1.6 ## Author: Pepopo ## Notes: Uses GUIDs to show units in combat and track curses ## Notes-zhCN: 使用的GUID功能来显示战斗中的单位并监视诅咒等 -## Version: 1.5 +## Version: 1.6 ## SavedVariablesPerCharacter: CursiveDB ## X-Embeds: Ace2, FuBarPlugin-2.0, CompostLib, DewDropLib, TabletLib @@ -38,6 +38,7 @@ spells\shaman.lua spells\warlock.lua spells\warrior.lua curses.lua +classicapi.lua filter.lua ui.lua commands.lua diff --git a/README.md b/README.md index a51e146..a3a94a4 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,59 @@ -# Cursive updated for octowow +# Cursive — OEVG fork --new settings menu +[![Nampower](https://img.shields.io/badge/Nampower-Required-purple.svg)](https://github.com/brues-code/nampower) +[![ClassicAPI](https://img.shields.io/badge/ClassicAPI-Optional-yellow.svg)](https://github.com/brues-code/ClassicAPI) --ghost dot icons +Cursive tracks curses, damage-over-time spells and enemies in combat, with commands for choosing and cursing targets. This is OEVG's fork of [Wayoff333/Cursive](https://github.com/Wayoff333/Cursive), building on the work of Pepopo and earlier contributors. --Shadow vulnerability icon +**Version 1.6**, for **OctoWoW / TurtleWoW 1.18.1** (WoW 1.12 API, Interface 11200, Lua 5.0). See the [changelog](CHANGELOG.md) for release details. --Global Cooldown bar +## Requirements --1.4 added sacrifice bar +- **Nampower 2.40.0 or newer is required**, as in the upstream addon. It supplies casting events, GUID tracking and improved range checks. +- **ClassicAPI is optional.** Aura features are detected separately and fall back to upstream tracking when unavailable. No separate SuperWoW dependency is introduced by this fork. +- **ShaguTweaks is optional.** OEVG's Darkened UI and Golden UI palettes are used for enabled tooltip borders and Classic menu artwork. Darkened UI takes priority when both modes are enabled. --1.5 added fonts +## What's new in 1.6 -image +- **ClassicAPI aura tracking:** confirm player-owned tracked debuffs from harmful aura slots, correct refresh and expiration times, and remove previously confirmed effects when absent. Target ghost icons and Shadow Vulnerability scans also use ClassicAPI's harmful aura index space. +- **Classic WoW menu:** Blizzard dialog and header artwork, a matching ShaguTweaks tint, and adjustable background opacity. At 100%, the background is opaque; text stays readable when opacity is lowered. +- **Blizzard Modern health texture:** bundled with Cursive; Puppeteer is not required. +- **Independent tooltip borders** for health bars and curse/DoT icons, matching the active ShaguTweaks tooltip palette. +- **Elite dragon:** a trimmed corner decoration above the health/name bar for elites, rare elites and world bosses only. Rare elites use silver artwork; ordinary rares and normal mobs remain unmarked. Aura icons leave room for the decoration. +- **Test Mode:** sample rows, health values, raid markers, curse timers and GCD for adjusting the real layout without a target. Includes elite, rare-elite and normal examples; resets on reload. +- **Quicker appearance updates:** row resizing applies after a short debounce, sliders respect their configured steps, and text previews follow selected font sizes. +ClassicAPI events update tracking as they arrive; rows retain the upstream display refresh cadence. They cannot remove server/network delay or reveal units outside the client's visibility. -image +## Install and configure +1. Exit WoW completely so it can discover the bundled textures. +2. Back up your existing Cursive folder, then place this repository's addon files in **Interface/AddOns/Cursive**. The manifest must be at **Interface/AddOns/Cursive/Cursive.toc**. +3. Start WoW and open the settings through the minimap icon. Use **Size & Appearance → Test Mode** to adjust the layout, then turn it off for real units. +4. Enable **Classic WoW Menu**, **Modern Blizzard Health Texture**, tooltip borders or **Elite Dragon** as desired. **Menu Opacity** sits directly below Classic WoW Menu. +5. Position the Cursive display using **Show Frame Background**, then turn that off and enable **Allow clickthrough** if desired. -> [!IMPORTANT] -> -> **This addon requires you to have Nampower >= 2.40.0 installed.** +Keep your saved settings when updating. Changing menu opacity affects the settings background, not the unit bars. -![image](https://github.com/pepopo978/Cursive/assets/149287158/801511af-29c7-4baf-b1ac-5e8c52f0f846) +ShaguTweaks Debuff Timer, default target aura displays and DoesItDie may display some of the same effects. Choose which displays you want to keep; these addons are not required by Cursive. -Cursive combines ShaguScan unit scanning with curse tracking similar to DotTimer and the ability to automatically curse -targets similar to Decursive. +## Validation -### Recommended setup +OEVG tested the appearance changes in game during development. Changed Lua files parse with Lua 5.0, and mocked tests cover aura reconciliation, menu styling/opacity, elite classifications and sample layout isolation. -Move the Cursive label to the desired position on the screen. You can turn on 'Show Frame Background' in the settings to -help with this. Once you have it where you want it, turn off 'Show Frame Background' and turn on 'Allow clickthrough' -so that it doesn't block your clicks when it's not displaying mobs. +ClassicAPI tracking still needs broader in-game checks: applications, refreshes, dispels, expiration, other casters, target changes, and operation with ClassicAPI disabled or absent. CPU and memory improvements have not been measured in game. + +From the repository root in Linux/WSL, using the workspace Lua 5.0 binary: + + ../tools/lua5.0/lua5.0 tests/classicapi.lua + ../tools/lua5.0/lua5.0 tests/appearance.lua + ../tools/lua5.0/lua5.0 tests/testmode.lua + +## Artwork and credits + +The bundled Blizzard Modern bar is copied from OEVG's Puppeteer. EliteDragon.tga is an unchanged copy of ShaguTweaks' UI-TargetingFrame-Elite.tga, sourced from the local ShaguTweaks-ClassicAPI fork; the addon uses cropped texture coordinates and desaturation. These are game-derived assets; the repository's code license does not establish separate ownership of Blizzard artwork. See [LICENSE](LICENSE) for the upstream code license. + +The command reference below is retained from upstream. ## Commands @@ -228,4 +250,3 @@ There is an option "always show current target" that will display your current t You can ignore mobs based on their unit name using the ignored mob list filter. It is comma separated and you need to press enter to get it to save. For example can do: `whelp,scarab` to ignore all mobs with those strings in their name. - diff --git a/classicapi.lua b/classicapi.lua new file mode 100644 index 0000000..bc02d8a --- /dev/null +++ b/classicapi.lua @@ -0,0 +1,59 @@ +-- Optional ClassicAPI bridge. Nampower remains the cast/range/GUID backend. +if not Cursive.nampower then return end +local slots, seen = {}, {} +local function Reconcile(unit) + if not Cursive.db.profile.enabled or not Cursive.db.profile.useclassicapi then return end + if not C_UnitAuras or not C_UnitAuras.GetAuraSlots or not C_UnitAuras.UnitAuraBySlot then return end + if not unit or not UnitExists(unit) then return end + local _, guid = UnitExists(unit) + if not guid and UnitGUID then guid = UnitGUID(unit) end + if not guid then return end + local curses = Cursive.curses + for key in pairs(seen) do seen[key] = nil end + local _, count = C_UnitAuras.GetAuraSlots(unit, "HARMFUL", nil, nil, slots) + if not count then return end -- no authoritative snapshot + for i = 1, count do + local name, icon, stacks, dispel, duration, expiration, source, removable, personal, id = + C_UnitAuras.UnitAuraBySlot(unit, slots[i]) + local tracked = id and curses.trackedCurseIds[id] + if tracked and source and (source == "player" or UnitIsUnit(source, "player")) then + seen[tracked.name] = true + if duration and duration > 0 and expiration and expiration > GetTime() then + local data = curses.guids[guid] and curses.guids[guid][tracked.name] + if not data then + local pending = curses.pendingCast + curses:ApplyCurse(id, guid, expiration - duration, duration) + curses.pendingCast = pending + data = curses.guids[guid][tracked.name] + end + data.start = expiration - duration + data.duration = duration + data.spellID = id + data.rank = tracked.rank + data.classicConfirmed = true + Cursive.core.addGuid(guid) + end + end + end + local entries = curses.guids[guid] + if entries then + for name, data in pairs(entries) do + -- Do not erase optimistic casts before the first confirming server aura. + if data.classicConfirmed and data.currentPlayer and not seen[name] then + curses:RemoveCurse(guid, name) + end + end + end +end +Cursive.ReconcileClassicAuras = Reconcile +local bridge = CreateFrame("Frame") +bridge:RegisterEvent("PLAYER_TARGET_CHANGED") +bridge:RegisterEvent("UNIT_AURA") +bridge:RegisterEvent("PLAYER_ENTERING_WORLD") +if C_UnitAuras and C_UnitAuras.GetAuraDataByAuraInstanceID then + bridge:RegisterEvent("UNIT_AURA_DELTA") +end +bridge:SetScript("OnEvent", function() + if event == "PLAYER_TARGET_CHANGED" or event == "PLAYER_ENTERING_WORLD" then Reconcile("target") + else Reconcile(arg1) end +end) \ No newline at end of file diff --git a/img/BlizzardRetailBar.tga b/img/BlizzardRetailBar.tga new file mode 100644 index 0000000000000000000000000000000000000000..18e973c235793487dba08a2ab0dc4e48dfa37705 GIT binary patch literal 12134 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 literal 0 HcmV?d00001 diff --git a/img/EliteDragon.tga b/img/EliteDragon.tga new file mode 100644 index 0000000000000000000000000000000000000000..aa81cde28cc945b0595cae24cb98124f589dc89d GIT binary patch literal 27236 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 literal 0 HcmV?d00001 diff --git a/settings.lua b/settings.lua index 9495833..a26e066 100644 --- a/settings.lua +++ b/settings.lua @@ -38,6 +38,7 @@ Cursive:RegisterDefaults("profile", { showtargetindicator = true, showraidicons = true, showhealthbar = true, + classicmenu = false, menuopacity = 100, showelitedragon = false, modernhealth = false, healthborder = false, auraborder = false, useclassicapi = true, showunitname = true, malediction = true, @@ -99,6 +100,47 @@ local function splitIgnoreString(str, delimiter) end local barOptions = { + ["menuopacity"] = { + type = "range", name = "Menu Opacity", desc = "Opacity of the settings window background; text remains readable.", + order = 36.1, min = 20, max = 100, step = 5, + get = function() return Cursive.db.profile.menuopacity end, + set = function(v) Cursive.db.profile.menuopacity = v; Cursive.ApplyMenuStyle() end, + }, + ["showelitedragon"] = { + type = "toggle", name = "Elite Dragon", desc = "Small gold dragon on the upper-right corner of elite/boss bars; silver for rare elites.", + order = 59.5, get = function() return Cursive.db.profile.showelitedragon end, + set = function(v) Cursive.db.profile.showelitedragon = v; Cursive.UpdateFramesFromConfig() end, + }, + ["testmode"] = { + type = "toggle", name = "Test Mode", desc = "Show sample unit bars and timers to test appearance. Resets on reload.", + order = 37, get = function() return Cursive.testMode or false end, + set = function(v) Cursive.SetTestMode(v) end, + }, + ["classicmenu"] = { + type = "toggle", name = "Classic WoW Menu", desc = "Blizzard dialog artwork for the settings window.", + order = 36, get = function() return Cursive.db.profile.classicmenu end, + set = function(v) Cursive.db.profile.classicmenu = v; Cursive.ApplyMenuStyle() end, + }, + ["modernhealth"] = { + type = "toggle", name = "Modern Blizzard Health Texture", desc = "Blizzard Modern texture from OEVG's Puppeteer.", + order = 56, get = function() return Cursive.db.profile.modernhealth end, + set = function(v) Cursive.db.profile.modernhealth = v; Cursive.UpdateFramesFromConfig() end, + }, + ["healthborder"] = { + type = "toggle", name = "Health Bar Tooltip Border", desc = "Match ShaguTweaks Darkened UI / Golden UI.", + order = 57, get = function() return Cursive.db.profile.healthborder end, + set = function(v) Cursive.db.profile.healthborder = v; Cursive.UpdateFramesFromConfig() end, + }, + ["auraborder"] = { + type = "toggle", name = "Curse / Debuff / DoT Tooltip Border", desc = "Border active, missing and Shadow Vulnerability icons; match ShaguTweaks.", + order = 58, get = function() return Cursive.db.profile.auraborder end, + set = function(v) Cursive.db.profile.auraborder = v; Cursive.UpdateFramesFromConfig() end, + }, + ["useclassicapi"] = { + type = "toggle", name = "Use ClassicAPI Aura Tracking", desc = "Reconcile confirmed player auras on aura events. Nampower remains required.", + order = 59, get = function() return Cursive.db.profile.useclassicapi end, + set = function(v) Cursive.db.profile.useclassicapi = v end, + }, ["flashonexpiring"] = { type = "toggle", name = "Flash Icon On Expiring", diff --git a/tests/appearance.lua b/tests/appearance.lua new file mode 100644 index 0000000..5e0c95b --- /dev/null +++ b/tests/appearance.lua @@ -0,0 +1,68 @@ +local file = assert(io.open("ui.lua","r")) +local source = file:read("*a"); file:close() +Cursive = {db={profile={classicmenu=true}}} +local function frame() + return { + SetBackdrop=function(self,v) self.backdrop=v end, + SetBackdropColor=function(self,r,g,b,a) self.backgroundAlpha=a end, + SetBackdropBorderColor=function(self,r,g,b,a) self.r=r; self.g=g end, + SetHeight=function(self,v) self.height=v end, + SetTexture=function(self,r,g,b,a) self.textureAlpha=a end, + SetVertexColor=function(self,r,g,b,a) self.tintR=r; self.tintG=g end, + Show=function(self) self.shown=true end, + Hide=function(self) self.shown=false end, + } +end +ShaguTweaks={GoldMode=true} +GameTooltip={GetBackdropBorderColor=function() return .8,.62,.32,1 end} +CursiveSettingsWindow=frame() +CursiveSettingsWindow.titleBar=frame() +CursiveSettingsWindow.classicHeader=frame() +CursiveSettingsWindow.menuFill=frame() +local start=assert(string.find(source,"local function ThemeBorder",1,true)) +local finish=assert(string.find(source,"local utils = Cursive.utils",start,true)) +assert(loadstring(string.sub(source,start,finish-1)))() +Cursive.db.profile.menuopacity=60 +Cursive.ApplyMenuStyle() +assert(CursiveSettingsWindow.backgroundAlpha==.6) +assert(CursiveSettingsWindow.menuFill.textureAlpha==.6) +assert(CursiveSettingsWindow.classicHeader.tintR==.8) +assert(CursiveSettingsWindow.r==.8) +assert(CursiveSettingsWindow.titleBar.backdrop==nil) +assert(CursiveSettingsWindow.classicHeader.shown) +Cursive.db.profile.menuopacity=100 +Cursive.ApplyMenuStyle() +assert(CursiveSettingsWindow.menuFill.textureAlpha==1) +Cursive.db.profile.classicmenu=false +Cursive.ApplyMenuStyle() +assert(CursiveSettingsWindow.r==.25) +assert(not CursiveSettingsWindow.classicHeader.shown) +assert(CursiveSettingsWindow.titleBar.backdrop) +local retired=frame(); function retired:IsShown() return true end +ui={unitFrames={{retired}},flashingCurseIcons={[{}]=3}} +function GetUIFont() return "font" end +function UpdateRootBarFrame() end +function Cursive:ScheduleEvent(name,fn,delay) self.delay=delay; self.scheduled=fn end +start=assert(string.find(source,"Cursive.UpdateFramesFromConfig = function()",1,true)) +finish=assert(string.find(source,"ui.BarEnter = function()",start,true)) +assert(loadstring(string.sub(source,start,finish-1)))() +Cursive.UpdateFramesFromConfig() +assert(Cursive.delay==.1) +Cursive.scheduled() +assert(next(ui.unitFrames)==nil) +assert(next(ui.flashingCurseIcons)==nil) +print("Menu style and resize refresh tests passed") +local start = assert(string.find(source,"local function UpdateEliteDragon",1,true)) +local finish = assert(string.find(source,"ui.BarUpdate = function()",start,true)) +local updateDragon = assert(loadstring(string.sub(source,start,finish-1).." return UpdateEliteDragon"))() +local dragon = frame() +function dragon:SetTexture(path) self.texture=path end +function dragon:SetDesaturated(value) self.silver=value end +local bar={eliteDragon=dragon} +updateDragon(bar,"elite"); assert(dragon.shown and string.find(dragon.texture,"Elite")) +updateDragon(bar,"rareelite"); assert(dragon.shown and dragon.silver) +updateDragon(bar,"worldboss"); assert(dragon.shown) +updateDragon(bar,"normal"); assert(not dragon.shown) +updateDragon(bar,"rare"); assert(not dragon.shown) +updateDragon({},"elite") +print("Elite dragon and opacity tests passed") \ No newline at end of file diff --git a/tests/classicapi.lua b/tests/classicapi.lua new file mode 100644 index 0000000..4e04531 --- /dev/null +++ b/tests/classicapi.lua @@ -0,0 +1,41 @@ +local callbacks = {} +local auras = {} +local removed = 0 +local pending = {} +Cursive = { nampower = true, db = {profile = {enabled=true,useclassicapi=true}}, + core = {addGuid=function() end}, + curses = {trackedCurseIds={[172]={name="corruption",rank=1}},guids={},pendingCast=pending} } +function Cursive.curses:ApplyCurse(id,guid,start,duration) + self.pendingCast = {} + self.guids[guid] = self.guids[guid] or {} + self.guids[guid].corruption = {currentPlayer=true,start=start,duration=duration} +end +function Cursive.curses:RemoveCurse(guid,name) self.guids[guid][name]=nil; removed=removed+1 end +function CreateFrame() return {RegisterEvent=function() end,SetScript=function(self,key,fn) callbacks[key]=fn end} end +function UnitExists(unit) return true,"mob" end +function UnitIsUnit(a,b) return a==b end +function GetTime() return 100 end +C_UnitAuras = { + GetAuraSlots=function(unit,filter,a,b,out) for i=1,table.getn(auras) do out[i]=i end; return nil,table.getn(auras) end, + UnitAuraBySlot=function(unit,slot) return unpack(auras[slot]) end, + GetAuraDataByAuraInstanceID=function() end, +} +dofile("classicapi.lua") +auras[1]={"Corruption","icon",1,"Magic",18,118,"player",false,false,172} +Cursive.ReconcileClassicAuras("target") +assert(Cursive.curses.guids.mob.corruption.start==100) +auras[1][6]=125 +Cursive.ReconcileClassicAuras("target") +assert(Cursive.curses.guids.mob.corruption.start==107) +auras={} +Cursive.ReconcileClassicAuras("target") +assert(removed==1) +Cursive.curses.guids.mob.corruption={currentPlayer=true} +Cursive.ReconcileClassicAuras("target") +assert(Cursive.curses.guids.mob.corruption) +auras[1]={"Corruption","icon",1,"Magic",18,118,"party1",false,false,172} +Cursive.ReconcileClassicAuras("target") +assert(not Cursive.curses.guids.mob.corruption.classicConfirmed) +C_UnitAuras=nil +Cursive.ReconcileClassicAuras("target") +print("ClassicAPI reconciliation tests passed") \ No newline at end of file diff --git a/tests/testmode.lua b/tests/testmode.lua new file mode 100644 index 0000000..c9cd411 --- /dev/null +++ b/tests/testmode.lua @@ -0,0 +1,48 @@ +local file=assert(io.open("ui.lua","r")); local source=file:read("*a"); file:close() +local function widget() + return { + SetScript=function() end, ClearAllPoints=function() end, + SetPoint=function(self,...) self.point=arg end, + SetText=function(self,value) self.text=value end, + SetMinMaxValues=function() end,SetValue=function() end,SetStatusBarColor=function() end, + SetTexture=function() end, SetAlpha=function() end, + Show=function(self) self.shown=true end,Hide=function(self) self.shown=false end, + } +end +Cursive={db={profile={maxcol=2,maxrow=3,maxcurses=4,notifyshadowvuln=true, + curseiconsize=20,healthborder=true}},ResetUnitFrames=function() ui.unitFrames={} end} +ui={unitFrames={},rootBarFrame={},padding=2} +local created=0 +function CreateBar(row,col,guid) + assert(guid==nil) -- never give samples a real unit identity + created=created+1 + local bar=widget() + bar.nameText=widget();bar.hpText=widget();bar.healthBar=widget() + bar.healthBar.border=widget();bar.icon=widget();bar.target_left=widget() + bar.shadowVulnIcon=widget();bar.shadowVulnIcon.timer=widget() + bar.ghostIcons={} + for i=1,4 do bar["curse"..i]=widget();bar["curse"..i].timer=widget();bar.ghostIcons[i]=widget() end + ui.unitFrames[col][row]=bar + return bar +end +function GetBarCords(row,col) return col*100,-row*20 end +function GetTime() return 100 end +function ThemeBorder() end +function UpdateEliteDragon() end +function UpdateAuraBorder() end +function SetRaidTargetIconTexture() end +local start=assert(string.find(source,"function Cursive.SetTestMode",1,true)) +local finish=assert(string.find(source,"local shouldDisplayGuids",start,true)) +local render=assert(loadstring(string.sub(source,start,finish-1).." return RenderTestBars"))() +Cursive.SetTestMode(true); render() +assert(created==6) +assert(ui.unitFrames[1][1].testMode) +assert(ui.unitFrames[1][1].curse1.shown) +assert(not ui.unitFrames[1][1].curse4.shown) +render();assert(created==6) -- reuse samples on each refresh +Cursive.db.profile.invertbars=true +Cursive.db.profile.expandupwards=true +render();assert(ui.unitFrames[1][1].point[1]=="BOTTOMLEFT") +Cursive.SetTestMode(false) +assert(not Cursive.testMode and next(ui.unitFrames)==nil) +print("Test mode layout and isolation tests passed") \ No newline at end of file diff --git a/ui.lua b/ui.lua index 81f21c7..86f162c 100644 --- a/ui.lua +++ b/ui.lua @@ -4,6 +4,79 @@ end local L = AceLibrary("AceLocale-2.2"):new("Cursive") +local function ThemeBorder(frame) + if ShaguTweaks and (ShaguTweaks.DarkMode or ShaguTweaks.GoldMode) and GameTooltip then + frame:SetBackdropBorderColor(GameTooltip:GetBackdropBorderColor()) + else + frame:SetBackdropBorderColor(1, 1, 1, 1) + end +end +local tooltipBorder = { edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", edgeSize = 8 } +local function AddAuraBorder(icon, parent) + if not Cursive.db.profile.auraborder then return end + local border = CreateFrame("Frame", nil, parent) + border:SetPoint("TOPLEFT", icon, "TOPLEFT", -2, 2) + border:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", 2, -2) + border:SetBackdrop(tooltipBorder) + border:EnableMouse(false) + border:Hide() + icon.tooltipBorder = border +end +local function UpdateAuraBorder(icon) + if icon.tooltipBorder then + if icon:IsShown() then ThemeBorder(icon.tooltipBorder); icon.tooltipBorder:Show() + else icon.tooltipBorder:Hide() end + end +end +function Cursive.ApplyMenuStyle() + local f = CursiveSettingsWindow + if not f then return end + if Cursive.db.profile.classicmenu then + f:SetBackdrop({bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", + edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", tile = true, + tileSize = 32, edgeSize = 32, insets = {left=8, right=8, top=8, bottom=8}}) + f:SetBackdropColor(1, 1, 1, (Cursive.db.profile.menuopacity or 100) / 100) + ThemeBorder(f) + else + f:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8X8", + edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1}) + f:SetBackdropColor(0.05, 0.05, 0.08, (Cursive.db.profile.menuopacity or 100) / 100) + f:SetBackdropBorderColor(0.25, 0.25, 0.35, 1) + end + if f.menuFill then + f.menuFill:SetTexture(0.025, 0.025, 0.035, (Cursive.db.profile.menuopacity or 100) / 100) + end + if f.classicHeader then + if ShaguTweaks and (ShaguTweaks.DarkMode or ShaguTweaks.GoldMode) and GameTooltip then + local r, g, b = GameTooltip:GetBackdropBorderColor() + f.classicHeader:SetVertexColor(r, g, b, 1) + else + f.classicHeader:SetVertexColor(1, 1, 1, 1) + end + end + if f.titleText then + f.titleText:ClearAllPoints() + if Cursive.db.profile.classicmenu then + f.titleText:SetPoint("TOP", f, "TOP", 0, -1) + else + f.titleText:SetPoint("LEFT", f.titleBar, "LEFT", 8, 0) + end + end + if f.titleBar then + + if Cursive.db.profile.classicmenu then + f.titleBar:SetBackdrop(nil) + f.classicHeader:Show() + f.titleBar:SetHeight(32) + else + f.titleBar:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8X8"}) + f.classicHeader:Hide() + f.titleBar:SetHeight(24) + f.titleBar:SetBackdropColor(0.22, 0.08, 0.38, 1) + end + end +end + local utils = Cursive.utils local filter = Cursive.filter @@ -102,10 +175,16 @@ local function GetBarThirdSectionWidth() return config.maxcurses * (config.curseiconsize + ui.padding) end +local function GetAuraGap() + -- Inverted rows put auras on the left, away from the top-right dragon. + local config = Cursive.db.profile + return (config.showelitedragon and not config.invertbars) and 12 or 0 +end + local function GetBarWidth() return GetBarFirstSectionWidth() + GetBarSecondSectionWidth() + - GetBarThirdSectionWidth() + GetBarThirdSectionWidth() + GetAuraGap() end local function UpdateRootBarFrame() @@ -221,6 +300,7 @@ end ui.unitFrames = {} -- holds all unitFrames for all columns/rows Cursive.UpdateFramesFromConfig = function() + if ui.UpdateFontPreview then ui.UpdateFontPreview(GetUIFont()) end for col, rows in pairs(ui.unitFrames) do for row, unitFrame in pairs(rows) do if unitFrame and unitFrame:IsShown() then @@ -233,8 +313,8 @@ Cursive.UpdateFramesFromConfig = function() UpdateRootBarFrame() end - -- after 3 seconds reset the unit frames so all changes are applied - Cursive:ScheduleEvent("resetUnitFrames", Cursive.ResetUnitFrames, 3) + -- Debounce slider movement, then rebuild rows with the new geometry. + Cursive:ScheduleEvent("resetUnitFrames", Cursive.ResetUnitFrames, 0.1) end Cursive.ResetUnitFrames = function() @@ -246,12 +326,15 @@ Cursive.ResetUnitFrames = function() end end end + -- Retired frames must not remain referenced by the flash driver. + for icon in pairs(ui.flashingCurseIcons) do ui.flashingCurseIcons[icon] = nil end -- clear cached frames so they are recreated ui.unitFrames = {} end ui.BarEnter = function() - if this.parent.healthBar then + if this.parent.testMode then return end + if this.parent.healthBar and not Cursive.db.profile.healthborder then this.parent.healthBar.border:SetBackdropBorderColor(1, 1, 1, 1) end this.parent.hover = true @@ -268,6 +351,23 @@ ui.BarLeave = function() GameTooltip:Hide() end +local function UpdateEliteDragon(bar, classification) + if not bar.eliteDragon then return end + if classification == "elite" or classification == "worldboss" or classification == "rareelite" then + bar.eliteDragon:SetTexture("Interface\\AddOns\\Cursive\\img\\EliteDragon") + bar.eliteDragon:SetDesaturated(classification == "rareelite") + bar.eliteDragon:Show() + if bar.eliteDragonTail then + bar.eliteDragonTail:SetTexture("Interface\\AddOns\\Cursive\\img\\EliteDragon") + bar.eliteDragonTail:SetDesaturated(classification == "rareelite") + bar.eliteDragonTail:Show() + end + else + bar.eliteDragon:Hide() + if bar.eliteDragonTail then bar.eliteDragonTail:Hide() end + end +end + ui.BarUpdate = function() if not this.guid or this.guid == 0 then this:Hide() @@ -280,6 +380,7 @@ ui.BarUpdate = function() this.tick = GetTime() + 0.05 end + UpdateEliteDragon(this, UnitClassification(this.guid)) -- update statusbar values if it exists if this.healthBar then this.healthBar:SetMinMaxValues(0, UnitHealthMax(this.guid)) @@ -290,7 +391,7 @@ ui.BarUpdate = function() this.healthBar:SetStatusBarColor(r, g, b, a) -- update health bar border - if this.healthBar.border then + if this.healthBar.border and not Cursive.db.profile.healthborder then if this.hover then this.healthBar.border:SetBackdropBorderColor(1, 1, 1, 1) elseif UnitAffectingCombat(this.guid) then @@ -354,6 +455,7 @@ ui.BarUpdate = function() end ui.BarClick = function() + if this.parent.testMode then return end if arg1 == "LeftButton" then TargetUnit(this.parent.guid) elseif arg1 == "RightButton" then @@ -375,7 +477,7 @@ local function CreateBarFirstSection(unitFrame, guid) -- Normal positioning (leftmost) firstSection:SetPoint("LEFT", unitFrame, "LEFT", 0, 0) end - + firstSection:SetWidth(GetBarFirstSectionWidth()) firstSection:SetHeight(config.height) firstSection:EnableMouse(false) @@ -424,7 +526,7 @@ local function CreateBarSecondSection(unitFrame, guid) -- Normal positioning relative to first section secondSection:SetPoint("LEFT", unitFrame.firstSection, "RIGHT", 0, 0) end - + secondSection:SetWidth(GetBarSecondSectionWidth()) secondSection:SetHeight(config.height) unitFrame.secondSection = secondSection @@ -438,7 +540,7 @@ local function CreateBarSecondSection(unitFrame, guid) -- create health bar if config.showhealthbar then local healthBar = CreateFrame("StatusBar", "CursiveHealthBar", secondSection) - healthBar:SetStatusBarTexture(config.bartexture) + healthBar:SetStatusBarTexture(config.modernhealth and "Interface\\AddOns\\Cursive\\img\\BlizzardRetailBar" or config.bartexture) healthBar:SetStatusBarColor(1, .8, .2, 1) healthBar:SetMinMaxValues(0, 100) healthBar:SetValue(20) @@ -465,18 +567,18 @@ local function CreateBarSecondSection(unitFrame, guid) end -- create health bar backdrops - if pfUI and pfUI.uf then + if pfUI and pfUI.uf and not config.healthborder then pfUI.api.CreateBackdrop(healthBar) healthBar.border = healthBar.backdrop else healthBar:SetBackdrop(ui.background) healthBar:SetBackdropColor(0, 0, 0, 1) - local border = CreateFrame("Frame", "CursiveBorder", healthBar.bar) - border:SetBackdrop(ui.border) + local border = CreateFrame("Frame", "CursiveBorder", healthBar) + border:SetBackdrop(config.healthborder and tooltipBorder or ui.border) border:SetBackdropColor(.2, .2, .2, 1) - border:SetPoint("TOPLEFT", healthBar.bar, "TOPLEFT", -2, 2) - border:SetPoint("BOTTOMRIGHT", healthBar.bar, "BOTTOMRIGHT", 2, -2) + border:SetPoint("TOPLEFT", healthBar, "TOPLEFT", -2, 2) + border:SetPoint("BOTTOMRIGHT", healthBar, "BOTTOMRIGHT", 2, -2) healthBar.border = border end else @@ -503,9 +605,9 @@ local function CreateBarThirdSection(unitFrame, guid) thirdSection:SetPoint("LEFT", unitFrame, "LEFT", 0, 0) else -- Normal positioning relative to second section - thirdSection:SetPoint("LEFT", unitFrame.secondSection, "RIGHT", 0, 0) + thirdSection:SetPoint("LEFT", unitFrame.secondSection, "RIGHT", GetAuraGap(), 0) end - + thirdSection:SetWidth(GetBarThirdSectionWidth()) thirdSection:SetHeight(config.height) thirdSection:EnableMouse(false) @@ -526,6 +628,7 @@ local function CreateBarThirdSection(unitFrame, guid) curse:SetPoint("LEFT", thirdSection, "LEFT", i * ui.padding + ((i - 1) * config.curseiconsize), 0) end + AddAuraBorder(curse, thirdSection) curse.timer = thirdSection:CreateFontString(nil, "OVERLAY", "GameFontNormal") curse.timer:SetFontObject(GameFontHighlight) SafeSetFont(curse.timer, GetUIFont(), config.cursetimersize, "OUTLINE") @@ -549,6 +652,7 @@ local function CreateBarThirdSection(unitFrame, guid) ghost:SetHeight(config.curseiconsize) ghost:SetAlpha(config.ghosticonalpha) ghost:Hide() + AddAuraBorder(ghost, thirdSection) unitFrame.ghostIcons[i] = ghost end @@ -561,6 +665,7 @@ local function CreateBarThirdSection(unitFrame, guid) shadowVulnIcon:SetTexture("Interface\\Icons\\Spell_Shadow_ShadowBolt") shadowVulnIcon:SetAlpha(0.3) + AddAuraBorder(shadowVulnIcon, thirdSection) shadowVulnIcon.timer = thirdSection:CreateFontString(nil, "OVERLAY", "GameFontNormal") shadowVulnIcon.timer:SetFontObject(GameFontHighlight) SafeSetFont(shadowVulnIcon.timer, GetUIFont(), config.cursetimersize, "OUTLINE") @@ -575,6 +680,27 @@ end local function CreateBar(row, col, guid) local unitFrame = CreateFrame("Frame", "CursiveUnitFrame", ui.rootBarFrame) unitFrame.guid = guid + if Cursive.db.profile.showelitedragon then + local dragonLayer = CreateFrame("Frame", nil, unitFrame) + dragonLayer:EnableMouse(false) + local dragon = dragonLayer:CreateTexture(nil, "OVERLAY") + unitFrame.eliteDragonLayer = dragonLayer + dragon:SetWidth(30) + dragon:SetHeight(28 * 34 / 108) + -- Anchored after the health/name section has been constructed. + -- Two adjoining crops keep the crest and right wing, leaving out + -- the portrait ring and the bottom-left curl. + dragon:SetTexCoord(140 / 256, 1, 0, 34 / 128) + local tail = dragonLayer:CreateTexture(nil, "OVERLAY") + tail:SetWidth(30 * 36 / 116) + tail:SetHeight(28 * 74 / 108) + tail:SetTexCoord(220 / 256, 1, 34 / 128, 108 / 128) + tail:SetPoint("TOPRIGHT", dragon, "BOTTOMRIGHT", 0, 0) + tail:Hide() + unitFrame.eliteDragonTail = tail + dragon:Hide() + unitFrame.eliteDragon = dragon + end unitFrame:SetScript("OnUpdate", ui.BarUpdate) @@ -596,6 +722,19 @@ local function CreateBar(row, col, guid) CreateBarThirdSection(unitFrame, guid) end + if unitFrame.eliteDragon then + -- Raise above the health StatusBar and its border child frame. + local anchor = unitFrame.healthBar or unitFrame.secondSection + local topLevel = anchor:GetFrameLevel() + if unitFrame.healthBar and unitFrame.healthBar.border then + topLevel = math.max(topLevel, unitFrame.healthBar.border:GetFrameLevel()) + end + unitFrame.eliteDragonLayer:SetFrameLevel(topLevel + 2) + unitFrame.eliteDragonLayer:SetAllPoints(anchor) + -- Curl the ornament around the upper-right corner of the health/name bar. + unitFrame.eliteDragon:SetPoint("TOPRIGHT", unitFrame.healthBar or unitFrame.secondSection, + "TOPRIGHT", 8, 5) + end ui.unitFrames[col][row] = unitFrame return unitFrame end @@ -650,6 +789,13 @@ local GHOST_CURSE_FALLBACK_ICON = "Interface\\Icons\\Spell_Shadow_CurseOfSargera -- focus frame support at all (that's a later-expansion addition), and -- UnitExists("focus") hard-errors here rather than just returning nil. local function SafeUnitDebuff(guid, i) + if Cursive.db.profile.useclassicapi and C_UnitAuras and C_UnitAuras.UnitAura then + local _, targetGuid = UnitExists("target") + if guid == targetGuid then + local name, texture = C_UnitAuras.UnitAura("target", i, "HARMFUL") + return texture + end + end local _, targetGuid = UnitExists("target") if targetGuid and guid == targetGuid then return UnitDebuff("target", i) @@ -945,6 +1091,12 @@ local function DisplayGuid(guid) end end + for i = 1, Cursive.db.profile.maxcurses do + UpdateAuraBorder(unitFrame["curse" .. i]) + UpdateAuraBorder(unitFrame.ghostIcons[i]) + end + UpdateAuraBorder(unitFrame.shadowVulnIcon) + if unitFrame.healthBar and Cursive.db.profile.healthborder then ThemeBorder(unitFrame.healthBar.border) end unitFrame:Show() ui.numDisplayed = ui.numDisplayed + 1 @@ -985,6 +1137,81 @@ local function CheckForCleanup(guid, time) end end +-- Session-only samples reuse the real row construction and layout. +function Cursive.SetTestMode(enabled) + Cursive.testMode = enabled and true or false + Cursive.ResetUnitFrames() +end + +local testIcons = { + "Interface\\Icons\\Spell_Shadow_CurseOfSargeras", + "Interface\\Icons\\Spell_Shadow_AbominationExplosion", + "Interface\\Icons\\Spell_Fire_Immolation", + "Interface\\Icons\\Spell_Shadow_Requiem", +} +local function RenderTestBars() + local config = Cursive.db.profile + local now = GetTime() + for col = 1, config.maxcol do + ui.unitFrames[col] = ui.unitFrames[col] or {} + for row = 1, config.maxrow do + local bar = ui.unitFrames[col][row] + if not bar then + bar = CreateBar(row, col, nil) + bar.testMode = true + bar:SetScript("OnUpdate", nil) + end + UpdateEliteDragon(bar, row == 1 and "elite" or (row == 2 and "rareelite" or "normal")) + local x, y = GetBarCords(row, col) + bar:ClearAllPoints() + bar:SetPoint(config.expandupwards and "BOTTOMLEFT" or "TOPLEFT", ui.rootBarFrame, + config.expandupwards and "BOTTOMLEFT" or "TOPLEFT", x, y) + if bar.nameText then bar.nameText:SetText(row == 1 and "Test: Defias Raider" or "Test: Training Dummy") end + if bar.hpText then bar.hpText:SetText("2453") end + if bar.healthBar then + bar.healthBar:SetMinMaxValues(0, 4000) + bar.healthBar:SetValue(2453) + bar.healthBar:SetStatusBarColor(0.8, 0.2 + math.mod(row, 3) * 0.2, 0.2, 1) + if config.healthborder then ThemeBorder(bar.healthBar.border) end + end + if bar.icon then SetRaidTargetIconTexture(bar.icon, math.mod(row - 1, 8) + 1); bar.icon:Show() end + if bar.target_left then + if row == 1 then bar.target_left:Show() else bar.target_left:Hide() end + end + local offset = config.notifyshadowvuln and 1 or 0 + if config.notifyshadowvuln then + bar.shadowVulnIcon:ClearAllPoints() + bar.shadowVulnIcon:SetPoint(config.invertbars and "RIGHT" or "LEFT", bar.thirdSection, + config.invertbars and "RIGHT" or "LEFT", config.invertbars and -ui.padding or ui.padding, 0) + bar.shadowVulnIcon:SetAlpha(1) + bar.shadowVulnIcon.timer:SetText("5") + bar.shadowVulnIcon.timer:Show() + bar.shadowVulnIcon:Show() + else bar.shadowVulnIcon:Hide() end + for i = 1, config.maxcurses do + local icon = bar["curse" .. i] + local slot = i + offset + if slot <= config.maxcurses then + local position = slot * ui.padding + (slot - 1) * config.curseiconsize + icon:ClearAllPoints() + icon:SetPoint(config.invertbars and "RIGHT" or "LEFT", bar.thirdSection, + config.invertbars and "RIGHT" or "LEFT", config.invertbars and -position or position, 0) + icon:SetTexture(testIcons[math.mod(i - 1, table.getn(testIcons)) + 1]) + icon:SetAlpha(1) + local remaining = 18 - math.mod(now + i * 3, 18) + icon.timer:SetText(config.curseshowdecimals and string.format("%.1f", remaining) or math.ceil(remaining)) + icon.timer:Show() + icon:Show() + else icon:Hide(); icon.timer:Hide() end + bar.ghostIcons[i]:Hide() + UpdateAuraBorder(icon) + end + UpdateAuraBorder(bar.shadowVulnIcon) + bar:Show() + end + end +end + local shouldDisplayGuids = {}; local displayedGuids = {}; @@ -992,7 +1219,7 @@ ui:SetAllPoints() ui:SetScript("OnUpdate", function() local config = Cursive.db.profile - if not config.enabled then + if not config.enabled and not Cursive.testMode then return end @@ -1011,6 +1238,11 @@ ui:SetScript("OnUpdate", function() return end + if Cursive.testMode then + RenderTestBars() + return + end + -- reset display data ui.row = 1 ui.col = 1 @@ -1207,7 +1439,7 @@ gcdTicker:SetScript("OnUpdate", function() -- Preview mode: triggered directly by changing a GCD bar setting, -- rather than trying to detect whether the options menu is open. - if ui.gcdPreviewUntil and GetTime() < ui.gcdPreviewUntil then + if (Cursive.testMode and bar.enabled) or (ui.gcdPreviewUntil and GetTime() < ui.gcdPreviewUntil) then bar:SetMinMaxValues(0, 1) bar:SetValue(0.6) if not bar:IsShown() then @@ -1744,7 +1976,10 @@ local function MakeRangeWidget(parent, entry, yOff) getglobal(sliderName .. "High"):SetText(tostring(entry.max or 100)) getglobal(sliderName .. "Text"):SetText("") slider:SetScript("OnValueChanged", function() - CallGetSet(entry, true, this:GetValue()) + local minimum, maximum, step = entry.min or 0, entry.max or 100, entry.step or 1 + local value = minimum + math.floor((this:GetValue() - minimum) / step + 0.5) * step + value = math.max(minimum, math.min(maximum, value)) + CallGetSet(entry, true, value) valLbl:SetText(tostring(CallGetSet(entry, false))) end) @@ -1956,8 +2191,14 @@ function ui.CreateSettingsWindow() edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 } }) - f:SetBackdropColor(0.05, 0.05, 0.08, 0.95) + f:SetBackdropColor(0.05, 0.05, 0.08, (Cursive.db.profile.menuopacity or 100) / 100) f:SetBackdropBorderColor(0.25, 0.25, 0.35, 1) + Cursive.ApplyMenuStyle() + local menuFill = f:CreateTexture(nil, "BACKGROUND") + menuFill:SetPoint("TOPLEFT", f, "TOPLEFT", 8, -8) + menuFill:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -8, 8) + f.menuFill = menuFill + Cursive.ApplyMenuStyle() f:SetMovable(true) f:EnableMouse(true) f:RegisterForDrag("LeftButton") @@ -1973,10 +2214,20 @@ function ui.CreateSettingsWindow() titleBar:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" }) titleBar:SetBackdropColor(0.22, 0.08, 0.38, 1) + f.titleBar = titleBar + local classicHeader = titleBar:CreateTexture(nil, "BACKGROUND") + classicHeader:SetTexture("Interface\\DialogFrame\\UI-DialogBox-Header") + classicHeader:SetWidth(360) + classicHeader:SetHeight(64) + classicHeader:SetPoint("TOP", f, "TOP", 0, 14) + f.classicHeader = classicHeader + Cursive.ApplyMenuStyle() + local title = titleBar:CreateFontString(nil, "OVERLAY") title:SetFont(STANDARD_TEXT_FONT, 12, "OUTLINE") title:SetTextColor(1, 0.84, 0, 1) title:SetPoint("Left", titleBar, "Left", 8, 0) + f.titleText = title local versionStr = GetAddOnMetadata("Cursive", "Version") or "?" title:SetText("Cursive — Settings (v"..versionStr..")") @@ -2006,7 +2257,7 @@ function ui.CreateSettingsWindow() previewLabel:SetFont(STANDARD_TEXT_FONT, 9, "") previewLabel:SetTextColor(0.6, 0.6, 0.65, 1) previewLabel:SetPoint("TopLeft", previewSection, "TopLeft", 4, -2) - previewLabel:SetText("Font Preview") + previewLabel:SetText("Text Preview (bar sizes apply to unit rows)") local previewName = previewSection:CreateFontString(nil, "OVERLAY") SafeSetFont(previewName, GetUIFont(), 12, "THINOUTLINE") @@ -2037,12 +2288,13 @@ function ui.CreateSettingsWindow() previewSacrifice:SetText("Sacrifice: 18s | 1200 absorb") local function ApplyPreviewFont(fontPath) - previewName:SetFont(fontPath, 12, "THINOUTLINE") - previewHp:SetFont(fontPath, 12, "THINOUTLINE") - previewCurseTimer:SetFont(fontPath, 12, "OUTLINE") + previewName:SetFont(fontPath, Cursive.db.profile.textsize, "THINOUTLINE") + previewHp:SetFont(fontPath, Cursive.db.profile.textsize, "THINOUTLINE") + previewCurseTimer:SetFont(fontPath, Cursive.db.profile.cursetimersize, "OUTLINE") previewSacrifice:SetFont(fontPath, 10, "OUTLINE") end ui.UpdateFontPreview = ApplyPreviewFont + ApplyPreviewFont(GetUIFont()) local scrollFrame = CreateFrame("ScrollFrame", "CursiveSettingsScrollFrame", f, "UIPanelScrollFrameTemplate") scrollFrame:SetPoint("TopLeft", f, "TopLeft", 16, -116) @@ -2056,6 +2308,7 @@ function ui.CreateSettingsWindow() local finalY = RenderOptionsInto(content, Cursive.cmdtable.args, -4) content:SetHeight(math.abs(finalY) + 30) + Cursive.ApplyMenuStyle() settingsWindow = f return f end