-- Freewood-OctoWoW-Core: UI-Bausteine im Stil "Azeroth Codex" -- Alles aus Farbflaechen, 1-Pixel-Raendern und Spielschriften. Keine eigenen Bilddateien. -- Farben stammen aus den DESIGN.md-Dateien der Projektideen. local ADDON, FW = ... local UI = {} FW.UI = UI ------------------------------------------------------------------------ -- Farben ------------------------------------------------------------------------ UI.hex = { bgDeep = "0c0e14", bg = "0f131f", panel = "141824", card = "1a2030", hover = "242b40", gold = "c89b3c", goldLight = "f0bf5c", goldAntique = "dfb75c", bronze = "9d7826", text = "e6e2d6", textDim = "9a978c", danger = "e0645a", success = "6cc070", } UI.rarity = { [0] = "9d9d9d", -- Schlecht [1] = "ffffff", -- Gewoehnlich [2] = "1eff00", -- Ungewoehnlich [3] = "0070dd", -- Selten [4] = "a335ee", -- Episch [5] = "ff8000", -- Legendaer [6] = "e6cc80", -- Artefakt [7] = "00ccff", -- Erbstueck } local colorCache = {} local function HexToRGB(hex) return tonumber(hex:sub(1, 2), 16) / 255, tonumber(hex:sub(3, 4), 16) / 255, tonumber(hex:sub(5, 6), 16) / 255 end -- key: Name aus UI.hex oder ein Hex-Wert wie "ff8000". Rueckgabe: r, g, b, a function UI.Color(key, alpha) local c = colorCache[key] if not c then c = { HexToRGB(UI.hex[key] or key) } colorCache[key] = c end return c[1], c[2], c[3], alpha or 1 end function UI.Hex(key) return UI.hex[key] or key end function UI.RarityHex(quality) return UI.rarity[quality or 1] or UI.rarity[1] end ------------------------------------------------------------------------ -- Schriften ------------------------------------------------------------------------ function UI.GetFontPath(kind) if kind == "heading" then return "Fonts\\FRIZQT__.TTF" end return STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF" end function UI.SetFont(fontString, kind, size, flags) local ok = fontString:SetFont(UI.GetFontPath(kind), size or 12, flags or "") if not ok then fontString:SetFont(STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF", size or 12, flags or "") end end -- opts: font ("body"/"heading"), size, color, justify, justifyV, flags, layer function UI.CreateLabel(parent, text, opts) opts = opts or {} local fs = parent:CreateFontString(nil, opts.layer or "OVERLAY") UI.SetFont(fs, opts.font or "body", opts.size or 12, opts.flags) fs:SetTextColor(UI.Color(opts.color or "text")) fs:SetJustifyH(opts.justify or "LEFT") fs:SetJustifyV(opts.justifyV or "MIDDLE") fs:SetText(text or "") return fs end ------------------------------------------------------------------------ -- Flaechen und Raender ------------------------------------------------------------------------ function UI.AddBorder(frame, colorKey, alpha, thickness) thickness = thickness or 1 local b = {} b.top = frame:CreateTexture(nil, "BORDER") b.top:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0) b.top:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0) b.top:SetHeight(thickness) b.bottom = frame:CreateTexture(nil, "BORDER") b.bottom:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, 0) b.bottom:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0) b.bottom:SetHeight(thickness) b.left = frame:CreateTexture(nil, "BORDER") b.left:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, -thickness) b.left:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, thickness) b.left:SetWidth(thickness) b.right = frame:CreateTexture(nil, "BORDER") b.right:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, -thickness) b.right:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, thickness) b.right:SetWidth(thickness) frame.fwBorder = b UI.SetBorderColor(frame, colorKey, alpha) return b end function UI.SetBorderColor(frame, colorKey, alpha) local b = frame.fwBorder if not b then return end local r, g, bl, a = UI.Color(colorKey, alpha) b.top:SetColorTexture(r, g, bl, a) b.bottom:SetColorTexture(r, g, bl, a) b.left:SetColorTexture(r, g, bl, a) b.right:SetColorTexture(r, g, bl, a) end function UI.CreatePanel(parent, bgKey, borderKey) local p = CreateFrame("Frame", nil, parent) p.bg = p:CreateTexture(nil, "BACKGROUND") p.bg:SetAllPoints() p.bg:SetColorTexture(UI.Color(bgKey or "panel")) if borderKey then UI.AddBorder(p, borderKey) end return p end function UI.CreateDivider(parent, colorKey, alpha) local t = parent:CreateTexture(nil, "ARTWORK") t:SetHeight(1) t:SetColorTexture(UI.Color(colorKey or "bronze", alpha or 0.6)) return t end ------------------------------------------------------------------------ -- Tooltips ------------------------------------------------------------------------ -- title: Text oder Funktion; lines: Text, Liste von Texten oder Funktion function UI.AttachTooltip(frame, title, lines, anchor) frame:HookScript("OnEnter", function(self) GameTooltip:SetOwner(self, anchor or "ANCHOR_RIGHT") local t = type(title) == "function" and title() or title GameTooltip:SetText(t or "", 1, 1, 1) local l = type(lines) == "function" and lines() or lines if type(l) == "string" then l = { l } end for _, line in ipairs(l or {}) do GameTooltip:AddLine(line, 0.8, 0.8, 0.8, true) end GameTooltip:Show() end) frame:HookScript("OnLeave", function() GameTooltip:Hide() end) end ------------------------------------------------------------------------ -- Knoepfe ------------------------------------------------------------------------ local BUTTON_STYLES = { default = { bg = "card", bgHover = "hover", border = "bronze", borderHover = "gold", text = "text" }, primary = { bg = "gold", bgHover = "goldLight", border = "goldLight", borderHover = "goldAntique", text = "bgDeep" }, ghost = { bg = "bg", bgHover = "hover", border = "bg", borderHover = "bronze", text = "text" }, -- Zerstoerende Aktionen: normal unauffaellig, beim Darueberfahren rot danger = { bg = "card", bgHover = "hover", border = "bronze", borderHover = "danger", text = "text" }, dangerActive = { bg = "card", bgHover = "hover", border = "danger", borderHover = "danger", text = "danger" }, } local function RefreshButton(b) local style = BUTTON_STYLES[b.fwStyleName] or BUTTON_STYLES.default local enabled = b:IsEnabled() and true or false local hovered = b.fwHover and enabled b.bg:SetColorTexture(UI.Color(hovered and style.bgHover or style.bg)) UI.SetBorderColor(b, hovered and style.borderHover or style.border) b.label:SetTextColor(UI.Color(style.text)) b:SetAlpha(enabled and 1 or 0.45) end -- styleName: "default", "primary" oder "ghost" function UI.CreateButton(parent, text, width, height, onClick, styleName) local b = CreateFrame("Button", nil, parent) b:SetSize(width or 120, height or 28) b.fwStyleName = styleName or "default" b.bg = b:CreateTexture(nil, "BACKGROUND") b.bg:SetAllPoints() UI.AddBorder(b, (BUTTON_STYLES[b.fwStyleName] or BUTTON_STYLES.default).border) b.label = UI.CreateLabel(b, text, { size = 12, justify = "CENTER" }) b.label:SetPoint("CENTER", 0, 0) b:SetScript("OnEnter", function(self) self = self or this if not self then return end self.fwHover = true RefreshButton(self) end) b:SetScript("OnLeave", function(self) self = self or this if not self then return end self.fwHover = false RefreshButton(self) end) b:SetScript("OnClick", function(self, ...) if onClick then onClick(self, ...) end end) function b:SetLabel(newText) self.label:SetText(newText) end function b:SetStyle(name) self.fwStyleName = name RefreshButton(self) end function b:SetButtonEnabled(on) if on then self:Enable() else self:Disable() end RefreshButton(self) end RefreshButton(b) return b end -- Tab-Knopf mit Goldlinie fuer den aktiven Zustand function UI.CreateTabButton(parent, text) local b = CreateFrame("Button", nil, parent) b:SetHeight(34) b.hl = b:CreateTexture(nil, "BACKGROUND") b.hl:SetAllPoints() b.hl:SetColorTexture(UI.Color("hover", 0.5)) b.hl:Hide() b.under = b:CreateTexture(nil, "ARTWORK") b.under:SetHeight(2) b.under:SetPoint("BOTTOMLEFT", b, "BOTTOMLEFT", 0, 0) b.under:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", 0, 0) b.under:SetColorTexture(UI.Color("gold")) b.under:Hide() b.label = UI.CreateLabel(b, text, { font = "heading", size = 13, color = "textDim", justify = "CENTER" }) b.label:SetPoint("CENTER", 0, 1) b:SetWidth(math.max(90, b.label:GetStringWidth() + 36)) function b:SetActive(active) self.fwActive = active if active then self.label:SetTextColor(UI.Color("goldLight")) self.under:Show() self.hl:Hide() else self.label:SetTextColor(UI.Color("textDim")) self.under:Hide() end end function b:SetLabel(newText) self.label:SetText(newText) self:SetWidth(math.max(90, self.label:GetStringWidth() + 36)) end b:SetScript("OnEnter", function(self) if not self.fwActive then self.hl:Show() self.label:SetTextColor(UI.Color("text")) end end) b:SetScript("OnLeave", function(self) self.hl:Hide() if not self.fwActive then self.label:SetTextColor(UI.Color("textDim")) end end) return b end ------------------------------------------------------------------------ -- Kontrollkaestchen ------------------------------------------------------------------------ -- Hoehe eines umbrochenen Textes (Zeilen mal Zeilenhoehe, falls die Schrift keine Hoehe meldet) function UI.TextHeight(fs, minHeight) local h = fs.GetStringHeight and fs:GetStringHeight() if type(h) ~= "number" or h <= 0 then h = 14 end return math.max(h, minHeight or 0) end -- Text auf eine Breite setzen und umbrechen; gibt die Hoehe zurueck function UI.FitLabel(fs, width, minHeight) if width and width > 0 then fs:SetWidth(width) end fs:SetWordWrap(true) fs:SetJustifyV("TOP") fs:SetHeight(0) -- feste Hoehe aufheben, sonst misst die Schrift nur die abgeschnittene Hoehe local h = UI.TextHeight(fs, minHeight) fs:SetHeight(h) return h end -- Kontrollkaestchen. c:Fit(breite) setzt die Breite, bricht den Text um und passt die Hoehe an -- (Rueckgabe: Hoehe). Haengt das Kaestchen links und rechts an einem Rahmen, passt es sich -- beim Aendern der Fenstergroesse selbst an. local CHECK_TEXT_X = 28 local CHECK_MIN_H = 22 function UI.CreateCheckbox(parent, labelText, getFn, setFn) local c = CreateFrame("Button", nil, parent) c:SetHeight(24) local box = UI.CreatePanel(c, "card", "bronze") box:SetSize(18, 18) box:SetPoint("LEFT", c, "LEFT", 0, 0) local check = box:CreateTexture(nil, "ARTWORK") check:SetPoint("TOPLEFT", box, "TOPLEFT", 4, -4) check:SetPoint("BOTTOMRIGHT", box, "BOTTOMRIGHT", -4, 4) check:SetColorTexture(UI.Color("goldLight")) c.label = UI.CreateLabel(c, labelText, { size = 12 }) c.label:SetPoint("LEFT", box, "RIGHT", 10, 0) c:SetWidth(math.max(120, c.label:GetStringWidth() + 40)) c.fitWidth = nil c.isFit = true function c:Fit(width) width = width or self:GetWidth() if not width or width <= 0 then return self:GetHeight() end if not self.fitWidth then -- erstes Fit: Kaestchen und Text oben ausrichten (mehrzeiliger Text) box:ClearAllPoints() box:SetPoint("TOPLEFT", self, "TOPLEFT", 0, -2) self.label:ClearAllPoints() self.label:SetPoint("TOPLEFT", self, "TOPLEFT", CHECK_TEXT_X, -4) end self.fitWidth = width self:SetWidth(width) self.label:SetWidth(math.max(40, width - CHECK_TEXT_X)) self.label:SetWordWrap(true) self.label:SetJustifyV("TOP") self.label:SetHeight(0) local h = math.max(CHECK_MIN_H, UI.TextHeight(self.label, 14) + 8) self.label:SetHeight(h - 4) self:SetHeight(h) return h end function c:SetLabel(text) self.label:SetText(text or "") if self.fitWidth then self:Fit(self.fitWidth) end end c:SetScript("OnSizeChanged", function(self) -- Breite immer vom Rahmen selbst lesen (die Argumente des Ereignisses sind im Client nicht verlaesslich) local w = self:GetWidth() if self.fitWidth and type(w) == "number" and w > 0 and math.abs(w - self.fitWidth) > 0.5 then self:Fit(w) end end) function c:Refresh() if getFn() then check:Show() else check:Hide() end end c:SetScript("OnClick", function(self) setFn(not getFn()) self:Refresh() end) c:SetScript("OnEnter", function() UI.SetBorderColor(box, "gold") end) c:SetScript("OnLeave", function() UI.SetBorderColor(box, "bronze") end) c:Refresh() return c end ------------------------------------------------------------------------ -- Vertikaler Stapel: ordnet Bausteine untereinander an und passt sich der Breite an -- local stack = UI.CreateStack(frame, { gap = 6, x = 0, y = 0 }) -- stack:Add(widget, { gap = 10, indent = 0, stretch = true, fit = true, height = 30 }) -- fit Kontrollkaestchen/Text: wird auf die Breite umgebrochen, Hoehe folgt dem Text -- stretch Baustein haengt auch am rechten Rand (Trennlinien, Textblock) -- height feste Hoehe (sonst die aktuelle Hoehe des Bausteins) -- stack:Layout() ordnet neu; stack.height ist die Gesamthoehe. Aenderungen der Breite des -- Rahmens ordnen automatisch neu an (stack.onHeight wird danach gerufen). ------------------------------------------------------------------------ function UI.CreateStack(parent, opts) opts = opts or {} local stack = { items = {}, gap = opts.gap or 6, x = opts.x or 0, y = opts.y or 0, pad = opts.pad or 0, height = 0 } function stack:Add(widget, o) o = o or {} self.items[#self.items + 1] = { w = widget, gap = o.gap, indent = o.indent or 0, stretch = o.stretch, fit = o.fit, height = o.height, minHeight = o.minHeight } return widget end function stack:Width() local w = parent.GetWidth and parent:GetWidth() or 0 return math.max(0, (w or 0) - self.x - self.pad) end function stack:Layout() local width = self:Width() local y = self.y for _, it in ipairs(self.items) do local w = it.w if not w.stackHidden then local avail = width - it.indent local h if it.fit then if w.isFit then h = w:Fit(avail) else h = UI.FitLabel(w, avail, it.minHeight) end elseif it.height then h = it.height w:SetHeight(h) else h = w:GetHeight() or 0 end y = y + (it.gap or self.gap) w:ClearAllPoints() w:SetPoint("TOPLEFT", parent, "TOPLEFT", self.x + it.indent, -y) if it.stretch then w:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -self.pad, -y) end y = y + h end end self.height = y + self.pad if self.onHeight then self.onHeight(self.height) end return self.height end function stack:Hook() local old = parent.GetScript and parent:GetScript("OnSizeChanged") parent:SetScript("OnSizeChanged", function(f, w, h) if old then old(f, w, h) end w = f:GetWidth() if type(w) == "number" and w > 0 and math.abs(w - (stack.lastW or 0)) > 0.5 then stack.lastW = w stack:Layout() end end) end stack:Hook() return stack end ------------------------------------------------------------------------ -- Schieberegler (waagerecht). Der Wert wird beim Loslassen uebernommen (setFn), waehrend des -- Ziehens zeigt nur die Anzeige den Wert; so ruckelt nichts, wenn der Regler selbst die Groesse aendert. -- local s = UI.CreateSlider(parent, 150, { min = 50, max = 150, step = 5, get = fn, set = fn, format = fn }) ------------------------------------------------------------------------ function UI.CreateSlider(parent, width, opts) local s = CreateFrame("Button", nil, parent) s:SetSize(width or 150, 20) local lo, hi, step = opts.min or 0, opts.max or 100, opts.step or 1 local track = s:CreateTexture(nil, "BACKGROUND") track:SetColorTexture(UI.Color("card")) track:SetHeight(4) track:SetPoint("LEFT", s, "LEFT", 0, 0) track:SetPoint("RIGHT", s, "RIGHT", 0, 0) local fill = s:CreateTexture(nil, "ARTWORK") fill:SetColorTexture(UI.Color("bronze")) fill:SetHeight(4) fill:SetPoint("LEFT", s, "LEFT", 0, 0) local thumb = s:CreateTexture(nil, "OVERLAY") thumb:SetSize(8, 16) thumb:SetColorTexture(UI.Color("goldLight")) local value = lo local function Snap(v) v = math.floor((v - lo) / step + 0.5) * step + lo return math.max(lo, math.min(hi, v)) end local function Show(v) value = Snap(v) local frac = (hi > lo) and ((value - lo) / (hi - lo)) or 0 local travel = math.max(1, (s:GetWidth() or width or 150) - 8) thumb:ClearAllPoints() thumb:SetPoint("LEFT", s, "LEFT", frac * travel, 0) fill:SetWidth(math.max(1, frac * travel + 4)) if s.onText then s.onText(value) end end function s:SetValue(v) Show(v) end function s:GetValue() return value end function s:Refresh() Show(opts.get and opts.get() or lo) end local function FromCursor() local x = GetCursorPosition() local scale = s:GetEffectiveScale() or 1 local left = s:GetLeft() if not x or not left then return value end local travel = math.max(1, (s:GetWidth() or 150) - 8) local frac = ((x / scale) - left - 4) / travel return lo + math.max(0, math.min(1, frac)) * (hi - lo) end s:SetScript("OnMouseDown", function(self) Show(FromCursor()) self:SetScript("OnUpdate", function() Show(FromCursor()) end) end) s:SetScript("OnMouseUp", function(self) self:SetScript("OnUpdate", nil) Show(FromCursor()) if opts.set then opts.set(value) end end) s:SetScript("OnSizeChanged", function() Show(value) end) s:SetScript("OnEnter", function() thumb:SetColorTexture(UI.Color("gold")) end) s:SetScript("OnLeave", function() thumb:SetColorTexture(UI.Color("goldLight")) end) return s end ------------------------------------------------------------------------ -- Scrollbereich mit schmaler eigener Leiste -- Ergebnis: holder (Frame). holder.child ist der Inhalt, holder:SetContentHeight(h) -- passt die Hoehe an. Groesse ueber SetSize oder Anker festlegen. ------------------------------------------------------------------------ function UI.CreateScrollFrame(parent, width, height) local holder = CreateFrame("Frame", nil, parent) if width and height then holder:SetSize(width, height) end local sf = CreateFrame("ScrollFrame", nil, holder) sf:SetPoint("TOPLEFT", holder, "TOPLEFT", 0, 0) sf:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", -12, 0) local child = CreateFrame("Frame", nil, sf) child:SetSize(1, 1) sf:SetScrollChild(child) local track = holder:CreateTexture(nil, "BACKGROUND") track:SetColorTexture(UI.Color("card", 0.7)) track:SetWidth(6) track:SetPoint("TOPRIGHT", holder, "TOPRIGHT", 0, 0) track:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", 0, 0) local thumb = CreateFrame("Button", nil, holder) thumb:SetWidth(6) local thumbTex = thumb:CreateTexture(nil, "OVERLAY") thumbTex:SetAllPoints() thumbTex:SetColorTexture(UI.Color("bronze")) thumb:Hide() thumb:SetScript("OnEnter", function() thumbTex:SetColorTexture(UI.Color("gold")) end) thumb:SetScript("OnLeave", function() thumbTex:SetColorTexture(UI.Color("bronze")) end) local contentHeight = 1 local function MaxScroll() return math.max(0, contentHeight - sf:GetHeight()) end local function Update() -- Breite des Inhalts nachziehen, falls OnSizeChanged sie nicht (rechtzeitig) gesetzt hat local sw = sf:GetWidth() if sw and sw > 1 and math.abs((child:GetWidth() or 0) - sw) > 0.5 then child:SetWidth(sw) end local viewH = sf:GetHeight() local range = MaxScroll() if range <= 0 or viewH <= 0 then thumb:Hide() track:Hide() sf:SetVerticalScroll(0) return end track:Show() local current = math.min(math.max(sf:GetVerticalScroll(), 0), range) if current ~= sf:GetVerticalScroll() then sf:SetVerticalScroll(current) end local thumbH = math.max(24, viewH * viewH / contentHeight) thumb:SetHeight(thumbH) thumb:ClearAllPoints() thumb:SetPoint("TOPRIGHT", holder, "TOPRIGHT", 0, -(current / range) * (viewH - thumbH)) thumb:Show() end sf:EnableMouseWheel(true) sf:SetScript("OnMouseWheel", function(self, delta) local target = self:GetVerticalScroll() - delta * 48 self:SetVerticalScroll(math.min(math.max(target, 0), MaxScroll())) Update() end) sf:SetScript("OnSizeChanged", function(self, w) w = tonumber(w) or self:GetWidth() if w and w > 0 then child:SetWidth(w) end Update() end) thumb:SetScript("OnMouseDown", function(self) local _, startY = GetCursorPosition() local startScroll = sf:GetVerticalScroll() local scale = self:GetEffectiveScale() self:SetScript("OnUpdate", function() local _, y = GetCursorPosition() local free = sf:GetHeight() - self:GetHeight() if free <= 0 then return end local target = startScroll + ((startY - y) / scale) * (MaxScroll() / free) sf:SetVerticalScroll(math.min(math.max(target, 0), MaxScroll())) Update() end) end) thumb:SetScript("OnMouseUp", function(self) self:SetScript("OnUpdate", nil) end) holder.scroll = sf holder.child = child holder.UpdateScroll = Update function holder:SetContentHeight(h) contentHeight = math.max(1, h) child:SetHeight(contentHeight) Update() end function holder:ScrollToTop() sf:SetVerticalScroll(0) Update() end return holder end ------------------------------------------------------------------------ -- Mehrzeiliges Textfeld (zum Kopieren und Einfuegen von Codes) -- Ergebnis: area (Frame) mit area.edit (EditBox) ------------------------------------------------------------------------ function UI.CreateTextArea(parent, width, height) local area = UI.CreateScrollFrame(parent, width, height) local back = area:CreateTexture(nil, "BACKGROUND", nil, -1) back:SetPoint("TOPLEFT", area, "TOPLEFT", 0, 0) back:SetPoint("BOTTOMRIGHT", area, "BOTTOMRIGHT", -12, 0) back:SetColorTexture(UI.Color("bgDeep", 0.9)) local frame = CreateFrame("Frame", nil, area) frame:SetPoint("TOPLEFT", area, "TOPLEFT", 0, 0) frame:SetPoint("BOTTOMRIGHT", area, "BOTTOMRIGHT", -12, 0) UI.AddBorder(frame, "bronze") local edit = CreateFrame("EditBox", nil, area.child) edit:SetMultiLine(true) edit:SetAutoFocus(false) edit:SetMaxLetters(420000) edit:SetTextInsets(8, 8, 6, 6) edit:SetPoint("TOPLEFT", area.child, "TOPLEFT", 0, 0) edit:SetPoint("TOPRIGHT", area.child, "TOPRIGHT", 0, 0) if not edit:SetFont(UI.GetFontPath("body"), 12, "") then edit:SetFont("Fonts\\FRIZQT__.TTF", 12, "") end edit:SetTextColor(UI.Color("text")) local function Resize() local h = math.max(area.scroll:GetHeight(), edit:GetHeight() or 0) area:SetContentHeight(h) end edit:SetScript("OnTextChanged", function() C_Timer.After(0, Resize) end) edit:SetScript("OnEscapePressed", function(self) self:ClearFocus() end) -- Klick in den leeren Bereich setzt den Cursor ins Feld area.scroll:EnableMouse(true) area.scroll:SetScript("OnMouseDown", function() edit:SetFocus() end) area.edit = edit function area:SetText(text) edit:SetText(text or "") C_Timer.After(0, Resize) end function area:GetText() return edit:GetText() end function area:SelectAll() edit:SetFocus() edit:HighlightText() end return area end ------------------------------------------------------------------------ -- Item-Platz (Symbol mit Seltenheitsrahmen) ------------------------------------------------------------------------ function UI.CreateItemSlot(parent, size) size = size or 44 local slot = UI.CreatePanel(parent, "bgDeep", "bronze") slot:SetSize(size, size) slot.icon = slot:CreateTexture(nil, "ARTWORK") slot.icon:SetPoint("TOPLEFT", slot, "TOPLEFT", 2, -2) slot.icon:SetPoint("BOTTOMRIGHT", slot, "BOTTOMRIGHT", -2, 2) slot.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93) function slot:SetItem(itemID) self.itemID = itemID if not itemID then self.icon:SetTexture(nil) UI.SetBorderColor(self, "bronze") return end FW.Util.RequestItem(itemID, function(info) if self.itemID ~= itemID or not info then return end self.icon:SetTexture(info.icon) UI.SetBorderColor(self, UI.RarityHex(info.quality)) end) end return slot end ------------------------------------------------------------------------ -- Kennzahl-Karte (kleine Beschriftung oben, Wert darunter) -- card:SetValue(text, colorKey) ------------------------------------------------------------------------ function UI.CreateStatCard(parent, labelText, width, height) local card = UI.CreatePanel(parent, "card", "bronze") card:SetSize(width or 150, height or 54) card.label = UI.CreateLabel(card, labelText, { size = 10, color = "textDim", flags = "" }) card.label:SetPoint("TOPLEFT", card, "TOPLEFT", 10, -8) card.value = UI.CreateLabel(card, "-", { font = "heading", size = 15, color = "text" }) card.value:SetPoint("BOTTOMLEFT", card, "BOTTOMLEFT", 10, 8) function card:SetValue(text, colorKey) self.value:SetText(text or "-") self.value:SetTextColor(UI.Color(colorKey or "text")) end function card:SetLabel(text) self.label:SetText(text) end return card end ------------------------------------------------------------------------ -- Marke (eckig, kein Pillenformat), z. B. fuer Raenge und Filter -- chip:SetText(text, colorKey) ------------------------------------------------------------------------ function UI.CreateChip(parent, text, colorKey) local chip = UI.CreatePanel(parent, "bgDeep", colorKey or "bronze") chip:SetHeight(20) chip.label = UI.CreateLabel(chip, text, { size = 11, color = colorKey or "goldLight", justify = "CENTER" }) chip.label:SetPoint("CENTER", chip, "CENTER", 0, 0) chip:SetWidth(math.max(28, chip.label:GetStringWidth() + 16)) function chip:SetText(newText, newColorKey) self.label:SetText(newText) if newColorKey then self.label:SetTextColor(UI.Color(newColorKey)) UI.SetBorderColor(self, newColorKey) end self:SetWidth(math.max(28, self.label:GetStringWidth() + 16)) end return chip end ------------------------------------------------------------------------ -- Dropdown-Auswahl -- opts.prefix Text vor dem Wert, zum Beispiel "Typ" ergibt "Typ: Waffe" -- opts.allText Text fuer "keine Auswahl" (Wert nil); fehlt er, gibt es diesen Eintrag nicht -- opts.getOptions Funktion, die eine Liste { { value = ..., text = ... }, ... } liefert (beim Oeffnen gelesen) -- opts.textOf Funktion(value) -> Text, spart das Lesen der ganzen Liste fuer die Beschriftung (optional) -- opts.onChange Funktion(value) nach einer Auswahl durch den Benutzer -- opts.maxRows sichtbare Zeilen, bevor gescrollt wird (Standard 12) -- Rueckgabe: Button mit :SetValue(v), :GetValue(), :Refresh(), :Open(), :Close() ------------------------------------------------------------------------ local DD_ROW = 20 local ddPopup, ddCatcher, ddOwner local function CloseDropdown() if ddPopup then ddPopup:Hide() end if ddCatcher then ddCatcher:Hide() end local owner = ddOwner ddOwner = nil if owner and owner.Refresh then owner:Refresh() end end UI.CloseDropdown = CloseDropdown local function EnsureDropdownPopup() if ddPopup then return end -- unsichtbare Flaeche hinter der Liste: ein Klick daneben schliesst sie ddCatcher = CreateFrame("Button", nil, UIParent) ddCatcher:SetAllPoints(UIParent) ddCatcher:SetFrameStrata("FULLSCREEN_DIALOG") ddCatcher:RegisterForClicks("AnyUp") ddCatcher:SetScript("OnClick", CloseDropdown) ddCatcher:Hide() ddPopup = UI.CreatePanel(UIParent, "bgDeep", "gold") ddPopup:SetFrameStrata("FULLSCREEN_DIALOG") ddPopup:SetFrameLevel((ddCatcher:GetFrameLevel() or 1) + 5) ddPopup:EnableMouse(true) ddPopup.list = UI.CreateScrollFrame(ddPopup) ddPopup.list:SetPoint("TOPLEFT", ddPopup, "TOPLEFT", 2, -2) ddPopup.list:SetPoint("BOTTOMRIGHT", ddPopup, "BOTTOMRIGHT", -2, 2) ddPopup.rows = {} ddPopup:Hide() end local function DropdownRow(index) local row = ddPopup.rows[index] if row then return row end local child = ddPopup.list.child row = CreateFrame("Button", nil, child) row:SetHeight(DD_ROW) row:SetPoint("TOPLEFT", child, "TOPLEFT", 0, -(index - 1) * DD_ROW) row:SetPoint("TOPRIGHT", child, "TOPRIGHT", 0, -(index - 1) * DD_ROW) row.hl = row:CreateTexture(nil, "BACKGROUND") row.hl:SetAllPoints() row.hl:SetColorTexture(UI.Color("hover", 0.9)) row.hl:Hide() row.label = UI.CreateLabel(row, "", { size = 12 }) row.label:SetPoint("LEFT", row, "LEFT", 8, 0) row.label:SetPoint("RIGHT", row, "RIGHT", -6, 0) row.label:SetWordWrap(false) row:SetScript("OnEnter", function(self) self.hl:Show() end) row:SetScript("OnLeave", function(self) self.hl:Hide() end) row:SetScript("OnClick", function(self) local owner = ddOwner local value = self.value CloseDropdown() if owner then owner:SetValue(value) if owner.fwOnChange then owner.fwOnChange(value) end end end) ddPopup.rows[index] = row return row end function UI.CreateDropdown(parent, width, height, opts) opts = opts or {} local dd = UI.CreateButton(parent, "", width or 150, height or 20, nil, "default") dd.fwOnChange = opts.onChange dd.fwValue = nil dd.label:ClearAllPoints() dd.label:SetPoint("LEFT", dd, "LEFT", 8, 0) dd.label:SetPoint("RIGHT", dd, "RIGHT", -20, 0) dd.label:SetJustifyH("LEFT") dd.label:SetWordWrap(false) dd.arrow = dd:CreateTexture(nil, "OVERLAY") dd.arrow:SetTexture("Interface\\Minimap\\ROTATING-MINIMAPARROW") dd.arrow:SetTexCoord(0, 1, 1, 0) -- Pfeil nach unten dd.arrow:SetVertexColor(UI.Color("goldLight")) dd.arrow:SetSize(12, 12) dd.arrow:SetPoint("RIGHT", dd, "RIGHT", -5, -1) local function Options() local list = {} if opts.allText then list[1] = { value = nil, text = opts.allText } end local given = opts.getOptions and opts.getOptions() or {} for _, o in ipairs(given) do list[#list + 1] = o end return list end local function TextOf(value) if value == nil then return opts.allText or "" end if opts.textOf then return opts.textOf(value) end for _, o in ipairs(Options()) do if o.value == value then return o.text end end return tostring(value) end -- Beschriftung und Hervorhebung nach dem Wert richten function dd:Refresh() local text = TextOf(self.fwValue) if opts.prefix then text = opts.prefix .. ": " .. text end self:SetLabel(text) if self.fwValue ~= nil or ddOwner == self then self:SetStyle("primary") else self:SetStyle("default") end end function dd:SetValue(value) self.fwValue = value self:Refresh() end function dd:GetValue() return self.fwValue end function dd:Close() if ddOwner == self then CloseDropdown() end end function dd:Open() EnsureDropdownPopup() local list = Options() if #list == 0 then return end ddOwner = self local rows = math.min(#list, opts.maxRows or 12) local w = math.max(self:GetWidth() or 0, 150) ddPopup:SetSize(w, rows * DD_ROW + 4) ddPopup:ClearAllPoints() local bottom = self:GetBottom() if bottom and bottom - (rows * DD_ROW + 8) < 0 then ddPopup:SetPoint("BOTTOMLEFT", self, "TOPLEFT", 0, 2) -- unten kein Platz: darueber oeffnen else ddPopup:SetPoint("TOPLEFT", self, "BOTTOMLEFT", 0, -2) end for i, o in ipairs(list) do local row = DropdownRow(i) row.value = o.value row.label:SetText(o.text) if o.value == self.fwValue then row.label:SetTextColor(UI.Color("goldLight")) else row.label:SetTextColor(UI.Color("text")) end row:Show() end for i = #list + 1, #ddPopup.rows do ddPopup.rows[i]:Hide() end ddPopup.list:SetContentHeight(#list * DD_ROW) ddPopup.list:ScrollToTop() ddCatcher:Show() ddPopup:Show() self:Refresh() end dd:SetScript("OnClick", function(self) if ddOwner == self then CloseDropdown() else if ddOwner then CloseDropdown() end self:Open() end end) dd:SetScript("OnHide", function(self) self:Close() end) dd:Refresh() return dd end