|
if FirstLoad then |
|
Dialogs = {} |
|
end |
|
|
|
function OpenDialog(template, parent, context, reason, id, prop_preset) |
|
id = id or template |
|
local dialog = GetDialog(id) |
|
if dialog then |
|
if context then |
|
dialog:SetContext(context) |
|
end |
|
local mode = ResolveValue(context, "Mode") |
|
if mode ~= nil then |
|
dialog:SetMode(mode) |
|
end |
|
else |
|
assert(Dialogs[id] == nil) |
|
dialog = XTemplateSpawn(template, parent, context) |
|
assert(IsKindOf(dialog, "XDialog")) |
|
if not dialog then |
|
return |
|
end |
|
dialog.XTemplate = XTemplates[template] and template or nil |
|
Dialogs[id] = dialog |
|
Dialogs[dialog] = id |
|
if not parent or parent.window_state == "open" then |
|
if prop_preset then |
|
dialog:CopyProperties(prop_preset, prop_preset:GatherTemplateProperties()) |
|
end |
|
dialog:Open() |
|
end |
|
end |
|
if IsKindOf(dialog, "XDialog") then |
|
dialog:AddOpenReason(reason) |
|
end |
|
return dialog |
|
end |
|
|
|
function CloseDialog(id, result, reason) |
|
local dialog = GetDialog(id) |
|
if dialog then |
|
if IsKindOf(dialog, "XDialog") then |
|
dialog:RemoveOpenReason(reason, result) |
|
else |
|
dialog:Close(result) |
|
end |
|
return dialog |
|
end |
|
end |
|
|
|
function ListDialogs() |
|
local dlgs = {} |
|
for k,v in pairs(Dialogs) do |
|
if type(k)=="string" then |
|
dlgs[#dlgs + 1] = k |
|
end |
|
end |
|
table.sort(dlgs) |
|
return dlgs |
|
end |
|
|
|
function ListAllDialogs() |
|
return PresetsCombo("XTemplate", nil, "InGameInterface")() |
|
end |
|
|
|
function RemoveOpenReason(reason, result) |
|
for dialog in pairs(Dialogs) do |
|
if IsKindOf(dialog, "XDialog") then |
|
dialog:RemoveOpenReason(reason, result) |
|
end |
|
end |
|
end |
|
|
|
function GetDialog(id_or_win) |
|
if type(id_or_win) == "table" and IsKindOf(id_or_win, "XWindow") then |
|
return GetParentOfKind(id_or_win, "XDialog") |
|
end |
|
return Dialogs[id_or_win] |
|
end |
|
|
|
function WaitDialog(...) |
|
local dialog = OpenDialog(...) |
|
if dialog then |
|
return dialog:Wait() |
|
end |
|
end |
|
|
|
function EnsureDialog(dlg) |
|
local dialog = GetDialog(dlg) |
|
if not dialog then |
|
local t = GetInGameInterface() |
|
if not t then |
|
ShowInGameInterface(true) |
|
t = GetInGameInterface() |
|
end |
|
dialog = OpenDialog(dlg, t) |
|
end |
|
return dialog |
|
end |
|
|
|
function GetDialogMode(id_or_win) |
|
local dlg = GetDialog(id_or_win) |
|
return dlg and dlg.Mode |
|
end |
|
|
|
function GetDialogModeParam(id_or_win) |
|
local dlg = GetDialog(id_or_win) |
|
return dlg and dlg.mode_param |
|
end |
|
|
|
function SetDialogMode(id_or_win, mode, mode_param) |
|
local dlg = GetDialog(id_or_win) |
|
if dlg then |
|
dlg:SetMode(mode, mode_param) |
|
end |
|
end |
|
|
|
function SetBackDialogMode(id_or_win) |
|
local dlg = GetDialog(id_or_win) |
|
if dlg then |
|
local mode = table.remove(dlg.mode_log) |
|
if mode then |
|
dlg:SetMode(mode[1], mode[2]) |
|
table.remove(dlg.mode_log) |
|
end |
|
end |
|
end |
|
|
|
function GetDialogContext(id_or_win) |
|
local dlg = GetDialog(id_or_win) |
|
return dlg and dlg.context |
|
end |
|
|
|
function _GetUIPath(win, parent) |
|
win = win or (parent or terminal.desktop):GetMouseTarget(terminal:GetMousePos()) |
|
parent = parent or false |
|
local wins = {} |
|
local w = win |
|
local f |
|
while w and w ~= parent do |
|
local name = Dialogs[w] or rawget(w, "XTemplate") or w.class |
|
local id = rawget(w, "Id") |
|
if id and name ~= id and id ~= "" then |
|
name = name .. "(" .. id .. ")" |
|
end |
|
local z = w.ZOrder ~= 1 and w.ZOrder |
|
if z then |
|
name = name .. "[" .. z .. "]" |
|
end |
|
if not w:IsVisible() then |
|
name = name .. "|X|" |
|
end |
|
if not f and w:IsFocused() then |
|
f = w |
|
name = name .. "*" |
|
end |
|
table.insert(wins, 1, name) |
|
w = w.parent or false |
|
end |
|
return table.concat(wins, " - ") |
|
end |
|
|
|
function _PrintDialogs(print_func, indent, except) |
|
except = except or {} |
|
indent = indent or "" |
|
print_func = print_func or print |
|
local texts = {} |
|
for name, dlg in pairs(Dialogs) do |
|
if type(name) == "string" and type(dlg) == "table" and not table.find(except,name) then |
|
texts[#texts + 1] = _GetUIPath(dlg, terminal.desktop) |
|
|
|
if dlg:IsKindOf("BaseLoadingScreen") then |
|
texts[#texts + 1] = "\t" .. ValueToLuaCode(dlg:GetOpenReasons()) |
|
end |
|
end |
|
end |
|
table.sort(texts) |
|
local indents = {} |
|
for i=1,#texts do |
|
local txt = texts[i] |
|
for j=i+1,#texts do |
|
local t = texts[j] |
|
local b, e = string.find(t, txt, 1, true) |
|
if b then |
|
indents[j] = (indents[j] or 0) + 1 |
|
texts[j] = string.sub(t, e+1) |
|
end |
|
end |
|
end |
|
for i=1,#texts do |
|
local txt = texts[i] |
|
if indents[i] then |
|
txt = string.rep("\t", indents[i]) .. txt |
|
end |
|
print_func(indent, txt) |
|
end |
|
end |
|
|
|
function OnMsg.BugReportStart(print_func) |
|
print_func("Screen size: ", UIL.GetScreenSize()) |
|
if next(Dialogs) ~= nil then |
|
print_func("Opened dialogs: (notation: (n) = Id, [n] = ZOrder, |X| = Invisible, * = Focused)") |
|
_PrintDialogs(print_func, "\t") |
|
print_func("") |
|
end |
|
end |
|
|
|
function CloseAllDialogs(except, force_loading_screens) |
|
if Platform.xbox then |
|
AsyncOpCancel(ActiveVirtualKeyboard) |
|
end |
|
CloseAllMessagesAndQuestions() |
|
local dialogs = ListDialogs() |
|
for i = 1, #dialogs do |
|
local dialog = dialogs[i] |
|
if except and except[dialog] then |
|
print("Skipping dialog " .. dialog) |
|
elseif not force_loading_screens and (IsKindOf(GetDialog(dialog), "BaseLoadingScreen") or IsKindOf(GetDialog(dialog), "BaseSavingScreen")) then |
|
|
|
else |
|
CloseDialog(dialog) |
|
end |
|
end |
|
end |
|
|
|
function CloseDialogs(id, ...) |
|
if not id then return end |
|
CloseDialog(id) |
|
return CloseDialogs(...) |
|
end |
|
|
|
|
|
|
|
DefineClass.XDialog = { |
|
__parents = { "XActionsHost" }, |
|
properties = { |
|
{ category = "General", id = "InitialMode", editor = "text", default = "" }, |
|
{ category = "General", id = "Mode", editor = "text", default = "", read_only = true, }, |
|
{ category = "General", id = "InternalModes", name = "Internal modes", editor = "text", default = "", help = "A list of internal modes. When present, any mode outside of the list will be propagated to the parent dialog."}, |
|
{ category = "General", id = "gamestate", Name = "Game state", editor = "text", default = "" }, |
|
{ category = "General", id = "FocusOnOpen", editor = "choice", default = "self", items = {"", "self", "child"}, }, |
|
{ category = "Visual", id = "HideInScreenshots", editor = "bool", default = false }, |
|
}, |
|
XTemplate = false, |
|
Translate = true, |
|
IdNode = true, |
|
open_reasons = false, |
|
result = false, |
|
close_controller_id = false, |
|
mode_log = false, |
|
mode_param = false, |
|
} |
|
|
|
function XDialog:Init(parent, context) |
|
self.InitialMode = ResolveValue(context, "Mode") or self.InitialMode |
|
self.mode_log = ResolveValue(context, "mode_log") or {} |
|
end |
|
|
|
function XDialog:Close(reason, source, controller_id, ...) |
|
if source == "gamepad" then |
|
self.close_controller_id = controller_id |
|
end |
|
XActionsHost.Close(self, reason, source, controller_id, ...) |
|
end |
|
|
|
function XDialog:Done(result) |
|
Msg("DialogClose", self, result) |
|
local id = Dialogs[self] |
|
Dialogs[self] = nil |
|
if id and Dialogs[id] == self then |
|
Dialogs[id] = nil |
|
if self.gamestate ~= "" then |
|
ChangeGameState(self.gamestate, false) |
|
end |
|
end |
|
self.result = result |
|
Msg(self) |
|
end |
|
|
|
function XDialog:Open(...) |
|
if not self.HostInParent then |
|
self:ResolveRelativeFocusOrder() |
|
end |
|
self:SetFocus_OnOpen(self.FocusOnOpen) |
|
XActionsHost.Open(self, ...) |
|
Msg("DialogOpen", self, self.InitialMode) |
|
if self.InitialMode ~= "" then |
|
self:SetMode(self.InitialMode) |
|
end |
|
if self.gamestate ~= "" then |
|
ChangeGameState(self.gamestate, true) |
|
end |
|
end |
|
|
|
function XDialog:SetFocus_OnOpen(focus) |
|
focus = focus or self.FocusOnOpen |
|
if focus == "self" then |
|
self:SetFocus() |
|
elseif focus == "child" then |
|
local child = self:GetRelativeFocus(point(1, 1), "nearest") |
|
if child then |
|
child:SetFocus() |
|
end |
|
end |
|
end |
|
|
|
function XDialog:Wait() |
|
assert(IsAsyncCode()) |
|
if self.window_state == "open" then |
|
assert(not self:GetThreadName(), "The window thread will be deleted before self:Wait() returns") |
|
WaitMsg(self) |
|
end |
|
return self.result, self, self.close_controller_id |
|
end |
|
|
|
function XDialog:AddOpenReason(reason) |
|
self.open_reasons = self.open_reasons or {} |
|
self.open_reasons[reason or true] = true |
|
end |
|
|
|
function XDialog:RemoveOpenReason(reason, result) |
|
local open_reasons = self.open_reasons |
|
reason = reason or true |
|
if open_reasons and open_reasons[reason] then |
|
open_reasons[reason] = nil |
|
if next(open_reasons) == nil and self.window_state ~= "destroying" then |
|
self:Close(result) |
|
return true |
|
end |
|
end |
|
end |
|
|
|
function XDialog:GetOpenReasons() |
|
return self.open_reasons or empty_table |
|
end |
|
|
|
local function callOnModeChange(win, mode, dialog) |
|
if win == dialog or not IsKindOf(win, "XDialog") then |
|
for _, win in ipairs(win or empty_table) do |
|
callOnModeChange(win, mode, dialog) |
|
end |
|
end |
|
win:OnDialogModeChange(mode, dialog) |
|
end |
|
|
|
function MatchDialogMode(mode, list) |
|
if not list or list == "" then return end |
|
if mode == "" then |
|
return list:starts_with(",") |
|
end |
|
if mode == list then |
|
return true |
|
end |
|
if not list:find(mode, 1, true) then |
|
return |
|
end |
|
for m in list:gmatch("([%w%-_]+)") do |
|
if m == mode then |
|
return true |
|
end |
|
end |
|
end |
|
|
|
function XDialog:GetModes(list) |
|
list = list or self.InternalModes or "" |
|
local arr = { list:starts_with(",") and "" or nil } |
|
for m in list:gmatch("([%w%-_]+)") do |
|
arr[#arr + 1] = m |
|
end |
|
return arr |
|
end |
|
|
|
function XDialog:SetMode(mode, mode_param) |
|
if not MatchDialogMode(mode, self.InternalModes) then |
|
local dlg = GetParentOfKind(self.parent, "XDialog") |
|
if dlg then |
|
dlg:SetMode(mode, mode_param) |
|
return |
|
end |
|
end |
|
self.mode_log[#self.mode_log + 1] = { self.Mode, self.mode_param } |
|
local old_mode = self.Mode |
|
self.Mode = mode |
|
self.mode_param = mode_param |
|
Msg("DialogSetMode", self, mode, mode_param, old_mode) |
|
self:CallOnModeChange() |
|
end |
|
|
|
function XDialog:CallOnModeChange() |
|
callOnModeChange(self, self.Mode, self) |
|
end |
|
|
|
|
|
|
|
DefineClass.XLayer = { |
|
__parents = { "XDialog" }, |
|
FocusOnOpen = "", |
|
} |
|
|
|
|
|
|
|
|
|
DefineClass.XOpenLayer = { |
|
__parents = { "XWindow" }, |
|
properties = { |
|
{ category = "General", id = "Layer", editor = "combo", default = "", items = function() return XTemplateCombo("XLayer") end, }, |
|
{ category = "General", id = "LayerId", editor = "text", default = "", }, |
|
{ category = "General", id = "Mode", editor = "text", default = false, }, |
|
}, |
|
Dock = "ignore", |
|
visible = false, |
|
dialog = false, |
|
xtemplate = false, |
|
} |
|
|
|
function XOpenLayer:Open() |
|
if self.Layer ~= "" then |
|
local context = self:GetContext() |
|
if self.Mode then |
|
context = SubContext(context, { Mode = self.Mode }) |
|
end |
|
local id = self.LayerId ~= "" and self.LayerId or nil |
|
self.dialog = OpenDialog(self.Layer, nil, context, self, id, self.xtemplate) |
|
end |
|
end |
|
|
|
function XOpenLayer:Done() |
|
if self.dialog then |
|
CreateRealTimeThread(self.dialog.RemoveOpenReason, self.dialog, self) |
|
end |
|
end |
|
|
|
|
|
|
|
|
|
DefineClass.XContentTemplate = { |
|
__parents = { "XActionsHost" }, |
|
properties = { |
|
{ category = "Template", id = "RespawnOnContext", name = "Respawn on context update", editor = "bool", default = true, }, |
|
{ category = "Template", id = "RespawnOnDialogMode", name = "Respawn on mode change", editor = "bool", default = true, }, |
|
{ category = "Template", id = "RespawnExpression", name = "Respawn on expression change", editor = "expression", default = empty_func, params = "self, context", |
|
dont_save = function(self) return self.RespawnOnContext end }, |
|
}, |
|
IdNode = true, |
|
HostInParent = true, |
|
xtemplate = false, |
|
respawn_value = false, |
|
} |
|
|
|
function XContentTemplate:Init(parent, context, xtemplate) |
|
self.xtemplate = xtemplate |
|
self.respawn_value = self:RespawnExpression(context) |
|
end |
|
|
|
function XContentTemplate:OnContextUpdate(context, ...) |
|
if self.RespawnOnContext then |
|
if self.window_state == "open" then |
|
self:RespawnContent() |
|
end |
|
else |
|
local respawn_value = self:RespawnExpression(context) |
|
if rawget(self, "respawn_value") ~= respawn_value then |
|
self.respawn_value = respawn_value |
|
if self.window_state == "open" then |
|
self:RespawnContent() |
|
end |
|
end |
|
end |
|
end |
|
|
|
function XContentTemplate:OnDialogModeChange(mode, dialog) |
|
if self.RespawnOnDialogMode then |
|
self:RespawnContent() |
|
end |
|
end |
|
|
|
function XContentTemplate:RespawnContent() |
|
local xtemplate = self.xtemplate |
|
if xtemplate then |
|
local desktop = self.desktop |
|
local focus = desktop.keyboard_focus |
|
local focus_order = focus and focus:IsWithin(self) and focus:GetFocusOrder() |
|
local gamepad_rollover = RolloverControl and RolloverControl == focus and RolloverGamepad |
|
local mouse_rollover = RolloverControl and RolloverControl == desktop.last_mouse_target and RolloverControl:IsWithin(self) |
|
self:DeleteChildren() |
|
self:ClearActions() |
|
xtemplate:EvalChildren(self, self.context) |
|
for _, win in ipairs(self) do |
|
win:Open() |
|
end |
|
self:InvalidateMeasure() |
|
self:InvalidateLayout() |
|
local host = GetActionsHost(self, true) |
|
if not host or host == self then |
|
self:ResolveRelativeFocusOrder() |
|
elseif host and not host:GetThread("resolve_focus") then |
|
host:CreateThread("resolve_focus", host.ResolveRelativeFocusOrder, host) |
|
end |
|
self:DeleteThread("rollover") |
|
self:CreateThread("rollover", function(self, focus_order, gamepad_rollover, mouse_rollover) |
|
local focus = self:GetRelativeFocus(focus_order, "nearest") |
|
if focus then |
|
focus:SetFocus() |
|
end |
|
if focus and gamepad_rollover then |
|
XCreateRolloverWindow(focus, true, true) |
|
elseif mouse_rollover then |
|
local win = XGetRolloverControl() |
|
if win and win:IsWithin(self) then |
|
XCreateRolloverWindow(win, false, true) |
|
end |
|
end |
|
end, self, focus_order, gamepad_rollover, mouse_rollover) |
|
Msg("XWindowRecreated", self) |
|
end |
|
end |
|
|
|
|
|
|
|
|
|
DefineClass.XContentTemplateScrollArea = { |
|
__parents = { "XScrollArea", "XContentTemplate" }, |
|
} |
|
|
|
|
|
|
|
|
|
DefineClass.XContentTemplateList = { |
|
__parents = { "XList", "XContentTemplate" }, |
|
MouseScroll = false, |
|
properties = { |
|
{ category = "General", id = "KeepSelectionOnRespawn", editor = "bool", default = false }, |
|
}, |
|
} |
|
|
|
function XContentTemplateList:OnShortcut(shortcut, source, ...) |
|
if XList.OnShortcut(self, shortcut, source, ...) == "break" then |
|
return "break" |
|
end |
|
return XActionsHost.OnShortcut(self, shortcut, source, ...) |
|
end |
|
|
|
function XContentTemplateList:Open(...) |
|
self:GenerateItemHashTable() |
|
XContentTemplate.Open(self, ...) |
|
self:CreateThread("SetInitialSelection", self.SetInitialSelection, self) |
|
end |
|
|
|
function XContentTemplateList:RespawnContent() |
|
local last_selection |
|
if self.KeepSelectionOnRespawn and next(self.selection) then |
|
last_selection = self.selection[1] |
|
end |
|
XContentTemplate.RespawnContent(self) |
|
self:GenerateItemHashTable() |
|
self:DeleteThread("SetInitialSelection") |
|
self:CreateThread("SetInitialSelection", self.SetInitialSelection, self, last_selection) |
|
end |