enUS Localization Update Announcement / localization-update-announcement (push) Canceled after 0s
345 lines
14 KiB
Lua
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)
|