enUS Localization Update Announcement / localization-update-announcement (push) Canceled after 0s
400 lines
16 KiB
Lua
400 lines
16 KiB
Lua
-- Bagshui Dialog Box
|
|
-- Exposes: BsDialog (and Bagshui.prototypes.Dialog)
|
|
--
|
|
-- Special dialog boxes for Bagshui functionality.
|
|
|
|
Bagshui:AddComponent(function()
|
|
local Ui = Bagshui.prototypes.Ui
|
|
|
|
-- Dialog properties and instances should be stored on the prototype so they're global.
|
|
Ui.dialogProperties = {}
|
|
Ui.dialogInstances = {}
|
|
|
|
--- Set up a new dialog. This is similar to Blizzard's `StaticPopupDialogs[]`.
|
|
--- It's built on Bagshui's ScrollableTextWindow class, so the dialog objects
|
|
--- are ScrollableTextWindow instances.
|
|
---
|
|
--- Properties can be anything accepted by `ScrollableTextWindow:New()`, plus:
|
|
--- ```
|
|
--- {
|
|
--- ---@type string? Text to at the top of the dialog. Will be overridden by `ShowDialog()` parameter if provided.
|
|
--- prompt,
|
|
--- ---@type string? Text to display on button1 (the left button). Will be overridden by `ShowDialog()` parameter if provided. An empty string will hide the button.
|
|
--- button1,
|
|
--- ---@type string? Text to display on button2 (the right button). Will be overridden by `ShowDialog()` parameter if provided. An empty string will hide the button.
|
|
--- button2,
|
|
--- ---@type boolean? Disable button1 unless there is text in the EditBox..
|
|
--- button1DisableOnEmptyText,
|
|
--- ---@type boolean? Disable button2 unless there is text in the EditBox..
|
|
--- button2DisableOnEmptyText,
|
|
--- ---@type string? Text to display on button2 (the right button). Will be overridden by `ShowDialog()` parameter if provided. An empty string will hide the button.
|
|
--- button2,
|
|
--- ---@type function? Called when button1 is clicked (see notes below).
|
|
--- OnAccept = function(dialog) -> nil,
|
|
--- ---@type function? Called when button2 is clicked (see notes below).
|
|
--- OnCancel = function(dialog) -> nil,
|
|
--- ---@type function? Perform any dialog customization at the end of the dialog's `InitUi()`.
|
|
--- dialogCustomizationFunc = function(dialog) -> nil,
|
|
--- }
|
|
--- ```
|
|
---
|
|
--- ### OnAccept/OnCancel
|
|
--- These functions will be passed the dialog instance as their only parameter.
|
|
--- From there, all `ScrollableTextWindow` properties are available.
|
|
--- The most important things are getting the EditBox contents and passing data
|
|
--- through from the calling function.
|
|
---
|
|
--- If OnAccept is defined as:
|
|
--- ```
|
|
--- OnAccept = function(dialog)
|
|
--- Bagshui:PrintDebug(dialog:GetText())
|
|
--- Bagshui:PrintDebug(dialog.data.someProperty)
|
|
--- end
|
|
--- ```
|
|
---
|
|
--- Then the calling function can do something like:
|
|
--- ```
|
|
--- local dialog = ui:ShowDialog("DIALOG_ID", "Enter some text:")
|
|
--- dialog.data.someProperty = "This will pass through to the callback."
|
|
--- ```
|
|
---@param id any Unique identifier.
|
|
---@param dialogProperties table List of properties.
|
|
function Ui:AddMultilineDialog(id, dialogProperties)
|
|
dialogProperties.width = dialogProperties.width or 400
|
|
dialogProperties.height = dialogProperties.height or 400
|
|
self.dialogProperties[id] = dialogProperties
|
|
end
|
|
|
|
--- Obtain a dialog instance as configured in `AddDialog()`.
|
|
---@param id any Unique identifier as set up in `AddDialog()`.
|
|
---@return table dialog Dialog-modified ScrollableTextWindow instance.
|
|
function Ui:GetDialog(id)
|
|
assert(self.dialogProperties[id], tostring(id) .. " is not a known dialog! Initialize with `AddDialog()` first.")
|
|
|
|
if not self.dialogInstances[id] then
|
|
self.dialogInstances[id] = {}
|
|
end
|
|
|
|
local dialogProperties = self.dialogProperties[id]
|
|
local dialog
|
|
|
|
-- Find an existing dialog to reuse.
|
|
for _, existingDialog in ipairs(self.dialogInstances[id]) do
|
|
if
|
|
existingDialog.recycle == true
|
|
or not existingDialog.uiFrame
|
|
or not existingDialog.uiFrame.IsVisible
|
|
or not existingDialog.uiFrame:IsVisible()
|
|
then
|
|
dialog = existingDialog
|
|
break
|
|
end
|
|
end
|
|
|
|
-- Nothing found; create a new one.
|
|
if not dialog then
|
|
dialogProperties.name = tostring(id) .. table.getn(self.dialogInstances[id])
|
|
dialog = self.ScrollableTextWindow:New(dialogProperties)
|
|
dialog.isDialog = true
|
|
dialog.ui = self
|
|
dialog.id = id
|
|
dialog.data = {}
|
|
dialog.prompt = nil
|
|
dialog.button1 = _G.OKAY
|
|
dialog.button2 = _G.CANCEL
|
|
dialog.dialogProperties = dialogProperties
|
|
|
|
--- Initialize the UI.
|
|
function dialog:InitUi()
|
|
assert(
|
|
self.prompt,
|
|
"Dialog prompt text is required, either via ShowDialog() parameter or in AddDialog() property list."
|
|
)
|
|
|
|
self._super.InitUi(self)
|
|
|
|
-- Needed for `Bagshui:CloseAllWindows()`.
|
|
self.uiFrame.isDialog = true
|
|
|
|
-- Keep it on top of everything else.
|
|
self.uiFrame:SetFrameStrata("DIALOG")
|
|
|
|
-- This is called every time ShowDialog() is called, so we need to avoid
|
|
-- customizing more than once.
|
|
if not self.uiFrame.bagshuiData.prompt then
|
|
-- Special stuff for dialog behaviors on top of the base ScrollableTextWindow scripts.
|
|
|
|
local oldOnShow = self.uiFrame:GetScript("OnShow")
|
|
self.uiFrame:SetScript("OnShow", function()
|
|
if self.suppressShowHideEvents then
|
|
return
|
|
end
|
|
if oldOnShow then
|
|
oldOnShow()
|
|
end
|
|
self:SetParentHasModalDialog(true)
|
|
self.recycle = false
|
|
end)
|
|
|
|
local oldOnHide = self.uiFrame:GetScript("OnHide")
|
|
self.uiFrame:SetScript("OnHide", function()
|
|
if self.suppressShowHideEvents then
|
|
return
|
|
end
|
|
if oldOnHide then
|
|
oldOnHide()
|
|
end
|
|
|
|
if self.modalParent then
|
|
self.ui:SetWindowShade(self.modalParent, false)
|
|
Bagshui:QueueClassCallback(self, self.SetParentHasModalDialog, 0.1, nil, false)
|
|
end
|
|
|
|
self.editBox:SetText("")
|
|
self.recycle = true
|
|
end)
|
|
|
|
-- EditBox changes.
|
|
local oldOnChanged = self.editBox:GetScript("OnTextChanged")
|
|
self.editBox:SetScript("OnTextChanged", function()
|
|
oldOnChanged()
|
|
if self.dialogProperties.button1DisableOnEmptyText or self.dialogProperties.button1DisableOnEmptyText then
|
|
local state = string.len(BsUtil.Trim(_G.this:GetText() or "")) > 0 and "Enable" or "Disable"
|
|
if self.dialogProperties.button1DisableOnEmptyText then
|
|
self.uiFrame.bagshuiData.button1[state](self.uiFrame.bagshuiData.button1)
|
|
end
|
|
if self.dialogProperties.button2DisableOnEmptyText then
|
|
self.uiFrame.bagshuiData.button2[state](self.uiFrame.bagshuiData.button2)
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- Prompt text.
|
|
self.uiFrame.bagshuiData.prompt = self.uiFrame:CreateFontString(nil, nil, "GameFontHighlight")
|
|
self.uiFrame.bagshuiData.prompt:SetText(self.prompt)
|
|
self.uiFrame.bagshuiData.prompt:SetJustifyH("LEFT")
|
|
self.uiFrame.bagshuiData.prompt:SetPoint(
|
|
"TOPLEFT",
|
|
self.uiFrame,
|
|
"TOPLEFT",
|
|
BsSkin.windowPadding,
|
|
-BsSkin.windowPadding
|
|
)
|
|
self.uiFrame.bagshuiData.prompt:SetWidth(self.dialogProperties.width - (BsSkin.windowPadding * 2))
|
|
|
|
-- Buttons.
|
|
local buttonWidth = math.min(self.dialogProperties.width / 2 - 10, 150)
|
|
|
|
-- Directly passing the default text parameter here to ensure button heights are set correctly.
|
|
self.uiFrame.bagshuiData.button1 = self.ui:CreateButton("Button1", self.uiFrame, _G.TEXT(_G.OKAY), function()
|
|
if self.dialogProperties.OnAccept then
|
|
self.dialogProperties.OnAccept(dialog)
|
|
end
|
|
self.uiFrame:Hide()
|
|
end)
|
|
self.uiFrame.bagshuiData.button1:SetPoint(
|
|
"BOTTOMLEFT",
|
|
self.uiFrame,
|
|
"BOTTOMLEFT",
|
|
BsSkin.windowPadding,
|
|
BsSkin.windowPadding
|
|
)
|
|
self.uiFrame.bagshuiData.button1:SetWidth(buttonWidth)
|
|
|
|
self.uiFrame.bagshuiData.button2 = self.ui:CreateButton(
|
|
"Button1",
|
|
self.uiFrame,
|
|
_G.TEXT(_G.CANCEL),
|
|
function()
|
|
if self.dialogProperties.OnCancel then
|
|
self.dialogProperties.OnCancel(dialog)
|
|
end
|
|
self.uiFrame:Hide()
|
|
end
|
|
)
|
|
self.uiFrame.bagshuiData.button2:SetPoint(
|
|
"BOTTOMRIGHT",
|
|
self.uiFrame,
|
|
"BOTTOMRIGHT",
|
|
-BsSkin.windowPadding,
|
|
BsSkin.windowPadding
|
|
)
|
|
self.uiFrame.bagshuiData.button2:SetWidth(buttonWidth)
|
|
|
|
-- Re-anchor ScrollFrame to prompt text and buttons.
|
|
self.ui:SetPoint(
|
|
self.uiFrame.bagshuiData.scrollFrame,
|
|
"TOPLEFT",
|
|
self.uiFrame.bagshuiData.prompt,
|
|
"BOTTOMLEFT",
|
|
0,
|
|
-4
|
|
)
|
|
self.ui:SetPoint(
|
|
self.uiFrame.bagshuiData.scrollFrame,
|
|
"BOTTOMRIGHT",
|
|
self.uiFrame.bagshuiData.button2,
|
|
"TOPRIGHT",
|
|
0,
|
|
4
|
|
)
|
|
|
|
if type(self.dialogProperties.dialogCustomizationFunc) == "function" then
|
|
self.dialogProperties.dialogCustomizationFunc(self)
|
|
end
|
|
end -- ScrollableTextWindow customization complete.
|
|
|
|
-- Update everything to reflect current parameters.
|
|
|
|
self.uiFrame.bagshuiData.prompt:SetText(self.prompt)
|
|
|
|
if string.len(tostring(self.button1)) == 0 then
|
|
self.uiFrame.bagshuiData.button1:Hide()
|
|
else
|
|
self.uiFrame.bagshuiData.button1:SetText(self.button1)
|
|
self.uiFrame.bagshuiData.button1:Show()
|
|
end
|
|
|
|
if string.len(tostring(self.button2)) == 0 then
|
|
self.uiFrame.bagshuiData.button2:Hide()
|
|
else
|
|
self.uiFrame.bagshuiData.button2:SetText(self.button2)
|
|
self.uiFrame.bagshuiData.button2:Show()
|
|
end
|
|
|
|
self.uiFrame:SetHeight(self.dialogProperties.height + self.uiFrame.bagshuiData.prompt:GetHeight())
|
|
end
|
|
|
|
--- Focusing the EditBox too quickly causes the cursor to visually "stick",
|
|
--- where it doesn't flash and doesn't disappear when escape is pressed.
|
|
--- A short delay on the SetFocus() call works around it. *shrug*
|
|
function dialog:FocusEditBox()
|
|
self.editBox:SetFocus()
|
|
end
|
|
|
|
--- Clearing the `hasModalDialog` property too quickly causes the check in
|
|
--- `Bagshui:CloseAllWindows()` to see it as false before it should be and
|
|
--- the parent window ends up closing when it shouldn't.
|
|
function dialog:SetParentHasModalDialog(hasModalDialog)
|
|
if type(self.modalParent) == "table" and self.modalParent.GetName and self.modalParent.bagshuiData then
|
|
self.modalParent.bagshuiData.hasModalDialog = hasModalDialog
|
|
end
|
|
end
|
|
|
|
--- Display the window.
|
|
---@param text string Text to place in the EditBox.
|
|
---@return boolean? willOpen false if the window won't open due to missing parameters.
|
|
function dialog:Open(text)
|
|
if not self._super.Open(self, text) then
|
|
return false
|
|
end
|
|
|
|
if self.modalParent then
|
|
-- Modal dialog behavior -- anchor and center it on the parent and
|
|
-- place a shade over the parent frame.
|
|
self.uiFrame:SetMovable(false)
|
|
----------
|
|
-- Can't use SetParent() because it messes up the ScrollFrame overflow. :(
|
|
-- Instead, we're putting the frame on the DIALOG strata during its' InitUi().
|
|
-- self.suppressShowHideEvents = true -- Changing parentage will trigger OnHide/OnShow and we don't want them to fire again.
|
|
-- self.uiFrame:SetParent(self.modalParent)
|
|
-- self.suppressShowHideEvents = false
|
|
----------
|
|
self.uiFrame:ClearAllPoints()
|
|
self.uiFrame:SetPoint("CENTER", self.modalParent, "CENTER", 0, 0)
|
|
self.ui:SetWindowShade(self.modalParent, true, self.uiFrame)
|
|
self.modalParent.bagshuiData.hasModalDialog = true
|
|
end
|
|
|
|
-- Focus the EditBox after a short delay (see dialog:FocusEditBox() comments for reasoning).
|
|
Bagshui:QueueClassCallback(self, self.FocusEditBox, 0.1)
|
|
|
|
return true
|
|
end
|
|
|
|
table.insert(self.dialogInstances[id], dialog)
|
|
end
|
|
|
|
return dialog
|
|
end
|
|
|
|
--- Display a multiline input dialog.
|
|
--- To get the input, use the OnAccept/OnCancel callbacks (see notes for `AddDialog()`).
|
|
---@param idOrDialog any|table Unique identifier set up in `AddDialog()` *or* return value from `GetDialog()`.
|
|
---@param prompt string? Text to display above the EditBox. If not provided, mut have been given to `AddDialog()`.
|
|
---@param button1Text string? Text to display on the left-hand button. If not provided, will fall back to dialog properties and then default to localized "Okay". An empty string will hide the button.
|
|
---@param button2Text string? Text to display on the right-hand button. If not provided, will fall back to dialog properties and then default to localized "Cancel". An empty string will hide the button.
|
|
---@param defaultText string? Initial text placed in the EditBox.
|
|
---@param modalParent table? Frame to attach this dialog to.
|
|
---@return table dialog The dialog box object that will be passed through to callbacks.
|
|
function Ui:ShowDialog(idOrDialog, prompt, button1Text, button2Text, defaultText, modalParent)
|
|
local dialog = idOrDialog
|
|
if type(idOrDialog) == "string" then
|
|
dialog = self:GetDialog(idOrDialog)
|
|
end
|
|
assert(dialog and dialog.isDialog, "Ui:ShowDialog() - " .. tostring(idOrDialog) .. " is not a valid dialog.")
|
|
|
|
-- Update properties that will be consumed by InitUi().
|
|
|
|
dialog.properties = self.dialogProperties[dialog.id] -- Refresh pointer to properties in case they've been updated.
|
|
dialog.prompt = prompt or self.dialogProperties[dialog.id].prompt or nil
|
|
dialog.button1 = button1Text or self.dialogProperties[dialog.id].button1 or _G.OKAY
|
|
dialog.button2 = button2Text or self.dialogProperties[dialog.id].button2 or _G.CANCEL
|
|
dialog.modalParent = modalParent
|
|
|
|
-- Create/update UI.
|
|
dialog:InitUi()
|
|
|
|
-- Display the dialog and wait for callback.
|
|
-- ScrollableTextWindow:Open() expects a non-nil string so pass an empty one if no default text was given.
|
|
dialog:Open(type(defaultText) == "string" and defaultText or "")
|
|
|
|
return dialog
|
|
end
|
|
|
|
--- Display a copyable URL.
|
|
---@param url string
|
|
function Ui:ShowUrl(url)
|
|
local dialogName = "BAGSHUI_COPY_URL"
|
|
|
|
if not _G.StaticPopupDialogs[dialogName] then
|
|
_G.StaticPopupDialogs[dialogName] = {
|
|
text = L.HowToUrl,
|
|
button1 = _G.OKAY,
|
|
hasEditBox = true,
|
|
hasWideEditBox = true,
|
|
timeout = 0,
|
|
whileDead = true,
|
|
hideOnEscape = true,
|
|
|
|
-- Extra property to be used in OnShow to fill the edit box.
|
|
-- Blizzard doesn't provide an easy way to do this in Vanilla as the
|
|
-- data property isn't accessible in OnShow, and all the FrameXML code uses
|
|
-- global variables and functions.
|
|
_bagshuiUrl = "",
|
|
|
|
-- Set the default value for the edit box
|
|
OnShow = function()
|
|
-- For whatever reason, Blizzard has hardcoded which dialogs are wide instead of
|
|
-- resizing based on hasWideEditBox, so the only way to get the dialog wide enough
|
|
-- is to set it manually.
|
|
_G.this:SetWidth(420)
|
|
_G[_G.this:GetName() .. "WideEditBox"]:SetText(_G.StaticPopupDialogs[dialogName]._bagshuiUrl)
|
|
_G[_G.this:GetName() .. "WideEditBox"]:SetFocus()
|
|
_G[_G.this:GetName() .. "WideEditBox"]:HighlightText()
|
|
-- We also need to reposition Button1 because apparently you're not supposed to
|
|
-- have a dialog with an EditBox and one button.
|
|
_G[_G.this:GetName() .. "Button1"]:ClearAllPoints()
|
|
_G[_G.this:GetName() .. "Button1"]:SetPoint("TOP", _G[_G.this:GetName() .. "EditBox"], "BOTTOM", 0, -8)
|
|
end,
|
|
|
|
-- Clear text on hide so it doesn't bleed over to other dialogs.
|
|
OnHide = BsUtil.StaticPopupDialogs_ClearTextOnHide,
|
|
}
|
|
end
|
|
|
|
_G.StaticPopupDialogs[dialogName]._bagshuiUrl = url
|
|
_G.StaticPopup_Show(dialogName)
|
|
end
|
|
end)
|