local MAJOR, MINOR = "DiesalGUI-2.0", 1 ---@alias Diesal.GUI DiesalGUI-2.0 ---@class DiesalGUI-2.0 ---@field ObjectBase Diesal.GUI.ObjectBase.Methods ---@field ObjectFactory Diesal.GUI.Object.Factory ---@field ObjectVersions Diesal.GUI.Object.Versions ---@field ObjectPool Diesal.GUI.Object.Pool ---@field counts Diesal.GUI.Object.Counts local DiesalGUI, oldMinor = LibStub:NewLibrary(MAJOR, MINOR) if not DiesalGUI then return end local DiesalGUIDebug = false local DiesalGUIDebugRedFont = RED_FONT_COLOR or CreateColor(1, 0.12549020349979, 0.12549020349979, 1) local DiesalGUIDebugWhiteFont = WHITE_FONT_COLOR or CreateColor(1, 1, 1, 1) local DiesalGUIDebugPrint = function(...) if not DiesalGUIDebug then return end local args = { ... } local msgPrefix = DiesalGUIDebugRedFont:WrapTextInColorCode("[DiesalGUI Debug]: ") local str = "" for i = 1, #args do str = str .. tostring(args[i]) .. " " end print(msgPrefix .. DiesalGUIDebugWhiteFont:WrapTextInColorCode(str)) end ---@class Diesal.GUI.Padding : { [1]: left, [2]: right, [3]: top, [4]: bottom } ---@alias Diesal.Object.Constructed DiesalAccordian | DiesalAccordianSection | DiesalBar | DiesalBranch | DiesalButton | DiesalCheckBox | DiesalComboBox | DiesalComboBoxItem | DiesalDropDown | DiesalDropDownItem | DiesalInput | DiesalQuickDoc | DiesalScrollFrame | DiesalScrollingEditBox | DiesalScrollingMessageFrame | DiesalSpinner | DiesalText | DiesalTree | DiesalWindow ---@alias Diesal.Container Diesal.GUI.Object.Window | Diesal.GUI.Object.Accordian ---@alias Diesal.Container.Contents DiesalAccordian | DiesalAccordianSection | DiesalBar | DiesalBranch | DiesalButton | DiesalCheckBox | DiesalComboBox | DiesalComboBoxItem | DiesalDropDown | DiesalDropDownItem | FontString | DiesalInput | DiesalQuickDoc | DiesalScrollFrame | DiesalScrollingEditBox | DiesalScrollingMessageFrame | DiesalSpinner | DiesalTree | DiesalText | DiesalWindow | Diesal.GUI.ObjectBase DiesalGUI.Debug = { Print = DiesalGUIDebugPrint, Enabled = DiesalGUIDebug, RedFont = DiesalGUIDebugRedFont, WhiteFont = DiesalGUIDebugWhiteFont, } local DUIParent = CreateFrame("Frame", "DUIParent", UIParent) DUIParent:SetFrameLevel(UIParent:GetFrameLevel()) DUIParent:SetSize(GetScreenWidth(), GetScreenHeight()) DUIParent:SetPoint("BOTTOM") DiesalGUI.UIParent = DUIParent DiesalGUI.Scaling = { physicalWidth = 0, physicalHeight = 0, screenWidth = 0, screenHeight = 0, originalHeight = DUIParent:GetHeight(), resolution = "0x0", perfect = 0, uiscale = UIParent:GetScale() } DiesalGUI.Scaling.physicalWidth, DiesalGUI.Scaling.physicalHeight = GetPhysicalScreenSize() DiesalGUI.Scaling.screenWidth, DiesalGUI.Scaling.screenHeight = GetScreenWidth(), GetScreenHeight() DiesalGUI.Scaling.resolution = format("%dx%d", DiesalGUI.Scaling.physicalWidth, DiesalGUI.Scaling.physicalHeight) DiesalGUI.Scaling.perfect = 768 / DiesalGUI.Scaling.physicalWidth function DiesalGUI:UIScale() local D = self D.Scaling.uiscale = UIParent:GetScale() D.Scaling.screenWidth = GetScreenWidth() * UIParent:GetEffectiveScale() / 768 D.Scaling.screenHeight = GetScreenWidth() * UIParent:GetEffectiveScale() / 768 local width, height = D.Scaling.screenWidth, D.Scaling.screenHeight D.UIParent:SetSize(width, height) D.Scaling.originalHeight = D.UIParent:GetHeight() end function DiesalGUI:PixelScaleChanged(event) if event == "UI_SCALE_CHANGED" then local S = DiesalGUI.Scaling S.physicalWidth, S.physicalHeight = GetPhysicalScreenSize() S.resolution = format("%dx%d", S.physicalWidth, S.physicalHeight) S.perfect = 768 / S.physicalHeight end self:UIScale() end DiesalGUI:UIScale() DUIParent:SetScript("OnEvent", function(self, event, ...) if event == "UI_SCALE_CHANGED" then DiesalGUI:PixelScaleChanged(event) end end) DUIParent:RegisterEvent("UI_SCALE_CHANGED") local CallbackHandler = LibStub("CallbackHandler-1.0") local DiesalTools = LibStub("DiesalTools-2.0") local DiesalStyle = LibStub("DiesalStyle-2.0") local type, select, tonumber = type, select, tonumber local setmetatable, getmetatable, next = setmetatable, getmetatable, next local pairs, ipairs = pairs, ipairs local tinsert, tremove = table.insert, table.remove local CreateFrame, UIParent = CreateFrame, UIParent DiesalGUI.callbacks = DiesalGUI.callbacks or CallbackHandler:New(DiesalGUI) ---@class Diesal.GUI.Object.Factory : { [Diesal.Object.Type]: fun(name?: string): Diesal.Object } DiesalGUI.ObjectFactory = DiesalGUI.ObjectFactory or {} ---@class Diesal.GUI.Object.Versions : { [Diesal.Object.Type]: number } DiesalGUI.ObjectVersions = DiesalGUI.ObjectVersions or {} ---@class Diesal.GUI.Object.Pool : { [Diesal.Object.Type]: { [Diesal.Object]: boolean } } DiesalGUI.ObjectPool = DiesalGUI.ObjectPool or {} ---@class Diesal.GUI.Object.Counts : { [Diesal.Object.Type]: number } DiesalGUI.counts = DiesalGUI.counts or {} ---@class Diesal.GUI.Object.AntiPool : { [Diesal.Object.Type]: boolean } DiesalGUI.AntiPool = DiesalGUI.AntiPool or {} local ObjectFactory = DiesalGUI.ObjectFactory local ObjectVersions = DiesalGUI.ObjectVersions local ObjectPool = DiesalGUI.ObjectPool local function OnMouse(frame, button) DiesalGUI:ClearFocus() end -- Capture mouse clicks on the WorldFrame ---@param frame Frame ---@param button Button local function WorldFrameOnMouse(frame, button) OnMouse(frame, button) end ---@alias Diesal.Object ---|DiesalAccordian ---|DiesalAccordianSection ---|DiesalBar ---|DiesalBranch ---|DiesalButton ---|DGUIBuilder.Container ---|DiesalCheckBox ---|DiesalComboBox ---|DiesalComboBoxItem ---|DiesalDropDown ---|DiesalDropDownItem ---|DiesalInput ---|DiesalObjectBase ---|DiesalQuickDoc ---|DiesalScrollFrame ---|DiesalScrollingEditBox ---|DiesalScrollingMessageFrame ---|DiesalSpinner ---|DiesalText ---|DiesalTree ---|DiesalWindow WorldFrame:HookScript("OnMouseDown", WorldFrameOnMouse) -- Objects (widgets) that are defined for use in DiesalGUI ---@alias Diesal.Object.Type ---|'"DiesalAccordian"' ---|'"DiesalAccordianSection"' ---|'"DiesalBar"' ---|'"DiesalBranch"' ---|'"DiesalButton"' ---|'"DiesalCheckBox"' ---|'"DiesalComboBox"' ---|'"DiesalComboBoxItem"' ---|'"DiesalDropDown"' ---|'"DiesalDropDownItem"' ---|'"DiesalInput"' ---|'"DiesalObjectBase"' ---|'"DiesalQuickDoc"' ---|'"DiesalScrollFrame"' ---|'"DiesalScrollingEditBox"' ---|'"DiesalScrollingMessageFrame"' ---|'"DiesalSpinner"' ---|'"DiesalText"' ---|'"DiesalTree"' ---|'"DiesalWindow"' ---|'"Container"' ---|'"VerticalLayout"' -- Returns a new object ---@generic T ---@param objectType `T` | Diesal.Object.Type ---@param base? boolean ---@return T newObj local function newObject(objectType, base) if not ObjectFactory[objectType] then error("Attempt to construct unknown Object type", 2) end if base then return ObjectFactory.DiesalObjectBase(objectType) end ObjectPool[objectType] = ObjectPool[objectType] or {} local newObj = next(ObjectPool[objectType]) if not newObj then newObj = ObjectFactory[objectType]() else ObjectPool[objectType][newObj] = nil end return newObj end -- Releases an object into ReleasedObjects ---@param obj Diesal.Object ---@param objectType Diesal.Object.Type local function releaseObject(obj, objectType) ObjectPool[objectType] = ObjectPool[objectType] or {} if ObjectPool[objectType][obj] then error("Attempt to Release Object that is already released", 2) end ObjectPool[objectType][obj] = true end -- Registers an Object constructor in the ObjectFactory ---@generic T : Diesal.Object.Type ---@param Type `T` ---@param constructor fun(): T ---@param version number function DiesalGUI:RegisterObjectConstructor(Type, constructor, version) assert(type(constructor) == "function") assert(type(version) == "number") local oldVersion = ObjectVersions[Type] if oldVersion and oldVersion >= version then return end ObjectVersions[Type] = version ObjectFactory[Type] = constructor end --[[ -- Registers an Object constructor in the ObjectFactory ---@generic T : Diesal.Object.Type ---@param Type `T` ---@param constructor fun(self: Diesal.GUI, type: string | Diesal.Object.Type): Diesal.GUI.ObjectBase ---@param version number function DiesalGUI:RegisterObjectBaseConstructor(Type, constructor, version) assert(type(constructor) == "function") assert(type(version) == "number") local oldVersion = ObjectVersions[Type] if oldVersion and oldVersion >= version then return end ObjectVersions[Type] = version ObjectFactory[Type] = constructor self.CreateObjectBase = constructor end ]] -- Create a new Object ---@generic T ---@param objectType `T` | Diesal.Object.Type ---@param name? string | boolean ---@param methods? table ---@return T object function DiesalGUI:Create(objectType, name, methods) local base = (name ~= nil and type(name) == "boolean") and name or nil local name = (name ~= nil and type(name) == "string") and name or nil if ObjectFactory[objectType] then local object if name then -- needs a specific name, bypass the objectPool and create a new object object = ObjectFactory[objectType](name) else object = newObject(objectType, base) end if type(methods) == "table" then object:SetMethods(methods) end object:ResetSettings() -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if object.OnAcquire then object:OnAcquire() end if object.SetScale then object:SetScale(DUIParent:GetEffectiveScale() * GetScreenHeight() / 768) end return object ---@diagnostic disable-next-line: missing-return end end ---@param duration number ---@param callback fun(...) function DiesalGUI:CreateThrottle(duration, callback) assert(callback and type(callback) == "function", "callback has to be a function ") assert(duration and type(duration) == "number", "duration has to be a number ") ---@class Throttle : AnimationGroup, Object, FrameScriptObject local throttle = CreateFrame("Frame", nil, DUIParent):CreateAnimationGroup() throttle.anim = throttle:CreateAnimation("Animation") throttle.args = {} local mt = getmetatable(throttle) ---@cast mt Throttle, metatable mt.__index.SetCallback = function(this, callback) assert(callback and type(callback) == "function", "callback required to be a function ") this:SetScript("OnFinished", function() callback(unpack(this.args)) end) end mt.__index.AddCallback = function(this, callback) assert(callback and type(callback) == "function", "callback required to be a function ") this:HookScript("OnFinished", function() callback(unpack(this.args)) end) end mt.__index.SetDuration = function(this, callback) assert(callback and type(callback) == "number", "duration has to be a number ") this.anim:SetDuration(callback) end mt.__call = function(this, ...) this.args = { ... } this:Stop() this:Play() end throttle = setmetatable(throttle, mt) throttle:SetScript("OnFinished", function() callback(unpack(throttle.args)) end) throttle:SetDuration(duration) return throttle end ---@param object Diesal.Object function DiesalGUI:Release(object) if object.OnRelease then object:OnRelease() end object:FireEvent("OnRelease") object:ReleaseChildren() object:ReleaseTextures() object:ResetFonts() object:ResetEventListeners() object.frame:ClearAllPoints() object.frame:Hide() object.frame:SetParent(UIParent) for k, v in pairs(object.userdata) do object.userdata[k] = nil end releaseObject(object, object.type) end ---@alias Diesal.GUI.Object.Focusable ---|Diesal.GUI.Object.ComboBox ---|Diesal.GUI.Object.DropDown ---|Diesal.GUI.Object.DropDownPullout -- Set FocusedObject: Menu, Dropdown, editBox etc.... ---@param object Diesal.GUI.Object.Focusable function DiesalGUI:SetFocus(object) if DiesalGUI.FocusedObject and DiesalGUI.FocusedObject ~= object then DiesalGUI:ClearFocus() end DiesalGUI.FocusedObject = object end -- Clear focus from the FocusedObject function DiesalGUI:ClearFocus() local FocusedObject = DiesalGUI.FocusedObject if FocusedObject then if FocusedObject.ClearFocus then -- FocusedObject is Focusable Frame FocusedObject:ClearFocus() end DiesalGUI.FocusedObject = nil end end -- Mouse Input capture for any DiesalGUI interactive region ---@param frame Frame ---@param button mouseButton | "MouseWheel" function DiesalGUI:OnMouse(frame, button) -- print(button) OnMouse(frame, button) DiesalGUI.callbacks:Fire("DiesalGUI_OnMouse", frame, button) end --- A type-based counter to count the number of widgets created. ---@param objectType Diesal.Object.Type function DiesalGUI:GetNextObjectNum(objectType) if not self.counts[objectType] then self.counts[objectType] = 0 end self.counts[objectType] = self.counts[objectType] + 1 return self.counts[objectType] end --- Return the number of created widgets for this type. ---@param objectType Diesal.Object.Type function DiesalGUI:GetObjectCount(objectType) return self.counts[objectType] or 0 end