-- Bagshui UI Class: Utilities Bagshui:AddComponent(function() local Ui = Bagshui.prototypes.Ui --- Create a `Bagshui(|)` string to name UI elements. ---@param suffix string Name that will be placed after the autogenerated prefix. ---@return string? # Element name. function Ui:CreateElementName(suffix) return (suffix and (self.inventory or self.namePrefix or self == Ui)) and ( "Bagshui" .. ( self.inventory and self.inventory.inventoryType or self.namePrefix or "" ) .. suffix ) or nil end --- Call the provided `creationFunction` if `elementNum` doesn't exist in `elementTable`. ---@param elementNum number Array index in `elementTable` to check. ---@param elementTable any[] Array containing UI elements. ---@param creationFunction function Callback. function Ui:CreateIfNotExists(elementNum, elementTable, creationFunction) assert(type(elementTable) == "table", "Ui:CreateIfNotExists() - elementTable is required") -- Don't create a new element if the requested element number already exists. if elementNum ~= nil and elementTable[elementNum] then return end -- This will be the next element in sequence if we weren't given a number. elementNum = elementNum or table.getn(elementTable) + 1 -- Create the element. creationFunction(elementNum, elementTable) end --- Create a Label: [Widget] pair contained in a holding frame. --- The returned holding frame will have a `bagshuiData` property table with `label` and `widget` properties. ---@param parent table Parent UI element. ---@param labelText string ---@param labelWidth number ---@param widget table UI widget. ---@param widgetWidth number ---@param widgetHeight number ---@param anchorToFrame table The holding frame will be anchored to this frame. ---@param anchorToPoint string The holding frame will be anchored to this point on anchorToFrame. ---@param xOffset number? Horizontal offset for holding frame (default: 0). ---@param yOffset number? Vertical offset for holding frame (default: -10). ---@return table holdingFrame function Ui:CreateLabeledWidget(parent, labelText, labelWidth, widget, widgetWidth, widgetHeight, anchorToFrame, anchorToPoint, xOffset, yOffset) parent = parent or widget:GetParent() local holdingFrame = _G.CreateFrame("Frame", nil, parent) holdingFrame:SetWidth(parent:GetWidth()) holdingFrame:SetHeight(widgetHeight) holdingFrame:SetPoint("TOPLEFT", anchorToFrame, anchorToPoint or "BOTTOMLEFT", xOffset or 0, yOffset or -10) local labelFrame, label = self:CreateLabel(holdingFrame, nil, "GameFontNormal", true) labelFrame:SetWidth(labelWidth) labelFrame:SetPoint("TOPLEFT", holdingFrame, "TOPLEFT") labelFrame:Show() label:SetText(labelText) label:SetJustifyH("RIGHT") label:SetJustifyV("TOP") label:ClearAllPoints() label:SetAllPoints(labelFrame) labelFrame:SetHeight(label:GetHeight()) self:SetWidth(widget, widgetWidth) widget:SetHeight(widgetHeight) self:SetPoint(widget, "TOPLEFT", labelFrame, "TOPRIGHT", 10, 3) holdingFrame.bagshuiData = { label = label, labelFrame = labelFrame, widget = widget, ui = self, initialWidgetWidth = widgetWidth, initialWidgetHeight = widgetHeight, } return holdingFrame end -- Iterable array for `Ui:PassMouseEventsThrough()`. local uiPassthroughEvents = { "OnEnter", "OnLeave", "OnMouseDown", "OnMouseUp", "OnDragStart", "OnDragStop", } --- Point all mouse script handlers for the given source frame to those of the destination frame. ---@param source table Frame receiving events. ---@param dest table Frame that should receive `source`'s events. ---@param clickOnly boolean? Exclude OnEnter/OnLeave. function Ui:PassMouseEventsThrough(source, dest, clickOnly) if not (source and dest) then return end for _, event in ipairs(uiPassthroughEvents) do if ( not clickOnly or ( clickOnly and event ~= "OnEnter" and event ~= "OnLeave" ) ) and source:HasScript(event) and dest:HasScript(event) then source:SetScript(event, dest:GetScript(event)) end end end --- Set widget width, taking scroll bars into account. --- This only really needs to be used on Bagshui ScrollFrames. ---@param widget table UI widget. ---@param newWidth number function Ui:SetWidth(widget, newWidth) local widthAdjustment = 0 if widget.bagshuiData and widget.bagshuiData.scrollBarWidth then widthAdjustment = -widget.bagshuiData.scrollBarWidth end widget:SetWidth(newWidth + widthAdjustment) end --- Get widget width, taking scroll bars into account. --- This only really needs to be used on Bagshui ScrollFrames. ---@param widget table UI widget. ---@return number width function Ui:GetWidth(widget) local width = widget:GetWidth() if widget.bagshuiData and widget.bagshuiData.scrollBarWidth then width = width + widget.bagshuiData.scrollBarWidth end return width end --- Set widget point, taking scroll bars, headers, and padding into account. --- This only really needs to be used on Bagshui ScrollFrames and Checkboxes. ---@param widget table UI widget. ---@param point string First `SetPoint()` parameter. ---@param attachToFrame table Second `SetPoint()` parameter. ---@param attachToPoint string Third `SetPoint()` parameter. ---@param xOffset number? Fourth `SetPoint()` parameter. ---@param yOffset number? Fifth `SetPoint()` parameter. function Ui:SetPoint(widget, point, attachToFrame, attachToPoint, xOffset, yOffset) local xAdjustment = 0 local yAdjustment = 0 if widget.bagshuiData then -- Scrollbars. if string.find(point, "RIGHT") and widget.bagshuiData.scrollBarWidth then xAdjustment = -widget.bagshuiData.scrollBarWidth end if string.find(point, "TOP") and widget.bagshuiData.headerHeight then yAdjustment = -widget.bagshuiData.headerHeight end end widget:SetPoint( point, attachToFrame, attachToPoint, (xOffset or 0) + xAdjustment, (yOffset or 0) + yAdjustment ) end --- Set the background and border of the given frame, using most of the same parameters as the native --- `SetBackdrop()` except that `insets` is propagated to all four sides. --- --- By default, a solid background (which can be tinted and opacity-adjusted using `:SetBackdropColor()`) --- and curved tooltip-style borders will be used. ---@param frame table WoW UI frame to style. ---@param borderStyle table|string? Blizzard-style border properties table or key for `BS_BORDER` (defaults to `CURVED` if not specified). ---@param bgFile string? Background texture (defaults to BsSkin.frameDefaultBackdrop if not specified). ---@param edgeFile string? Edge texture (defaults to `borderStyle.edgeFile` if not specified). ---@param tileSize number? Background tile size (defaults to `8` if not specified). ---@param edgeSize number? Edge tile size (defaults to `borderStyle.edgeSize` if not specified). ---@param insets number? Edge insets for all four sides (defaults to `borderStyle.insets` if not specified). function Ui:SetFrameBackdrop(frame, borderStyle, bgFile, edgeFile, tileSize, edgeSize, insets) -- Call custom per-skin window styling function. if frame.bagshuiData and frame.bagshuiData.isWindow and BsSkin.windowSkinFunc then BsSkin.windowSkinFunc(frame) end local finalBorderStyle = -- A full border styling table was provided. type(borderStyle) == "table" and borderStyle -- If a key from BS_BORDER was given, use that. or type(borderStyle) == "string" and BS_BORDER[string.upper(borderStyle)] -- Per-skin default border style specified as a table. or type(BsSkin.frameDefaultBorderStyle) == "table" and BsSkin.frameDefaultBorderStyle -- Per-skin default border style specified as a BS_BORDER key. or BS_BORDER[BsSkin.frameDefaultBorderStyle] -- Nothing found, so use the default. or BS_BORDER.CURVED local finalInsets = insets or BsSkin.frameBorderInsets or finalBorderStyle.insets frame:SetBackdrop({ bgFile = bgFile or BsSkin.frameDefaultBackdrop, edgeFile = edgeFile or BsSkin.frameEdgeFile or finalBorderStyle.edgeFile, tile = true, tileSize = tileSize or 8, edgeSize = edgeSize or BsSkin.frameEdgeSize or finalBorderStyle.edgeSize, insets = { left = finalInsets, top = finalInsets, right = finalInsets, bottom = finalInsets }, }) end --- Apply the same styling to all edit widgets (EditBox, Scrollable Lists, etc.). ---@param frame table WoW UI frame to style. ---@param borderStyle table|string? Blizzard-style border properties table or key for `BS_BORDER` (defaults to `SOLID` if not specified). function Ui:SetFrameBackdropAndBorderForEditWidgets(frame, borderStyle, insets) self:SetFrameBackdrop(frame, borderStyle or "SOLID", nil, nil, nil, nil, insets) frame:SetBackdropColor(unpack(BsSkin.editBoxBackgroundColor)) frame:SetBackdropBorderColor(unpack(BsSkin.editBoxBorderColor)) end --- Call CloseMenusAndClearFocuses() from the owning class. ---@param menus boolean? Pass `false` to keep menus open. ---@param editBoxes boolean? Pass `false` to preserve text box focus. ---@param listSelections boolean? Pass `false` to keep list selections. function Ui:CloseMenusAndClearFocuses(menus, editBoxes, listSelections) if self.owningClass and self.owningClass.CloseMenusAndClearFocuses then self.owningClass:CloseMenusAndClearFocuses(menus, editBoxes, listSelections) end end --- Determine whether the given frame is visible. --- Avoids the need to constantly check for frame existence before calling `:IsVisible()`. ---@param frame table|string UI frame or frame name. ---@return boolean|integer|nil visible `IsVisible()` returns `1` or `nil` in 1.12, so test for truthyness, not `true`. function Ui:IsFrameVisible(frame) if type(frame) == "string" then frame = _G[frame] end return (type(frame) == "table" and frame.IsVisible and frame:IsVisible()) end --- Determine whether the given frame is shown (would be visible if parent was visible). --- Avoids the need to constantly check for frame existence before calling `:IsShown()`. ---@param frame table|string UI frame or frame name. ---@return boolean|integer|nil visible `IsShown()` returns `1` or `nil` in 1.12, so test for truthyness, not `true`. function Ui:IsFrameShown(frame) if type(frame) == "string" then frame = _G[frame] end return (type(frame) == "table" and frame.IsShown and frame:IsShown()) end --- Traverse upwards to find the first parent frame that has the `bagshuiData.isWindow` property. ---@param element table ---@return table? windowFrame function Ui:FindWindowFrame(element) local i = 0 while not (element and element.bagshuiData and element.bagshuiData.isWindow) and element ~= uiPassthroughEvents and i < 100 do element = element:GetParent() i = i + 1 end if element ~= _G.UIParent then return element end end end)