542 lines
18 KiB
Lua
542 lines
18 KiB
Lua
-- 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,
|
|
})
|