0.1.175: Einstellungen mit Unter-Reitern, Tooltip-Einstellungen im Core

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016onJCSHLwhs9udhC9ZLzk9
This commit is contained in:
Lacustat
2026-10-29 16:19:11 +01:00
co-authored by Claude Sonnet 5.5
parent e7d2d14bb4
commit 99e6820776
4 changed files with 344 additions and 26 deletions
+204
View File
@@ -0,0 +1,204 @@
-- Freewood-Core: Tooltip-Einstellungen (Standard fuer alle Module)
-- Das Freewood-Design und der Tooltip an der Maus gehoeren dem Core. Module melden ihre Zusatzinfos mit
-- FW.Tooltips.RegisterOption(key, labelKey, { default = true, order = 10, available = function() return true end }) an;
-- sie erscheinen dann unter Einstellungen -> Tooltips. Lesen: FW.Tooltips.Get(key), Schreiben: FW.Tooltips.Set(key, wert).
-- Aenderungen melden sich ueber FW:Fire("QUEST_TIP_CHANGED", key) (Name bleibt aus Kompatibilitaet zu den Modulen).
local FW = _G.Freewood
if not FW then return end
local T = {}
FW.Tooltips = T
local BG = { 0.059, 0.075, 0.122, 0.97 }
local GOLD = { 0.616, 0.471, 0.149, 1 }
local WHITE = "Interface\\Buttons\\WHITE8X8"
local TIPS = { "GameTooltip", "ItemRefTooltip", "ShoppingTooltip1", "ShoppingTooltip2", "FreewoodCmpTip1", "FreewoodCmpTip2" }
local defaults = { tipSkin = false, tipCursor = false }
local options = {} -- Zusatzoptionen der Module
local function Store()
local s = FW.settings
if not s then return nil end
if type(s.tooltips) ~= "table" then s.tooltips = {} end
return s.tooltips
end
function T.Raw(key)
local st = Store()
return st and st[key]
end
function T.Get(key)
local st = Store()
local v = st and st[key]
if v == nil then return defaults[key] end
return v
end
function T.Set(key, value)
local st = Store()
if not st then return end
st[key] = value
FW:Fire("QUEST_TIP_CHANGED", key)
end
-- Alte Werte uebernehmen (Modul ruft es einmal mit seinen gespeicherten Werten auf); nur wenn im Core noch nichts steht
function T.Migrate(old)
local st = Store()
if not st or type(old) ~= "table" then return end
for k, v in pairs(old) do
if type(k) == "string" and k:sub(1, 3) == "tip" and st[k] == nil and type(v) == "boolean" then
st[k] = v
end
end
end
function T.RegisterOption(key, labelKey, def)
def = def or {}
if defaults[key] == nil then defaults[key] = def.default ~= false end
for _, o in ipairs(options) do
if o.key == key then return end
end
options[#options + 1] = { key = key, label = labelKey, order = def.order or 100, available = def.available }
table.sort(options, function(a, b)
if a.order ~= b.order then return a.order < b.order end
return a.key < b.key
end)
if T.RefreshSettings then T.RefreshSettings() end
end
------------------------------------------------------------------------
-- Design
------------------------------------------------------------------------
local function SkinOn(tt)
if not tt.SetBackdrop then return end
tt:SetBackdrop({ bgFile = WHITE, edgeFile = WHITE, tile = false, edgeSize = 1,
insets = { left = 1, right = 1, top = 1, bottom = 1 } })
tt:SetBackdropColor(BG[1], BG[2], BG[3], BG[4])
tt:SetBackdropBorderColor(GOLD[1], GOLD[2], GOLD[3], GOLD[4])
tt.fwSkinned = true
end
local function SkinOff(tt)
if not tt.fwSkinned or not tt.SetBackdrop then return end
tt:SetBackdrop({ bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 16,
insets = { left = 5, right = 5, top = 5, bottom = 5 } })
tt:SetBackdropColor(0.09, 0.09, 0.19, 1)
tt:SetBackdropBorderColor(1, 1, 1, 1)
tt.fwSkinned = nil
end
local hooked = {}
local function Apply(tt)
if T.Get("tipSkin") == true then SkinOn(tt) else SkinOff(tt) end
end
function T.Hook(tt)
if not tt or hooked[tt] or not tt.HookScript then return end
hooked[tt] = true
tt:HookScript("OnShow", function(self) pcall(Apply, self) end)
pcall(tt.HookScript, tt, "OnTooltipSetItem", function(self) pcall(Apply, self) end)
pcall(tt.HookScript, tt, "OnTooltipSetUnit", function(self) pcall(Apply, self) end)
pcall(Apply, tt)
end
local function HookAll()
for _, name in ipairs(TIPS) do T.Hook(_G[name]) end
end
------------------------------------------------------------------------
-- Tooltip an der Maus (nur Welt: Gegner, Objekte; Inventar bleibt am Item)
------------------------------------------------------------------------
local follow = _G.CreateFrame("Frame")
follow:SetScript("OnUpdate", function()
if T.Get("tipCursor") ~= true then return end
local tt = _G.GameTooltip
if not tt or not tt:IsShown() then return end
local owner = tt.GetOwner and tt:GetOwner()
if owner ~= _G.UIParent then return end
local x, y = _G.GetCursorPosition()
local s = _G.UIParent:GetEffectiveScale()
local ts = tt:GetEffectiveScale()
if not x or not s or s == 0 or not ts or ts == 0 then return end
x, y = x / s, y / s
local w, h = (tt:GetWidth() or 0) * ts / s, (tt:GetHeight() or 0) * ts / s
local sw, sh = _G.UIParent:GetWidth(), _G.UIParent:GetHeight()
local px = x + 16
if px + w > sw then px = x - 16 - w end
local py = y + 16
if py + h > sh then py = y - 16 - h end
if px < 0 then px = 0 end
if py < 0 then py = 0 end
tt:ClearAllPoints()
tt:SetPoint("BOTTOMLEFT", _G.UIParent, "BOTTOMLEFT", px * s / ts, py * s / ts)
end)
local function Reapply()
HookAll()
for _, name in ipairs(TIPS) do
local tt = _G[name]
if tt then pcall(Apply, tt) end
end
end
FW:On("QUEST_TIP_CHANGED", function(key)
if key == "tipSkin" then Reapply() end
end, T)
local init = _G.CreateFrame("Frame")
init:RegisterEvent("PLAYER_ENTERING_WORLD")
init:SetScript("OnEvent", function() Reapply() end)
------------------------------------------------------------------------
-- Einstellungen (Unter-Reiter "Tooltips")
------------------------------------------------------------------------
local UI = FW.UI
if UI and UI.RegisterSettingsSection then
UI:RegisterSettingsSection("tooltips", {
order = 10,
category = "tooltips",
height = 300,
build = function(parent)
local L = FW.L
local f, stack = UI.CreateSection(parent, L.TIPSET_HEADER)
local note = UI.CreateLabel(f, L.TIPSET_NOTE, { size = 11, color = "textDim", justifyV = "TOP" })
stack:Add(note, { fit = true, gap = 8 })
local boxes, made = {}, {}
local function Box(key, label)
local cb = UI.CreateCheckbox(f, label, function() return T.Get(key) == true end,
function(on) T.Set(key, on and true or false) end)
stack:Add(cb, { fit = true, gap = 6 })
boxes[#boxes + 1] = cb
return cb
end
Box("tipSkin", L.TIPSET_SKIN)
Box("tipCursor", L.TIPSET_CURSOR)
function f.Refresh()
for _, o in ipairs(options) do
if not made[o.key] and (not o.available or o.available()) then
made[o.key] = true
Box(o.key, FW.L[o.label] or o.label)
end
end
for _, cb in ipairs(boxes) do cb:Refresh() end
stack:Layout()
end
T.settingsFrame = f
f.Refresh()
return f
end,
refresh = function(frame)
frame.Refresh()
end,
})
function T.RefreshSettings()
local f = T.settingsFrame
if f and f.Refresh then f.Refresh() end
end
end
+2 -1
View File
@@ -3,7 +3,7 @@
## Notes: Base addon of the Freewood family: data storage, UI kit, export/import and the main window.
## Notes-deDE: Basis-Addon der Freewood-Familie: Datenspeicher, UI-Bausteine, Export/Import und Hauptfenster.
## Author: Lacustat
## Version: 0.1.174
## Version: 0.1.175
## SavedVariables: FreewoodDB
## SavedVariablesPerCharacter: FreewoodCharDB
@@ -16,5 +16,6 @@ UI\Kit.lua
UI\MainFrame.lua
UI\MainWindow.lua
UI\Tabs.lua
Core\Tooltips.lua
UI\Minimap.lua
Core\Slash.lua
+18
View File
@@ -82,6 +82,15 @@ FW:AddLocale("enUS", {
SETTINGS_LANGUAGE = "Language",
SETTINGS_LANGUAGE_NOTE = "A language change takes effect after /reload.",
SETTINGS_RESET_POS = "Reset window position and size",
SETTINGS_CAT_GENERAL = "General",
SETTINGS_CAT_DATABASE = "Database",
SETTINGS_CAT_TOOLTIPS = "Tooltips",
SETTINGS_CAT_QUEST = "Quests and hints",
SETTINGS_CAT_MODULES = "Modules",
TIPSET_HEADER = "Tooltips",
TIPSET_NOTE = "Look and extra information in item and creature tooltips. Each part can be switched off.",
TIPSET_SKIN = "Freewood look for tooltips (dark blue, gold border)",
TIPSET_CURSOR = "Show creature and object tooltips at the mouse",
WINDOW_RESIZE = "Resize",
WINDOW_RESIZE_TIP = "Drag to change the window size. The size is remembered.",
WINDOW_SCALE = "Window scale",
@@ -155,6 +164,15 @@ FW:AddLocale("deDE", {
SETTINGS_LANGUAGE = "Sprache",
SETTINGS_LANGUAGE_NOTE = "Eine Sprachänderung wirkt nach /reload.",
SETTINGS_RESET_POS = "Position und Größe zurücksetzen",
SETTINGS_CAT_GENERAL = "Allgemein",
SETTINGS_CAT_DATABASE = "Datenbank",
SETTINGS_CAT_TOOLTIPS = "Tooltips",
SETTINGS_CAT_QUEST = "Quests und Hinweise",
SETTINGS_CAT_MODULES = "Module",
TIPSET_HEADER = "Tooltips",
TIPSET_NOTE = "Aussehen und Zusatzinfos in Item- und Gegner-Tooltips. Jeder Teil lässt sich einzeln ausschalten.",
TIPSET_SKIN = "Freewood-Design für Tooltips (dunkelblau, Goldrand)",
TIPSET_CURSOR = "Gegner- und Objekt-Tooltips an der Maus anzeigen",
WINDOW_RESIZE = "Größe ändern",
WINDOW_RESIZE_TIP = "Ziehen, um die Fenstergröße zu ändern. Die Größe wird gespeichert.",
WINDOW_SCALE = "Fenstergröße",
+120 -25
View File
@@ -245,12 +245,58 @@ end
-- Module koennen eigene Abschnitte in den Einstellungen anmelden:
-- UI:RegisterSettingsSection(id, { order = 100, height = 260, build = function(parent) return frame end, refresh = function(frame) end })
-- Die Abschnitte stehen in zwei Spalten rechts neben den allgemeinen Einstellungen. Die Hoehe eines Abschnitts
-- def.category (optional) ordnet den Abschnitt einem Unter-Reiter zu: "general" (Standard, mit den allgemeinen Einstellungen
-- links), "database", "tooltips", "quest" oder "modules". Reiter ohne Abschnitte werden nicht gezeigt.
-- Die Abschnitte stehen in Spalten (allgemein: zwei Spalten rechts neben den allgemeinen Einstellungen). Die Hoehe eines Abschnitts
-- ist der Wert frame.flowH, wenn er ihn meldet (UI.CreateSection tut das), sonst def.height. Die ganze Seite
-- scrollt, wenn sie hoeher ist als das Fenster; die Breiten richten sich nach der Fensterbreite.
local settingsSections = {}
local settingsFrame, settingsBody, settingsScroll, settingsLeft
local vline1, vline2
local vlines = {}
local catBar, catButtons = nil, {}
local currentCat
local CATS = { "general", "database", "tooltips", "quest", "modules" }
local CAT_LABEL = {
general = "SETTINGS_CAT_GENERAL", database = "SETTINGS_CAT_DATABASE", tooltips = "SETTINGS_CAT_TOOLTIPS",
quest = "SETTINGS_CAT_QUEST", modules = "SETTINGS_CAT_MODULES",
}
local function CatOf(def)
return CAT_LABEL[def.category] and def.category or "general"
end
local function CountCats()
local counts = {}
for _, def in ipairs(settingsSections) do
local c = CatOf(def)
counts[c] = (counts[c] or 0) + 1
end
return counts
end
-- gewaehlter Unter-Reiter (gemerkt); ohne Abschnitte faellt er auf "general" zurueck
local function CurrentCat()
if not currentCat then
local c = FW.settings and FW.settings.settingsCat
currentCat = CAT_LABEL[c] and c or "general"
end
if currentCat ~= "general" and not CountCats()[currentCat] then
currentCat = "general"
end
return currentCat
end
local function UpdateCatBar()
if not catBar then return end
local cat, counts, x = CurrentCat(), CountCats(), 0
for _, b in ipairs(catButtons) do
if b.cat == "general" or counts[b.cat] then
b:Show()
b:ClearAllPoints()
b:SetPoint("TOPLEFT", catBar, "TOPLEFT", x, 0)
x = x + b:GetWidth() + 6
b:SetStyle(b.cat == cat and "primary" or "default")
else
b:Hide()
end
end
end
-- Abschnitt mit Ueberschrift, Trennlinie und einem Stapel darunter: local f, stack = UI.CreateSection(parent, "Titel")
function UI.CreateSection(parent, titleText)
@@ -292,35 +338,66 @@ local function LayoutSections()
if W < 200 then
W = 1000
end
local cat = CurrentCat()
local general = cat == "general"
local leftW = LeftWidth(W)
local x1 = leftW + LEFT_GAP
local colW = math.max(200, math.floor((W - x1 - SECTION_GAP) / 2))
local x2 = x1 + colW + SECTION_GAP
local x0 = general and (leftW + LEFT_GAP) or 0
local ncol = general and 2 or FW.Util.Clamp(math.floor((W + SECTION_GAP) / (420 + SECTION_GAP)), 1, 3)
local colW = math.max(200, math.floor((W - x0 - (ncol - 1) * SECTION_GAP) / ncol))
local xs, ypos = {}, {}
for i = 1, ncol do
xs[i] = x0 + (i - 1) * (colW + SECTION_GAP)
ypos[i] = 0
end
settingsLeft:SetShown(general)
settingsLeft:SetWidth(leftW)
local ypos = { 0, 0 }
for i, def in ipairs(settingsSections) do
def.col = (ypos[1] >= ypos[2]) and 1 or 2 -- jeweils in die kuerzere Spalte
local h = (def.frame and def.frame.flowH) or def.height or 260
def.y = ypos[def.col]
ypos[def.col] = ypos[def.col] - h - 10
for _, def in ipairs(settingsSections) do
local frame = def.frame
if frame then
frame:ClearAllPoints()
frame:SetPoint("TOPLEFT", settingsBody, "TOPLEFT", def.col == 2 and x2 or x1, def.y)
frame:SetWidth(colW)
frame:SetHeight(h)
if CatOf(def) ~= cat then
if frame then frame:Hide() end
else
local best = 1 -- jeweils in die kuerzeste Spalte
for i = 2, ncol do
if ypos[i] > ypos[best] then best = i end
end
local h = (frame and frame.flowH) or def.height or 260
def.col, def.y = best, ypos[best]
ypos[best] = ypos[best] - h - 10
if frame then
frame:Show()
frame:ClearAllPoints()
frame:SetPoint("TOPLEFT", settingsBody, "TOPLEFT", xs[best], def.y)
frame:SetWidth(colW)
frame:SetHeight(h)
end
end
end
local contentH = math.max(-ypos[1], -ypos[2], settingsLeft.flowH or 0) + 20
local contentH = general and (settingsLeft.flowH or 0) or 0
for i = 1, ncol do
contentH = math.max(contentH, -ypos[i])
end
contentH = contentH + 20
settingsBody.contentH = contentH
settingsBody:SetHeight(contentH)
settingsScroll:SetContentHeight(contentH)
vline1:ClearAllPoints()
vline1:SetPoint("TOPLEFT", settingsBody, "TOPLEFT", x1 - 20, 0)
vline1:SetPoint("BOTTOMLEFT", settingsBody, "TOPLEFT", x1 - 20, -contentH)
vline2:ClearAllPoints()
vline2:SetPoint("TOPLEFT", settingsBody, "TOPLEFT", x2 - SECTION_GAP / 2, 0)
vline2:SetPoint("BOTTOMLEFT", settingsBody, "TOPLEFT", x2 - SECTION_GAP / 2, -contentH)
local pos = {}
if general then
pos[1] = x0 - 20
pos[2] = xs[2] - SECTION_GAP / 2
else
for i = 2, ncol do pos[#pos + 1] = xs[i] - SECTION_GAP / 2 end
end
for i, line in ipairs(vlines) do
if pos[i] then
line:Show()
line:ClearAllPoints()
line:SetPoint("TOPLEFT", settingsBody, "TOPLEFT", pos[i], 0)
line:SetPoint("BOTTOMLEFT", settingsBody, "TOPLEFT", pos[i], -contentH)
else
line:Hide()
end
end
UpdateCatBar()
layingOut = false
if pendingLayout and layoutDepth < 4 then
pendingLayout = false
@@ -331,6 +408,12 @@ local function LayoutSections()
pendingLayout = false
end
local function SelectCategory(cat)
currentCat = cat
if FW.settings then FW.settings.settingsCat = cat end
LayoutSections()
end
local function BuildSection(def)
if def.frame or not settingsBody then
return
@@ -367,9 +450,21 @@ local function BuildSettings(content)
divider:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -30)
divider:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -30)
-- Unter-Reiter (Kategorien)
catBar = CreateFrame("Frame", nil, f)
catBar:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -42)
catBar:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -42)
catBar:SetHeight(28)
catButtons = {}
for _, cat in ipairs(CATS) do
local b = UI.CreateButton(catBar, L[CAT_LABEL[cat]], 160, 26, function() SelectCategory(cat) end)
b.cat = cat
catButtons[#catButtons + 1] = b
end
-- Alles darunter scrollt, wenn das Fenster zu niedrig ist
local scroll = UI.CreateScrollFrame(f)
scroll:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -42)
scroll:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -78)
scroll:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
local body = scroll.child
settingsScroll, settingsBody, settingsFrame = scroll, body, f
@@ -496,7 +591,7 @@ local function BuildSettings(content)
line:SetColorTexture(UI.Color("gold", 0.35))
return line
end
vline1, vline2 = VLine(), VLine()
vlines = { VLine(), VLine(), VLine() }
body:SetScript("OnSizeChanged", function(self)
local w = self:GetWidth()