diff --git a/Core/Tooltips.lua b/Core/Tooltips.lua new file mode 100644 index 0000000..ddbc6ee --- /dev/null +++ b/Core/Tooltips.lua @@ -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 diff --git a/Freewood-OctoWoW-Core.toc b/Freewood-OctoWoW-Core.toc index 7b82e31..5440502 100644 --- a/Freewood-OctoWoW-Core.toc +++ b/Freewood-OctoWoW-Core.toc @@ -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 diff --git a/Locales.lua b/Locales.lua index 8de216b..52f481c 100644 --- a/Locales.lua +++ b/Locales.lua @@ -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", diff --git a/UI/Tabs.lua b/UI/Tabs.lua index 7d733ba..5d77150 100644 --- a/UI/Tabs.lua +++ b/UI/Tabs.lua @@ -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()