myspace / CommonLua /UI /StdDialogs.lua
sirnii's picture
Upload 1816 files
b6a38d7 verified
raw
history blame
21 kB
if FirstLoad then
g_OpenMessageBoxes = {}
end
DefineClass.StdDialog = {
__parents = { "XDialog", "XDarkModeAwareDialog" },
HAlign = "center",
VAlign = "center",
BorderWidth = 1,
BorderColor = RGB(0, 0, 0),
Background = RGBA(0, 0, 0, 255),
MinWidth = 350,
MinHeight = 150,
Translate = true,
}
function StdDialog:Init(parent, context)
context = context or empty_table
if context.title then
XLabel:new({
Id = "idTitle",
Dock = "top",
Margins = box(4, 4, 4, 0),
TextStyle = "GedTitle",
Translate = context.translate,
}, self)
self.idTitle:SetText(context.title)
end
XWindow:new({
Id = "idContainer",
Background = RGB(240, 240, 240),
BorderWidth = 1,
BorderColor = RGB(160, 160, 160),
Margins = box(6, 6, 6, 6),
Padding = box(8, 8, 8, 8),
MaxWidth = 900,
}, self)
XWindow:new({
Id = "idButtonContainer",
Dock = "bottom",
LayoutMethod = "HList",
LayoutHSpacing = 4,
HAlign = "center",
Margins = box(0, 11, 0, 0),
}, self.idContainer)
self:SetModal()
local dark_mode
if context.dark_mode ~= nil then
dark_mode = context.dark_mode
else
dark_mode = GetDarkModeSetting()
end
self:SetDarkMode(dark_mode)
end
function StdDialog:Open(...)
RegisterMessageBox(self)
XDialog.Open(self, ...)
self:UpdateControlDarkMode(self)
self:UpdateChildrenDarkMode(self)
end
function StdDialog:Close(...)
UnregisterMessageBox(self)
XDialog.Close(self, ...)
end
local list_bg = RGB(64, 64, 66)
local list_focus = RGB(150, 150, 150)
local l_list_bg = RGB(255, 255, 255)
local l_list_focus = RGB(255, 255, 255)
local item_selection = RGB(100, 100, 100)
local l_item_selection = RGB(204, 232, 255)
local btn_bg = RGB(100, 100, 100)
local l_btn_bg = RGB(240, 240, 240)
local btn_selected = RGB(150, 150, 150)
local btn_rollover = RGB(120, 120, 120)
local l_btn_selected = RGB(204, 232, 255)
local l_btn_rollover = RGB(180, 180, 180)
local scroll = RGB(128, 128, 128)
local scroll_background = RGB(64, 64, 66)
local l_scroll = RGB(169, 169, 169)
local l_scroll_background = RGB(240, 240, 240)
function StdDialog:UpdateChildrenDarkMode(win)
if IsKindOf(win, "XSleekScroll") then
return
end
XDarkModeAwareDialog.UpdateChildrenDarkMode(self, win)
end
function StdDialog:UpdateControlDarkMode(control)
XDarkModeAwareDialog.UpdateControlDarkMode(self, control)
local dark_mode = self.dark_mode
if IsKindOf(control, "XList") then
control:SetBackground(dark_mode and list_bg or l_list_bg)
control:SetFocusedBackground(dark_mode and list_focus or l_list_focus)
end
if IsKindOf(control, "XListItem") then
control:SetBackground(dark_mode and list_bg or l_list_bg)
control:SetSelectionBackground(dark_mode and item_selection or l_item_selection)
end
if IsKindOf(control, "XTextButton") then
if control:GetBackground() ~= RGBA(0,0,0,0) then
control:SetBackground(dark_mode and btn_bg or l_btn_bg)
control:SetRolloverBackground(dark_mode and btn_rollover or l_btn_rollover)
control:SetPressedBackground(dark_mode and btn_selected or l_btn_selected)
end
end
if IsKindOf(control, "XSleekScroll") then
control.idThumb:SetBackground(dark_mode and scroll or l_scroll)
control:SetBackground(dark_mode and scroll_background or l_scroll_background)
end
end
----- StdStatusDialog
DefineClass.StdStatusDialog = {
__parents = { "StdDialog" },
HandleMouse = false,
MinWidth = 200,
MinHeight = 50,
DrawOnTop = true,
}
function StdStatusDialog:Init(parent, context)
XText:new({
Id = "idText",
TextHAlign = "center",
TextVAlign = "center",
Translate = context and context.translate,
Margins = box(10, 7, 10, 7),
}, self)
self.idText:SetText(context and context.status or "")
self:SetModal(false)
end
function StdStatusDialog:SetStatus(text)
self.idText:SetText(text)
WaitNextFrame(3)
end
----- StdMessageDialog
DefineClass.StdMessageDialog = {
__parents = { "StdDialog" },
HandleKeyboard = true,
DrawOnTop = true,
}
function StdMessageDialog:Init(parent, context)
context = context or empty_table
XScrollArea:new({
Id = "idScrollArea",
VAlign = "top",
LayoutMethod = "VList",
VScroll = "idScroll",
IdNode = false,
}, self.idContainer)
XSleekScroll:new({
Dock = "right",
Target = "idScrollArea",
Id = "idScroll",
AutoHide = true,
}, self.idContainer)
XText:new({
Id = "idText",
TextVAlign = "center",
Translate = context.translate,
}, self.idScrollArea)
self.idText:SetText(context.text or "")
if context.choices then
for i=1,#context.choices do
XTextButton:new({
Id = "idChoice" .. i,
MinWidth = 100,
Translate = context.translate,
Text = context.choices[i],
LayoutMethod = "VList",
OnPress = function() self:Close(i) end,
}, self.idButtonContainer)
end
else
XTextButton:new({
Id = "idOKText",
MinWidth = 100,
Translate = context.translate,
Text = context.ok_text or context.translate and T(325411474155, "OK") or "OK",
LayoutMethod = "VList",
ActionShortcut = "Enter",
ActionGamepad = "ButtonA",
OnPress = function() self:Close("ok") end,
}, self.idButtonContainer)
if context.question then
XTextButton:new({
Id = "idCancelText",
MinWidth = 100,
Translate = context.translate,
Text = context.cancel_text or context.translate and T(967444875712, "Cancel") or "Cancel",
LayoutMethod = "VList",
ActionShortcut = "Escape",
ActionGamepad = "ButtonB",
OnPress = function() self:Close("cancel") end,
}, self.idButtonContainer)
end
end
self:SetFocus()
end
function StdMessageDialog:PreventClose()
if self:HasMember("idOKText") then
self.idOKText:SetVisible(false)
elseif self:HasMember("idCancelText") then
self.idCancelText:SetVisible(false)
end
self.OnShortcut = empty_func
end
function StdMessageDialog:OnShortcut(shortcut, ...)
if self:HasMember("idOKText") and self.idOKText:IsVisible() and (shortcut == "Enter" or shortcut == "ButtonA") then
self:Close("ok", ...)
return "break"
elseif self:HasMember("idCancelText") and self.idCancelText:IsVisible() and (shortcut == "Escape" or shortcut == "ButtonB") then
self:Close("cancel", ...)
return "break"
end
end
----- StdInputDialog
DefineClass.StdInputDialog = {
__parents = { "StdDialog" },
FocusOnOpen = "",
}
function StdInputDialog:Init(parent, context)
if context.free_input then
XWindow:new({
Id = "idSubContainer",
Dock = "top",
}, self.idContainer)
XText:new({
Id = "idFreeLabel",
Dock = "left",
Translate = true,
}, self.idSubContainer):SetText(T(998885500683, "Input: "))
XEdit:new({
Id = "idFreeInput",
Dock = "top",
Margins = box(0, 0, 0, 7),
Background = RGB(255, 255, 255),
FocusedBackground = RGB(255, 255, 255),
AutoSelectAll = true,
AllowEscape = false,
MaxLen = context.max_len,
}, self.idSubContainer)
end
XTextButton:new({
Id = "idOKText",
MinWidth = 100,
Translate = true,
Text = T(325411474155, "OK"),
LayoutMethod = "VList",
OnPress = function() self:SelectAndClose() end,
}, self.idButtonContainer)
XTextButton:new({
Id = "idCancelText",
MinWidth = 100,
Translate = true,
Text = T(967444875712, "Cancel"),
LayoutMethod = "VList",
OnPress = function() self:Close() end,
}, self.idButtonContainer)
if context.items and context.combo then
XCombo:new({
Id = "idInput",
VAlign = "center",
Background = RGB(255, 255, 255),
FocusedBackground = RGB(255, 255, 255),
Items = context.items,
VirtualItems = true,
}, self.idContainer)
self.idInput:SetValue(context.items[context.default])
self.idInput:SetFocus()
elseif context.items then
if context.free_input then
XWindow:new({
Id = "idSubContainer2",
Dock = "top",
}, self.idContainer)
XText:new({
Id = "idFilterLabel",
Dock = "left",
Translate = true,
}, self.idSubContainer2):SetText(T(173389874804, "Filter:"))
end
XEdit:new({
Id = "idFilter",
Dock = "top",
Margins = box(0, 0, 0, 7),
AllowEscape = false,
OnTextChanged = function(edit)
self.idInput:Clear()
local pattern = edit:GetText()
local lower_pattern = string.lower(pattern)
local sorted_items = {}
for idx, item in ipairs(context.items) do
local match, score, match_indices = string.fuzzy_match(pattern, item)
if pattern == "" or match then
local s, e = string.find(string.lower(item), lower_pattern, 1, true)
if s then
match_indices = {}
for i = s, e do
match_indices[#match_indices + 1] = i
end
sorted_items[#sorted_items + 1] = {
idx = idx,
text = HighlightFuzzyMatches(item, match_indices, "<style GedSearchHighlightPartial>", "</style>"),
score = 1000000,
}
else
sorted_items[#sorted_items + 1] = {
idx = idx,
text = match_indices and HighlightFuzzyMatches(item, match_indices, "<style GedSearchHighlight>", "</style>") or item,
score = score,
}
end
end
end
table.stable_sort(sorted_items, function(a, b) return a.score > b.score end)
for k, v in ipairs(sorted_items) do
local item = self.idInput:CreateTextItem(v.text, { selectable = true })
rawset(item, "choice_idx", v.idx)
end
self.idInput:SetSelection(1)
Msg("XWindowRecreated", self.idInput)
end,
OnShortcut = function(edit, shortcut, ...)
if shortcut == "Up" or shortcut == "Down" or shortcut == "Ctrl-Home" or shortcut == "Ctrl-End" or
shortcut == "Pageup" or shortcut == "Pagedown" or shortcut == "DPadUp" or shortcut == "DPadDown" then
return self.idInput:OnShortcut(shortcut, ...)
end
return XEdit.OnShortcut(edit, shortcut, ...)
end,
}, context.free_input and self.idSubContainer2 or self.idContainer)
XWindow:new({
Id = "idListParent",
BorderWidth = 1,
}, self.idContainer)
local list = XList:new({
Id = "idInput",
VAlign = "center",
WorkUnfocused = true,
FocusedBackground = RGB(255, 255, 255),
VScroll = "idScroll",
MultipleSelection = context.multiple,
BorderWidth = 0,
OnDoubleClick = function(this, item_idx)
local item = context.items[this[item_idx].choice_idx]
self:Close(context.multiple and { item } or item)
end,
}, self.idListParent)
XSleekScroll:new({
Id = "idScroll",
Dock = "right",
AutoHide = true,
MinThumbSize = 30,
FixedSizeThumb = false,
Target = "idInput",
}, self.idListParent)
if context.multiple then
XText:new({ Dock = "bottom", TextHAlign = "center" }, self.idContainer):SetText("(hold Ctrl or Shift to select multiple items)")
end
for k, v in ipairs(context.items) do
local text = v
if type(v) == "table" and v.text then
text = v.text
end
local item = list:CreateTextItem(text, { selectable = true })
rawset(item, "choice_idx", k)
end
local itemHeight = #list > 0 and list[1][1]:GetFontHeight() or 0
local lCnt = Clamp(context.lines or 18, 5, 18)
list:SetMaxHeight(lCnt * itemHeight)
list:SetMinHeight(Min(lCnt, #list) * itemHeight)
if context.multiple then
list:SetSelection(context.multiple and context.default)
else
list:SetSelection(table.find(context.items, context.default) or 1)
end
if context.free_input then
self.idFreeInput:SetFocus()
else
self.idFilter:SetFocus()
end
else
XText:new({
Id = "idError",
Dock = "bottom",
Translate = true,
HideOnEmpty = true,
FoldWhenHidden = true,
}, self.idContainer)
if context.description and context.description ~= "" then
local desc = XText:new({
Dock = "top",
Translate = context.translate,
TextHAlign = "center",
}, self.idContainer)
desc:SetText(context.description .. "\n\n")
end
XEdit:new({
Id = "idInput",
VAlign = "center",
Background = RGB(255, 255, 255),
FocusedBackground = RGB(255, 255, 255),
AutoSelectAll = true,
AllowEscape = false,
MaxLen = context.max_len,
OnTextChanged = function(ctrl)
if (self.idError:GetText() or "") ~= "" then
self:VerifyInputText()
end
XEdit.OnTextChanged(ctrl)
end
}, self.idContainer)
self.idInput:SetText(context.default)
self.idInput:SetFocus()
end
end
function StdInputDialog:VerifyInputText()
local free_text = self.idFreeInput and self.idFreeInput:GetText() or ""
local closeParam = (free_text ~= "") and free_text or self.idInput:GetText()
local error_text = self.context.verifier and self.context.verifier(closeParam) or ""
self.idError:SetText(error_text)
return (error_text or "") == ""
end
function StdInputDialog:OpenControllerTextInput(title, description)
if not self:IsThreadRunning("keyboard") then
self:CreateThread("keyboard", function()
local current_text = self.idInput and self.idInput:GetText() or ""
local text, err, shown = WaitControllerTextInput(current_text, title,
description, 256)
if not err and self.window_state ~= "destroying" then
text = text:trim_spaces()
if text ~= current_text then
self:OnControllerTextInput(text)
end
end
end)
end
end
function StdInputDialog:OnControllerTextInput(text)
self.idInput:SetText(text)
end
function StdInputDialog:SelectAndClose(...)
local input = self.idInput
local closeParam = false
local closeCond = true
local free_text = self.idFreeInput and self.idFreeInput:GetText() or ""
if free_text ~= "" then
closeParam = free_text
elseif input:IsKindOf("XCombo") then
closeParam = input:GetValue()
elseif input:IsKindOf("XList") then
local list = self.idInput
local items = self.context.items
if self.context.multiple then
local selection = input:GetSelection()
closeParam = selection and table.map(selection, function(sel_idx) return items[list[sel_idx].choice_idx] end)
else
closeParam = input:GetFocusedItem() and items[list[input:GetFocusedItem()].choice_idx]
end
else
closeParam = input:GetText()
closeCond = self:VerifyInputText()
end
if closeCond then
self:Close(closeParam, ...)
end
end
function StdInputDialog:OnShortcut(shortcut, ...)
if shortcut == "ButtonY" then
if HasControllerTextInput() then
self:OpenControllerTextInput(IsT(self.context.title) and self.context.title or Untranslated(self.context.title), "")
end
return
end
if self.idOKText:IsVisible() and (shortcut == "Enter" or shortcut == "ButtonA") then
self:SelectAndClose(...)
return "break"
elseif self.idCancelText:IsVisible() and (shortcut == "Escape" or shortcut == "ButtonB") then
self:Close(nil, ...)
return "break"
end
end
----- StdChoiceDialog
DefineClass.StdChoiceDialog = {
__parents = {"StdDialog"},
MaxWidth = 900,
}
function StdChoiceDialog:Init(parent, context)
XCameraLockLayer:new({}, self)
XPauseLayer:new({}, self)
XScrollArea:new({
Id = "idScrollArea",
VAlign = "top",
LayoutMethod = "VList",
VScroll = "idScroll",
IdNode = false,
}, self.idContainer)
XSleekScroll:new({
Dock = "right",
Target = "idScrollArea",
Id = "idScroll",
AutoHide = true,
}, self.idContainer)
XText:new({
Id = "idText",
TextVAlign = "center",
Translate = context.translate,
}, self.idScrollArea)
self.idText:SetText(context.text or "")
local i = 1
local disabled = context.disabled
local buttons = self.idButtonContainer
buttons:SetLayoutMethod("VList")
buttons:SetLayoutVSpacing(5)
while true do
local choice = context["choice" .. i]
if not choice and i == 1 then
choice = T(325411474155, "OK")
end
if not choice then break end
local res = i
local button = XTextButton:new({
OnPress = function(self, gamepad)
GetDialog(self):Close(res)
end,
Background = RGB(175,175,175),
}, buttons)
local text = XText:new({
Translate = context.translate,
}, button)
text:SetText(T{choice, context.params, context})
if disabled and disabled[i] then
button:SetEnabled(false)
end
i = i + 1
end
end
function WaitPopupChoice(parent, context, id)
local dialog = StdChoiceDialog:new({Id = id}, parent or terminal.desktop, context)
dialog:Open()
return dialog:Wait()
end
function WaitInputText(parent, caption, text, max_len, verifier, id, description)
if not caption or caption == "" then caption = "Enter text:" end
if not text or text == "" then text = "Text..." end
local dialog = StdInputDialog:new({Id = id}, parent or terminal.desktop, { title = caption, default = text, max_len = max_len, verifier = verifier, description = description })
dialog:Open()
if HasControllerTextInput() and GetUIStyleGamepad() then
dialog:OpenControllerTextInput(IsT(caption) and caption or Untranslated(caption), "")
end
return dialog:Wait()
end
function WaitListChoice(parent, items, caption, start_selection, lines, free_input, id)
if not caption or caption == "" then caption = "Please select:" end
if not items or type(items) ~= "table" or #items == 0 then items = {""} end
if not start_selection then start_selection = items[1] end
local dialog = StdInputDialog:new({Id = id}, parent or terminal.desktop, {
title = caption, default = start_selection, items = items, lines = lines, free_input = free_input})
dialog:Open()
return dialog:Wait()
end
function WaitListMultipleChoice(parent, items, caption, start_selection, lines, id)
if not caption or caption == "" then caption = "Please select one or more:" end
if not items or type(items) ~= "table" or #items == 0 then items = {""} end
if not start_selection then start_selection = {1} end
local dialog = StdInputDialog:new({Id = id}, parent or terminal.desktop, { multiple = true, title = caption, default = start_selection, items = items, lines = lines } )
dialog:Open()
return dialog:Wait()
end
-- Message Box functions ---
function CreateMessageBox(parent, caption, text, ok_text, obj)
if not caption or caption == "" then caption = Untranslated("Enter text:") end
if not text then text = "" end
ok_text = ok_text or T(325411474155, "OK")
parent = parent or terminal.desktop
local dialog = StdMessageDialog:new({}, parent, { title = caption, text = text, translate = true, obj = obj })
dialog.idOKText:SetText(ok_text)
dialog:Open()
return dialog
end
-- function should always be called in a thread
function WaitMessage(parent, caption, text, ok_text, obj)
local dialog = CreateMessageBox(parent, caption, text, ok_text, obj)
local result, dataset, controller_id = dialog:Wait()
return result, dataset, controller_id
end
-- Message Question Box functions ---
function CreateQuestionBox(parent, caption, text, ok_text, cancel_text, obj)
local dialog = StdMessageDialog:new({}, parent or terminal.desktop, {
title = caption or "",
text = text or "",
ok_text = ok_text or T(325411474155, "OK"),
cancel_text = cancel_text or T(967444875712, "Cancel"),
translate = true,
question = true,
obj = obj
})
dialog:Open()
return dialog
end
function WaitQuestion(parent, caption, text, ok_text, cancel_text, obj)
parent = parent or terminal.desktop
if type(caption) == "string" then caption = Untranslated(caption) end
if type(text) == "string" then text = Untranslated(text) end
assert(type(parent) == "table" and parent.IsKindOf and parent:IsKindOf("XWindow"), "The first argument must be a parent window. Don't just create 'global' messages, attach them to the correct parent so they'd share their lifetimes.", 1)
local dialog
if IsKindOf(caption, "XDialog") then
dialog = caption
else
dialog = CreateQuestionBox(parent, caption, text, ok_text, cancel_text, obj)
end
local result, dataset, controller_id = dialog:Wait()
return result, dataset, controller_id
end
function CreateMultiChoiceQuestionBox(parent, caption, text, obj, ...)
local dialog = StdMessageDialog:new({}, parent or terminal.desktop, {
title = caption or "",
text = text or "",
choices = { ... },
translate = true,
obj = obj
})
dialog:Open()
return dialog
end
function WaitMultiChoiceQuestion(parent, caption, text, obj, ...)
assert(type(parent) == "table" and parent.IsKindOf and parent:IsKindOf("XWindow"), "The first argument must be a parent window. Don't just create 'global' messages, attach them to the correct parent so they'd share their lifetimes.", 1)
local dialog
if IsKindOf(caption, "XDialog") then
dialog = caption
else
dialog = CreateMultiChoiceQuestionBox(parent, caption, text, obj, ...)
end
local result, dataset, controller_id = dialog:Wait()
return result, dataset, controller_id
end
function RegisterMessageBox(message_box)
g_OpenMessageBoxes[message_box] = true
Msg("MessageBoxRegister", message_box)
end
function UnregisterMessageBox(message_box)
g_OpenMessageBoxes[message_box] = nil
Msg("MessageBoxUnregister", message_box)
end
function CloseAllMessagesAndQuestions()
for window,dummy in pairs(g_OpenMessageBoxes) do
if window.window_state ~= "destroying" then
window:Close()
end
end
end
function AreMessageBoxesOpen()
return next(g_OpenMessageBoxes)
end
function IsMessageBoxOpen(id_or_dlg)
if g_OpenMessageBoxes[id_or_dlg] then return true end
for message_box in pairs(g_OpenMessageBoxes) do
if message_box.Id == id_or_dlg then
return true
end
end
end