From ab7eea8742d106fc4336ae833738873ddae9db0c Mon Sep 17 00:00:00 2001 From: praxis <889+praxis@noreply.octowow.st> Date: Sun, 13 Sep 2026 09:46:21 +0000 Subject: [PATCH] Add api/ui-widgets.lua --- api/ui-widgets.lua | 1287 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1287 insertions(+) create mode 100644 api/ui-widgets.lua diff --git a/api/ui-widgets.lua b/api/ui-widgets.lua new file mode 100644 index 0000000..ba8dec6 --- /dev/null +++ b/api/ui-widgets.lua @@ -0,0 +1,1287 @@ +-- load pfUI environment +setfenv(1, pfUI:GetEnvironment()) + +do -- statusbars + local animations = {} + local stepsize, val + local width, height, point + + local animate = CreateFrame("Frame", "pfStatusBarAnimation", UIParent) + animate:SetScript("OnUpdate", function() + stepsize = tonumber(pfUI_config.unitframes.animation_speed) + + for bar in pairs(animations) do + if not bar.val_ or abs(bar.val_ - bar.val) < stepsize or bar.instant then + bar:DisplayValue(bar.val) + elseif bar.val ~= bar.val_ then + bar:DisplayValue(bar.val_ + min((bar.val-bar.val_) / stepsize, max(bar.val-bar.val_, 30 / GetFramerate()))) + end + end + end) + + local handlers = { + ["DisplayValue"] = function(self, val) + val = val > self.max and self.max or val + val = val < self.min and self.min or val + + -- remove animation queue + if val == self.val_ then + animations[self] = nil + end + + -- set current visible value + self.val_ = val + + if self.mode == "vertical" then + height = self:GetHeight() + if pfUI.expansion == "vanilla" then height = height / self:GetEffectiveScale() end + point = height / (self.max - self.min) * (val - self.min) + + -- keep values in limits + point = math.min(height, point) + point = math.max(0, point) + + -- set point to zero if value and max is zero + if val == 0 then point = 0 end + + -- set status bar position/size + self.bar:SetPoint("TOPLEFT", self, "TOPLEFT", 0, - height + point) + self.bar:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", 0, 0) + + -- set background bar position/size + self.bg:SetPoint("TOPLEFT", self, "TOPLEFT", 0, 0) + self.bg:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", 0, point) + else + width = self:GetWidth() + if pfUI.expansion == "vanilla" then width = width / self:GetEffectiveScale() end + point = width / (self.max - self.min) * (val - self.min) + + -- keep values in limits + point = math.min(width, point) + point = math.max(0, point) + + -- set point to zero if value and max is zero + if val == 0 then point = 0 end + + -- set status bar position/size + self.bar:SetPoint("TOPLEFT", self, "TOPLEFT", 0, 0) + self.bar:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", - width + point, 0) + + -- set background bar position/size + self.bg:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", 0, 0) + self.bg:SetPoint("TOPLEFT", self, "TOPLEFT", point, 0) + end + end, + + ["SetMinMaxValues"] = function(self, smin, smax, smooth) + -- smoothen the transition by keeping the value at the same percentage as before + if smooth and self.max and self.max > 0 and smax > 0 and self.max ~= smax then + self.val_ = (self.val_ or self.val) / self.max * smax + end + + self.min, self.max = smin, smax + self:DisplayValue(self.val_ or self.val) + end, + + ["SetValue"] = function(self, val) + self.val = val or 0 + + -- start animation on difference + if self.val_ ~= self.val then + animations[self] = true + end + end, + + ["SetStatusBarTexture"] = function(self, r, g, b, a) + self.bar:SetTexture(r, g, b, a) + end, + + ["SetStatusBarColor"] = function(self, r, g, b, a) + self.bar:SetVertexColor(r, g, b, a) + end, + + ["SetStatusBarBackgroundTexture"] = function(self, r, g, b, a) + self.bg:SetTexture(r, g, b, a) + end, + + ["SetStatusBarBackgroundColor"] = function(self, r, g, b, a) + self.bg:SetVertexColor(r, g, b, a) + end, + + ["SetOrientation"] = function(self, mode) + self.mode = strlower(mode) + end, + } + + function pfUI.api.CreateStatusBar(name, parent) + local f = CreateFrame("Button", name, parent) + f:EnableMouse(false) + + f.bar = f:CreateTexture(nil, "NORMAL") + f.bar:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0) + f.bar:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0) + + f.bg = f:CreateTexture(nil, "BACKGROUND") + f.bg:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0) + f.bg:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0) + + -- set some default values + f.min, f.max, f.val = 0, 100, 0 + + -- add all handler functions to the object + for name, func in pairs(handlers) do + f[name] = func + end + + return f + end +end + +do -- dropdown + local _, class = UnitClass("player") + local color = RAID_CLASS_COLORS[class] + + local function ListEntryOnShow() + if this.parent.id == this.id then + this.icon:Show() + else + this.icon:Hide() + end + end + + local function ListEntryOnClick() + this.parent:SetSelection(this.id) + + if this.parent.mode == "MULTISELECT" then + this.parent:ShowMenu() + else + this.parent:HideMenu() + end + + if this.parent.menu[this.id].func then + this.parent.menu[this.id].func() + end + end + + local function ListEntryOnEnter() + this.hover:Show() + end + + local function ListEntryOnLeave() + this.hover:Hide() + end + + local function ListButtonOnClick() + if this.ToggleMenu then + this:ToggleMenu() + else + this:GetParent():ToggleMenu() + end + end + + local function MenuOnUpdate() + if not MouseIsOver(this, 100, -100, -100, 100) then + this.button:HideMenu() + end + end + + local function ListButtonOnEnter() + this.button:SetBackdropBorderColor(this.button.cr,this.button.cg,this.button.cb,(this.button.ca or 1)) + end + + local function ListButtonOnLeave() + this.button:SetBackdropBorderColor(this.button.rr,this.button.rg,this.button.rb,(this.button.ra or 1)) + end + + local handlers = { + ["SetSelection"] = function(self, id) + if id and self.menu and self.menu[id] then + self.text:SetText(self.menu[id].text) + self.id = id + end + end, + ["SetSelectionByText"] = function(self, name) + self:UpdateMenu() + for id, entry in pairs(self.menu) do + if entry.text == name then + self:SetSelection(id) + return true + end + end + + self.text:SetText(name) + return nil + end, + ["GetSelection"] = function(self) + self:UpdateMenu() + if self.menu and self.menu[self.id] then + return self.id, self.menu[self.id].text, self.menu[self.id].func + end + end, + ["SetMenu"] = function(self, menu) + if type(menu) == "function" then + self.menu = menu() + self.menufunc = menu + else + self.menu = menu + self.menufunc = nil + end + end, + ["GetMenu"] = function(self) + self:UpdateMenu() + return self.menu + end, + ["ShowMenu"] = function(self) + self:UpdateMenu() + self.menuframe:SetFrameLevel(self:GetFrameLevel() + 8) + self.menuframe:SetHeight(table.getn(self.menu)*20+4) + self.menuframe:Show() + end, + ["HideMenu"] = function(self) + self.menuframe:Hide() + end, + ["ToggleMenu"] = function(self) + if self.menuframe:IsShown() then + self:HideMenu() + else + self:ShowMenu() + end + end, + ["UpdateMenu"] = function(self) + -- run/reload menu function if available + if self.menufunc then self.menu = self.menufunc() end + if not self.menu then return end + + -- set caption to the current value + self.text:SetText(self.menu[self.id] and self.menu[self.id].text or "") + + -- refresh menu buttons + for id, element in pairs(self.menuframe.elements) do + element:Hide() + end + + for id, data in pairs(self.menu) do + self:CreateMenuEntry(id) + end + end, + ["CreateMenuEntry"] = function(self, id) + if not self.menu[id] then return end + + local frame, entry + for count, existing in pairs(self.menuframe.elements) do + if not existing:IsShown() then + frame = existing + entry = count + break + end + end + + if not frame and not entry then + entry = table.getn(self.menuframe.elements) + 1 + frame = CreateFrame("Button", nil, self.menuframe) + frame:SetFrameStrata("FULLSCREEN") + frame:ClearAllPoints() + frame:SetPoint("TOPLEFT", self.menuframe, "TOPLEFT", 2, -(entry-1)*20-2) + frame:SetPoint("TOPRIGHT", self.menuframe, "TOPRIGHT", -2, -(entry-1)*20-2) + frame:SetHeight(20) + frame.parent = self + + frame.icon = frame:CreateTexture(nil, "OVERLAY") + frame.icon:SetPoint("RIGHT", frame, "RIGHT", -2, 0) + frame.icon:SetHeight(16) + frame.icon:SetWidth(16) + frame.icon:SetTexture("Interface\\Buttons\\UI-CheckBox-Check") + + frame.text = frame:CreateFontString(nil, "OVERLAY") + frame.text:SetFontObject(GameFontWhite) + frame.text:SetFont(pfUI.font_default, pfUI_config.global.font_size-1, "OUTLINE") + frame.text:SetJustifyH("RIGHT") + frame.text:SetPoint("LEFT", frame, "LEFT", 2, 0) + frame.text:SetPoint("RIGHT", frame.icon, "LEFT", -2, 0) + + frame.hover = frame:CreateTexture(nil, "BACKGROUND") + frame.hover:SetAllPoints(frame) + frame.hover:SetTexture(.4,.4,.4,.4) + frame.hover:Hide() + + table.insert(self.menuframe.elements, frame) + end + + frame.id = id + frame.text:SetText(self.menu[id].text) + + frame:SetScript("OnShow", ListEntryOnShow) + frame:SetScript("OnClick", ListEntryOnClick) + frame:SetScript("OnEnter", ListEntryOnEnter) + frame:SetScript("OnLeave", ListEntryOnLeave) + frame:Show() + end, + } + function pfUI.api.CreateDropDownButton(name, parent) + local frame = CreateFrame("Button", name, parent) + frame:SetScript("OnEnter", ListButtonOnEnter) + frame:SetScript("OnLeave", ListButtonOnLeave) + frame:SetScript("OnClick", ListButtonOnClick) + frame:SetHeight(20) + frame.id = nil + + CreateBackdrop(frame, nil, true) + + local button = CreateFrame("Button", nil, frame) + button:SetPoint("RIGHT", frame, "RIGHT", -2, 0) + button:SetWidth(16) + button:SetHeight(16) + button:SetScript("OnClick", ListButtonOnClick) + SkinArrowButton(button, "down") + button.icon:SetVertexColor(1,.9,.1) + + local text = frame:CreateFontString(nil, "OVERLAY") + text:SetFontObject(GameFontWhite) + text:SetFont(pfUI.font_default, pfUI_config.global.font_size-1, "OUTLINE") + text:SetPoint("RIGHT", button, "LEFT", -4, 0) + text:SetJustifyH("RIGHT") + + local menuframe = CreateFrame("Frame", tostring(frame).."menu", parent) + menuframe.button = frame + menuframe.elements = {} + menuframe:SetPoint("TOPLEFT", frame, "BOTTOMLEFT", 0, -2) + menuframe:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 2) + menuframe:SetScript("OnUpdate", MenuOnUpdate) + menuframe:Hide() + CreateBackdrop(menuframe, nil, true) + + for name, func in pairs(handlers) do + frame[name] = func + end + + frame.menuframe = menuframe + frame.button = button + frame.text = text + + return frame + end +end + +function pfUI.api.CreateTabChild(self, title, bwidth, bheight, bottom, static) + -- create tab button + local b = CreateFrame("Button", "pfConfig" .. title .. "Button", self, "UIPanelButtonTemplate") + b:SetText(title) + + -- setup env + local childcount = table.getn(self.childs) + 1 + local button_width = bwidth + local button_height = bheight or 20 + local border = 4 + + if not button_width then + button_width = 150 + elseif button_width == true then + button_width = _G["pfConfig" .. title .. "ButtonText"]:GetStringWidth() + 4 * border + end + + -- set dimensions + b:SetHeight(button_height) + b:SetWidth(button_width) + b:SetID(childcount) + + if not self.align or self.align == "LEFT" then + local outside = self.outside and -2 * border - button_width or 0 + if bottom then + b:SetPoint("BOTTOMLEFT", self, "BOTTOMLEFT", border + outside, (self.bottomcount-1) * (button_height) + (self.bottomcount * border) ) + else + b:SetPoint("TOPLEFT", self, "TOPLEFT", border + outside, -(childcount-1) * (button_height) - (childcount * border) ) + end + elseif self.align == "TOP" then + local outside = self.outside and 2 * border + button_height or 0 + local prev_button = self.buttons[getn(self.buttons)] + if prev_button then + b:SetPoint("TOPLEFT", prev_button, "TOPRIGHT", border, 0) + else + b:SetPoint("TOPLEFT", self, "TOPLEFT", border + (self.outside and -border), -border + outside ) + end + end + + SkinButton(b,.2,1,.8) + + if childcount ~= 1 then + b:SetTextColor(.5,.5,.5) + else + b:SetTextColor(.2,1,.8) + end + + b:SetScript("OnClick", function() + for k,v in pairs(self.childs) do + v:Hide() + end + self.childs[this:GetID()]:Show() + + for k,v in pairs(self.buttons) do + v.active = false + v:SetTextColor(.5,.5,.5) + end + self.buttons[this:GetID()]:SetTextColor(.2,1,.8) + end) + + self.buttons[childcount] = b + self.bottomcount = bottom and self.bottomcount + 1 or self.bottomcount + + -- create child frame + local child, scrollchild = nil, nil + if not static then + child = CreateScrollFrame("pfConfig" .. title .. "Frame", self) + scrollchild = CreateScrollChild("pfConfig" .. title .. "ScrollChild", child) + else + child = CreateFrame("Frame", "pfConfig" .. title .. "Frame", self) + end + + if childcount ~= 1 then child:Hide() end + + if not self.align or self.align == "LEFT" then + child:SetPoint("TOPLEFT", self, "TOPLEFT", button_width + 2*border + 5, -border -5) + child:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", -border -5 , border + 5) + elseif self.align == "TOP" then + if self.outside then + child:SetPoint("TOPLEFT", self, "TOPLEFT", 5, -5) + child:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", -5, 5) + end + end + + CreateBackdrop(child) + SetAllPointsOffset(child.backdrop, child, -5,5) + + local ret = scrollchild or child + ret.button = b + table.insert(self.childs, child) + return ret +end + +function pfUI.api.CreateTabFrame(parent, align, outside) + local f = CreateFrame("Frame", nil, parent) + + f:SetPoint("TOPLEFT", parent, "TOPLEFT", -5, 5) + f:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 5, -5) + + -- setup env + f.childs = { } + f.buttons = { } + f.align = align + f.outside = outside + f.bottomcount = 1 + + -- Create Child Frame + f.CreateTabChild = pfUI.api.CreateTabChild + + return f +end + +function pfUI.api.CreateScrollFrame(name, parent) + local f = CreateFrame("ScrollFrame", name, parent) + + -- create slider + f.slider = CreateFrame("Slider", nil, f) + f.slider:SetOrientation('VERTICAL') + f.slider:SetPoint("TOPLEFT", f, "TOPRIGHT", -7, 0) + f.slider:SetPoint("BOTTOMRIGHT", 0, 0) + f.slider:SetThumbTexture(pfUI.media["img:col"]) + f.slider.thumb = f.slider:GetThumbTexture() + f.slider.thumb:SetHeight(50) + f.slider.thumb:SetTexture(.3,1,.8,.5) + + f.slider:SetScript("OnValueChanged", function() + f:SetVerticalScroll(this:GetValue()) + f.UpdateScrollState() + end) + + f.UpdateScrollState = function() + f.slider:SetMinMaxValues(0, f:GetVerticalScrollRange()) + f.slider:SetValue(f:GetVerticalScroll()) + + local m = f:GetHeight()+f:GetVerticalScrollRange() + local v = f:GetHeight() + local ratio = v / m + + if ratio < 1 then + local size = math.floor(v * ratio) + f.slider.thumb:SetHeight(size) + f.slider:Show() + else + f.slider:Hide() + end + end + + f.Scroll = function(self, step) + local step = step or 0 + + local current = f:GetVerticalScroll() + local max = f:GetVerticalScrollRange() + local new = current - step + + if new >= max then + f:SetVerticalScroll(max) + elseif new <= 0 then + f:SetVerticalScroll(0) + else + f:SetVerticalScroll(new) + end + + f:UpdateScrollState() + end + + f:EnableMouseWheel(1) + f:SetScript("OnMouseWheel", function() + this:Scroll(arg1*10) + end) + + return f +end + +function pfUI.api.CreateScrollChild(name, parent) + local f = CreateFrame("Frame", name, parent) + + -- dummy values required + f:SetWidth(1) + f:SetHeight(1) + f:SetAllPoints(parent) + + parent:SetScrollChild(f) + + f:SetScript("OnUpdate", function() + this:GetParent():UpdateScrollState() + end) + + return f +end + +-- [ CreateTextBox ] +-- Creates and returns a default pfUI skinned EditBox +function pfUI.api.CreateTextBox(name, parent) + local f = CreateFrame("EditBox", name, parent) + f:SetScript("OnEscapePressed", function() this:ClearFocus() end) + f:SetAutoFocus(false) + f:SetTextInsets(5, 5, 5, 5) + f:SetFontObject(GameFontNormal) + CreateBackdrop(f, nil, true) + return f +end + +-- [ EnableClickRotate ] +-- Enables Modelframes to be rotated by click-drag +-- 'frame' [frame] the modelframe that should be used +function pfUI.api.EnableClickRotate(frame) + frame:EnableMouse(true) + HookScript(frame, "OnUpdate", function() + if this.rotate then + local x,_ = GetCursorPosition() + if this.curx > x then + this.rotation = this.rotation - abs(x-this.curx) * 0.025 + elseif this.curx < x then + this.rotation = this.rotation + abs(x-this.curx) * 0.025 + end + this:SetRotation(this.rotation) + this.curx, this.cury = x, y + end + end) + + HookScript(frame, "OnMouseDown", function() + if arg1 == "LeftButton" then + this.rotate = true + this.curx, this.cury = GetCursorPosition() + end + end) + + HookScript(frame, "OnMouseUp", function() + this.rotate, this.curx, this.cury = nil, nil, nil + end) +end + +local function SetHighlightEnter() + if this.locked then return end + (this.backdrop or this):SetBackdropBorderColor(this.cr,this.cg,this.cb,(this.ca or 1)) +end + +local function SetHighlightLeave() + if this.locked then return end + (this.backdrop or this):SetBackdropBorderColor(this.rr,this.rg,this.rb,(this.ra or 1)) +end + +function pfUI.api.SetHighlight(frame, cr, cg, cb) + if not frame then return end + if not cr or not cg or not cb then + local _, class = UnitClass("player") + local color = RAID_CLASS_COLORS[class] + cr, cg, cb = color.r , color.g, color.b + end + + frame.cr, frame.cg, frame.cb = cr, cg, cb, ca + frame.rr, frame.rg, frame.rb, frame.ra = GetStringColor(pfUI_config.appearance.border.color) + + if not frame.pfEnterLeave then + if not frame.HookScript then frame.HookScript = HookScript end + local enter, leave = frame:GetScript("OnEnter"), frame:GetScript("OnLeave") + + if enter then + frame:HookScript("OnEnter", SetHighlightEnter) + else + frame:SetScript("OnEnter", SetHighlightEnter) + end + + if leave then + frame:HookScript("OnLeave", SetHighlightLeave) + else + frame:SetScript("OnLeave", SetHighlightLeave) + end + + frame.pfEnterLeave = true + end +end + +function pfUI.api.HandleIcon(frame, icon) + if not frame or not icon then return end + + SetAllPointsOffset(icon, frame, 3) + icon:SetTexCoord(.08, .92, .08, .92) +end + +-- [ Skin Button ] +-- Applies pfUI skin to buttons: +-- 'button' [frame/string] the button that should be skinned. +-- 'cr' [int] mouseover color (red), defaults to classcolor. +-- 'cg' [int] mouseover color (green), defaults to classcolor. +-- 'cb' [int] mouseover color (blue), defaults to classcolor. +-- 'icon' [texture] the button icon that should be skinned. +-- 'disableHighlight' [bool] disable mouseover highlight. +function pfUI.api.SkinButton(button, cr, cg, cb, icon, disableHighlight) + local b = _G[button] + if not b then b = button end + if not b then return end + if not cr or not cg or not cb then + local _, class = UnitClass("player") + local color = RAID_CLASS_COLORS[class] + cr, cg, cb = color.r , color.g, color.b + end + pfUI.api.CreateBackdrop(b, nil, true) + b:SetNormalTexture("") + b:SetHighlightTexture("") + b:SetPushedTexture("") + b:SetDisabledTexture("") + + if b.SetCheckedTexture and b:GetCheckedTexture() then + b:GetCheckedTexture():SetTexture(cr, cg, cb, .25) + end + + if not disableHighlight then + SetHighlight(b, cr, cg, cb) + end + + if icon then + HandleIcon(b, icon) + b:SetPushedTexture(nil) + end + + b:SetFont(pfUI.font_default, pfUI_config.global.font_size, "OUTLINE") + + b.LockHighlight = function() + b:SetBackdropBorderColor(cr,cg,cb,1) + b.locked = true + end + + b.UnlockHighlight = function() + if not MouseIsOver(b) then + b:SetBackdropBorderColor(GetStringColor(pfUI_config.appearance.border.color)) + end + b.locked = false + end +end + +-- [ Skin Collapse Button ] +-- Applies pfUI skin to collapse/expand buttons: +-- 'button' [frame/string] the button that should be skinned. +function pfUI.api.SkinCollapseButton(button, all) + local b = _G[button] + if not b then b = button end + if not b then return end + local name = b:GetName() .. "CollapseButton" + local size = 10 + + b.icon = _G[name] or CreateFrame("Button", name, b) + if all then size = 14 end + b.icon:SetWidth(size) + b.icon:SetHeight(size) + b.icon:SetPoint("LEFT", 2, 2) + CreateBackdrop(b.icon) + b.icon.text = b.icon:CreateFontString(nil, "OVERLAY") + b.icon.text:SetFontObject(GameFontWhite) + b.icon.text:SetPoint("CENTER", -1, 0) + b:SetNormalTexture(nil) + b.SetNormalTexture = function(self, tex) + if not tex or tex == "" then + self.icon:Hide() + else + self.icon.text:SetText(strfind(tex, "MinusButton") and "-" or "+") + self.icon:Show() + end + end + + local highlight = _G[b:GetName().."Highlight"] + if highlight then + highlight:SetTexture("") + highlight.SetTexture = function(self, tex) return end + end +end + +-- [ Skin Rotate Button] +-- Applies pfUI skin to rotation buttons like in character pane: +-- 'button' [frame/string] the button that should be skinned. +function pfUI.api.SkinRotateButton(button) + pfUI.api.CreateBackdrop(button) + + local _, class = UnitClass("player") + local color = RAID_CLASS_COLORS[class] + local cr, cg, cb = color.r , color.g, color.b + + button:SetWidth(button:GetWidth() - 18) + button:SetHeight(button:GetHeight() - 18) + + button:GetNormalTexture():SetTexCoord(0.3, 0.29, 0.3, 0.65, 0.69, 0.29, 0.69, 0.65) + button:GetPushedTexture():SetTexCoord(0.3, 0.29, 0.3, 0.65, 0.69, 0.29, 0.69, 0.65) + + button:GetHighlightTexture():SetTexture(cr, cg, cb, .25) + + button:GetPushedTexture():SetAllPoints(button:GetNormalTexture()) + button:GetHighlightTexture():SetAllPoints(button:GetNormalTexture()) +end + +-- [ Skin Close Button ] +-- Applies pfUI close skin to buttons and can also be positioned +-- 'button' [frame] the button that should be skinned. +-- 'parentFrame' [frame] will anchor to the top right of the parent. +-- 'offsetX' [integer] offsets the button horizontally +-- 'offsetY' [integer] offsets the button vertically +function pfUI.api.SkinCloseButton(button, parentFrame, offsetX, offsetY) + if not button then return end + + SkinButton(button, 1, .25, .25) + + button:SetWidth(15) + button:SetHeight(15) + + if parentFrame then + button:ClearAllPoints() + button:SetPoint("TOPRIGHT", parentFrame, "TOPRIGHT", offsetX, offsetY) + end + + button.texture = button:CreateTexture("pfQuestionDialogCloseTex") + button.texture:SetTexture(pfUI.media["img:close"]) + button.texture:ClearAllPoints() + button.texture:SetAllPoints(button) + button.texture:SetVertexColor(1,.25,.25,1) +end + +function pfUI.api.SkinArrowButton(button, dir, size) + if not button then return end + + SkinButton(button) + + button:SetHitRectInsets(-3,-3,-3,-3) + + button:SetNormalTexture(nil) + button:SetPushedTexture(nil) + button:SetHighlightTexture(nil) + button:SetDisabledTexture(nil) + + if size then + button:SetWidth(size) + button:SetHeight(size) + end + + if not button.icon then + button.icon = button:CreateTexture(nil, "ARTWORK") + button.icon:SetAlpha(.8) + SetAllPointsOffset(button.icon, button, 3) + end + + button.icon:SetTexture(pfUI.media["img:"..dir]) + + if not button.pficonfade then + local button, state = button, nil + button.pficonfade = CreateFrame("Frame", nil, button) + button.pficonfade:SetScript("OnUpdate", function() + if state == button:IsEnabled() then return end + state = button:IsEnabled() + + if state > 0 then + button.icon:SetVertexColor(.8,.8,.8,1) + else + button.icon:SetVertexColor(.2,.2,.2,1) + end + end) + end +end + +function pfUI.api.SkinScrollbar(frame, always) + local parent = frame:GetParent() + local name = frame:GetName() + local up = _G[name .. "ScrollUpButton"] + local down = _G[name .. "ScrollDownButton"] + local thumb = frame:GetThumbTexture() + + pfUI.api.SkinArrowButton(up, "up") + pfUI.api.SkinArrowButton(down, "down") + + if not frame.bg then + frame.bg = CreateFrame("Frame", nil, frame) + frame.bg:SetPoint("TOPLEFT", up, "BOTTOMLEFT", 0, -3) + frame.bg:SetPoint("BOTTOMRIGHT", down, "TOPRIGHT", 0, 3) + CreateBackdrop(frame.bg, nil, true) + end + + if not frame.thumb then + thumb:SetTexture(nil) + frame.thumb = frame.bg:CreateTexture(nil, "ARTWORK") + frame.thumb:SetTexture(.8,.8,.8,.8) + frame.thumb:SetPoint("TOPLEFT", thumb, "TOPLEFT", 1, -4) + frame.thumb:SetPoint("BOTTOMRIGHT", thumb, "BOTTOMRIGHT", -1, 4) + end + + -- always show parent frame + if always then + RunOOC(function() + if not parent.HookScript then parent.HookScript = HookScript end + parent:HookScript("OnHide", function() this:Show() end) + end) + end +end + +-- [ CenterFrame ] +-- Clears points and centers a frame +-- 'frame' [frame] the frame that should be centered. +-- 'relativeFrame' [frame] frame that should be used for centering if not use ui parent. +function pfUI.api.CenterFrame(frame, relativeFrame) + frame:ClearAllPoints() + if relativeFrame then + frame:SetPoint("CENTER", relativeFrame, "CENTER", 0, 0) + else + frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0) + end +end + +-- [ StripTextures ] +-- Strips all textures off a frame. +-- 'frame' [frame] the frame that should be stripped. +-- 'layer' [string] texture layer. +function pfUI.api.StripTextures(frame, hide, layer) + if not frame then return end + for _,v in ipairs({frame:GetRegions()}) do + if v.SetTexture then + local check = true + if layer and v:GetDrawLayer() ~= layer then check = false end + + if check then + if hide then + v:Hide() + else + v:SetTexture(nil) + end + end + end + end +end + +function pfUI.api.SetAllPointsOffset(frame, parent, offset) + frame:SetPoint("TOPLEFT", parent, "TOPLEFT", offset, -offset) + frame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", -offset, offset) +end + +function pfUI.api.SkinCheckbox(frame, size) + if not frame then return end + frame:SetNormalTexture("") + frame:SetPushedTexture("") + frame:SetHighlightTexture("") + if size then + frame:SetWidth(size) + frame:SetHeight(size) + end + CreateBackdrop(frame) + SetAllPointsOffset(frame.backdrop, frame, 4) + + if frame.backdrop_border then + -- make sure the blizzard border doesn't overlap the checkmark + frame.backdrop_border:SetFrameLevel(frame.backdrop:GetFrameLevel()) + end +end + +function pfUI.api.SkinDropDown(frame, cr, cg, cb, useSmall) + if not frame then return end + StripTextures(frame) + CreateBackdrop(frame) + frame.backdrop:SetPoint("TOPLEFT", 15, -1) + frame.backdrop:SetPoint("BOTTOMRIGHT", -15, 6) + + local button = _G[frame:GetName() .. "Button"] + button:SetNormalTexture(nil) + button:SetPushedTexture(nil) + button:SetHighlightTexture(nil) + button:SetDisabledTexture(nil) + button:SetAllPoints(frame.backdrop) + + CreateBackdrop(button) + + button.backdrop:ClearAllPoints() + button.backdrop:SetWidth(18) + button.backdrop:SetHeight(18) + button.backdrop:SetPoint("RIGHT", frame.backdrop, "RIGHT", -2, 0) + + if not button.icon then + button.icon = button:CreateTexture(nil, "OVERLAY") + button.icon:SetTexture(pfUI.media["img:down"]) + button.icon:SetVertexColor(1,.9,.1) + button.icon:SetAlpha(.8) + SetAllPointsOffset(button.icon, button.backdrop, 5) + end + + if not cr or not cg or not cb then + local _, class = UnitClass("player") + local color = RAID_CLASS_COLORS[class] + cr, cg, cb = color.r , color.g, color.b + end + + SetHighlight(button, cr, cg, cb) + + if not useSmall then + -- fix small width + button:Click() -- open list + local list_width = DropDownList1:GetWidth() + if frame:GetWidth() < list_width then + UIDropDownMenu_SetWidth(list_width, frame) + end + CloseDropDownMenus() + end + + local funcc = button:GetScript("OnClick") + button:SetScript("OnClick", function() + if funcc then funcc() end + local DropDownListWidth = DropDownList1:GetWidth() + local DropDownFrameWidth = frame.backdrop:GetWidth() + if DropDownListWidth < DropDownFrameWidth then + local diff = DropDownFrameWidth - DropDownListWidth + DropDownList1:SetWidth(DropDownList1:GetWidth() + diff) + for i=1, UIDROPDOWNMENU_MAXBUTTONS do + _G["DropDownList1Button" .. i]:SetWidth(_G["DropDownList1Button" .. i]:GetWidth() + diff) + end + end + + DropDownList1:SetPoint("TOPLEFT", frame.backdrop, "BOTTOMLEFT", 0, -4) + end) + + frame.button = button +end + +function pfUI.api.SkinTab(frame, fixed) + frame:SetHeight(20) + StripTextures(frame) + CreateBackdrop(frame) + + if not fixed then + frame:SetScript("OnShow", function() + this:SetWidth(this:GetTextWidth() + 20) + if this.GetFontString and this:GetFontString() then + this:GetFontString():SetPoint("CENTER", 0, 0) + end + end) + end +end + +function pfUI.api.SkinSlider(frame) + local orientation = frame:GetOrientation() + local thumb = frame:GetThumbTexture() + + CreateBackdrop(frame, nil, true) + thumb:SetTexture(1, .82, 0) + + for i,region in ipairs({frame:GetRegions()}) do + if region:GetObjectType() == 'FontString' then + local point, anchor, anchorPoint, x, y = region:GetPoint() + if orientation == 'VERTICAL' then + if string.find(anchorPoint, "TOP") then -- top text + region:ClearAllPoints() + region:SetPoint("BOTTOM", anchor, "TOP", 0, 4) + elseif string.find(anchorPoint, "BOTTOM") then -- bottom text + region:ClearAllPoints() + region:SetPoint("TOP", anchor, "BOTTOM", 0, -4) + end + anchor:SetHeight(anchor:GetHeight() - 4) + else + if string.find(anchorPoint, 'BOTTOM') then + region:SetPoint(point, anchor, anchorPoint, x, y - 6) + elseif string.find(anchorPoint, 'TOP') then + region:SetPoint(point, anchor, anchorPoint, x, y + 2) + end + end + end + end + + if orientation == 'VERTICAL' then + frame:SetWidth(10) + thumb:SetHeight(22) + thumb:SetWidth(frame:GetWidth()) + else + frame:SetHeight(10) + thumb:SetHeight(frame:GetHeight()) + thumb:SetWidth(17) + end +end + +-- [ Question Dialog ] +-- Creates a pfUI user dialog popup: +-- 'text' [string] text that will be displayed. +-- 'yes' [function] function that is triggered on 'Okay' button. +-- 'no' [function] function that is triggered on 'Cancel' button. +-- 'editbox' [bool] if set, a inputfield will be shown. it can be. +-- accessed with "GetParent().input". +function pfUI.api.CreateQuestionDialog(text, yes, no, editbox, onclose) + -- do not allow multiple instances of question dialogs + if _G["pfQuestionDialog"] and _G["pfQuestionDialog"]:IsShown() then + _G["pfQuestionDialog"]:Hide() + _G["pfQuestionDialog"] = nil + return + end + + local yes, no = yes, no + local yescap, nocap = YES, NO + + if yes and type(yes) == "table" then + yescap = yes[1] + yes = yes[2] + end + + if no and type(no) == "table" then + nocap = no[1] + no = no[2] + end + + if not text then text = "Are you sure?" end + + local rawborder, border = GetBorderSize() + local padding = 15 + + -- frame + local question = CreateFrame("Frame", "pfQuestionDialog", UIParent) + question:ClearAllPoints() + question:SetPoint("CENTER", 0, 0) + question:SetFrameStrata("TOOLTIP") + question:SetMovable(true) + question:EnableMouse(true) + question:RegisterForDrag("LeftButton") + question:SetScript("OnDragStart",function() + this:StartMoving() + end) + + question:SetScript("OnDragStop",function() + this:StopMovingOrSizing() + end) + + question:SetScript("OnHide", onclose) + + pfUI.api.CreateBackdrop(question, nil, nil, .85) + pfUI.api.CreateBackdropShadow(question) + + -- text + question.text = question:CreateFontString("Status", "LOW", "GameFontNormal") + question.text:SetFontObject(GameFontWhite) + question.text:SetPoint("TOPLEFT", question, "TOPLEFT", padding, -padding) + question.text:SetPoint("TOPRIGHT", question, "TOPRIGHT", -padding, -padding) + question.text:SetText(text) + + -- editbox + if editbox then + question.input = CreateFrame("EditBox", "pfQuestionDialogEdit", question) + pfUI.api.CreateBackdrop(question.input) + question.input:SetTextColor(.2,1,.8,1) + question.input:SetJustifyH("CENTER") + question.input:SetAutoFocus(false) + question.input:SetPoint("TOPLEFT", question.text, "BOTTOMLEFT", border, -padding) + question.input:SetPoint("TOPRIGHT", question.text, "BOTTOMRIGHT", -border, -padding) + question.input:SetHeight(20) + + question.input:SetFontObject(GameFontNormal) + question.input:SetScript("OnEscapePressed", function() this:ClearFocus() end) + question.input:SetAutoFocus(true) + end + + -- buttons + question.yes = CreateFrame("Button", "pfQuestionDialogYes", question, "UIPanelButtonTemplate") + pfUI.api.SkinButton(question.yes) + question.yes:SetWidth(100) + question.yes:SetHeight(22) + question.yes:SetText(yescap) + question.yes:SetScript("OnClick", function() + if yes then yes() end + this:GetParent():Hide() + end) + + if question.input then + question.yes:SetPoint("TOPRIGHT", question.input, "BOTTOM", -3*border, -padding) + else + question.yes:SetPoint("TOPRIGHT", question.text, "BOTTOM", -3*border, -padding) + end + + question.no = CreateFrame("Button", "pfQuestionDialogNo", question, "UIPanelButtonTemplate") + pfUI.api.SkinButton(question.no) + question.no:SetWidth(100) + question.no:SetHeight(22) + question.no:SetText(nocap) + question.no:SetScript("OnClick", function() + if no then no() end + this:GetParent():Hide() + end) + + if question.input then + question.no:SetPoint("TOPLEFT", question.input, "BOTTOM", 3*border, -padding) + else + question.no:SetPoint("TOPLEFT", question.text, "BOTTOM", 3*border, -padding) + end + + question.close = CreateFrame("Button", "pfQuestionDialogClose", question) + question.close:SetPoint("TOPRIGHT", -border, -border) + pfUI.api.CreateBackdrop(question.close) + question.close:SetHeight(10) + question.close:SetWidth(10) + question.close.texture = question.close:CreateTexture("pfQuestionDialogCloseTex") + question.close.texture:SetTexture(pfUI.media["img:close"]) + question.close.texture:ClearAllPoints() + question.close.texture:SetAllPoints(question.close) + question.close.texture:SetVertexColor(1,.25,.25,1) + question.close:SetScript("OnEnter", function () + this.backdrop:SetBackdropBorderColor(1,.25,.25,1) + end) + + question.close:SetScript("OnLeave", function () + pfUI.api.CreateBackdrop(this) + end) + + question.close:SetScript("OnClick", function() + this:GetParent():Hide() + end) + + -- resize window + local textspace = question.text:GetHeight() + padding + local inputspace = 0 + if question.input then inputspace = question.input:GetHeight() + padding end + local buttonspace = question.no:GetHeight() + padding + question:SetHeight(textspace + inputspace + buttonspace + padding) + + local width = 200 + + -- delay the auto sizing, to make sure the font rendering happened + question:SetScript("OnUpdate", function() + if question.text:GetStringWidth() > width then width = question.text:GetStringWidth() end + question:SetWidth( width + 2*padding) + this:SetScript("OnUpdate", nil) + end) +end + + +-- [ Question Dialog ] +-- Creates a pfUI infobox popup window: +-- 'text' [string] text that will be displayed. +-- 'time' [number] time in seconds till the popup will be faded +-- 'parent' [frame] frame which will be used as parent for the dialog (defaults to UIParent) +-- 'height' [number] manual height of the popup (defaults to 100) +function pfUI.api.CreateInfoBox(text, time, parent, height) + if not text then return end + if not time then time = 5 end + if not parent then parent = UIParent end + if not height then height = 100 end + + local infobox = pfInfoBox + if not infobox then + infobox = CreateFrame("Button", "pfInfoBox", UIParent) + infobox:Hide() + + infobox:SetScript("OnUpdate", function() + local time = infobox.lastshow + infobox.duration - GetTime() + infobox.timeout:SetValue(time) + + if GetTime() > infobox.lastshow + infobox.duration then + infobox:SetAlpha(infobox:GetAlpha()-0.05) + + if infobox:GetAlpha() <= 0.1 then + infobox:Hide() + infobox:SetAlpha(1) + end + elseif MouseIsOver(this) then + this:SetAlpha(max(0.4, this:GetAlpha() - .1)) + else + this:SetAlpha(min(1, this:GetAlpha() + .1)) + end + end) + + infobox:SetScript("OnClick", function() + this:Hide() + end) + + infobox.text = infobox:CreateFontString("Status", "HIGH", "GameFontNormal") + infobox.text:ClearAllPoints() + infobox.text:SetFontObject(GameFontWhite) + + infobox.timeout = CreateFrame("StatusBar", nil, infobox) + infobox.timeout:SetStatusBarTexture(pfUI.media["img:bar"]) + infobox.timeout:SetStatusBarColor(.3,1,.8,1) + + infobox:ClearAllPoints() + infobox.text:SetAllPoints(infobox) + infobox.text:SetFont(pfUI.font_default, 14, "OUTLINE") + + pfUI.api.CreateBackdrop(infobox) + infobox:SetPoint("TOP", 0, -25) + + infobox.timeout:ClearAllPoints() + infobox.timeout:SetPoint("TOPLEFT", infobox, "TOPLEFT", 3, -3) + infobox.timeout:SetPoint("TOPRIGHT", infobox, "TOPRIGHT", -3, 3) + infobox.timeout:SetHeight(2) + end + + infobox.text:SetText(text) + infobox.timeout:SetMinMaxValues(0, time) + infobox.timeout:SetValue(time) + + infobox.duration = time + infobox.lastshow = GetTime() + + infobox:SetWidth(infobox.text:GetStringWidth() + 50) + infobox:SetParent(parent) + infobox:SetHeight(height) + + infobox:SetFrameStrata("FULLSCREEN_DIALOG") + infobox:Show() +end + +function pfUI.api.SkinMoneyInputFrame(frame) + local gold_editbox = _G[frame:GetName().."Gold"] + StripTextures(gold_editbox, true, "BACKGROUND") + CreateBackdrop(gold_editbox, nil, true) + local goldIcon = GetNoNameObject(gold_editbox, "Texture", nil, "MoneyIcons") + goldIcon:Show() + goldIcon:ClearAllPoints() + goldIcon:SetPoint("LEFT", gold_editbox, "RIGHT", 2, 0) + + local silver_editbox = _G[frame:GetName().."Silver"] + StripTextures(silver_editbox, true, "BACKGROUND") + CreateBackdrop(silver_editbox, nil, true) + silver_editbox:ClearAllPoints() + silver_editbox:SetPoint("LEFT", goldIcon, "RIGHT", 2, 0) + local silverIcon = GetNoNameObject(silver_editbox, "Texture", nil, "MoneyIcons") + silverIcon:Show() + silverIcon:ClearAllPoints() + silverIcon:SetPoint("LEFT", silver_editbox, "RIGHT", 2, 0) + + local copper_editbox = _G[frame:GetName().."Copper"] + StripTextures(copper_editbox, true, "BACKGROUND") + CreateBackdrop(copper_editbox, nil, true) + copper_editbox:ClearAllPoints() + copper_editbox:SetPoint("LEFT", silverIcon, "RIGHT", 2, 0) + local copperIcon = GetNoNameObject(copper_editbox, "Texture", nil, "MoneyIcons") + copperIcon:Show() + copperIcon:ClearAllPoints() + copperIcon:SetPoint("LEFT", copper_editbox, "RIGHT", 2, 0) +end