Files
Bagshui/Components/Ui.Util.lua
T
OEVG 896f46f0d1
enUS Localization Update Announcement / localization-update-announcement (push) Canceled after 0s
Bagshui 1.6.4
2026-10-30 15:55:43 +00:00

345 lines
14 KiB
Lua

-- Bagshui UI Class: Utilities
Bagshui:AddComponent(function()
local Ui = Bagshui.prototypes.Ui
--- Create a `Bagshui(<inventoryType>|<namePrefix>)<suffix>` 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 anchorPoint string? Point on the holding frame to anchor (default: TOPLEFT).
---@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 (default: BOTTOMLEFT).
---@param xOffset number? Horizontal offset for holding frame (default: 0).
---@param yOffset number? Vertical offset for holding frame (default: -10).
---@param tooltipTitle string? Tooltip title to display when widget is moused over. Can be updated by modifying `<holdingFrame>.bagshuiData.tooltipTitle`.
---@param tooltipText string? Tooltip body to display when widget is moused over. Can be updated by modifying `<holdingFrame>.bagshuiData.tooltipText`.
---@return table holdingFrame
function Ui:CreateLabeledWidget(
parent,
labelText,
labelWidth,
widget,
widgetWidth,
widgetHeight,
anchorPoint,
anchorToFrame,
anchorToPoint,
xOffset,
yOffset,
tooltipTitle,
tooltipText
)
parent = parent or widget:GetParent()
local holdingFrame = _G.CreateFrame("Frame", nil, parent)
holdingFrame:SetWidth(parent:GetWidth())
holdingFrame:SetHeight(widgetHeight)
holdingFrame:SetPoint(
anchorPoint or "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,
tooltipTitle = tooltipTitle,
tooltipText = tooltipText,
}
local oldOnEnter = widget:GetScript("OnEnter")
widget:SetScript("OnEnter", function()
if oldOnEnter then
oldOnEnter()
end
if holdingFrame.bagshuiData.tooltipTitle or holdingFrame.bagshuiData.tooltipText then
_G.GameTooltip:ClearLines()
_G.GameTooltip_SetDefaultAnchor(_G.GameTooltip, _G.this)
if holdingFrame.bagshuiData.tooltipTitle then
_G.GameTooltip:AddLine(
holdingFrame.bagshuiData.tooltipTitle,
HIGHLIGHT_FONT_COLOR.r,
HIGHLIGHT_FONT_COLOR.g,
HIGHLIGHT_FONT_COLOR.b,
true
)
end
if holdingFrame.bagshuiData.tooltipText then
_G.GameTooltip:AddLine(tooltipText, nil, nil, nil, true)
end
_G.GameTooltip:SetWidth(10)
Bagshui:ShowTooltipAfterDelay(_G.GameTooltip, _G.this, holdingFrame)
end
end)
local oldOnLeave = widget:GetScript("OnLeave")
widget:SetScript("OnLeave", function()
if oldOnLeave then
oldOnLeave()
end
if _G.GameTooltip:IsOwned(_G.this) then
Bagshui:ShortenTooltipDelay(_G.this, true)
_G.GameTooltip:Hide()
end
end)
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 `<frame>: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
-- Skins can give windows their own backdrop (the Classic skin uses the Blizzard dialog frame).
-- This only applies when no explicit style was requested so groups and widgets are unaffected.
local windowBackdrop = BsSkin.windowBackdrop
if
windowBackdrop
and borderStyle == nil
and bgFile == nil
and frame.bagshuiData
and frame.bagshuiData.isWindow
then
local inset = windowBackdrop.insets
frame:SetBackdrop({
bgFile = windowBackdrop.bgFile,
edgeFile = windowBackdrop.edgeFile,
tile = true,
tileSize = windowBackdrop.tileSize,
edgeSize = windowBackdrop.edgeSize,
insets = { left = inset, top = inset, right = inset, bottom = inset },
})
return
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)