myspace / CommonLua /X /XDesktop.lua
sirnii's picture
Upload 1816 files
b6a38d7 verified
raw
history blame
15.9 kB
DefineClass.XDesktop = {
__parents = { "XActionsHost" },
LayoutMethod = "Box",
keyboard_focus = false,
modal_window = false,
modal_log = false,
mouse_capture = false,
touch = false,
last_mouse_pos = false,
last_mouse_target = false,
inactive = false,
mouse_target_update = false,
last_event_at = false,
terminal_target_priority = -1,
layout_thread = false,
mouse_target_thread = false,
focus_logging_enabled = false,
rollover_logging_enabled = false,
HandleMouse = true,
IdNode = true,
}
function XDesktop:Init()
self.desktop = self
self.focus_log = {}
self.modal_log = {}
self.touch = {}
self.modal_window = self
end
function XDesktop:WindowLeaving(win)
local last_target = self.last_mouse_target
if last_target and last_target:IsWithin(win) then
local current = last_target
while current do
current:OnMouseLeft(self.last_mouse_pos, last_target)
if current == win then break end
current = current.parent
end
repeat
win = win.parent
until not win or win.HandleMouse
self.last_mouse_target = win
end
end
function XDesktop:WindowLeft(win)
if self.mouse_capture == win then
self:SetMouseCapture(false)
end
self:RemoveModalWindow(win)
self:RemoveKeyboardFocus(win)
if RolloverControl == win then
XDestroyRolloverWindow("immediate")
end
for id, touch in pairs(self.touch) do
if touch.target == win then
touch.target = nil
end
if touch.capture == win then
touch.capture = nil
end
end
end
----- keyboard
function XDesktop:NextFocusCandidate()
for i = #self.focus_log, 1, -1 do
local win = self.focus_log[i]
if win:IsWithin(self.modal_window) and win:IsVisible() and win:GetEnabled() then
return win
end
end
return false
end
function XDesktop:RestoreFocus()
self:SetKeyboardFocus(self:NextFocusCandidate())
end
function XDesktop:SetKeyboardFocus(focus)
assert(not focus or focus:IsWithin(self))
local last_focus = self.keyboard_focus
if last_focus == focus or focus and not focus:IsWithin(self) then
return
end
if self.focus_logging_enabled then
print("New Focus:", FormatWindowPath(focus))
print(GetStack(2))
end
if focus then
table.remove_entry(self.focus_log, focus)
table.insert(self.focus_log, focus or nil)
if not focus:IsWithin(self.modal_window) or not focus:IsVisible() then
return
end
end
if self.inactive then
self.keyboard_focus = focus
return
end
self.keyboard_focus = focus
local common_parent = XFindCommonParent(last_focus, focus)
local win = last_focus
while win and win ~= common_parent do
win:OnKillFocus(focus)
win = win.parent
end
win = focus
while win and win ~= common_parent do
win:OnSetFocus(focus)
win = win.parent
end
end
function XDesktop:RemoveKeyboardFocus(win, children)
local is_focused = win:IsFocused(children)
local log = self.focus_log
if children then
for i = #log, 1, -1 do
if log[i]:IsWithin(win) then
table.remove(log, i)
end
end
else
table.remove_entry(log, win)
end
if is_focused then
self:RestoreFocus()
end
end
function XDesktop:GetKeyboardFocus()
return self.keyboard_focus
end
function GetKeyboardFocus()
local desktop = terminal and terminal.desktop
return desktop and desktop:GetKeyboardFocus()
end
function WaitSwitchControls(state, title, text, ok_text, cancel_text)
ok_text = ok_text or T(1138, "Yes")
cancel_text = cancel_text or T(1139, "No")
if WaitQuestion(terminal.desktop, title, text, ok_text, cancel_text, {forced_ui_style = state}) == "ok" then
SwitchControls(state == "gamepad")
end
end
function XDesktop:KeyboardEvent(event, button, ...)
if config.AutoControllerHandling and event == "OnXButtonDown" and AccountStorage and not GetUIStyleGamepad() then
if config.AutoControllerHandlingType == "popup" then
if not IsValidThread(SwitchControlQuestionThread) then
SwitchControlQuestionThread = CreateRealTimeThread(function()
WaitSwitchControls("gamepad", T(383758760550, "Switch to controller?"), T(207085726945, "Are you sure you want to use a controller?"))
end)
return "break"
end
elseif config.AutoControllerHandlingType == "auto" then
SwitchControls(true)
end
end
self.last_event_at = RealTime()
local target = self.keyboard_focus
while target do
if target.HandleKeyboard then
if target[event](target, button, ...) == "break" then
-- print(tostring(target), event, ...)
return "break"
end
end
target = target.parent
end
end
function XDesktop:OnShortcut(shortcut, source, ...)
if source == "mouse" then
local target = self.last_mouse_target or self.mouse_capture
while target and target ~= self do
if target.window_state ~= "destroying" then
if target:OnShortcut(shortcut, source, ...) == "break" then
return "break"
end
end
target = target.parent
end
else
local focus = self.keyboard_focus
while focus and focus ~= self do
if focus.HandleKeyboard then
if focus:OnShortcut(shortcut, source, ...) == "break" then
return "break"
end
end
focus = focus.parent
end
end
end
function XDesktop:OnSystemVirtualKeyboard()
ConsoleLogResize()
ConsoleResize()
end
----- gamepad
function XDesktop:XEvent(event, ...)
if not self.inactive then
if not IsMouseViaGamepadActive() then
self:SetLastMouseTarget(false)
self:ResetMousePosTarget()
end
return self:KeyboardEvent(event, ...)
end
end
----- mouse
function XDesktop:SetMouseCapture(win)
if not win or not win:IsWithin(self.modal_window) then
win = false
end
local old_capture = self.mouse_capture
if old_capture == win then return end
self.mouse_capture = win
if old_capture then
old_capture:OnCaptureLost(self.last_mouse_pos)
end
end
function XDesktop:GetMouseCapture()
return self.mouse_capture
end
function XDesktop:RestoreModalWindow()
local win
local log = self.modal_log
for i = #log,1,-1 do
if log[i]:IsVisible() and (not win or log[i]:IsOnTop(win)) then
win = log[i]
end
end
self.desktop:SetModalWindow(win or self)
end
function XDesktop:SetModalWindow(win)
if not win or not win:IsWithin(self) or win == self.modal_window then
return
end
table.remove_entry(self.modal_log, win)
if win ~= self then
self.modal_log[#self.modal_log + 1] = win
end
if not win:IsVisible() or not win:IsOnTop(self.modal_window) then
return
end
if self.focus_logging_enabled then
print("Modal window:", FormatWindowPath(win))
end
self.modal_window = win
for id, touch in pairs(self.touch) do
if not touch.capture and not touch.target:IsWithin(win) then
touch.target:OnTouchCancelled(id, touch.pos, touch)
touch.target = nil
end
end
if RolloverControl and not RolloverControl:IsWithin(win) then
XDestroyRolloverWindow("immediate")
end
if self.keyboard_focus and not self.keyboard_focus:IsWithin(win) then
self:SetKeyboardFocus(false)
end
self:RestoreFocus()
if self.mouse_capture and not self.mouse_capture:IsWithin(win) then
self:SetMouseCapture(false)
end
self:UpdateMouseTarget()
end
function XDesktop:RemoveModalWindow(win)
table.remove_entry(self.modal_log, win)
if self.modal_window == win then
self.modal_window = false
self:RestoreModalWindow()
assert(self.modal_window)
end
end
function XDesktop:GetModalWindow()
return self.modal_window
end
if FirstLoad then
prev_cursor = false
end
function XDesktop:UpdateCursor(pt)
pt = pt or self.last_mouse_pos
if not pt then return end
local target, cursor = self.modal_window:GetMouseTarget(pt)
target = target or self.modal_window
if self.mouse_capture and target ~= self.mouse_capture then
cursor = self.mouse_capture:GetMouseCursor()
target = false
end
local curr_cursor = cursor or const.DefaultMouseCursor
if prev_cursor ~= curr_cursor then
SetUIMouseCursor(curr_cursor)
Msg("MouseCursor", curr_cursor)
prev_cursor = curr_cursor
end
return target
end
function XDesktop:SetLastMouseTarget(target, pt)
local last_target = self.last_mouse_target
if last_target == target then return end
pt = pt or self.last_mouse_pos
assert(not self.mouse_target_update, "Recursive mouse target update!")
if self.rollover_logging_enabled then
print("MouseTarget:", FormatWindowPath(target))
if last_target then last_target:Invalidate() end
if target then target:Invalidate() end
end
self.mouse_target_update = true
self.last_mouse_target = target
local common_parent = XFindCommonParent(last_target, target)
local win = last_target
while win and win ~= common_parent do
win:OnMouseLeft(pt, last_target)
win = win.parent
end
win = target
while win and win ~= common_parent do
win:OnMouseEnter(pt, target)
win = win.parent
end
self.mouse_target_update = false
end
function XDesktop:UpdateMouseTarget(pt)
pt = pt or self.last_mouse_pos
local target = self:UpdateCursor(pt) or false
self:SetLastMouseTarget(target, pt)
return target or self.mouse_capture
end
function XDesktop:MouseEvent(event, pt, button, time)
local target = self:UpdateMouseTarget(pt)
if config.AutoControllerHandling and event == "OnMouseButtonDown" and GetUIStyleGamepad() and (button == "L" or button == "R")
and not GetParentOfKind(target, "DeveloperInterface")
and not GetParentOfKind(target, "XPopupMenu")
and not GetParentOfKind(target, "XBugReportDlg")
and time ~= "gamepad"
then
if config.AutoControllerHandlingType == "popup" then
if not IsValidThread(SwitchControlQuestionThread) then
SwitchControlQuestionThread = CreateRealTimeThread(function()
ForceShowMouseCursor("control scheme change")
WaitSwitchControls("keyboard", T(477820487236, "Switch to mouse?"), T(184341668469, "Are you sure you want to switch to keyboard/mouse controls?"))
UnforceShowMouseCursor("control scheme change")
end)
return "break"
end
elseif config.AutoControllerHandlingType == "auto" then
SwitchControls(false)
end
end
self.last_mouse_pos = pt
self.last_event_at = RealTime()
while target do
if target.window_state ~= "destroying" then
if target[event](target, pt, button) == "break" then
-- print(event, button, pt, _GetUIPath(target))
--[[
if event == "OnMouseButtonDown" then
local info = debug.getinfo(target[event])
print("\t", info.short_src, info.linedefined)
end
--]]
return "break"
end
end
target = target.parent
end
end
function XDesktop:ResetMousePosTarget()
self.last_mouse_pos = false
self.last_mouse_target = false
end
----- activation
function XDesktop:OnSystemActivate()
if self.inactive then
self:KeyboardEvent("OnSetFocus")
self.inactive = false
Msg("SystemActivate")
end
end
function XDesktop:OnSystemInactivate()
if not self.inactive then
self.inactive = true
self:KeyboardEvent("OnKillFocus")
self:SetMouseCapture(false)
Msg("SystemInactivate")
end
end
function XDesktop:OnSystemMinimize()
Msg("SystemMinimize")
end
function XDesktop:OnSystemSize(pt)
local x, y = pt:xy()
if x == 0 or y == 0 then
return
end
local scale = GetUIScale(pt)
self:SetOutsideScale(point(scale, scale))
self:SetBox(0, 0, x, y)
self:InvalidateMeasure()
self:InvalidateLayout()
Msg("SystemSize", pt)
end
function XDesktop:OnMouseInside()
Msg("MouseInside")
end
function XDesktop:OnMouseOutside()
Msg("MouseOutside")
end
function XDesktop:OnFileDrop(filename)
if Platform.developer or Platform.asserts then
if string.ends_with(filename, ".sav", true) then
CreateRealTimeThread(function()
WaitDataLoaded()
local err = LoadGame(filename, { save_as_last = true })
if err then
OpenPreGameMainMenu()
end
end)
end
if string.ends_with(filename, ".fbx", true) then
OpenSceneImportEditor(filename)
end
end
end
-- touch
function XDesktop:TouchEvent(event, id, pos)
local touch = self.touch[id]
if touch then
touch.event = event
touch.pos = pos
else
touch = { id = id, event = event, pos = pos }
self.touch[id] = touch
end
if event == "OnTouchEnded" or event == "OnTouchCancelled" then
self.touch[id] = nil
end
local result
if touch.capture then
result = touch.capture[event](touch.capture, id, pos, touch)
end
if not result then
local target = self:UpdateMouseTarget(pos)
while target do
if target.window_state ~= "destroying" then
touch.target = target
result = target[event](target, id, pos, touch)
if result then
break
end
end
target = target.parent
end
end
if result == "capture" then
touch.capture = touch.target
result = "break"
end
return result
end
-- draw
local UIL = UIL
function XDesktop:Invalidate()
if self.invalidated then return end
self.invalidated = true
UIL.Invalidate()
end
if false then -- debug invalidation
IgnoreInvalidateSources = {
"DeveloperInterface.lua",
"uiConsoleLog.lua",
"XControl.lua.* SetText",
"KeyboardEventDispatch",
"method ChangeHappiness",
}
function XDesktop:Invalidate()
if not self.invalidated then
local stack = GetStack()
local show = true
for _, text in ipairs(IgnoreInvalidateSources) do
if stack:find(text) then
show = false
break
end
end
if show then
self.invalidated = true
print(stack)
end
end
UIL.Invalidate()
end
function XTranslateText:OnTextChanged(text)
if not GetParentOfKind(self, "DeveloperInterface") then
print(string.concat(" ", "TEXT CHANGE", self.text, "-->", text))
end
end
end
function XDesktop:InvalidateMeasure(child)
if self.measure_update then return end
XActionsHost.InvalidateMeasure(self, child)
if self.invalidated then return end
self:RequestLayout()
end
function XDesktop:InvalidateLayout()
if self.layout_update then return end
XActionsHost.InvalidateLayout(self)
if self.invalidated then return end
self:RequestLayout()
end
function XDesktop:MeasureAndLayout()
local w, h = self.box:sizexyz()
self:UpdateMeasure(w, h)
self:UpdateLayout()
self:UpdateMouseTarget()
if self.measure_update or self.layout_update then
self:UpdateMeasure(w, h)
self:UpdateLayout()
self:UpdateMouseTarget()
end
end
function XDesktop:RequestLayout()
if IsValidThread(self.layout_thread) then
Wakeup(self.layout_thread)
else
self.layout_thread = CreateRealTimeThread(function(self)
while true do
if next(TextStyles) and not self.invalidated then -- if there is a redraw pending let it do the MeasureAndLayout
PauseInfiniteLoopDetection("XDesktop.MeasureAndLayout")
procall(self.MeasureAndLayout, self)
ResumeInfiniteLoopDetection("XDesktop.MeasureAndLayout")
end
WaitWakeup()
end
end, self)
if Platform.developer then
ThreadsSetThreadSource(self.layout_thread, "LayoutThread")
end
end
end
function XDesktop:RequestUpdateMouseTarget()
if IsValidThread(self.mouse_target_thread) then
Wakeup(self.mouse_target_thread)
else
self.mouse_target_thread = CreateRealTimeThread(function(self)
while true do
procall(self.UpdateMouseTarget, self)
WaitWakeup()
end
end, self)
end
end
function XRender()
if not next(TextStyles) then return end
PauseInfiniteLoopDetection("XRender")
local desktop = terminal.desktop
desktop:MeasureAndLayout()
desktop:DrawWindow(desktop.box)
ResumeInfiniteLoopDetection("XRender")
end
-- start
function OnMsg.Start()
terminal.desktop = XDesktop:new()
terminal.AddTarget(terminal.desktop)
UIL.Register("XRender", terminal.desktop.terminal_target_priority)
terminal.desktop:OnSystemSize(UIL.GetScreenSize())
terminal.desktop:Open()
Msg("DesktopCreated")
end
-- margin update
function OnMsg.EngineOptionsSaved()
local desktop = terminal.desktop
if desktop then
desktop:InvalidateMeasure()
desktop:InvalidateLayout()
end
end
-- debug
if Platform.developer then
function FormatWindowPath(win)
if not win then return "" end
local path = {}
repeat
table.insert(path, 1, _InternalTranslate(T(357840043382, "<class> <Id>"), win, false))
win = win.parent
until not win
return table.concat(path, " / ")
end
end