Dateien nach "UI" hochladen

This commit is contained in:
2026-10-27 14:06:11 +00:00
parent dec785d709
commit 25b4ed3abf
5 changed files with 2160 additions and 0 deletions
+1039
View File
File diff suppressed because it is too large Load Diff
+127
View File
@@ -0,0 +1,127 @@
-- Freewood-OctoWoW-Core: Aufbau des Hauptfensters (OctoWoW)
--
-- Ersetzt MainWindow.xml. OctoWoW (1.12.1) bricht beim Laden ab, sobald ein <Size> nur x ODER y hat
-- ("No x or y attributes in element: Size"); dann entstehen keine Unterframes und das ganze Fenster fehlt.
-- Der Aufbau in Lua ist unabhaengig vom XML-Parser des Clients. Die Struktur ist dieselbe wie vorher:
-- FreewoodMainFrame.Header (Bg, Accent, Logo, Title, Subtitle)
-- FreewoodMainFrame.TabBar (Bg, Line)
-- FreewoodMainFrame.Footer (Bg, Left)
-- FreewoodMainFrame.Content
-- Farben: Hintergrund #0f131f, Kopfzeile #0c0e14, Leiste #141824, Gold #c89b3c, Bronze #9d7826.
local ADDON, FW = ...
if _G.FreewoodMainFrame then
return
end
local function Solid(tex, r, g, b, a)
tex:SetTexture(r, g, b, a)
end
local function NewTexture(parent, layer, r, g, b, a)
local t = parent:CreateTexture(nil, layer)
Solid(t, r, g, b, a)
return t
end
local frame = CreateFrame("Frame", "FreewoodMainFrame", UIParent)
frame:SetFrameStrata("HIGH")
frame:SetToplevel(true)
frame:SetMovable(true)
frame:SetClampedToScreen(true)
frame:EnableMouse(true)
frame:SetWidth(960)
frame:SetHeight(760)
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
frame:Hide()
-- Hintergrund und Rahmen
frame.Bg = NewTexture(frame, "BACKGROUND", 0.059, 0.075, 0.122, 0.97)
frame.Bg:SetAllPoints(frame)
local gold = { 0.616, 0.471, 0.149, 1 }
frame.BorderTop = NewTexture(frame, "BORDER", gold[1], gold[2], gold[3], gold[4])
frame.BorderTop:SetHeight(1)
frame.BorderTop:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
frame.BorderTop:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
frame.BorderBottom = NewTexture(frame, "BORDER", gold[1], gold[2], gold[3], gold[4])
frame.BorderBottom:SetHeight(1)
frame.BorderBottom:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, 0)
frame.BorderBottom:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0)
frame.BorderLeft = NewTexture(frame, "BORDER", gold[1], gold[2], gold[3], gold[4])
frame.BorderLeft:SetWidth(1)
frame.BorderLeft:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
frame.BorderLeft:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, 0)
frame.BorderRight = NewTexture(frame, "BORDER", gold[1], gold[2], gold[3], gold[4])
frame.BorderRight:SetWidth(1)
frame.BorderRight:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
frame.BorderRight:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0)
-- Kopfzeile mit Titel
local header = CreateFrame("Frame", nil, frame)
header:SetHeight(48)
header:SetPoint("TOPLEFT", frame, "TOPLEFT", 1, -1)
header:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -1, -1)
frame.Header = header
header.Bg = NewTexture(header, "BACKGROUND", 0.047, 0.055, 0.078, 1)
header.Bg:SetAllPoints(header)
header.Accent = NewTexture(header, "ARTWORK", 0.784, 0.608, 0.235, 0.55)
header.Accent:SetHeight(1)
header.Accent:SetPoint("BOTTOMLEFT", header, "BOTTOMLEFT", 0, 0)
header.Accent:SetPoint("BOTTOMRIGHT", header, "BOTTOMRIGHT", 0, 0)
header.Logo = header:CreateTexture(nil, "ARTWORK")
header.Logo:SetTexture("Interface\\AddOns\\Freewood-OctoWoW-Core\\Media\\FreeWood_Logo")
header.Logo:SetWidth(40)
header.Logo:SetHeight(40)
header.Logo:SetPoint("LEFT", header, "LEFT", 14, 0)
header.Title = header:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
header.Title:SetJustifyH("LEFT")
header.Title:SetPoint("LEFT", header.Logo, "RIGHT", 10, 0)
header.Subtitle = header:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
header.Subtitle:SetJustifyH("LEFT")
header.Subtitle:SetPoint("LEFT", header.Title, "RIGHT", 12, -2)
-- Leiste fuer die Tabs
local tabBar = CreateFrame("Frame", nil, frame)
tabBar:SetHeight(36)
tabBar:SetPoint("TOPLEFT", header, "BOTTOMLEFT", 0, 0)
tabBar:SetPoint("TOPRIGHT", header, "BOTTOMRIGHT", 0, 0)
frame.TabBar = tabBar
tabBar.Bg = NewTexture(tabBar, "BACKGROUND", 0.078, 0.094, 0.141, 1)
tabBar.Bg:SetAllPoints(tabBar)
tabBar.Line = NewTexture(tabBar, "ARTWORK", 0.616, 0.471, 0.149, 0.5)
tabBar.Line:SetHeight(1)
tabBar.Line:SetPoint("BOTTOMLEFT", tabBar, "BOTTOMLEFT", 0, 0)
tabBar.Line:SetPoint("BOTTOMRIGHT", tabBar, "BOTTOMRIGHT", 0, 0)
-- Fusszeile
local footer = CreateFrame("Frame", nil, frame)
footer:SetHeight(26)
footer:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 1, 1)
footer:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -1, 1)
frame.Footer = footer
footer.Bg = NewTexture(footer, "BACKGROUND", 0.047, 0.055, 0.078, 1)
footer.Bg:SetAllPoints(footer)
footer.Left = footer:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
footer.Left:SetJustifyH("LEFT")
footer.Left:SetPoint("LEFT", footer, "LEFT", 16, 0)
-- Inhaltsbereich (hier setzen die Tabs ihre Frames ein)
local content = CreateFrame("Frame", nil, frame)
content:SetPoint("TOPLEFT", tabBar, "BOTTOMLEFT", 20, -18)
content:SetPoint("BOTTOMRIGHT", footer, "TOPRIGHT", -20, 12)
frame.Content = content
+353
View File
@@ -0,0 +1,353 @@
-- Freewood-OctoWoW-Core: Verhalten des Hauptfensters
-- Aufbau und Farben stehen in MainWindow.xml. Hier: Ziehen, Position merken,
-- Tabs registrieren und umschalten, Oeffnen/Schliessen.
local ADDON, FW = ...
local UI = FW.UI
local frame = _G.FreewoodMainFrame
if not frame then
return
end
UI.window = frame
table.insert(UISpecialFrames, "FreewoodMainFrame") -- Escape schliesst das Fenster
local tabs = {} -- Reihenfolge der Tabs
local tabsById = {}
local activeId
local built = false
------------------------------------------------------------------------
-- Kopf, Ziehen, Position
------------------------------------------------------------------------
local header = frame.Header
UI.SetFont(header.Title, "heading", 20)
header.Title:SetTextColor(UI.Color("goldLight"))
header.Title:SetText("Freewood")
UI.SetFont(header.Subtitle, "body", 11)
header.Subtitle:SetTextColor(UI.Color("textDim"))
UI.SetFont(frame.Footer.Left, "body", 11)
frame.Footer.Left:SetTextColor(UI.Color("textDim"))
local closeButton = UI.CreateButton(header, "X", 30, 30, function()
frame:Hide()
end, "ghost")
closeButton:SetPoint("RIGHT", header, "RIGHT", -10, 0)
-- Regler fuer die Fenstergroesse (50 bis 150 Prozent) in der Kopfzeile
local scaleText = UI.CreateLabel(header, "100%", { size = 11, color = "textDim", justify = "RIGHT" })
scaleText:SetWidth(40)
scaleText:SetPoint("RIGHT", closeButton, "LEFT", -12, 0)
local scaleSlider = UI.CreateSlider(header, 150, {
min = 50, max = 150, step = 5,
get = function() return math.floor(UI:GetScale() * 100 + 0.5) end,
set = function(v) UI:SetScale(v / 100) end,
})
scaleSlider:SetPoint("RIGHT", scaleText, "LEFT", -8, 0)
scaleSlider.onText = function(v) scaleText:SetText(string.format("%d%%", v)) end
UI.AttachTooltip(scaleSlider, function() return FW.L.WINDOW_SCALE end, function() return FW.L.WINDOW_SCALE_TIP end)
UI.scaleSlider = scaleSlider
local scaleListeners = {}
-- Andere Bedienelemente (Einstellungen: - / +) melden sich an, um bei einer Aenderung nachzuziehen
function UI:OnScaleChanged(fn)
scaleListeners[#scaleListeners + 1] = fn
end
header:EnableMouse(true)
header:RegisterForDrag("LeftButton")
header:SetScript("OnDragStart", function()
frame:StartMoving()
end)
header:SetScript("OnDragStop", function()
frame:StopMovingOrSizing()
local point, _, relativePoint, x, y = frame:GetPoint(1)
if point and FW.settings then
FW.settings.windowPos = { point = point, relativePoint = relativePoint, x = x, y = y }
end
end)
function UI:ApplyPosition()
local pos = FW.settings and FW.settings.windowPos
frame:ClearAllPoints()
if type(pos) == "table" and pos.point then
frame:SetPoint(pos.point, UIParent, pos.relativePoint or pos.point, pos.x or 0, pos.y or 0)
else
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
end
end
------------------------------------------------------------------------
-- Groesse: Standard, Grenzen, Ziehgriff unten rechts
------------------------------------------------------------------------
local DEFAULT_W, DEFAULT_H = 1240, 840
local MIN_W, MIN_H = 1240, 840 -- so gross, dass die Einstellungen komplett passen
local MAX_W, MAX_H = 1500, 1100
-- Grenzen passend zum Bildschirm (Fenster soll nicht groesser als der Bildschirm werden)
local function SizeLimits()
local scale = frame:GetScale() or 1
local sw = ((UIParent and UIParent:GetWidth()) or 1920) / scale
local sh = ((UIParent and UIParent:GetHeight()) or 1080) / scale
local maxW = math.max(MIN_W, math.min(MAX_W, sw - 20))
local maxH = math.max(MIN_H, math.min(MAX_H, sh - 20))
return maxW, maxH
end
local function ClampSize(w, h)
local maxW, maxH = SizeLimits()
w = math.min(math.max(tonumber(w) or DEFAULT_W, MIN_W), maxW)
h = math.min(math.max(tonumber(h) or DEFAULT_H, MIN_H), maxH)
return w, h
end
function UI:ApplySize()
local saved = FW.settings and FW.settings.windowSize
local w, h = DEFAULT_W, DEFAULT_H
if type(saved) == "table" then
w, h = saved.w or w, saved.h or h
end
frame:SetSize(ClampSize(w, h))
end
local function SetupResize()
if frame.SetResizable then
frame:SetResizable(true)
end
local maxW, maxH = SizeLimits()
if frame.SetResizeBounds then
frame:SetResizeBounds(MIN_W, MIN_H, maxW, maxH)
else
if frame.SetMinResize then frame:SetMinResize(MIN_W, MIN_H) end
if frame.SetMaxResize then frame:SetMaxResize(maxW, maxH) end
end
local grip = CreateFrame("Button", nil, frame)
grip:SetSize(18, 18)
grip:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -3, 3)
grip:SetFrameLevel((frame:GetFrameLevel() or 1) + 30)
grip:SetNormalTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Up")
grip:SetPushedTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Down")
grip:SetHighlightTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Highlight")
grip:SetScript("OnMouseDown", function()
local mw, mh = SizeLimits()
if frame.SetResizeBounds then
frame:SetResizeBounds(MIN_W, MIN_H, mw, mh)
end
frame:StartSizing("BOTTOMRIGHT")
end)
grip:SetScript("OnMouseUp", function()
frame:StopMovingOrSizing()
local w, h = ClampSize(frame:GetWidth(), frame:GetHeight())
frame:SetSize(w, h)
if FW.settings then
FW.settings.windowSize = { w = math.floor(w + 0.5), h = math.floor(h + 0.5) }
end
local point, _, relativePoint, x, y = frame:GetPoint(1)
if point and FW.settings then
FW.settings.windowPos = { point = point, relativePoint = relativePoint, x = x, y = y }
end
end)
UI.AttachTooltip(grip, function() return FW.L.WINDOW_RESIZE end, function() return FW.L.WINDOW_RESIZE_TIP end)
frame.resizeGrip = grip
end
function UI:ResetPosition()
if FW.settings then
FW.settings.windowPos = false
FW.settings.windowSize = false
end
self:ApplySize()
self:ApplyPosition()
end
function UI:SetScale(scale)
scale = FW.Util.Clamp(math.floor(scale * 20 + 0.5) / 20, 0.5, 1.5)
if FW.settings then
FW.settings.scale = scale
end
frame:SetScale(scale)
self:ApplySize()
scaleSlider:Refresh()
for _, fn in ipairs(scaleListeners) do
pcall(fn, scale)
end
end
function UI:GetScale()
return (FW.settings and FW.settings.scale) or 1
end
FW:On("FW_DB_READY", function()
frame:SetScale(UI:GetScale())
scaleSlider:Refresh()
SetupResize()
UI:ApplySize()
UI:ApplyPosition()
header.Subtitle:SetText("v" .. FW.version)
end)
------------------------------------------------------------------------
-- Tabs
-- def.label Text oder Funktion, die den Text liefert
-- def.order Reihenfolge (kleiner = weiter links)
-- def.build function(content) -> Frame (wird beim ersten Anzeigen aufgerufen)
-- def.onShow function(tabFrame) (bei jedem Anzeigen)
------------------------------------------------------------------------
local function ResolveLabel(def)
local label = def.label
if type(label) == "function" then
label = label()
end
return label or "?"
end
local TAB_ROW_H = 34
-- Tabs in Zeilen umbrechen, wenn sie nicht in eine Zeile passen; die Leiste waechst mit,
-- der Inhaltsbereich haengt an ihrem unteren Rand und rutscht mit.
local function LayoutTabs()
table.sort(tabs, function(a, b)
if a.order ~= b.order then
return a.order < b.order
end
return a.seq < b.seq
end)
local barW = frame.TabBar:GetWidth()
if not barW or barW < 100 then
barW = (frame:GetWidth() or 1000) - 2
end
local limit = barW - 12
local x, row = 12, 0
local rowOf = {}
for _, tab in ipairs(tabs) do
if tab.button then
local w = tab.button:GetWidth()
if x + w > limit and x > 12 then
row = row + 1
x = 12
end
rowOf[tab] = { row = row, x = x }
x = x + w + 4
end
end
local rows = row + 1
frame.TabBar:SetHeight(rows * TAB_ROW_H + 2)
for _, tab in ipairs(tabs) do
local at = rowOf[tab]
if at then
tab.button:ClearAllPoints()
tab.button:SetPoint("BOTTOMLEFT", frame.TabBar, "BOTTOMLEFT", at.x, 1 + (rows - 1 - at.row) * TAB_ROW_H)
end
end
end
local function CreateTabButton(tab)
local button = UI.CreateTabButton(frame.TabBar, ResolveLabel(tab.def))
button:SetScript("OnClick", function()
UI:SelectTab(tab.id)
end)
tab.button = button
end
function UI:RegisterTab(id, def)
if tabsById[id] then
return tabsById[id]
end
local tab = { id = id, def = def, order = def.order or 100, seq = #tabs + 1 }
tabsById[id] = tab
tabs[#tabs + 1] = tab
if built then
CreateTabButton(tab)
LayoutTabs()
end
return tab
end
function UI:SelectTab(id)
local tab = tabsById[id]
if not tab then
return
end
for _, other in ipairs(tabs) do
if other.frame then
other.frame:Hide()
end
if other.button then
other.button:SetActive(other == tab)
end
end
if not tab.frame and tab.def.build then
tab.frame = tab.def.build(frame.Content)
if tab.frame then
tab.frame:SetAllPoints(frame.Content)
end
end
if tab.frame then
tab.frame:Show()
if tab.def.onShow then
FW.SafeCall(tab.def.onShow, tab.frame)
end
end
activeId = id
end
local function BuildOnce()
if built then
return
end
built = true
for _, tab in ipairs(tabs) do
CreateTabButton(tab)
end
LayoutTabs()
frame:HookScript("OnSizeChanged", function()
LayoutTabs()
end)
end
frame:SetScript("OnShow", function()
BuildOnce()
local _, _, _, toc = GetBuildInfo()
local left = FW.L.TITLE .. " | " .. string.format(FW.L.SLASH_VERSION, FW.version, FW.API_VERSION, tostring(toc or "?"))
frame.Footer.Left:SetText(left)
UI:SelectTab(activeId or (tabs[1] and tabs[1].id))
end)
------------------------------------------------------------------------
-- Oeffnen und Schliessen
------------------------------------------------------------------------
function UI:Show(tabId)
if tabId then
activeId = tabId
end
if frame:IsShown() then
if tabId then
self:SelectTab(tabId)
end
else
frame:Show() -- OnShow waehlt den Tab
end
end
function UI:Hide()
frame:Hide()
end
function UI:Toggle(tabId)
if frame:IsShown() then
frame:Hide()
else
self:Show(tabId)
end
end
+100
View File
@@ -0,0 +1,100 @@
-- Freewood-OctoWoW-Core: Minimap-Knopf
-- Links: Fenster, Rechts: Einstellungen, Ziehen: um die Minimap verschieben.
-- Annahme: runde Minimap.
local ADDON, FW = ...
local UI = FW.UI
local Minimap_ = _G.Minimap
local M = {}
FW.Minimap = M
local button = CreateFrame("Button", "FreewoodMinimapButton", Minimap_)
button:SetSize(32, 32)
button:SetFrameStrata("MEDIUM")
button:SetFrameLevel(8)
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
button:RegisterForDrag("LeftButton")
local overlay = button:CreateTexture(nil, "OVERLAY")
overlay:SetSize(53, 53)
overlay:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")
overlay:SetPoint("TOPLEFT", button, "TOPLEFT", 0, 0)
local background = button:CreateTexture(nil, "BACKGROUND")
background:SetSize(20, 20)
background:SetTexture("Interface\\Minimap\\UI-Minimap-Background")
background:SetPoint("TOPLEFT", button, "TOPLEFT", 7, -5)
local icon = button:CreateTexture(nil, "ARTWORK")
icon:SetSize(17, 17)
icon:SetTexture("Interface\\AddOns\\Freewood-OctoWoW-Core\\Media\\FreeWood_Logo.tga")
icon:SetPoint("TOPLEFT", button, "TOPLEFT", 7, -6)
button:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
local atan2 = math.atan2 or math.atan
local function UpdatePosition()
local angle = math.rad((FW.settings and FW.settings.minimap.angle) or 215)
local radius = (Minimap_:GetWidth() / 2) + 5
button:ClearAllPoints()
button:SetPoint("CENTER", Minimap_, "CENTER", math.cos(angle) * radius, math.sin(angle) * radius)
end
button:SetScript("OnDragStart", function(self)
self:SetScript("OnUpdate", function()
local mx, my = Minimap_:GetCenter()
local px, py = GetCursorPosition()
local scale = Minimap_:GetEffectiveScale()
px, py = px / scale, py / scale
FW.settings.minimap.angle = math.deg(atan2(py - my, px - mx)) % 360
UpdatePosition()
end)
end)
button:SetScript("OnDragStop", function(self)
self:SetScript("OnUpdate", nil)
end)
button:SetScript("OnClick", function(_, mouseButton)
if mouseButton == "RightButton" then
UI:Show("settings")
else
UI:Toggle()
end
end)
button:SetScript("OnEnter", function(self)
local L = FW.L
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:SetText("Freewood", 0.94, 0.75, 0.36)
GameTooltip:AddLine(L.MINIMAP_TIP_LEFT, 0.8, 0.8, 0.8)
GameTooltip:AddLine(L.MINIMAP_TIP_RIGHT, 0.8, 0.8, 0.8)
GameTooltip:AddLine(L.MINIMAP_TIP_DRAG, 0.6, 0.6, 0.6)
GameTooltip:Show()
end)
button:SetScript("OnLeave", function()
GameTooltip:Hide()
end)
function M:SetHidden(hidden)
FW.settings.minimap.hide = hidden and true or false
if hidden then
button:Hide()
else
button:Show()
end
end
M.button = button
FW:On("FW_DB_READY", function()
UpdatePosition()
if FW.settings.minimap.hide then
button:Hide()
else
button:Show()
end
end)
+541
View File
@@ -0,0 +1,541 @@
-- Freewood-OctoWoW-Core: die drei eigenen Tabs (Uebersicht, Austausch, Einstellungen)
-- Module registrieren ihre Tabs ueber Freewood.UI:RegisterTab(...), siehe MainWindow.lua.
local ADDON, FW = ...
local UI = FW.UI
local Export = FW.Export
------------------------------------------------------------------------
-- Uebersicht: geladene Module
------------------------------------------------------------------------
local function BuildOverview(content)
local L = FW.L
local f = CreateFrame("Frame", nil, content)
local title = UI.CreateLabel(f, L.OVERVIEW_HEADER, { font = "heading", size = 16, color = "goldLight" })
title:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
local _, build, _, toc = GetBuildInfo()
local gameVersion = (GetBuildInfo())
local info = UI.CreateLabel(f,
string.format(L.OVERVIEW_INFO, FW.version, tostring(gameVersion), tostring(build)),
{ size = 11, color = "textDim", justify = "RIGHT" })
info:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -2)
local divider = UI.CreateDivider(f)
divider:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -30)
divider:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -30)
local scroll = UI.CreateScrollFrame(f)
scroll:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -44)
scroll:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
local empty = UI.CreateLabel(scroll.child, L.OVERVIEW_NONE, { size = 12, color = "textDim", justifyV = "TOP" })
empty:SetPoint("TOPLEFT", scroll.child, "TOPLEFT", 0, 0)
empty:SetPoint("TOPRIGHT", scroll.child, "TOPRIGHT", 0, 0)
local ROW_H, GAP = 58, 8
local rows = {}
local function GetRow(i)
local row = rows[i]
if row then
return row
end
row = UI.CreatePanel(scroll.child, "card", "bronze")
row:SetHeight(ROW_H)
row.name = UI.CreateLabel(row, "", { font = "heading", size = 14, color = "goldLight" })
row.name:SetPoint("TOPLEFT", row, "TOPLEFT", 14, -10)
row.version = UI.CreateLabel(row, "", { size = 11, color = "textDim" })
row.version:SetPoint("LEFT", row.name, "RIGHT", 10, 0)
row.desc = UI.CreateLabel(row, "", { size = 12, color = "text" })
row.desc:SetPoint("TOPLEFT", row.name, "BOTTOMLEFT", 0, -6)
row.status = UI.CreateLabel(row, "", { size = 12, justify = "RIGHT" })
row.status:SetPoint("RIGHT", row, "RIGHT", -14, 0)
rows[i] = row
return row
end
function f.Refresh()
local list = FW.moduleList
if #list == 0 then
empty:Show()
else
empty:Hide()
end
for i, mod in ipairs(list) do
local row = GetRow(i)
local y = (i - 1) * (ROW_H + GAP)
row:ClearAllPoints()
row:SetPoint("TOPLEFT", scroll.child, "TOPLEFT", 0, -y)
row:SetPoint("TOPRIGHT", scroll.child, "TOPRIGHT", 0, -y)
row.name:SetText(mod.title or mod.name)
row.version:SetText(mod.version and ("v" .. mod.version) or "")
row.desc:SetText(mod.description or "")
if mod:IsEnabled() then
row.status:SetText("|cff" .. UI.Hex("success") .. L.MODULE_ACTIVE .. "|r")
else
row.status:SetText("|cff" .. UI.Hex("textDim") .. L.MODULE_INACTIVE .. "|r")
end
row:Show()
end
for i = #list + 1, #rows do
rows[i]:Hide()
end
scroll:SetContentHeight(math.max(#list * (ROW_H + GAP), 40))
end
f.Refresh()
return f
end
------------------------------------------------------------------------
-- Austausch: Export und Import
------------------------------------------------------------------------
local function BuildExchange(content)
local L = FW.L
local f = CreateFrame("Frame", nil, content)
f.selected = nil
local intro = UI.CreateLabel(f, L.EXCHANGE_INTRO, { size = 12, color = "textDim", justifyV = "TOP" })
intro:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
intro:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, 0)
local kinds = CreateFrame("Frame", nil, f)
kinds:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -46)
kinds:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -46)
kinds:SetHeight(28)
local none = UI.CreateLabel(kinds, L.EXCHANGE_NO_HANDLERS, { size = 12, color = "textDim" })
none:SetPoint("LEFT", kinds, "LEFT", 0, 0)
local area = UI.CreateTextArea(f)
area:SetPoint("TOPLEFT", kinds, "BOTTOMLEFT", 0, -12)
area:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 46)
local status = UI.CreateLabel(f, "", { size = 12 })
status:SetWidth(520)
local function SetStatus(message, colorKey)
status:SetText("|cff" .. UI.Hex(colorKey or "text") .. message .. "|r")
end
local function ErrorText(key, extra)
local text = L[key]
if key == "ERR_NO_HANDLER" then
text = string.format(text, tostring(extra))
elseif key == "ERR_INVALID" and type(extra) == "string" then
text = text .. " (" .. extra .. ")"
end
return text
end
local kindButtons = {}
local function RefreshKindButtons()
for _, b in ipairs(kindButtons) do
b:SetStyle(b.kind == f.selected and "primary" or "default")
end
end
local function RebuildKinds()
for _, b in ipairs(kindButtons) do
b:Hide()
end
local list = Export.handlerList
if #list == 0 then
none:Show()
f.selected = nil
return
end
none:Hide()
local found = false
for _, h in ipairs(list) do
if h.kind == f.selected then
found = true
end
end
if not found then
f.selected = list[1].kind
end
local x = 0
for i, handler in ipairs(list) do
local b = kindButtons[i]
if not b then
b = UI.CreateButton(kinds, "", 150, 28, function(self)
f.selected = self.kind
RefreshKindButtons()
end)
kindButtons[i] = b
end
b.kind = handler.kind
local label = Export.GetLabel(handler)
b:SetLabel(label)
b:SetWidth(math.max(110, b.label:GetStringWidth() + 32))
b:ClearAllPoints()
b:SetPoint("LEFT", kinds, "LEFT", x, 0)
b:Show()
x = x + b:GetWidth() + 8
end
RefreshKindButtons()
end
local exportButton = UI.CreateButton(f, L.EXCHANGE_EXPORT, 130, 30, function()
if not f.selected then
return -- der Hinweis "kein Modul" steht schon ueber dem Textfeld
end
local code, err, extra = Export.ExportKind(f.selected)
if code then
area:SetText(code)
area:SelectAll()
SetStatus(string.format(L.EXCHANGE_EXPORTED, #code), "success")
else
SetStatus(ErrorText(err, extra), "danger")
end
end, "primary")
exportButton:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 0, 0)
local importButton = UI.CreateButton(f, L.EXCHANGE_IMPORT, 130, 30, function()
local text = area:GetText() or ""
if not text:find("%S") then
SetStatus(L.EXCHANGE_EMPTY, "danger")
return
end
local ok, result, extra = Export.Import(text)
if ok then
local message = type(result) == "string" and result ~= "" and result or L.EXCHANGE_IMPORTED
SetStatus(message, "success")
else
SetStatus(ErrorText(result, extra), "danger")
end
end)
importButton:SetPoint("LEFT", exportButton, "RIGHT", 8, 0)
local clearButton = UI.CreateButton(f, L.EXCHANGE_CLEAR, 100, 30, function()
area:SetText("")
status:SetText("")
end, "ghost")
clearButton:SetPoint("LEFT", importButton, "RIGHT", 8, 0)
status:SetPoint("LEFT", clearButton, "RIGHT", 16, 0)
function f.Refresh()
RebuildKinds()
end
f.Refresh()
return f
end
------------------------------------------------------------------------
-- Einstellungen
------------------------------------------------------------------------
-- 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
-- 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
-- Abschnitt mit Ueberschrift, Trennlinie und einem Stapel darunter: local f, stack = UI.CreateSection(parent, "Titel")
function UI.CreateSection(parent, titleText)
local f = CreateFrame("Frame", nil, parent)
local title = UI.CreateLabel(f, titleText, { font = "heading", size = 14, color = "goldLight" })
title:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
local divider = UI.CreateDivider(f)
divider:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -26)
divider:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -26)
local stack = UI.CreateStack(f, { y = 30, gap = 6, pad = 0 })
stack.onHeight = function(h)
f.flowH = h + 14
f:SetHeight(f.flowH)
if f.onFlow then f.onFlow() end
end
f.stack = stack
f.title = title
return f, stack
end
local SECTION_GAP = 36 -- Abstand zwischen den beiden Spalten
local LEFT_GAP = 40 -- Abstand zwischen allgemeinen Einstellungen und Abschnitten
local function LeftWidth(W)
return FW.Util.Clamp(math.floor(W * 0.28), 300, 420)
end
local layingOut, pendingLayout, layoutDepth = false, false, 0
local function LayoutSections()
if not settingsBody then
return
end
if layingOut then
pendingLayout = true -- eine Hoehe hat sich waehrend des Anordnens geaendert: danach noch einmal
return
end
layingOut = true
local W = settingsBody:GetWidth() or 0
if W < 200 then
W = 1000
end
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
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
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)
end
end
local contentH = math.max(-ypos[1], -ypos[2], settingsLeft.flowH or 0) + 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)
layingOut = false
if pendingLayout and layoutDepth < 4 then
pendingLayout = false
layoutDepth = layoutDepth + 1
LayoutSections()
layoutDepth = layoutDepth - 1
end
pendingLayout = false
end
local function BuildSection(def)
if def.frame or not settingsBody then
return
end
local ok, frame = pcall(def.build, settingsBody)
if ok and frame then
def.frame = frame
frame.onFlow = LayoutSections
if def.refresh then
pcall(def.refresh, frame)
end
end
end
function UI:RegisterSettingsSection(id, def)
def.id = id
def.order = def.order or 100
settingsSections[#settingsSections + 1] = def
table.sort(settingsSections, function(a, b)
return a.order < b.order
end)
BuildSection(def)
LayoutSections()
end
local function BuildSettings(content)
local L = FW.L
local f = CreateFrame("Frame", nil, content)
local title = UI.CreateLabel(f, L.SETTINGS_HEADER, { font = "heading", size = 16, color = "goldLight" })
title:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
local divider = UI.CreateDivider(f)
divider:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -30)
divider:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -30)
-- Alles darunter scrollt, wenn das Fenster zu niedrig ist
local scroll = UI.CreateScrollFrame(f)
scroll:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -42)
scroll:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
local body = scroll.child
settingsScroll, settingsBody, settingsFrame = scroll, body, f
-- allgemeine Einstellungen links (Stapel)
local lf = CreateFrame("Frame", nil, body)
lf:SetPoint("TOPLEFT", body, "TOPLEFT", 0, 0)
lf:SetWidth(400)
settingsLeft = lf
local stack = UI.CreateStack(lf, { y = 0, gap = 8 })
stack.onHeight = function(h)
lf.flowH = h
lf:SetHeight(h)
end
local minimapBox = UI.CreateCheckbox(lf, L.SETTINGS_MINIMAP,
function()
return not FW.settings.minimap.hide
end,
function(value)
FW.Minimap:SetHidden(not value)
end)
stack:Add(minimapBox, { fit = true })
local debugBox = UI.CreateCheckbox(lf, L.SETTINGS_DEBUG,
function()
return FW.settings.debug and true or false
end,
function(value)
FW.settings.debug = value and true or false
end)
stack:Add(debugBox, { fit = true })
-- Fenstergroesse: Zeile mit - / Prozent / +
local scaleLabel = UI.CreateLabel(lf, L.SETTINGS_SCALE, { size = 12 })
stack:Add(scaleLabel, { height = 16, gap = 18 })
local scaleRow = CreateFrame("Frame", nil, lf)
scaleRow:SetHeight(26)
stack:Add(scaleRow, { height = 26, gap = 4, stretch = true })
local scaleValue = UI.CreateLabel(scaleRow, "", { size = 12, color = "goldLight", justify = "CENTER" })
scaleValue:SetWidth(60)
local function RefreshScale()
scaleValue:SetText(string.format("%d%%", math.floor(UI:GetScale() * 100 + 0.5)))
end
UI:OnScaleChanged(function() RefreshScale() end)
local minus = UI.CreateButton(scaleRow, "-", 28, 26, function()
UI:SetScale(UI:GetScale() - 0.05)
end)
minus:SetPoint("LEFT", scaleRow, "LEFT", 0, 0)
scaleValue:SetPoint("LEFT", minus, "RIGHT", 4, 0)
local plus = UI.CreateButton(scaleRow, "+", 28, 26, function()
UI:SetScale(UI:GetScale() + 0.05)
end)
plus:SetPoint("LEFT", scaleValue, "RIGHT", 4, 0)
-- Sprache: Beschriftung oben, drei Knoepfe gleich breit darunter
local langLabel = UI.CreateLabel(lf, L.SETTINGS_LANGUAGE, { size = 12 })
stack:Add(langLabel, { height = 16, gap = 18 })
local langRow = CreateFrame("Frame", nil, lf)
langRow:SetHeight(26)
stack:Add(langRow, { height = 26, gap = 4, stretch = true })
local langButtons = {}
local function RefreshLang()
for _, b in ipairs(langButtons) do
b:SetStyle(FW.settings.locale == b.code and "primary" or "default")
end
end
local options = {
{ code = "auto", text = L.LANG_AUTO },
{ code = "deDE", text = L.LANG_DE },
{ code = "enUS", text = L.LANG_EN },
}
for i, option in ipairs(options) do
local b = UI.CreateButton(langRow, option.text, 110, 26, function(self)
FW.settings.locale = self.code
RefreshLang()
end)
b.code = option.code
langButtons[#langButtons + 1] = b
end
local function LayoutLang(w)
w = w or langRow:GetWidth() or 0
if w < 60 then return end
local bw = math.floor((w - 12) / 3)
for i, b in ipairs(langButtons) do
b:ClearAllPoints()
b:SetWidth(bw)
b:SetPoint("LEFT", langRow, "LEFT", (i - 1) * (bw + 6), 0)
end
end
langRow:SetScript("OnSizeChanged", function(self) LayoutLang(self:GetWidth()) end)
LayoutLang(stack:Width())
local note = UI.CreateLabel(lf, L.SETTINGS_LANGUAGE_NOTE, { size = 11, color = "textDim", justifyV = "TOP" })
stack:Add(note, { fit = true, gap = 8 })
local reset = UI.CreateButton(lf, L.SETTINGS_RESET_POS, 220, 28, function()
UI:ResetPosition()
end)
stack:Add(reset, { height = 28, gap = 16, stretch = true })
function f.Refresh()
minimapBox:Refresh()
debugBox:Refresh()
RefreshScale()
RefreshLang()
for _, def in ipairs(settingsSections) do
if def.frame and def.refresh then
pcall(def.refresh, def.frame)
end
end
stack:Layout()
LayoutSections()
end
-- senkrechte Trennstriche zwischen den Spalten
local function VLine()
local line = body:CreateTexture(nil, "ARTWORK")
line:SetWidth(1)
line:SetColorTexture(UI.Color("gold", 0.35))
return line
end
vline1, vline2 = VLine(), VLine()
body:SetScript("OnSizeChanged", function(self)
local w = self:GetWidth()
if type(w) == "number" and w > 0 then
stack:Layout()
LayoutSections()
end
end)
for _, def in ipairs(settingsSections) do
BuildSection(def)
end
stack:Layout()
LayoutSections()
f.Refresh()
return f
end
------------------------------------------------------------------------
-- Anmelden
------------------------------------------------------------------------
UI:RegisterTab("overview", {
label = function() return FW.L.TAB_OVERVIEW end,
order = 10,
build = BuildOverview,
onShow = function(f) f.Refresh() end,
})
UI:RegisterTab("exchange", {
label = function() return FW.L.TAB_EXCHANGE end,
order = 110,
build = BuildExchange,
onShow = function(f) f.Refresh() end,
})
UI:RegisterTab("settings", {
label = function() return FW.L.TAB_SETTINGS end,
order = 20,
build = BuildSettings,
onShow = function(f) f.Refresh() end,
})