98 Commits

Author SHA1 Message Date
d5f64dcbaa some mac changes?
Some checks failed
Build Android / Game (Android, Release ARM64) (push) Has been cancelled
Build iOS / Game (iOS, Release ARM64) (push) Has been cancelled
Build Linux / Editor (Linux, Development x64) (push) Has been cancelled
Build Linux / Game (Linux, Release x64) (push) Has been cancelled
Build macOS / Editor (Mac, Development ARM64) (push) Has been cancelled
Build macOS / Game (Mac, Release ARM64) (push) Has been cancelled
Build Windows / Editor (Windows, Development x64) (push) Has been cancelled
Build Windows / Game (Windows, Release x64) (push) Has been cancelled
Cooker / Cook (Mac) (push) Has been cancelled
Tests / Tests (Linux) (push) Has been cancelled
Tests / Tests (Windows) (push) Has been cancelled
2025-10-12 18:16:53 +03:00
ef83dd5377 Fix logging in gamepad related events 2025-04-26 00:33:07 +03:00
bc241afc09 Fix unlock mouse hotkey not unlocking CursorLockMode.Locked cursor 2025-04-26 00:31:39 +03:00
1357d882cd Fix EditorViewport buttons activating items on scene 2025-04-26 00:31:39 +03:00
fd0617f3ae Cleanup 2025-04-26 00:31:38 +03:00
61cadd0fdf Implement Wayland text clipboard data handling 2025-04-26 00:31:38 +03:00
9552103c58 Add support for VK_EXT_metal_surface extension 2025-04-26 00:31:38 +03:00
51132b1bd4 Update volk to 1.4.304 2025-04-26 00:31:37 +03:00
02f446ccfd Fix mouse relative mode activation triggering mouse move events on Mac 2025-04-26 00:31:37 +03:00
26012d0b74 Initial support for building and running SDL platform on macOS 2025-04-26 00:31:37 +03:00
ef8f6b8d47 Fix VC++ project file building on macOS Rider 2025-04-26 00:31:36 +03:00
43968c1a5e Reduce lock usage during window events 2025-04-26 00:31:36 +03:00
3b8c7f0d8d Fix handling of WindowsManager locks 2025-04-26 00:31:36 +03:00
a2414f596a Handle Wayland dragging actions ending prematurely 2025-04-26 00:31:35 +03:00
e6f90898cb Fix editor viewport activating while dragging a window 2025-04-26 00:31:35 +03:00
018282bc33 Fix occluded or hidden windows causing the engine to freeze on Wayland 2025-04-26 00:31:35 +03:00
7dfd7de0b7 Allow context menu to show when activating scene tree with right click 2025-04-26 00:31:34 +03:00
04e6943b2d Fix crash when relative mode was left active on removed window 2025-04-26 00:31:34 +03:00
6a26441567 Ensure mouse position is up-to-date during button down events 2025-04-26 00:31:34 +03:00
0f1d4c9f59 Fix rubber band selector activating outside of the viewport
This usually happens while trying to drag the window from window handle
2025-04-26 00:31:33 +03:00
16f06b5848 Use sensible window minimum size limits in editor windows 2025-04-26 00:31:33 +03:00
2a2652302f Fix build on Linux 2025-04-26 00:31:33 +03:00
da3b60b606 Fix merge 2025-04-26 00:31:33 +03:00
ebca32e6d1 Apply drag and drop styling on refactored WindowDragHelper
Reapplies changes from commit 38b00f458c
2025-04-26 00:31:32 +03:00
74c8c6c9c7 Fix cursor escaping constrained area when initially outside on Windows 2025-04-26 00:31:32 +03:00
4f8a73b830 Refix dragged maximized window generating wrong mouse position events 2025-04-26 00:31:32 +03:00
ff4122a96c Focus viewports earlier to fix CursorLockMode not always activating 2025-04-26 00:31:31 +03:00
33326d1c57 Fix documentation generation 2025-04-26 00:31:31 +03:00
6efb46fbbc Fix cursor locking bounds calculation 2025-04-26 00:31:31 +03:00
e681dcfc2d Fix crash when Game viewport was hidden 2025-04-26 00:31:30 +03:00
8ba4725226 Fix X11 external drag and drop not releasing consistently 2025-04-26 00:31:30 +03:00
c1f76abc22 Fix restoring locked cursor state when window gains focus again 2025-04-26 00:31:30 +03:00
d355f9eff5 Fix CursorLockMode.Locked not working 2025-04-26 00:31:29 +03:00
a8f93beeb8 Fix dragged maximized window generating wrong mouse position events 2025-04-26 00:31:29 +03:00
d6cb587d1d Fix window handle clicking to not restore window from maximized state 2025-04-26 00:31:29 +03:00
1702ab0e31 Fix rubber band selector activating when using Alt-key to orbit 2025-04-26 00:31:28 +03:00
43a83a9ec9 Fix error when entering playmode while holding right mouse button 2025-04-26 00:31:28 +03:00
f8f846c2bb Fix accepting drag and drop for files in Wayland 2025-04-26 00:31:28 +03:00
de1a93f3ff Fix error while dragging files around Content window 2025-04-26 00:31:27 +03:00
765319ee25 Release rubber band selection when viewport starts controlling the view 2025-04-26 00:31:27 +03:00
3730176783 Update SDL to 3.2.10 2025-04-26 00:31:27 +03:00
f96a5623e4 Avoid showing tooltips in inactive windows 2025-04-26 00:31:27 +03:00
f57759b250 Update SDL3 to 3.2.4 2025-04-26 00:31:26 +03:00
50531cd6f0 Fix window dragging when not supported by Wayland compositor
(cherry picked from commit 3554747a67)
2025-04-26 00:31:26 +03:00
bdaa98c8ca Show current display server in Editor window tooltip
(cherry picked from commit 62968dd437)
2025-04-26 00:31:26 +03:00
ed89597f5c Properly mark floating windows with transparency support
(cherry picked from commit c660fac524)
2025-04-26 00:31:25 +03:00
438f3f034a Enable transparency support in Vulkan swapchains
(cherry picked from commit 431a69e357)
2025-04-26 00:31:25 +03:00
6409d6f48e Fix compilation for game builds
(cherry picked from commit f4fcc07288)
2025-04-26 00:31:25 +03:00
67769d3950 Fix cloning SDL repository 2025-04-26 00:31:24 +03:00
e236c9a1a4 Fix text input not working on X11 2025-04-26 00:31:24 +03:00
3cbc0522b4 Fix button latching on Windows after drag and drop operation 2025-04-26 00:31:24 +03:00
3646c5a06a Implement new window dragging system 2025-04-26 00:31:23 +03:00
14a90d32c2 Fix mouse resetting issues after ending relative mode 2025-04-26 00:31:23 +03:00
709a7b0495 Fix frame stutter when window is focused 2025-04-26 00:31:23 +03:00
89c39b1fb7 Fix error when docking to sides of tabbed panel 2025-04-26 00:31:22 +03:00
364927c363 Cleanup Linux SDL implementation 2025-04-26 00:31:22 +03:00
294b36a275 Support compiling third party library C files as C code 2025-04-26 00:31:22 +03:00
fdba9aa096 Implement Wayland protocols module and file generation 2025-04-26 00:31:21 +03:00
ed83eab112 Fix mouse warping after ending relative mode 2025-04-26 00:31:21 +03:00
fd163d2146 Add git fetch method for dependencies 2025-04-26 00:31:21 +03:00
e9dfd1a54d Fix window ShowInTaskbar setting 2025-04-26 00:31:20 +03:00
e3b242f2ba Fix various issues with child window positioning 2025-04-26 00:31:20 +03:00
ebe09403f5 Add Window.IsAlwaysOnTop property 2025-04-26 00:31:20 +03:00
b56edeb956 Use SDL locale 2025-04-26 00:31:19 +03:00
006d261d6c Allow window with single tab to be dragged from tab area 2025-04-26 00:31:19 +03:00
db8de18d58 Fix ValueBox mouse position resetting after releasing the button 2025-04-26 00:31:19 +03:00
1c3b81093c Fix SDL build process on Linux 2025-04-26 00:31:18 +03:00
e5290c8dee Update SDL to 3.2.0 2025-04-26 00:31:18 +03:00
2b169b6aa7 Force cursor to center of Game Window when tab handle is clicked 2025-04-26 00:31:18 +03:00
Chandler Cox
185e6f663c Fix rotation using SDL 2025-04-26 00:31:17 +03:00
0de4528168 Fix Linux compilation without SDL 2025-04-26 00:31:17 +03:00
8d9bb545cf Fix compilation 2025-04-26 00:31:17 +03:00
988a547f72 Update SDL3 2025-04-26 00:31:16 +03:00
4a181f6839 Fix compilation issues 2025-04-26 00:31:16 +03:00
4428083c2f Fix windows not being hidden initially 2025-04-26 00:31:16 +03:00
7288980caa Fix parent window position handling with popup/tooltip windows 2025-04-26 00:31:15 +03:00
8dbd227122 Fix compilation errors in other platforms 2025-04-26 00:31:15 +03:00
b88fa91d82 Fix CI for Linux 2025-04-26 00:31:15 +03:00
86bc79e5db Prevent building with SDL in unsupported platforms 2025-04-26 00:31:14 +03:00
ae77a6e579 Fallback to X11 message box implementation when SDL fails 2025-04-26 00:31:14 +03:00
9bfa652567 Fix popup and context menus not working on Wayland 2025-04-26 00:31:14 +03:00
97557688a6 Hide warnings for unsupported SDL operations on Wayland 2025-04-26 00:31:14 +03:00
c959f3dee6 Log a warning for not implemented Wayland functionality 2025-04-26 00:31:13 +03:00
795afabfb4 Fix compilation in Linux 2025-04-26 00:31:13 +03:00
dd88abe46d Enable warning sound in question dialogs 2025-04-26 00:31:13 +03:00
de0cb8d08e Enable modern Windows dialog boxes 2025-04-26 00:31:12 +03:00
87f2e168f9 Implement relative mouse mode (raw input) for SDL platform 2025-04-26 00:31:12 +03:00
3babc62c5a Add flag for Window types 2025-04-26 00:31:12 +03:00
7c5917b725 Enable native windowing system settings with SDL platform 2025-04-26 00:31:11 +03:00
8fcc3ef607 Add command-line switches to force X11 and Wayland SDL drivers 2025-04-26 00:31:11 +03:00
6310d41ca1 Implement SDL platform, windowing and input handling 2025-04-26 00:31:11 +03:00
6cde5e9376 Refactor application window class name 2025-04-26 00:31:10 +03:00
bb47c638f1 Move Window related enums to separate header file 2025-04-26 00:31:10 +03:00
1de615c517 Refactor Windows drag and drop implementation 2025-04-26 00:31:10 +03:00
b88fc89951 Refactor ScreenUtilities 2025-04-26 00:31:09 +03:00
2e32bb8c4b Add more helper methods for managing Git repos 2025-04-26 00:31:09 +03:00
45ae959aa3 Fix centered window location on X11 2025-04-26 00:31:09 +03:00
8a65384f2a Fix initial position of Tooltips 2025-04-26 00:31:08 +03:00
218 changed files with 97064 additions and 3458 deletions

View File

@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev libwayland-dev
- name: Setup Vulkan - name: Setup Vulkan
uses: ./.github/actions/vulkan uses: ./.github/actions/vulkan
- name: Setup .NET - name: Setup .NET
@@ -44,7 +44,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev libwayland-dev
- name: Setup Vulkan - name: Setup Vulkan
uses: ./.github/actions/vulkan uses: ./.github/actions/vulkan
- name: Setup .NET - name: Setup .NET

View File

@@ -28,7 +28,7 @@ jobs:
git lfs pull git lfs pull
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev libwayland-dev
- name: Build - name: Build
run: | run: |
./GenerateProjectFiles.sh -vs2022 -log -verbose -printSDKs -dotnet=8 ./GenerateProjectFiles.sh -vs2022 -log -verbose -printSDKs -dotnet=8

View File

@@ -13,6 +13,7 @@
"Configuration": { "Configuration": {
"UseCSharp": true, "UseCSharp": true,
"UseLargeWorlds": false, "UseLargeWorlds": false,
"UseDotNet": true "UseDotNet": true,
"UseSDL": true
} }
} }

View File

@@ -12,6 +12,6 @@ cd "`dirname "$0"`"
bash ./Development/Scripts/Mac/CallBuildTool.sh --genproject "$@" bash ./Development/Scripts/Mac/CallBuildTool.sh --genproject "$@"
# Build bindings for all editor configurations # Build bindings for all editor configurations
echo Building C# bindings... #echo Building C# bindings...
# TODO: Detect the correct architecture here # TODO: Detect the correct architecture here
Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor #Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor

View File

@@ -282,7 +282,7 @@ namespace FlaxEditor.Content
if (data is DragDataFiles) if (data is DragDataFiles)
return DragDropEffect.Copy; return DragDropEffect.Copy;
return _dragOverItems.Effect; return _dragOverItems?.Effect ?? DragDropEffect.None;
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -20,7 +20,7 @@ namespace FlaxEditor.Content
} }
/// <inheritdoc /> /// <inheritdoc />
public override string TypeDescription => Path.EndsWith(".h") ? "C++ Header File" : "C++ Source Code"; public override string TypeDescription => Path.EndsWith(".h") || Path.EndsWith(".hpp") ? "C++ Header File" : "C++ Source Code";
/// <inheritdoc /> /// <inheritdoc />
public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.CPPScript128; public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.CPPScript128;

View File

@@ -186,12 +186,12 @@ namespace FlaxEditor.CustomEditors.Dedicated
foreach (var file in files) foreach (var file in files)
FindNewKeysCSharp(file, newKeys, allKeys); FindNewKeysCSharp(file, newKeys, allKeys);
// C++ // C/C++
files = Directory.GetFiles(Globals.ProjectSourceFolder, "*.cpp", SearchOption.AllDirectories); files = Directory.GetFiles(Globals.ProjectSourceFolder, "*.cpp", SearchOption.AllDirectories).Concat(Directory.GetFiles(Globals.ProjectSourceFolder, "*.c", SearchOption.AllDirectories)).ToArray();
filesCount += files.Length; filesCount += files.Length;
foreach (var file in files) foreach (var file in files)
FindNewKeysCpp(file, newKeys, allKeys); FindNewKeysCpp(file, newKeys, allKeys);
files = Directory.GetFiles(Globals.ProjectSourceFolder, "*.h", SearchOption.AllDirectories); files = Directory.GetFiles(Globals.ProjectSourceFolder, "*.h", SearchOption.AllDirectories).Concat(Directory.GetFiles(Globals.ProjectSourceFolder, "*.hpp", SearchOption.AllDirectories)).ToArray();;
filesCount += files.Length; filesCount += files.Length;
foreach (var file in files) foreach (var file in files)
FindNewKeysCpp(file, newKeys, allKeys); FindNewKeysCpp(file, newKeys, allKeys);

View File

@@ -1,7 +1,10 @@
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS || PLATFORM_SDL
#define USE_IS_FOREGROUND #define USE_IS_FOREGROUND
#else #else
#endif #endif
#if PLATFORM_SDL
#define USE_SDL_WORKAROUNDS
#endif
// Copyright (c) Wojciech Figat. All rights reserved. // Copyright (c) Wojciech Figat. All rights reserved.
using System.Collections.Generic; using System.Collections.Generic;
@@ -121,7 +124,7 @@ namespace FlaxEditor.GUI.ContextMenu
} }
/// <summary> /// <summary>
/// Shows the empty menu popup o na screen. /// Shows the empty menu popup on a screen.
/// </summary> /// </summary>
/// <param name="control">The target control.</param> /// <param name="control">The target control.</param>
/// <param name="area">The target control area to cover.</param> /// <param name="area">The target control area to cover.</param>
@@ -256,7 +259,9 @@ namespace FlaxEditor.GUI.ContextMenu
desc.AllowMaximize = false; desc.AllowMaximize = false;
desc.AllowDragAndDrop = false; desc.AllowDragAndDrop = false;
desc.IsTopmost = true; desc.IsTopmost = true;
desc.IsRegularWindow = false; desc.Type = WindowType.Popup;
desc.Parent = parentWin.Window;
desc.Title = "ContextMenu";
desc.HasSizingFrame = false; desc.HasSizingFrame = false;
OnWindowCreating(ref desc); OnWindowCreating(ref desc);
_window = Platform.CreateWindow(ref desc); _window = Platform.CreateWindow(ref desc);
@@ -266,6 +271,12 @@ namespace FlaxEditor.GUI.ContextMenu
_window.LostFocus += OnWindowLostFocus; _window.LostFocus += OnWindowLostFocus;
} }
#if USE_IS_FOREGROUND && USE_SDL_WORKAROUNDS
// The focus between popup and parent windows doesn't change, force hide the popup when clicked on parent
parentWin.Window.MouseDown += OnWindowMouseDown;
_window.Closed += () => parentWin.Window.MouseDown -= OnWindowMouseDown;
#endif
// Attach to the window // Attach to the window
_parentCM = parent as ContextMenuBase; _parentCM = parent as ContextMenuBase;
Parent = _window.GUI; Parent = _window.GUI;
@@ -426,6 +437,17 @@ namespace FlaxEditor.GUI.ContextMenu
} }
} }
#if USE_SDL_WORKAROUNDS
private void OnWindowGotFocus()
{
}
private void OnWindowMouseDown(ref Float2 mousePosition, MouseButton button, ref bool handled)
{
// The user clicked outside the popup window
Hide();
}
#else
private void OnWindowGotFocus() private void OnWindowGotFocus()
{ {
var child = _childCM; var child = _childCM;
@@ -439,6 +461,7 @@ namespace FlaxEditor.GUI.ContextMenu
}); });
} }
} }
#endif
private void OnWindowLostFocus() private void OnWindowLostFocus()
{ {
@@ -537,7 +560,12 @@ namespace FlaxEditor.GUI.ContextMenu
// Let root context menu to check if none of the popup windows // Let root context menu to check if none of the popup windows
if (_parentCM == null && UseVisibilityControl && !IsForeground) if (_parentCM == null && UseVisibilityControl && !IsForeground)
{ {
#if USE_SDL_WORKAROUNDS
if (!IsMouseOver)
Hide(); Hide();
#else
Hide();
#endif
} }
} }
#endif #endif

View File

@@ -1,545 +0,0 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.GUI.Docking
{
/// <summary>
/// Helper class used to handle docking windows dragging and docking.
/// </summary>
public class DockHintWindow
{
private FloatWindowDockPanel _toMove;
private Float2 _dragOffset;
private Float2 _defaultWindowSize;
private Rectangle _rectDock;
private Rectangle _rectWindow;
private Float2 _mouse;
private DockState _toSet;
private DockPanel _toDock;
private bool _lateDragOffsetUpdate;
private Rectangle _rLeft, _rRight, _rBottom, _rUpper, _rCenter;
private DockHintWindow(FloatWindowDockPanel toMove)
{
_toMove = toMove;
_toSet = DockState.Float;
var window = toMove.Window.Window;
// Remove focus from drag target
_toMove.Focus();
_toMove.Defocus();
// Focus window
window.Focus();
// Check if window is maximized and restore window.
if (window.IsMaximized)
{
// Restore window and set position to mouse.
var mousePos = window.MousePosition;
var previousSize = window.Size;
window.Restore();
window.Position = Platform.MousePosition - mousePos * window.Size / previousSize;
}
// Calculate dragging offset and move window to the destination position
var mouseScreenPosition = Platform.MousePosition;
// If the _toMove window was not focused when initializing this window, the result vector only contains zeros
// and to prevent a failure, we need to perform an update for the drag offset at later time which will be done in the OnMouseMove event handler.
if (mouseScreenPosition != Float2.Zero)
CalculateDragOffset(mouseScreenPosition);
else
_lateDragOffsetUpdate = true;
// Get initial size
_defaultWindowSize = window.Size;
// Init proxy window
Proxy.Init(ref _defaultWindowSize);
// Bind events
Proxy.Window.MouseUp += OnMouseUp;
Proxy.Window.MouseMove += OnMouseMove;
Proxy.Window.LostFocus += OnLostFocus;
// Start tracking mouse
Proxy.Window.StartTrackingMouse(false);
// Update window GUI
Proxy.Window.GUI.PerformLayout();
// Update rectangles
UpdateRects();
// Hide base window
window.Hide();
// Enable hit window presentation
Proxy.Window.RenderingEnabled = true;
Proxy.Window.Show();
Proxy.Window.Focus();
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
public void Dispose()
{
// End tracking mouse
Proxy.Window.EndTrackingMouse();
// Disable rendering
Proxy.Window.RenderingEnabled = false;
// Unbind events
Proxy.Window.MouseUp -= OnMouseUp;
Proxy.Window.MouseMove -= OnMouseMove;
Proxy.Window.LostFocus -= OnLostFocus;
// Hide the proxy
Proxy.Hide();
if (_toMove == null)
return;
// Check if window won't be docked
if (_toSet == DockState.Float)
{
var window = _toMove.Window?.Window;
if (window == null)
return;
var mouse = Platform.MousePosition;
// Move base window
window.Position = mouse - _dragOffset;
// Show base window
window.Show();
}
else
{
bool hasNoChildPanels = _toMove.ChildPanelsCount == 0;
// Check if window has only single tab
if (hasNoChildPanels && _toMove.TabsCount == 1)
{
// Dock window
_toMove.GetTab(0).Show(_toSet, _toDock);
}
// Check if dock as tab and has no child panels
else if (hasNoChildPanels && _toSet == DockState.DockFill)
{
// Dock all tabs
while (_toMove.TabsCount > 0)
{
_toMove.GetTab(0).Show(DockState.DockFill, _toDock);
}
}
else
{
var selectedTab = _toMove.SelectedTab;
// Dock the first tab into the target location
var firstTab = _toMove.GetTab(0);
firstTab.Show(_toSet, _toDock);
// Dock rest of the tabs
while (_toMove.TabsCount > 0)
{
_toMove.GetTab(0).Show(DockState.DockFill, firstTab);
}
// Keep selected tab being selected
selectedTab?.SelectTab();
}
// Focus target window
_toDock.Root.Focus();
}
_toMove = null;
}
/// <summary>
/// Creates the new dragging hit window.
/// </summary>
/// <param name="toMove">Floating dock panel to move.</param>
/// <returns>The dock hint window object.</returns>
public static DockHintWindow Create(FloatWindowDockPanel toMove)
{
if (toMove == null)
throw new ArgumentNullException();
return new DockHintWindow(toMove);
}
/// <summary>
/// Creates the new dragging hit window.
/// </summary>
/// <param name="toMove">Dock window to move.</param>
/// <returns>The dock hint window object.</returns>
public static DockHintWindow Create(DockWindow toMove)
{
if (toMove == null)
throw new ArgumentNullException();
// Show floating
toMove.ShowFloating();
// Move window to the mouse position (with some offset for caption bar)
var window = (WindowRootControl)toMove.Root;
var mouse = Platform.MousePosition;
window.Window.Position = mouse - new Float2(8, 8);
// Get floating panel
var floatingPanelToMove = window.GetChild(0) as FloatWindowDockPanel;
return new DockHintWindow(floatingPanelToMove);
}
/// <summary>
/// Calculates window rectangle in the dock window.
/// </summary>
/// <param name="state">Window dock state.</param>
/// <param name="rect">Dock panel rectangle.</param>
/// <returns>Calculated window rectangle.</returns>
public static Rectangle CalculateDockRect(DockState state, ref Rectangle rect)
{
Rectangle result = rect;
switch (state)
{
case DockState.DockFill:
result.Location.Y += DockPanel.DefaultHeaderHeight;
result.Size.Y -= DockPanel.DefaultHeaderHeight;
break;
case DockState.DockTop:
result.Size.Y *= DockPanel.DefaultSplitterValue;
break;
case DockState.DockLeft:
result.Size.X *= DockPanel.DefaultSplitterValue;
break;
case DockState.DockBottom:
result.Location.Y += result.Size.Y * (1 - DockPanel.DefaultSplitterValue);
result.Size.Y *= DockPanel.DefaultSplitterValue;
break;
case DockState.DockRight:
result.Location.X += result.Size.X * (1 - DockPanel.DefaultSplitterValue);
result.Size.X *= DockPanel.DefaultSplitterValue;
break;
}
return result;
}
private void CalculateDragOffset(Float2 mouseScreenPosition)
{
var baseWinPos = _toMove.Window.Window.Position;
_dragOffset = mouseScreenPosition - baseWinPos;
}
private void UpdateRects()
{
// Cache mouse position
_mouse = Platform.MousePosition;
// Check intersection with any dock panel
var uiMouse = _mouse;
_toDock = _toMove.MasterPanel.HitTest(ref uiMouse, _toMove);
// Check dock state to use
bool showProxyHints = _toDock != null;
bool showBorderHints = showProxyHints;
bool showCenterHint = showProxyHints;
if (showProxyHints)
{
// If moved window has not only tabs but also child panels disable docking as tab
if (_toMove.ChildPanelsCount > 0)
showCenterHint = false;
// Disable docking windows with one or more dock panels inside
if (_toMove.ChildPanelsCount > 0)
showBorderHints = false;
// Get dock area
_rectDock = _toDock.DockAreaBounds;
// Cache dock rectangles
var size = _rectDock.Size;
var offset = _rectDock.Location;
var borderMargin = 4.0f;
var hintWindowsSize = Proxy.HintWindowsSize * Platform.DpiScale;
var hintWindowsSize2 = hintWindowsSize * 0.5f;
var centerX = size.X * 0.5f;
var centerY = size.Y * 0.5f;
_rUpper = new Rectangle(centerX - hintWindowsSize2, borderMargin, hintWindowsSize, hintWindowsSize) + offset;
_rBottom = new Rectangle(centerX - hintWindowsSize2, size.Y - hintWindowsSize - borderMargin, hintWindowsSize, hintWindowsSize) + offset;
_rLeft = new Rectangle(borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
_rRight = new Rectangle(size.X - hintWindowsSize - borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
_rCenter = new Rectangle(centerX - hintWindowsSize2, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
// Hit test
DockState toSet = DockState.Float;
if (showBorderHints)
{
if (_rUpper.Contains(_mouse))
toSet = DockState.DockTop;
else if (_rBottom.Contains(_mouse))
toSet = DockState.DockBottom;
else if (_rLeft.Contains(_mouse))
toSet = DockState.DockLeft;
else if (_rRight.Contains(_mouse))
toSet = DockState.DockRight;
}
if (showCenterHint && _rCenter.Contains(_mouse))
toSet = DockState.DockFill;
_toSet = toSet;
// Show proxy hint windows
Proxy.Down.Position = _rBottom.Location;
Proxy.Left.Position = _rLeft.Location;
Proxy.Right.Position = _rRight.Location;
Proxy.Up.Position = _rUpper.Location;
Proxy.Center.Position = _rCenter.Location;
}
else
{
_toSet = DockState.Float;
}
// Update proxy hint windows visibility
Proxy.Down.IsVisible = showProxyHints & showBorderHints;
Proxy.Left.IsVisible = showProxyHints & showBorderHints;
Proxy.Right.IsVisible = showProxyHints & showBorderHints;
Proxy.Up.IsVisible = showProxyHints & showBorderHints;
Proxy.Center.IsVisible = showProxyHints & showCenterHint;
// Calculate proxy/dock/window rectangles
if (_toDock == null)
{
// Floating window over nothing
_rectWindow = new Rectangle(_mouse - _dragOffset, _defaultWindowSize);
}
else
{
if (_toSet == DockState.Float)
{
// Floating window over dock panel
_rectWindow = new Rectangle(_mouse - _dragOffset, _defaultWindowSize);
}
else
{
// Use only part of the dock panel to show hint
_rectWindow = CalculateDockRect(_toSet, ref _rectDock);
}
}
// Update proxy window
Proxy.Window.ClientBounds = _rectWindow;
}
private void OnMouseUp(ref Float2 location, MouseButton button, ref bool handled)
{
if (button == MouseButton.Left)
{
Dispose();
}
}
private void OnMouseMove(ref Float2 mousePos)
{
// Recalculate the drag offset because the current mouse screen position was invalid when we initialized the window
if (_lateDragOffsetUpdate)
{
// Calculate dragging offset and move window to the destination position
CalculateDragOffset(mousePos);
// Reset state
_lateDragOffsetUpdate = false;
}
UpdateRects();
}
private void OnLostFocus()
{
Dispose();
}
/// <summary>
/// Contains helper proxy windows shared across docking panels. They are used to visualize docking window locations.
/// </summary>
public static class Proxy
{
/// <summary>
/// The drag proxy window.
/// </summary>
public static Window Window;
/// <summary>
/// The left hint proxy window.
/// </summary>
public static Window Left;
/// <summary>
/// The right hint proxy window.
/// </summary>
public static Window Right;
/// <summary>
/// The up hint proxy window.
/// </summary>
public static Window Up;
/// <summary>
/// The down hint proxy window.
/// </summary>
public static Window Down;
/// <summary>
/// The center hint proxy window.
/// </summary>
public static Window Center;
/// <summary>
/// The hint windows size.
/// </summary>
public const float HintWindowsSize = 32.0f;
/// <summary>
/// Initializes the hit proxy windows. Those windows are used to indicate drag target areas (left, right, top, bottom, etc.).
/// </summary>
public static void InitHitProxy()
{
CreateProxy(ref Left, "DockHint.Left");
CreateProxy(ref Right, "DockHint.Right");
CreateProxy(ref Up, "DockHint.Up");
CreateProxy(ref Down, "DockHint.Down");
CreateProxy(ref Center, "DockHint.Center");
}
/// <summary>
/// Initializes the hint window.
/// </summary>
/// <param name="initSize">Initial size of the proxy window.</param>
public static void Init(ref Float2 initSize)
{
if (Window == null)
{
var settings = CreateWindowSettings.Default;
settings.Title = "DockHint.Window";
settings.Size = initSize;
settings.AllowInput = true;
settings.AllowMaximize = false;
settings.AllowMinimize = false;
settings.HasBorder = false;
settings.HasSizingFrame = false;
settings.IsRegularWindow = false;
settings.SupportsTransparency = true;
settings.ShowInTaskbar = false;
settings.ShowAfterFirstPaint = false;
settings.IsTopmost = true;
Window = Platform.CreateWindow(ref settings);
Window.Opacity = 0.6f;
Window.GUI.BackgroundColor = Style.Current.Selection;
Window.GUI.AddChild<DragVisuals>();
}
else
{
// Resize proxy
Window.ClientSize = initSize;
}
InitHitProxy();
}
private sealed class DragVisuals : Control
{
public DragVisuals()
{
AnchorPreset = AnchorPresets.StretchAll;
Offsets = Margin.Zero;
}
public override void Draw()
{
Render2D.DrawRectangle(new Rectangle(Float2.Zero, Size), Style.Current.SelectionBorder);
}
}
private static void CreateProxy(ref Window win, string name)
{
if (win != null)
return;
var settings = CreateWindowSettings.Default;
settings.Title = name;
settings.Size = new Float2(HintWindowsSize * Platform.DpiScale);
settings.AllowInput = false;
settings.AllowMaximize = false;
settings.AllowMinimize = false;
settings.HasBorder = false;
settings.HasSizingFrame = false;
settings.IsRegularWindow = false;
settings.SupportsTransparency = true;
settings.ShowInTaskbar = false;
settings.ActivateWhenFirstShown = false;
settings.IsTopmost = true;
settings.ShowAfterFirstPaint = false;
win = Platform.CreateWindow(ref settings);
win.Opacity = 0.6f;
win.GUI.BackgroundColor = Style.Current.Selection;
win.GUI.AddChild<DragVisuals>();
}
/// <summary>
/// Hides proxy windows.
/// </summary>
public static void Hide()
{
HideProxy(ref Window);
HideProxy(ref Left);
HideProxy(ref Right);
HideProxy(ref Up);
HideProxy(ref Down);
HideProxy(ref Center);
}
private static void HideProxy(ref Window win)
{
if (win)
{
win.Hide();
}
}
/// <summary>
/// Releases proxy data and windows.
/// </summary>
public static void Dispose()
{
DisposeProxy(ref Window);
DisposeProxy(ref Left);
DisposeProxy(ref Right);
DisposeProxy(ref Up);
DisposeProxy(ref Down);
DisposeProxy(ref Center);
}
private static void DisposeProxy(ref Window win)
{
if (win)
{
win.Close(ClosingReason.User);
win = null;
}
}
}
}
}

View File

@@ -50,6 +50,11 @@ namespace FlaxEditor.GUI.Docking
/// </summary> /// </summary>
public Float2 MousePosition = Float2.Minimum; public Float2 MousePosition = Float2.Minimum;
/// <summary>
/// The mouse position.
/// </summary>
public Float2 MouseStartPosition = Float2.Minimum;
/// <summary> /// <summary>
/// The start drag asynchronous window. /// The start drag asynchronous window.
/// </summary> /// </summary>
@@ -165,7 +170,7 @@ namespace FlaxEditor.GUI.Docking
if (_panel.ChildPanelsCount == 0 && _panel.TabsCount == 1 && _panel.IsFloating) if (_panel.ChildPanelsCount == 0 && _panel.TabsCount == 1 && _panel.IsFloating)
{ {
// Create docking hint window but in an async manner // Create docking hint window but in an async manner
DockHintWindow.Create(_panel as FloatWindowDockPanel); WindowDragHelper.StartDragging(_panel as FloatWindowDockPanel);
} }
else else
{ {
@@ -176,7 +181,7 @@ namespace FlaxEditor.GUI.Docking
_panel.SelectTab(index - 1); _panel.SelectTab(index - 1);
// Create docking hint window // Create docking hint window
DockHintWindow.Create(win); WindowDragHelper.StartDragging(win, _panel.RootWindow.Window);
} }
} }
} }
@@ -355,6 +360,7 @@ namespace FlaxEditor.GUI.Docking
if (IsSingleFloatingWindow) if (IsSingleFloatingWindow)
return base.OnMouseDown(location, button); return base.OnMouseDown(location, button);
MouseDownWindow = GetTabAtPos(location, out IsMouseDownOverCross); MouseDownWindow = GetTabAtPos(location, out IsMouseDownOverCross);
MouseStartPosition = location;
// Check buttons // Check buttons
if (button == MouseButton.Left) if (button == MouseButton.Left)
@@ -441,6 +447,20 @@ namespace FlaxEditor.GUI.Docking
StartDrag(MouseDownWindow); StartDrag(MouseDownWindow);
MouseDownWindow = null; MouseDownWindow = null;
} }
// Check if single tab is tried to be moved
else if (MouseDownWindow != null && _panel.TabsCount <= 1)
{
if ((MousePosition - MouseStartPosition).Length > 3)
{
// Clear flag
IsMouseLeftButtonDown = false;
// Check tab under the mouse
if (!IsMouseDownOverCross && MouseDownWindow != null)
StartDrag(MouseDownWindow);
MouseDownWindow = null;
}
}
// Check if has more than one tab to change order // Check if has more than one tab to change order
else if (MouseDownWindow != null && _panel.TabsCount > 1) else if (MouseDownWindow != null && _panel.TabsCount > 1)
{ {

View File

@@ -182,6 +182,25 @@ namespace FlaxEditor.GUI.Docking
/// <param name="size">Window size, set <see cref="Float2.Zero"/> to use default.</param> /// <param name="size">Window size, set <see cref="Float2.Zero"/> to use default.</param>
/// <param name="position">Window location.</param> /// <param name="position">Window location.</param>
public void ShowFloating(Float2 location, Float2 size, WindowStartPosition position = WindowStartPosition.CenterParent) public void ShowFloating(Float2 location, Float2 size, WindowStartPosition position = WindowStartPosition.CenterParent)
{
CreateFloating(location, size, position, true);
}
/// <summary>
/// Creates the window in a floating state.
/// </summary>
public void CreateFloating()
{
CreateFloating(Float2.Zero, Float2.Zero);
}
/// <summary>
/// Creates the window in a floating state.
/// </summary>
/// <param name="location">Window location.</param>
/// <param name="size">Window size, set <see cref="Float2.Zero"/> to use default.</param>
/// <param name="position">Window location.</param>
/// <param name="showWindow">Window visibility.</param>
public void CreateFloating(Float2 location, Float2 size, WindowStartPosition position = WindowStartPosition.CenterParent, bool showWindow = false)
{ {
Undock(); Undock();
@@ -199,6 +218,8 @@ namespace FlaxEditor.GUI.Docking
windowGUI.UnlockChildrenRecursive(); windowGUI.UnlockChildrenRecursive();
windowGUI.PerformLayout(); windowGUI.PerformLayout();
if (showWindow)
{
// Show // Show
window.Show(); window.Show();
window.BringToFront(); window.BringToFront();
@@ -208,6 +229,7 @@ namespace FlaxEditor.GUI.Docking
// Perform layout again // Perform layout again
windowGUI.PerformLayout(); windowGUI.PerformLayout();
} }
}
/// <summary> /// <summary>
/// Shows the window. /// Shows the window.

View File

@@ -52,7 +52,7 @@ namespace FlaxEditor.GUI.Docking
return; return;
// Create docking hint window // Create docking hint window
DockHintWindow.Create(this); WindowDragHelper.StartDragging(this);
} }
/// <summary> /// <summary>
@@ -71,18 +71,18 @@ namespace FlaxEditor.GUI.Docking
settings.Title = title; settings.Title = title;
settings.Size = size; settings.Size = size;
settings.Position = location; settings.Position = location;
settings.MinimumSize = new Float2(1); settings.MinimumSize = new Float2(200, 150);
settings.MaximumSize = Float2.Zero; // Unlimited size settings.MaximumSize = Float2.Zero; // Unlimited size
settings.Fullscreen = false; settings.Fullscreen = false;
settings.HasBorder = true; settings.HasBorder = true;
settings.SupportsTransparency = false; settings.SupportsTransparency = true;
settings.ActivateWhenFirstShown = true; settings.ActivateWhenFirstShown = true;
settings.AllowInput = true; settings.AllowInput = true;
settings.AllowMinimize = true; settings.AllowMinimize = true;
settings.AllowMaximize = true; settings.AllowMaximize = true;
settings.AllowDragAndDrop = true; settings.AllowDragAndDrop = true;
settings.IsTopmost = false; settings.IsTopmost = false;
settings.IsRegularWindow = true; settings.Type = WindowType.Regular;
settings.HasSizingFrame = true; settings.HasSizingFrame = true;
settings.ShowAfterFirstPaint = false; settings.ShowAfterFirstPaint = false;
settings.ShowInTaskbar = true; settings.ShowInTaskbar = true;

View File

@@ -81,7 +81,6 @@ namespace FlaxEditor.GUI.Docking
public DockPanel HitTest(ref Float2 position, FloatWindowDockPanel excluded) public DockPanel HitTest(ref Float2 position, FloatWindowDockPanel excluded)
{ {
// Check all floating windows // Check all floating windows
// TODO: gather windows order and take it into account when performing test
for (int i = 0; i < FloatingPanels.Count; i++) for (int i = 0; i < FloatingPanels.Count; i++)
{ {
var win = FloatingPanels[i]; var win = FloatingPanels[i];
@@ -94,9 +93,44 @@ namespace FlaxEditor.GUI.Docking
} }
// Base // Base
//if (!Root?.RootWindow.Window.IsFocused ?? false)
// return null;
return base.HitTest(ref position); return base.HitTest(ref position);
} }
/// <summary>
/// Performs hit test over dock panel.
/// </summary>
/// <param name="position">Window space position to test.</param>
/// <param name="excluded">Floating window to omit during searching (and all docked to that one).</param>
/// <param name="hitResults">Results of the hit test</param>
/// <returns>True if any dock panels were hit, otherwise false.</returns>
public bool HitTest(ref Float2 position, FloatWindowDockPanel excluded, out DockPanel[] hitResults)
{
// Check all floating windows
List<DockPanel> results = new(FloatingPanels.Count);
for (int i = 0; i < FloatingPanels.Count; i++)
{
var win = FloatingPanels[i];
if (win.Visible && win != excluded)
{
var result = win.HitTest(ref position);
if (result != null)
results.Add(result);
}
}
// Base
//if (!Root?.RootWindow.Window.IsFocused ?? false)
// return null;
var baseResult = base.HitTest(ref position);
if (baseResult != null)
results.Add(baseResult);
hitResults = results.ToArray();
return hitResults.Length > 0;
}
internal void LinkWindow(DockWindow window) internal void LinkWindow(DockWindow window)
{ {
// Add to the windows list // Add to the windows list

View File

@@ -0,0 +1,458 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.GUI.Docking
{
/// <summary>
/// Helper class used to handle docking windows dragging and docking.
/// </summary>
public class WindowDragHelper
{
private FloatWindowDockPanel _toMove;
private Float2 _dragOffset;
private Rectangle _rectDock;
private Float2 _mouse;
private DockState _toSet;
private DockPanel _toDock;
private Window _dragSourceWindow;
private Rectangle _rLeft, _rRight, _rBottom, _rUpper, _rCenter;
private Control _dockHintDown, _dockHintUp, _dockHintLeft, _dockHintRight, _dockHintCenter;
/// <summary>
/// The hint control size.
/// </summary>
public const float HintControlSize = 32.0f;
/// <summary>
/// The opacity of the dragged window when hint controls are shown.
/// </summary>
public const float DragWindowOpacity = 0.4f;
/// <summary>
/// Returns true if any windows are being dragged.
/// </summary>
public static bool IsDragActive { get; private set; }
private WindowDragHelper(FloatWindowDockPanel toMove, Window dragSourceWindow)
{
IsDragActive = true;
_toMove = toMove;
_toSet = DockState.Float;
var window = toMove.Window.Window;
// Bind events
FlaxEngine.Scripting.Update += OnUpdate;
window.MouseUp += OnMouseUp;
// Update rectangles
UpdateRects(Platform.MousePosition);
// Ensure the dragged window stays on top of every other window
window.IsAlwaysOnTop = true;
_dragSourceWindow = dragSourceWindow;
if (_dragSourceWindow != null) // Detaching a tab from existing window
{
_dragOffset = new Float2(window.Size.X / 2, 10.0f);
_dragSourceWindow.MouseUp += OnMouseUp; // The mouse up event is sent to the source window on Windows
// TODO: when detaching tab in floating window (not main window), the drag source window is still main window?
var dragSourceWindowWayland = toMove.MasterPanel?.RootWindow.Window ?? Editor.Instance.Windows.MainWindow;
window.DoDragDrop(window.Title, _dragOffset, dragSourceWindowWayland);
}
else
{
_dragOffset = window.MousePosition;
window.DoDragDrop(window.Title, _dragOffset, window);
}
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
public void Dispose()
{
IsDragActive = false;
var window = _toMove?.Window?.Window;
// Unbind events
FlaxEngine.Scripting.Update -= OnUpdate;
if (window != null)
window.MouseUp -= OnMouseUp;
if (_dragSourceWindow != null)
_dragSourceWindow.MouseUp -= OnMouseUp;
RemoveDockHints();
if (_toMove == null)
return;
if (window != null)
{
window.Opacity = 1.0f;
window.IsAlwaysOnTop = false;
window.BringToFront();
}
// Check if window won't be docked
if (_toSet == DockState.Float)
{
if (window == null)
return;
// Show base window
window.Show();
}
else
{
bool hasNoChildPanels = _toMove.ChildPanelsCount == 0;
// Check if window has only single tab
if (hasNoChildPanels && _toMove.TabsCount == 1)
{
// Dock window
_toMove.GetTab(0).Show(_toSet, _toDock);
}
// Check if dock as tab and has no child panels
else if (hasNoChildPanels && _toSet == DockState.DockFill)
{
// Dock all tabs
while (_toMove.TabsCount > 0)
{
_toMove.GetTab(0).Show(DockState.DockFill, _toDock);
}
}
else
{
var selectedTab = _toMove.SelectedTab;
// Dock the first tab into the target location
if (_toMove.TabsCount > 0)
{
var firstTab = _toMove.GetTab(0);
firstTab.Show(_toSet, _toDock);
// Dock rest of the tabs
while (_toMove.TabsCount > 0)
{
_toMove.GetTab(0).Show(DockState.DockFill, firstTab);
}
}
// Keep selected tab being selected
selectedTab?.SelectTab();
}
// Focus target window
_toDock.Root.Focus();
}
_toMove = null;
}
/// <summary>
/// Start dragging a floating dock panel.
/// </summary>
/// <param name="toMove">Floating dock panel to move.</param>
/// <returns>The window drag helper object.</returns>
public static WindowDragHelper StartDragging(FloatWindowDockPanel toMove)
{
if (toMove == null)
throw new ArgumentNullException();
return new WindowDragHelper(toMove, null);
}
/// <summary>
/// Start dragging a docked panel into a floating window.
/// </summary>
/// <param name="toMove">Dock window to move.</param>
/// <param name="dragSourceWindow">The window where dragging started from.</param>
/// <returns>The window drag helper object.</returns>
public static WindowDragHelper StartDragging(DockWindow toMove, Window dragSourceWindow)
{
if (toMove == null)
throw new ArgumentNullException();
// Create floating window
toMove.CreateFloating();
// Get floating panel
var window = (WindowRootControl)toMove.Root;
var floatingPanelToMove = window.GetChild(0) as FloatWindowDockPanel;
return new WindowDragHelper(floatingPanelToMove, dragSourceWindow);
}
private sealed class DragVisuals : Control
{
public DragVisuals()
{
AnchorPreset = AnchorPresets.StretchAll;
Offsets = Margin.Zero;
}
public override void Draw()
{
base.Draw();
Render2D.DrawRectangle(new Rectangle(Float2.Zero, Size), Style.Current.SelectionBorder);
}
}
private void AddDockHints()
{
if (_toDock == null)
return;
if (_toDock.RootWindow.Window != _dragSourceWindow)
_toDock.RootWindow.Window.MouseUp += OnMouseUp;
_dockHintDown = AddHintControl(new Float2(0.5f, 1));
_dockHintUp = AddHintControl(new Float2(0.5f, 0));
_dockHintLeft = AddHintControl(new Float2(0, 0.5f));
_dockHintRight = AddHintControl(new Float2(1, 0.5f));
_dockHintCenter = AddHintControl(new Float2(0.5f, 0.5f));
Control AddHintControl(Float2 pivot)
{
DragVisuals hintControl = _toDock.AddChild<DragVisuals>();
hintControl.Size = new Float2(HintControlSize);
hintControl.BackgroundColor = Style.Current.Selection.AlphaMultiplied(0.6f);
hintControl.Pivot = pivot;
hintControl.PivotRelative = true;
return hintControl;
}
}
private void RemoveDockHints()
{
if (_toDock == null)
return;
if (_toDock.RootWindow.Window != _dragSourceWindow)
_toDock.RootWindow.Window.MouseUp -= OnMouseUp;
_dockHintDown?.Parent.RemoveChild(_dockHintDown);
_dockHintUp?.Parent.RemoveChild(_dockHintUp);
_dockHintLeft?.Parent.RemoveChild(_dockHintLeft);
_dockHintRight?.Parent.RemoveChild(_dockHintRight);
_dockHintCenter?.Parent.RemoveChild(_dockHintCenter);
_dockHintDown = _dockHintUp = _dockHintLeft = _dockHintRight = _dockHintCenter = null;
}
private void UpdateRects(Float2 mousePos)
{
// Cache mouse position
_mouse = mousePos;
// Check intersection with any dock panel
DockPanel dockPanel = null;
if (_toMove.MasterPanel.HitTest(ref _mouse, _toMove, out var hitResults))
{
dockPanel = hitResults[0];
// Prefer panel which currently has focus
foreach (var hit in hitResults)
{
if (hit.RootWindow.Window.IsFocused)
{
dockPanel = hit;
break;
}
}
// Prefer panel in the same window we hit earlier
if (dockPanel?.RootWindow != _toDock?.RootWindow)
{
foreach (var hit in hitResults)
{
if (hit.RootWindow == _toDock?.RootWindow)
{
dockPanel = _toDock;
break;
}
}
}
}
if (dockPanel != _toDock)
{
RemoveDockHints();
_toDock = dockPanel;
AddDockHints();
// Make sure the all the dock hint areas are not under other windows
_toDock?.RootWindow.Window.BringToFront();
//_toDock?.RootWindow.Window.Focus();
// Make the dragged window transparent when dock hints are visible
_toMove.Window.Window.Opacity = _toDock == null ? 1.0f : DragWindowOpacity;
}
// Check dock state to use
bool showProxyHints = _toDock != null;
bool showBorderHints = showProxyHints;
bool showCenterHint = showProxyHints;
Control hoveredHintControl = null;
Float2 hoveredSizeOverride = Float2.Zero;
if (showProxyHints)
{
// If moved window has not only tabs but also child panels disable docking as tab
if (_toMove.ChildPanelsCount > 0)
showCenterHint = false;
// Disable docking windows with one or more dock panels inside
if (_toMove.ChildPanelsCount > 0)
showBorderHints = false;
// Get dock area
_rectDock = _toDock.DockAreaBounds;
// Cache dock rectangles
var size = _rectDock.Size / Platform.DpiScale;
var offset = _toDock.PointFromScreen(_rectDock.Location);
var borderMargin = 4.0f;
var hintWindowsSize = HintControlSize;
var hintWindowsSize2 = hintWindowsSize * 0.5f;
var hintPreviewSize = new Float2(Math.Max(HintControlSize * 2, size.X * 0.5f), Math.Max(HintControlSize * 2, size.Y * 0.5f));
var centerX = size.X * 0.5f;
var centerY = size.Y * 0.5f;
_rUpper = new Rectangle(centerX - hintWindowsSize2, borderMargin, hintWindowsSize, hintWindowsSize) + offset;
_rBottom = new Rectangle(centerX - hintWindowsSize2, size.Y - hintWindowsSize - borderMargin, hintWindowsSize, hintWindowsSize) + offset;
_rLeft = new Rectangle(borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
_rRight = new Rectangle(size.X - hintWindowsSize - borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
_rCenter = new Rectangle(centerX - hintWindowsSize2, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
// Hit test, and calculate the approximation for filled area when hovered over the hint
DockState toSet = DockState.Float;
if (showBorderHints)
{
if (_rUpper.Contains(_toDock.PointFromScreen(_mouse)))
{
toSet = DockState.DockTop;
hoveredHintControl = _dockHintUp;
hoveredSizeOverride = new Float2(size.X, hintPreviewSize.Y);
}
else if (_rBottom.Contains(_toDock.PointFromScreen(_mouse)))
{
toSet = DockState.DockBottom;
hoveredHintControl = _dockHintDown;
hoveredSizeOverride = new Float2(size.X, hintPreviewSize.Y);
}
else if (_rLeft.Contains(_toDock.PointFromScreen(_mouse)))
{
toSet = DockState.DockLeft;
hoveredHintControl = _dockHintLeft;
hoveredSizeOverride = new Float2(hintPreviewSize.X, size.Y);
}
else if (_rRight.Contains(_toDock.PointFromScreen(_mouse)))
{
toSet = DockState.DockRight;
hoveredHintControl = _dockHintRight;
hoveredSizeOverride = new Float2(hintPreviewSize.X, size.Y);
}
}
if (showCenterHint && _rCenter.Contains(_toDock.PointFromScreen(_mouse)))
{
toSet = DockState.DockFill;
hoveredHintControl = _dockHintCenter;
hoveredSizeOverride = new Float2(size.X, size.Y);
}
_toSet = toSet;
}
else
{
_toSet = DockState.Float;
}
// Update sizes and opacity of hint controls
if (_toDock != null)
{
if (hoveredHintControl != _dockHintDown)
{
_dockHintDown.Size = new Float2(HintControlSize);
_dockHintDown.BackgroundColor = Style.Current.Selection.AlphaMultiplied(0.6f);
}
if (hoveredHintControl != _dockHintLeft)
{
_dockHintLeft.Size = new Float2(HintControlSize);
_dockHintLeft.BackgroundColor = Style.Current.Selection.AlphaMultiplied(0.6f);
}
if (hoveredHintControl != _dockHintRight)
{
_dockHintRight.Size = new Float2(HintControlSize);
_dockHintRight.BackgroundColor = Style.Current.Selection.AlphaMultiplied(0.6f);
}
if (hoveredHintControl != _dockHintUp)
{
_dockHintUp.Size = new Float2(HintControlSize);
_dockHintUp.BackgroundColor = Style.Current.Selection.AlphaMultiplied(0.6f);
}
if (hoveredHintControl != _dockHintCenter)
{
_dockHintCenter.Size = new Float2(HintControlSize);
_dockHintCenter.BackgroundColor = Style.Current.Selection.AlphaMultiplied(0.6f);
}
if (_toSet != DockState.Float)
{
if (hoveredHintControl != null)
{
hoveredHintControl.BackgroundColor = Style.Current.Selection.AlphaMultiplied(1.0f);
hoveredHintControl.Size = hoveredSizeOverride;
}
}
}
// Update hint controls visibility and location
if (showProxyHints)
{
if (hoveredHintControl != _dockHintDown)
_dockHintDown.Location = _rBottom.Location;
if (hoveredHintControl != _dockHintLeft)
_dockHintLeft.Location = _rLeft.Location;
if (hoveredHintControl != _dockHintRight)
_dockHintRight.Location = _rRight.Location;
if (hoveredHintControl != _dockHintUp)
_dockHintUp.Location = _rUpper.Location;
if (hoveredHintControl != _dockHintCenter)
_dockHintCenter.Location = _rCenter.Location;
_dockHintDown.Visible = showProxyHints & showBorderHints;
_dockHintLeft.Visible = showProxyHints & showBorderHints;
_dockHintRight.Visible = showProxyHints & showBorderHints;
_dockHintUp.Visible = showProxyHints & showBorderHints;
_dockHintCenter.Visible = showProxyHints & showCenterHint;
}
}
private void OnMouseUp(ref Float2 location, MouseButton button, ref bool handled)
{
if (button == MouseButton.Left)
Dispose();
}
private void OnUpdate()
{
var mousePos = Platform.MousePosition;
if (_mouse != mousePos)
OnMouseMove(mousePos);
}
private void OnMouseMove(Float2 mousePos)
{
if (_dragSourceWindow != null)
_toMove.Window.Window.Position = mousePos - _dragOffset;
UpdateRects(mousePos);
}
}
}

View File

@@ -266,6 +266,7 @@ namespace FlaxEditor.GUI.Input
return base.OnMouseDown(location, button); return base.OnMouseDown(location, button);
} }
#if !PLATFORM_SDL
/// <inheritdoc /> /// <inheritdoc />
public override void OnMouseMove(Float2 location) public override void OnMouseMove(Float2 location)
{ {
@@ -292,13 +293,45 @@ namespace FlaxEditor.GUI.Input
base.OnMouseMove(location); base.OnMouseMove(location);
} }
#else
/// <inheritdoc />
public override void OnMouseMoveRelative(Float2 mouseMotion)
{
var location = Root.TrackingMouseOffset;
if (_isSliding)
{
// Update sliding
ApplySliding(Root.TrackingMouseOffset.X * _slideSpeed);
return;
}
// Update cursor type so user knows they can slide value
if (CanUseSliding && SlideRect.Contains(location) && !_isSliding)
{
Cursor = CursorType.SizeWE;
_cursorChanged = true;
}
else if (_cursorChanged && !_isSliding)
{
Cursor = CursorType.Default;
_cursorChanged = false;
}
base.OnMouseMoveRelative(mouseMotion);
}
#endif
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button) public override bool OnMouseUp(Float2 location, MouseButton button)
{ {
if (button == MouseButton.Left && _isSliding) if (button == MouseButton.Left && _isSliding)
{ {
#if !PLATFORM_SDL
// End sliding and return mouse to original location // End sliding and return mouse to original location
RootWindow.MousePosition = _mouseClickedPosition; RootWindow.MousePosition = _mouseClickedPosition;
#endif
EndSliding(); EndSliding();
return true; return true;
} }

View File

@@ -12,7 +12,7 @@ namespace FlaxEditor.GUI
/// <seealso cref="FlaxEngine.GUI.ContainerControl" /> /// <seealso cref="FlaxEngine.GUI.ContainerControl" />
public sealed class MainMenu : ContainerControl public sealed class MainMenu : ContainerControl
{ {
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS || PLATFORM_SDL
private bool _useCustomWindowSystem; private bool _useCustomWindowSystem;
private Image _icon; private Image _icon;
private Label _title; private Label _title;
@@ -67,7 +67,7 @@ namespace FlaxEditor.GUI
AutoFocus = false; AutoFocus = false;
AnchorPreset = AnchorPresets.HorizontalStretchTop; AnchorPreset = AnchorPresets.HorizontalStretchTop;
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS || PLATFORM_SDL
_useCustomWindowSystem = !Editor.Instance.Options.Options.Interface.UseNativeWindowSystem; _useCustomWindowSystem = !Editor.Instance.Options.Options.Interface.UseNativeWindowSystem;
if (_useCustomWindowSystem) if (_useCustomWindowSystem)
{ {
@@ -84,13 +84,15 @@ namespace FlaxEditor.GUI
ScriptsBuilder.GetBinariesConfiguration(out _, out _, out _, out var configuration); ScriptsBuilder.GetBinariesConfiguration(out _, out _, out _, out var configuration);
var driver = Platform.DisplayServer;
_icon = new Image _icon = new Image
{ {
Margin = new Margin(6, 6, 6, 6), Margin = new Margin(6, 6, 6, 6),
Brush = new TextureBrush(windowIcon), Brush = new TextureBrush(windowIcon),
Color = Style.Current.Foreground, Color = Style.Current.Foreground,
KeepAspectRatio = false, KeepAspectRatio = false,
TooltipText = string.Format("{0}\nVersion {1}\nConfiguration {3}\nGraphics {2}", _window.Title, Globals.EngineVersion, GPUDevice.Instance.RendererType, configuration), TooltipText = string.Format("{0}\nVersion {1}\nConfiguration {3}\nGraphics {2} {4}", _window.Title, Globals.EngineVersion, GPUDevice.Instance.RendererType, configuration, driver),
Parent = this, Parent = this,
}; };
@@ -166,7 +168,7 @@ namespace FlaxEditor.GUI
} }
} }
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS || PLATFORM_SDL
/// <inheritdoc /> /// <inheritdoc />
public override void Update(float deltaTime) public override void Update(float deltaTime)
{ {
@@ -291,7 +293,7 @@ namespace FlaxEditor.GUI
if (base.OnMouseDoubleClick(location, button)) if (base.OnMouseDoubleClick(location, button))
return true; return true;
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS || PLATFORM_SDL
var child = GetChildAtRecursive(location); var child = GetChildAtRecursive(location);
if (_useCustomWindowSystem && child is not Button && child is not MainMenuButton) if (_useCustomWindowSystem && child is not Button && child is not MainMenuButton)
{ {
@@ -321,7 +323,7 @@ namespace FlaxEditor.GUI
{ {
float x = 0; float x = 0;
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS || PLATFORM_SDL
if (_useCustomWindowSystem) if (_useCustomWindowSystem)
{ {
// Icon // Icon
@@ -349,7 +351,7 @@ namespace FlaxEditor.GUI
} }
} }
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS || PLATFORM_SDL
if (_useCustomWindowSystem) if (_useCustomWindowSystem)
{ {
// Buttons // Buttons
@@ -367,7 +369,7 @@ namespace FlaxEditor.GUI
#endif #endif
} }
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS || PLATFORM_SDL
/// <inheritdoc /> /// <inheritdoc />
public override void OnDestroy() public override void OnDestroy()
{ {

View File

@@ -42,7 +42,7 @@ namespace FlaxEditor.GUI
Text = text; Text = text;
var style = Style.Current; var style = Style.Current;
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS || PLATFORM_SDL
if (Editor.Instance.Options.Options.Interface.UseNativeWindowSystem) if (Editor.Instance.Options.Options.Interface.UseNativeWindowSystem)
{ {
BackgroundColorMouseOver = style.BackgroundHighlighted; BackgroundColorMouseOver = style.BackgroundHighlighted;

View File

@@ -402,10 +402,11 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa
break; break;
} }
} }
WindowsManager::WindowsLocker.Unlock();
for (const auto& e : inputEvents) for (const auto& e : inputEvents)
{ {
auto window = e.Target ? e.Target : defaultWindow; auto window = e.Target ? e.Target : defaultWindow;
if (!window) if (!window || window->IsClosed())
continue; continue;
switch (e.Type) switch (e.Type)
{ {
@@ -435,12 +436,14 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa
case InputDevice::EventType::MouseMove: case InputDevice::EventType::MouseMove:
window->OnMouseMove(window->ScreenToClient(e.MouseData.Position)); window->OnMouseMove(window->ScreenToClient(e.MouseData.Position));
break; break;
case InputDevice::EventType::MouseMoveRelative:
window->OnMouseMoveRelative(e.MouseMovementData.PositionRelative);
break;
case InputDevice::EventType::MouseLeave: case InputDevice::EventType::MouseLeave:
window->OnMouseLeave(); window->OnMouseLeave();
break; break;
} }
} }
WindowsManager::WindowsLocker.Unlock();
} }
WindowsManager::WindowsLocker.Lock(); WindowsManager::WindowsLocker.Lock();
Array<Window*, InlinedAllocation<32>> windows; Array<Window*, InlinedAllocation<32>> windows;

View File

@@ -1013,7 +1013,7 @@ namespace FlaxEditor.Modules
ContentItem item; ContentItem item;
if (path.EndsWith(".cs")) if (path.EndsWith(".cs"))
item = new CSharpScriptItem(path); item = new CSharpScriptItem(path);
else if (path.EndsWith(".cpp") || path.EndsWith(".h")) else if (path.EndsWith(".cpp") || path.EndsWith(".h") || path.EndsWith(".c") || path.EndsWith(".hpp"))
item = new CppScriptItem(path); item = new CppScriptItem(path);
else if (path.EndsWith(".shader") || path.EndsWith(".hlsl")) else if (path.EndsWith(".shader") || path.EndsWith(".hlsl"))
item = new ShaderSourceItem(path); item = new ShaderSourceItem(path);

View File

@@ -222,7 +222,7 @@ namespace FlaxEditor.Modules
outputExtension = extension; outputExtension = extension;
// Check if can place source files here // Check if can place source files here
if (!targetLocation.CanHaveScripts && (extension == ".cs" || extension == ".cpp" || extension == ".h")) if (!targetLocation.CanHaveScripts && (extension == ".cs" || extension == ".cpp" || extension == ".h" || extension == ".c" || extension == ".hpp"))
{ {
// Error // Error
Editor.LogWarning(string.Format("Cannot import \'{0}\' to \'{1}\'. The target directory cannot have scripts.", inputPath, targetLocation.Node.Path)); Editor.LogWarning(string.Format("Cannot import \'{0}\' to \'{1}\'. The target directory cannot have scripts.", inputPath, targetLocation.Node.Path));

View File

@@ -267,7 +267,7 @@ namespace FlaxEditor.Modules
} }
/// <inheritdoc /> /// <inheritdoc />
public override void OnPlayBegin() public override void OnPlayBeginning()
{ {
Editor.Windows.FlashMainWindow(); Editor.Windows.FlashMainWindow();

View File

@@ -16,7 +16,7 @@ using FlaxEditor.Windows;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Json; using FlaxEngine.Json;
using DockHintWindow = FlaxEditor.GUI.Docking.DockHintWindow; using WindowDragHelper = FlaxEditor.GUI.Docking.WindowDragHelper;
using MasterDockPanel = FlaxEditor.GUI.Docking.MasterDockPanel; using MasterDockPanel = FlaxEditor.GUI.Docking.MasterDockPanel;
using FlaxEditor.Content.Settings; using FlaxEditor.Content.Settings;
using FlaxEditor.Options; using FlaxEditor.Options;
@@ -381,7 +381,7 @@ namespace FlaxEditor.Modules
Editor.Options.OptionsChanged += OnOptionsChanged; Editor.Options.OptionsChanged += OnOptionsChanged;
// Add dummy control for drawing the main window borders if using a custom style // Add dummy control for drawing the main window borders if using a custom style
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS || PLATFORM_SDL
if (!Editor.Options.Options.Interface.UseNativeWindowSystem) if (!Editor.Options.Options.Interface.UseNativeWindowSystem)
#endif #endif
{ {
@@ -458,13 +458,6 @@ namespace FlaxEditor.Modules
UpdateToolstrip(); UpdateToolstrip();
} }
/// <inheritdoc />
public override void OnExit()
{
// Cleanup dock panel hint proxy windows (Flax will destroy them by var but it's better to clear them earlier)
DockHintWindow.Proxy.Dispose();
}
private IColorPickerDialog ShowPickColorDialog(Control targetControl, Color initialValue, ColorValueBox.ColorPickerEvent colorChanged, ColorValueBox.ColorPickerClosedEvent pickerClosed, bool useDynamicEditing) private IColorPickerDialog ShowPickColorDialog(Control targetControl, Color initialValue, ColorValueBox.ColorPickerEvent colorChanged, ColorValueBox.ColorPickerClosedEvent pickerClosed, bool useDynamicEditing)
{ {
var dialog = new ColorPickerDialog(initialValue, colorChanged, pickerClosed, useDynamicEditing); var dialog = new ColorPickerDialog(initialValue, colorChanged, pickerClosed, useDynamicEditing);

View File

@@ -758,6 +758,7 @@ namespace FlaxEditor.Modules
var settings = CreateWindowSettings.Default; var settings = CreateWindowSettings.Default;
settings.Title = "Flax Editor"; settings.Title = "Flax Editor";
settings.Size = Platform.DesktopSize * 0.75f; settings.Size = Platform.DesktopSize * 0.75f;
settings.MinimumSize = new Float2(200, 150);
settings.StartPosition = WindowStartPosition.CenterScreen; settings.StartPosition = WindowStartPosition.CenterScreen;
settings.ShowAfterFirstPaint = true; settings.ShowAfterFirstPaint = true;
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS
@@ -765,8 +766,10 @@ namespace FlaxEditor.Modules
{ {
settings.HasBorder = false; settings.HasBorder = false;
#if !PLATFORM_SDL
// Skip OS sizing frame and implement it using LeftButtonHit // Skip OS sizing frame and implement it using LeftButtonHit
settings.HasSizingFrame = false; settings.HasSizingFrame = false;
#endif
} }
#elif PLATFORM_LINUX #elif PLATFORM_LINUX
settings.HasBorder = false; settings.HasBorder = false;

View File

@@ -167,7 +167,7 @@ namespace FlaxEditor.Options
[EditorDisplay("Interface"), EditorOrder(10), Tooltip("Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required.")] [EditorDisplay("Interface"), EditorOrder(10), Tooltip("Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required.")]
public float InterfaceScale { get; set; } = 1.0f; public float InterfaceScale { get; set; } = 1.0f;
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS || PLATFORM_SDL
/// <summary> /// <summary>
/// Gets or sets a value indicating whether use native window title bar. Editor restart required. /// Gets or sets a value indicating whether use native window title bar. Editor restart required.
/// </summary> /// </summary>

View File

@@ -120,9 +120,13 @@ void ScriptsBuilderImpl::sourceDirEvent(const String& path, FileSystemAction act
// Discard non-source files or generated files // Discard non-source files or generated files
if ((!path.EndsWith(TEXT(".cs")) && if ((!path.EndsWith(TEXT(".cs")) &&
!path.EndsWith(TEXT(".cpp")) && !path.EndsWith(TEXT(".cpp")) &&
!path.EndsWith(TEXT(".c")) &&
!path.EndsWith(TEXT(".hpp")) &&
!path.EndsWith(TEXT(".h"))) || !path.EndsWith(TEXT(".h"))) ||
path.EndsWith(TEXT(".Gen.cs"))) path.EndsWith(TEXT(".Gen.cs")))
{
return; return;
}
ScopeLock scopeLock(_locker); ScopeLock scopeLock(_locker);
_lastSourceCodeEdited = DateTime::Now(); _lastSourceCodeEdited = DateTime::Now();

View File

@@ -1,131 +0,0 @@
// Copyright (c) Wojciech Figat. All rights reserved.
#include "ScreenUtilities.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Core/Delegate.h"
#include "Engine/Core/Log.h"
#include "Engine/Profiler/ProfilerCPU.h"
Delegate<Color32> ScreenUtilities::PickColorDone;
#if PLATFORM_WINDOWS
#include <Windows.h>
#pragma comment(lib, "Gdi32.lib")
static HHOOK MouseCallbackHook;
LRESULT CALLBACK OnScreenUtilsMouseCallback(_In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
if (nCode >= 0 && wParam == WM_LBUTTONDOWN)
{
UnhookWindowsHookEx(MouseCallbackHook);
// Push event with the picked color
const Float2 cursorPos = Platform::GetMousePosition();
const Color32 colorPicked = ScreenUtilities::GetColorAt(cursorPos);
ScreenUtilities::PickColorDone(colorPicked);
return 1;
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
Color32 ScreenUtilities::GetColorAt(const Float2& pos)
{
PROFILE_CPU();
HDC deviceContext = GetDC(NULL);
COLORREF color = GetPixel(deviceContext, (int)pos.X, (int)pos.Y);
ReleaseDC(NULL, deviceContext);
return Color32(GetRValue(color), GetGValue(color), GetBValue(color), 255);
}
void ScreenUtilities::PickColor()
{
MouseCallbackHook = SetWindowsHookEx(WH_MOUSE_LL, OnScreenUtilsMouseCallback, NULL, NULL);
if (MouseCallbackHook == NULL)
{
LOG(Warning, "Failed to set mouse hook.");
LOG(Warning, "Error: {0}", GetLastError());
}
}
#elif PLATFORM_LINUX
#include "Engine/Platform/Linux/LinuxPlatform.h"
#include "Engine/Platform/Linux/IncludeX11.h"
Color32 ScreenUtilities::GetColorAt(const Float2& pos)
{
X11::XColor color;
X11::Display* display = (X11::Display*) LinuxPlatform::GetXDisplay();
int defaultScreen = X11::XDefaultScreen(display);
X11::XImage* image;
image = X11::XGetImage(display, X11::XRootWindow(display, defaultScreen), (int)pos.X, (int)pos.Y, 1, 1, AllPlanes, XYPixmap);
color.pixel = XGetPixel(image, 0, 0);
X11::XFree(image);
X11::XQueryColor(display, X11::XDefaultColormap(display, defaultScreen), &color);
Color32 outputColor;
outputColor.R = color.red / 256;
outputColor.G = color.green / 256;
outputColor.B = color.blue / 256;
outputColor.A = 255;
return outputColor;
}
void OnScreenUtilsXEventCallback(void* eventPtr)
{
X11::XEvent* event = (X11::XEvent*) eventPtr;
X11::Display* display = (X11::Display*)LinuxPlatform::GetXDisplay();
if (event->type == ButtonPress)
{
const Float2 cursorPos = Platform::GetMousePosition();
const Color32 colorPicked = ScreenUtilities::GetColorAt(cursorPos);
X11::XUngrabPointer(display, CurrentTime);
ScreenUtilities::PickColorDone(colorPicked);
LinuxPlatform::xEventRecieved.Unbind(OnScreenUtilsXEventCallback);
}
}
void ScreenUtilities::PickColor()
{
PROFILE_CPU();
X11::Display* display = (X11::Display*) LinuxPlatform::GetXDisplay();
X11::Window rootWindow = X11::XRootWindow(display, X11::XDefaultScreen(display));
X11::Cursor cursor = XCreateFontCursor(display, 130);
int grabbedPointer = X11::XGrabPointer(display, rootWindow, 0, ButtonPressMask, GrabModeAsync, GrabModeAsync, rootWindow, cursor, CurrentTime);
if (grabbedPointer != GrabSuccess)
{
LOG(Error, "Failed to grab cursor for events.");
X11::XFreeCursor(display, cursor);
return;
}
X11::XFreeCursor(display, cursor);
LinuxPlatform::xEventRecieved.Bind(OnScreenUtilsXEventCallback);
}
#elif PLATFORM_MAC
#include <Cocoa/Cocoa.h>
#include <AppKit/AppKit.h>
Color32 ScreenUtilities::GetColorAt(const Float2& pos)
{
// TODO: implement ScreenUtilities for macOS
return { 0, 0, 0, 255 };
}
void ScreenUtilities::PickColor()
{
// This is what C# calls to start the color picking sequence
// This should stop mouse clicks from working for one click, and that click is on the selected color
// There is a class called NSColorSample that might implement that for you, but maybe not.
}
#endif

View File

@@ -2,32 +2,4 @@
#pragma once #pragma once
#include "Engine/Core/Types/BaseTypes.h" #include "Engine/Platform/ScreenUtilities.h"
#include "Engine/Core/Math/Color32.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Core/Delegate.h"
/// <summary>
/// Platform-dependent screen utilities.
/// </summary>
API_CLASS(Static) class FLAXENGINE_API ScreenUtilities
{
DECLARE_SCRIPTING_TYPE_MINIMAL(ScreenUtilities);
/// <summary>
/// Gets the pixel color at the specified coordinates.
/// </summary>
/// <param name="pos">Screen-space coordinate to read.</param>
/// <returns>Pixel color at the specified coordinates.</returns>
API_FUNCTION() static Color32 GetColorAt(const Float2& pos);
/// <summary>
/// Starts async color picking. Color will be returned through PickColorDone event when the actions ends (user selected the final color with a mouse). When action is active, GetColorAt can be used to read the current value.
/// </summary>
API_FUNCTION() static void PickColor();
/// <summary>
/// Called when PickColor action is finished.
/// </summary>
API_EVENT() static Delegate<Color32> PickColorDone;
};

View File

@@ -77,7 +77,7 @@ namespace FlaxEditor.Viewport
public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root); public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
/// <inheritdoc /> /// <inheritdoc />
public Float2 MouseDelta => _mouseDelta; public Float2 MouseDelta => FlaxEngine.Input.MousePositionDelta;
/// <inheritdoc /> /// <inheritdoc />
public bool UseSnapping => Root?.GetKey(KeyboardKeys.Control) ?? false; public bool UseSnapping => Root?.GetKey(KeyboardKeys.Control) ?? false;

View File

@@ -4,6 +4,7 @@ using System;
using System.Linq; using System.Linq;
using FlaxEditor.Content.Settings; using FlaxEditor.Content.Settings;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Docking;
using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Input;
using FlaxEditor.Options; using FlaxEditor.Options;
using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Cameras;
@@ -158,18 +159,22 @@ namespace FlaxEditor.Viewport
private float _movementSpeed; private float _movementSpeed;
private float _minMovementSpeed; private float _minMovementSpeed;
private float _maxMovementSpeed; private float _maxMovementSpeed;
#if !PLATFORM_SDL
private float _mouseAccelerationScale; private float _mouseAccelerationScale;
private bool _useMouseFiltering; private bool _useMouseFiltering;
private bool _useMouseAcceleration; private bool _useMouseAcceleration;
#endif
// Input // Input
internal bool _disableInputUpdate; internal bool _disableInputUpdate;
private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown; private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown;
private int _deltaFilteringStep;
private Float2 _startPos; private Float2 _startPos;
#if !PLATFORM_SDL
private Float2 _mouseDeltaLast; private Float2 _mouseDeltaLast;
private int _deltaFilteringStep;
private Float2[] _deltaFilteringBuffer = new Float2[FpsCameraFilteringFrames]; private Float2[] _deltaFilteringBuffer = new Float2[FpsCameraFilteringFrames];
#endif
/// <summary> /// <summary>
/// The previous input (from the previous update). /// The previous input (from the previous update).
@@ -522,10 +527,11 @@ namespace FlaxEditor.Viewport
: base(task) : base(task)
{ {
_editor = Editor.Instance; _editor = Editor.Instance;
#if !PLATFORM_SDL
_mouseAccelerationScale = 0.1f; _mouseAccelerationScale = 0.1f;
_useMouseFiltering = false; _useMouseFiltering = false;
_useMouseAcceleration = false; _useMouseAcceleration = false;
#endif
_camera = camera; _camera = camera;
if (_camera != null) if (_camera != null)
_camera.Viewport = this; _camera.Viewport = this;
@@ -1460,7 +1466,9 @@ namespace FlaxEditor.Viewport
// Hide cursor and start tracking mouse movement // Hide cursor and start tracking mouse movement
win.StartTrackingMouse(false); win.StartTrackingMouse(false);
win.Cursor = CursorType.Hidden; win.Cursor = CursorType.Hidden;
win.MouseMoveRelative += OnMouseMoveRelative;
#if !PLATFORM_SDL
// Center mouse position if it's too close to the edge // Center mouse position if it's too close to the edge
var size = Size; var size = Size;
var center = Float2.Round(size * 0.5f); var center = Float2.Round(size * 0.5f);
@@ -1469,6 +1477,7 @@ namespace FlaxEditor.Viewport
_viewMousePos = center; _viewMousePos = center;
win.MousePosition = PointToWindow(_viewMousePos); win.MousePosition = PointToWindow(_viewMousePos);
} }
#endif
} }
/// <summary> /// <summary>
@@ -1480,6 +1489,7 @@ namespace FlaxEditor.Viewport
// Restore cursor and stop tracking mouse movement // Restore cursor and stop tracking mouse movement
win.Cursor = CursorType.Default; win.Cursor = CursorType.Default;
win.EndTrackingMouse(); win.EndTrackingMouse();
win.MouseMoveRelative -= OnMouseMoveRelative;
} }
/// <summary> /// <summary>
@@ -1561,18 +1571,15 @@ namespace FlaxEditor.Viewport
// Get parent window // Get parent window
var win = (WindowRootControl)Root; var win = (WindowRootControl)Root;
if (win.IsFocused)
// Get current mouse position in the view
{ {
// When the window is not focused, the position in window does not return sane values // Get current mouse position in the view
Float2 pos = PointFromWindow(win.MousePosition); _viewMousePos = PointFromWindow(win.MousePosition);
if (!float.IsInfinity(pos.LengthSquared))
_viewMousePos = pos;
} }
// Update input // Update input
var window = win.Window; var window = win.Window;
var canUseInput = window != null && window.IsFocused && window.IsForegroundWindow; var canUseInput = window != null && window.IsFocused && window.IsForegroundWindow && !WindowDragHelper.IsDragActive;
{ {
// Get input buttons and keys (skip if viewport has no focus or mouse is over a child control) // Get input buttons and keys (skip if viewport has no focus or mouse is over a child control)
var isViewportControllingMouse = canUseInput && IsControllingMouse; var isViewportControllingMouse = canUseInput && IsControllingMouse;
@@ -1584,9 +1591,17 @@ namespace FlaxEditor.Viewport
else else
EndMouseCapture(); EndMouseCapture();
} }
bool useMouse = IsControllingMouse || (Mathf.IsInRange(_viewMousePos.X, 0, Width) && Mathf.IsInRange(_viewMousePos.Y, 0, Height));
_prevInput = _input; _prevInput = _input;
#if PLATFORM_SDL
bool useMouse = IsControllingMouse || ContainsPoint(ref _viewMousePos) || _prevInput.IsControllingMouse;
var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl) && !(c is UIEditorRoot)); var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl) && !(c is UIEditorRoot));
if (_prevInput.IsControllingMouse)
hit = null;
#else
bool useMouse = IsControllingMouse || ContainsPoint(ref _viewMousePos);
var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl) && !(c is UIEditorRoot));
#endif
if (canUseInput && ContainsFocus && hit == null) if (canUseInput && ContainsFocus && hit == null)
_input.Gather(win.Window, useMouse, ref _prevInput); _input.Gather(win.Window, useMouse, ref _prevInput);
else else
@@ -1699,6 +1714,10 @@ namespace FlaxEditor.Viewport
if (_input.IsControlDown) if (_input.IsControlDown)
moveDelta *= 0.3f; moveDelta *= 0.3f;
#if PLATFORM_SDL
var mouseDelta = _mouseDelta;
_mouseDelta = Float2.Zero;
#else
// Calculate smooth mouse delta not dependant on viewport size // Calculate smooth mouse delta not dependant on viewport size
var offset = _viewMousePos - _startPos; var offset = _viewMousePos - _startPos;
if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown && !_isOrtho && !rmbWheel && !_isVirtualMouseRightDown) if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown && !_isOrtho && !rmbWheel && !_isVirtualMouseRightDown)
@@ -1740,6 +1759,7 @@ namespace FlaxEditor.Viewport
mouseDelta += _mouseDeltaLast * _mouseAccelerationScale; mouseDelta += _mouseDeltaLast * _mouseAccelerationScale;
_mouseDeltaLast = currentDelta; _mouseDeltaLast = currentDelta;
} }
#endif
// Update // Update
moveDelta *= dt * (60.0f * 4.0f); moveDelta *= dt * (60.0f * 4.0f);
@@ -1748,12 +1768,14 @@ namespace FlaxEditor.Viewport
mouseDelta *= new Float2(1, -1); mouseDelta *= new Float2(1, -1);
UpdateView(dt, ref moveDelta, ref mouseDelta, out var centerMouse); UpdateView(dt, ref moveDelta, ref mouseDelta, out var centerMouse);
#if !PLATFORM_SDL
// Move mouse back to the root position // Move mouse back to the root position
if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown || _isVirtualMouseRightDown)) if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown || _isVirtualMouseRightDown))
{ {
var center = PointToWindow(_startPos); var center = PointToWindow(_startPos);
win.MousePosition = center; win.MousePosition = center;
} }
#endif
// Change Ortho size on mouse scroll // Change Ortho size on mouse scroll
if (_isOrtho && !rmbWheel) if (_isOrtho && !rmbWheel)
@@ -1765,6 +1787,8 @@ namespace FlaxEditor.Viewport
} }
else else
{ {
#if PLATFORM_SDL
#else
if (_input.IsMouseLeftDown || _input.IsMouseRightDown || _isVirtualMouseRightDown) if (_input.IsMouseLeftDown || _input.IsMouseRightDown || _isVirtualMouseRightDown)
{ {
// Calculate smooth mouse delta not dependant on viewport size // Calculate smooth mouse delta not dependant on viewport size
@@ -1779,6 +1803,7 @@ namespace FlaxEditor.Viewport
_mouseDelta = Float2.Zero; _mouseDelta = Float2.Zero;
} }
_mouseDeltaLast = Float2.Zero; _mouseDeltaLast = Float2.Zero;
#endif
if (ContainsFocus) if (ContainsFocus)
{ {
@@ -1828,6 +1853,12 @@ namespace FlaxEditor.Viewport
_input.MouseWheelDelta = 0; _input.MouseWheelDelta = 0;
} }
/// <inheritdoc />
public void OnMouseMoveRelative(ref Float2 mouseMotion)
{
_mouseDelta += mouseMotion;
}
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button) public override bool OnMouseDown(Float2 location, MouseButton button)
{ {
@@ -1893,6 +1924,7 @@ namespace FlaxEditor.Viewport
if (_isControllingMouse) if (_isControllingMouse)
{ {
if (RootWindow?.Window != null)
OnControlMouseEnd(RootWindow.Window); OnControlMouseEnd(RootWindow.Window);
_isControllingMouse = false; _isControllingMouse = false;
_isVirtualMouseRightDown = false; _isVirtualMouseRightDown = false;

View File

@@ -611,15 +611,24 @@ namespace FlaxEditor.Viewport
// Don't allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled // Don't allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled
bool canStart = !(IsControllingMouse || IsRightMouseButtonDown || IsAltKeyDown) && bool canStart = !(IsControllingMouse || IsRightMouseButtonDown || IsAltKeyDown) &&
Gizmos?.Active is TransformGizmo && !Gizmos.Active.IsControllingMouse; Gizmos?.Active is TransformGizmo;
_rubberBandSelector.TryCreateRubberBand(canStart, _viewMousePos); _rubberBandSelector.TryCreateRubberBand(canStart, _viewMousePos);
} }
/// <inheritdoc />
protected override void OnControlMouseBegin(Window win)
{
_rubberBandSelector.ReleaseRubberBandSelection();
base.OnControlMouseBegin(win);
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OnLeftMouseButtonDown() protected override void OnLeftMouseButtonDown()
{ {
base.OnLeftMouseButtonDown(); base.OnLeftMouseButtonDown();
if (!IsAltKeyDown)
_rubberBandSelector.TryStartingRubberBandSelection(); _rubberBandSelector.TryStartingRubberBandSelection();
} }

View File

@@ -1061,7 +1061,7 @@ namespace FlaxEditor.Windows
_cursorVisible = Screen.CursorVisible; _cursorVisible = Screen.CursorVisible;
_cursorLockMode = Screen.CursorLock; _cursorLockMode = Screen.CursorLock;
Screen.CursorVisible = true; Screen.CursorVisible = true;
if (Screen.CursorLock == CursorLockMode.Clipped) if (Screen.CursorLock == CursorLockMode.Clipped || Screen.CursorLock == CursorLockMode.Locked)
Screen.CursorLock = CursorLockMode.None; Screen.CursorLock = CursorLockMode.None;
// Defocus // Defocus
@@ -1150,8 +1150,11 @@ namespace FlaxEditor.Windows
if (Editor.StateMachine.IsPlayMode && !Editor.StateMachine.PlayingState.IsPaused) if (Editor.StateMachine.IsPlayMode && !Editor.StateMachine.PlayingState.IsPaused)
{ {
// Make sure the cursor is always in the viewport when cursor is locked
bool forceCenter = _cursorLockMode != CursorLockMode.None && !IsMouseOver;
// Center mouse in play mode // Center mouse in play mode
if (CenterMouseOnFocus) if (CenterMouseOnFocus || forceCenter)
{ {
var center = PointToWindow(Size * 0.5f); var center = PointToWindow(Size * 0.5f);
Root.MousePosition = center; Root.MousePosition = center;
@@ -1176,9 +1179,10 @@ namespace FlaxEditor.Windows
_cursorVisible = Screen.CursorVisible; _cursorVisible = Screen.CursorVisible;
_cursorLockMode = Screen.CursorLock; _cursorLockMode = Screen.CursorLock;
// Restore cursor visibility (could be hidden by the game) // Restore cursor state, could be hidden or locked by the game
if (!_cursorVisible) if (!_cursorVisible)
Screen.CursorVisible = true; Screen.CursorVisible = true;
Screen.CursorLock = CursorLockMode.None;
} }
} }

View File

@@ -26,7 +26,6 @@ namespace FlaxEditor.Windows
private Tree _tree; private Tree _tree;
private Panel _sceneTreePanel; private Panel _sceneTreePanel;
private bool _isUpdatingSelection; private bool _isUpdatingSelection;
private bool _isMouseDown;
private DragAssets _dragAssets; private DragAssets _dragAssets;
private DragActorType _dragActorType; private DragActorType _dragActorType;
@@ -317,10 +316,7 @@ namespace FlaxEditor.Windows
return true; return true;
if (buttons == MouseButton.Right) if (buttons == MouseButton.Right)
{
_isMouseDown = true;
return true; return true;
}
return false; return false;
} }
@@ -331,10 +327,8 @@ namespace FlaxEditor.Windows
if (base.OnMouseUp(location, buttons)) if (base.OnMouseUp(location, buttons))
return true; return true;
if (_isMouseDown && buttons == MouseButton.Right) if (buttons == MouseButton.Right)
{ {
_isMouseDown = false;
if (Editor.StateMachine.CurrentState.CanEditScene) if (Editor.StateMachine.CurrentState.CanEditScene)
{ {
// Show context menu // Show context menu
@@ -359,14 +353,6 @@ namespace FlaxEditor.Windows
return false; return false;
} }
/// <inheritdoc />
public override void OnLostFocus()
{
_isMouseDown = false;
base.OnLostFocus();
}
/// <inheritdoc /> /// <inheritdoc />
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{ {

View File

@@ -28,7 +28,7 @@ const Char* SplashScreenQuotes[] =
#elif PLATFORM_LINUX #elif PLATFORM_LINUX
TEXT("Try it on a Raspberry"), TEXT("Try it on a Raspberry"),
TEXT("Trying to exit vim"), TEXT("Trying to exit vim"),
TEXT("Sudo flax --loadproject"), TEXT("sudo flax --project HelloWorld.flaxproj"),
#elif PLATFORM_MAC #elif PLATFORM_MAC
TEXT("don't compare Macbooks to oranges."), TEXT("don't compare Macbooks to oranges."),
TEXT("Why does macbook heat up?\nBecause it doesn't have windows"), TEXT("Why does macbook heat up?\nBecause it doesn't have windows"),
@@ -104,6 +104,7 @@ const Char* SplashScreenQuotes[] =
TEXT("You have my bow.\nAnd my axe!"), TEXT("You have my bow.\nAnd my axe!"),
TEXT("To the bridge of Khazad-dum."), TEXT("To the bridge of Khazad-dum."),
TEXT("One ring to rule them all.\nOne ring to find them."), TEXT("One ring to rule them all.\nOne ring to find them."),
TEXT("Where there's a whip, there's a way."),
TEXT("That's what she said"), TEXT("That's what she said"),
TEXT("We could be compiling shaders here"), TEXT("We could be compiling shaders here"),
TEXT("Hello There"), TEXT("Hello There"),
@@ -164,7 +165,7 @@ void SplashScreen::Show()
settings.AllowMaximize = false; settings.AllowMaximize = false;
settings.AllowDragAndDrop = false; settings.AllowDragAndDrop = false;
settings.IsTopmost = false; settings.IsTopmost = false;
settings.IsRegularWindow = false; settings.Type = WindowType::Utility;
settings.HasSizingFrame = false; settings.HasSizingFrame = false;
settings.ShowAfterFirstPaint = true; settings.ShowAfterFirstPaint = true;
settings.StartPosition = WindowStartPosition::CenterScreen; settings.StartPosition = WindowStartPosition::CenterScreen;

View File

@@ -145,6 +145,12 @@ bool CommandLine::Parse(const Char* cmdLine)
PARSE_BOOL_SWITCH("-monolog ", MonoLog); PARSE_BOOL_SWITCH("-monolog ", MonoLog);
PARSE_BOOL_SWITCH("-mute ", Mute); PARSE_BOOL_SWITCH("-mute ", Mute);
PARSE_BOOL_SWITCH("-lowdpi ", LowDPI); PARSE_BOOL_SWITCH("-lowdpi ", LowDPI);
#if PLATFORM_LINUX && PLATFORM_SDL
PARSE_BOOL_SWITCH("-wayland ", Wayland);
PARSE_BOOL_SWITCH("-x11 ", X11);
#endif
#if USE_EDITOR #if USE_EDITOR
PARSE_BOOL_SWITCH("-clearcache ", ClearCache); PARSE_BOOL_SWITCH("-clearcache ", ClearCache);
PARSE_BOOL_SWITCH("-clearcooker ", ClearCookerCache); PARSE_BOOL_SWITCH("-clearcooker ", ClearCookerCache);

View File

@@ -127,6 +127,20 @@ public:
/// </summary> /// </summary>
Nullable<bool> LowDPI; Nullable<bool> LowDPI;
#if PLATFORM_LINUX && PLATFORM_SDL
/// <summary>
/// -wayland (prefer Wayland over X11 as display server)
/// </summary>
Nullable<bool> Wayland;
/// <summary>
/// -x11 (prefer X11 over Wayland as display server)
/// </summary>
Nullable<bool> X11;
#endif
#if USE_EDITOR #if USE_EDITOR
/// <summary> /// <summary>
/// -project !path! (Startup project path) /// -project !path! (Startup project path)

View File

@@ -99,13 +99,14 @@ int32 Engine::Main(const Char* cmdLine)
CommandLine::Options.Std = true; CommandLine::Options.Std = true;
#endif #endif
Platform::SetHighDpiAwarenessEnabled(!CommandLine::Options.LowDPI.IsTrue());
if (Platform::Init()) if (Platform::Init())
{ {
Platform::Fatal(TEXT("Cannot init platform.")); Platform::Fatal(TEXT("Cannot init platform."));
return -1; return -1;
} }
Platform::SetHighDpiAwarenessEnabled(!CommandLine::Options.LowDPI.IsTrue());
Time::StartupTime = DateTime::Now(); Time::StartupTime = DateTime::Now();
Globals::StartupFolder = Globals::BinariesFolder = Platform::GetMainDirectory(); Globals::StartupFolder = Globals::BinariesFolder = Platform::GetMainDirectory();
#if USE_EDITOR #if USE_EDITOR

View File

@@ -6,6 +6,8 @@
#include "Engine/Core/Types/Nullable.h" #include "Engine/Core/Types/Nullable.h"
#include "Engine/Platform/Window.h" #include "Engine/Platform/Window.h"
#include "Engine/Engine/EngineService.h" #include "Engine/Engine/EngineService.h"
#include "Engine/Input/Input.h"
#include "Engine/Input/Mouse.h"
#if USE_EDITOR #if USE_EDITOR
#include "Editor/Editor.h" #include "Editor/Editor.h"
#include "Editor/Managed/ManagedEditor.h" #include "Editor/Managed/ManagedEditor.h"
@@ -13,10 +15,14 @@
#include "Engine/Engine/Engine.h" #include "Engine/Engine/Engine.h"
#endif #endif
Nullable<bool> Fullscreen; namespace
Nullable<Float2> Size; {
bool CursorVisible = true; Nullable<bool> Fullscreen;
CursorLockMode CursorLock = CursorLockMode::None; Nullable<Float2> Size;
bool CursorVisible = true;
CursorLockMode CursorLock = CursorLockMode::None;
bool LastGameViewportFocus = false;
}
class ScreenService : public EngineService class ScreenService : public EngineService
{ {
@@ -101,9 +107,9 @@ void Screen::SetCursorVisible(const bool value)
const auto win = Engine::MainWindow; const auto win = Engine::MainWindow;
#endif #endif
if (win && Engine::HasGameViewportFocus()) if (win && Engine::HasGameViewportFocus())
{
win->SetCursor(value ? CursorType::Default : CursorType::Hidden); win->SetCursor(value ? CursorType::Default : CursorType::Hidden);
} else if (win)
win->SetCursor(CursorType::Default);
CursorVisible = value; CursorVisible = value;
} }
@@ -116,21 +122,31 @@ void Screen::SetCursorLock(CursorLockMode mode)
{ {
#if USE_EDITOR #if USE_EDITOR
const auto win = Editor::Managed->GetGameWindow(true); const auto win = Editor::Managed->GetGameWindow(true);
Rectangle bounds(Editor::Managed->GameViewportToScreen(Float2::Zero), Editor::Managed->GetGameWindowSize());
if (win)
bounds = Rectangle(win->ScreenToClient(bounds.GetTopLeft()), bounds.Size);
#else #else
const auto win = Engine::MainWindow; const auto win = Engine::MainWindow;
Rectangle bounds = win != nullptr ? win->GetClientBounds() : Rectangle();
#endif #endif
if (win && mode == CursorLockMode::Clipped) if (win)
{ {
#if USE_EDITOR bool inRelativeMode = Input::Mouse->IsRelative();
Rectangle bounds(Editor::Managed->GameViewportToScreen(Float2::Zero), Editor::Managed->GetGameWindowSize()); if (mode == CursorLockMode::Clipped)
#else
Rectangle bounds = win->GetClientBounds();
#endif
win->StartClippingCursor(bounds); win->StartClippingCursor(bounds);
} else if (mode == CursorLockMode::Locked)
else if (win && CursorLock == CursorLockMode::Clipped)
{ {
// Use mouse clip region to restrict the cursor in one spot
win->StartClippingCursor(Rectangle(bounds.GetCenter(), Float2(1, 1)));
}
else if (CursorLock == CursorLockMode::Locked || CursorLock == CursorLockMode::Clipped)
win->EndClippingCursor(); win->EndClippingCursor();
// Enable relative mode when cursor is restricted
if (mode != CursorLockMode::None)
Input::Mouse->SetRelativeMode(true, win);
else if (mode == CursorLockMode::None && inRelativeMode)
Input::Mouse->SetRelativeMode(false, win);
} }
CursorLock = mode; CursorLock = mode;
} }
@@ -190,7 +206,11 @@ void ScreenService::Update()
{ {
#if USE_EDITOR #if USE_EDITOR
// Sync current cursor state in Editor (eg. when viewport focus can change) // Sync current cursor state in Editor (eg. when viewport focus can change)
const auto win = Editor::Managed->GetGameWindow(true);
bool gameViewportFocus = win && Engine::HasGameViewportFocus();
if (gameViewportFocus != LastGameViewportFocus)
Screen::SetCursorVisible(CursorVisible); Screen::SetCursorVisible(CursorVisible);
LastGameViewportFocus = gameViewportFocus;
#endif #endif
} }

View File

@@ -16,6 +16,7 @@ API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API Time
friend class Engine; friend class Engine;
friend class TimeService; friend class TimeService;
friend class PhysicsSettings; friend class PhysicsSettings;
friend Window;
public: public:
/// <summary> /// <summary>

View File

@@ -4,6 +4,7 @@
#include "AndroidVulkanPlatform.h" #include "AndroidVulkanPlatform.h"
#include "../RenderToolsVulkan.h" #include "../RenderToolsVulkan.h"
#include "Engine/Platform/Window.h"
void AndroidVulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers) void AndroidVulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers)
{ {
@@ -17,8 +18,10 @@ void AndroidVulkanPlatform::GetDeviceExtensions(Array<const char*>& extensions,
extensions.Add(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); extensions.Add(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
} }
void AndroidVulkanPlatform::CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface) void AndroidVulkanPlatform::CreateSurface(Window* window, GPUDeviceVulkan* device, VkInstance instance, VkSurfaceKHR* surface)
{ {
ASSERT(window);
void* windowHandle = window->GetNativePtr();
ASSERT(windowHandle); ASSERT(windowHandle);
VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo; VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR); RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR);

View File

@@ -9,6 +9,8 @@
// Support more backbuffers in case driver decides to use more // Support more backbuffers in case driver decides to use more
#define VULKAN_BACK_BUFFERS_COUNT_MAX 8 #define VULKAN_BACK_BUFFERS_COUNT_MAX 8
class GPUDeviceVulkan;
/// <summary> /// <summary>
/// The implementation for the Vulkan API support for Android platform. /// The implementation for the Vulkan API support for Android platform.
/// </summary> /// </summary>
@@ -17,7 +19,7 @@ class AndroidVulkanPlatform : public VulkanPlatformBase
public: public:
static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers); static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers);
static void GetDeviceExtensions(Array<const char*>& extensions, Array<const char*>& layers); static void GetDeviceExtensions(Array<const char*>& extensions, Array<const char*>& layers);
static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface); static void CreateSurface(Window* window, GPUDeviceVulkan* device, VkInstance instance, VkSurfaceKHR* surface);
}; };
typedef AndroidVulkanPlatform VulkanPlatform; typedef AndroidVulkanPlatform VulkanPlatform;

View File

@@ -192,7 +192,7 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height)
ASSERT_LOW_LAYER(_backBuffers.Count() == 0); ASSERT_LOW_LAYER(_backBuffers.Count() == 0);
// Create platform-dependent surface // Create platform-dependent surface
VulkanPlatform::CreateSurface(windowHandle, GPUDeviceVulkan::Instance, &_surface); VulkanPlatform::CreateSurface(_window, _device, GPUDeviceVulkan::Instance, &_surface);
if (_surface == VK_NULL_HANDLE) if (_surface == VK_NULL_HANDLE)
{ {
LOG(Warning, "Failed to create Vulkan surface."); LOG(Warning, "Failed to create Vulkan surface.");
@@ -349,13 +349,22 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height)
LOG(Error, "Vulkan swapchain dimensions are invalid {}x{} (minImageExtent={}x{}, maxImageExtent={}x{})", width, height, surfProperties.minImageExtent.width, surfProperties.minImageExtent.height, surfProperties.maxImageExtent.width, surfProperties.maxImageExtent.height); LOG(Error, "Vulkan swapchain dimensions are invalid {}x{} (minImageExtent={}x{}, maxImageExtent={}x{})", width, height, surfProperties.minImageExtent.width, surfProperties.minImageExtent.height, surfProperties.maxImageExtent.width, surfProperties.maxImageExtent.height);
return true; return true;
} }
uint32_t backbuffersCount = VULKAN_BACK_BUFFERS_COUNT;
#if PLATFORM_SDL && PLATFORM_LINUX && USE_EDITOR
// Wayland compositor might block one of the backbuffers while the window is minimized or fully occluded,
// make sure we have at least 3 backbuffers available so double-buffering can be used while we are blocked.
if (Platform::UsesWayland())
backbuffersCount = Math::Max<uint32_t>(backbuffersCount, 3);
#endif
ASSERT(surfProperties.minImageCount <= VULKAN_BACK_BUFFERS_COUNT_MAX); ASSERT(surfProperties.minImageCount <= VULKAN_BACK_BUFFERS_COUNT_MAX);
VkSwapchainCreateInfoKHR swapChainInfo; VkSwapchainCreateInfoKHR swapChainInfo;
RenderToolsVulkan::ZeroStruct(swapChainInfo, VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR); RenderToolsVulkan::ZeroStruct(swapChainInfo, VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR);
swapChainInfo.surface = _surface; swapChainInfo.surface = _surface;
swapChainInfo.minImageCount = surfProperties.maxImageCount > 0 // A value of 0 means that there is no limit on the number of image swapChainInfo.minImageCount = surfProperties.maxImageCount > 0 // A value of 0 means that there is no limit on the number of image
? Math::Min<uint32_t>(VULKAN_BACK_BUFFERS_COUNT, surfProperties.maxImageCount) ? Math::Min<uint32_t>(backbuffersCount, surfProperties.maxImageCount)
: VULKAN_BACK_BUFFERS_COUNT; : backbuffersCount;
swapChainInfo.minImageCount = Math::Max<uint32_t>(swapChainInfo.minImageCount, surfProperties.minImageCount); swapChainInfo.minImageCount = Math::Max<uint32_t>(swapChainInfo.minImageCount, surfProperties.minImageCount);
swapChainInfo.minImageCount = Math::Min<uint32_t>(swapChainInfo.minImageCount, VULKAN_BACK_BUFFERS_COUNT_MAX); swapChainInfo.minImageCount = Math::Min<uint32_t>(swapChainInfo.minImageCount, VULKAN_BACK_BUFFERS_COUNT_MAX);
swapChainInfo.imageFormat = result.format; swapChainInfo.imageFormat = result.format;
@@ -374,7 +383,9 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height)
swapChainInfo.presentMode = presentMode; swapChainInfo.presentMode = presentMode;
swapChainInfo.clipped = VK_TRUE; swapChainInfo.clipped = VK_TRUE;
swapChainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; swapChainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
if (surfProperties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) if (_window->GetSettings().SupportsTransparency && surfProperties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR)
swapChainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
else if (surfProperties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)
swapChainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; swapChainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
// Create swap chain // Create swap chain

View File

@@ -4,62 +4,61 @@
#include "LinuxVulkanPlatform.h" #include "LinuxVulkanPlatform.h"
#include "../RenderToolsVulkan.h" #include "../RenderToolsVulkan.h"
#include "Engine/Platform/Window.h"
// Contents of vulkan\vulkan_xlib.h inlined here to prevent typename collisions with engine types due to X11 types
#include "Engine/Platform/Linux/IncludeX11.h" #include "Engine/Platform/Linux/IncludeX11.h"
#ifdef __cplusplus #define Display X11::Display
extern "C" { #define Window X11::Window
#endif #define VisualID X11::VisualID
#define VK_KHR_xlib_surface 1 #include "vulkan/vulkan_xlib.h"
#define VK_KHR_XLIB_SURFACE_SPEC_VERSION 6 #undef Display
#define VK_KHR_XLIB_SURFACE_EXTENSION_NAME "VK_KHR_xlib_surface" #undef Window
typedef VkFlags VkXlibSurfaceCreateFlagsKHR; #undef VisualID
typedef struct VkXlibSurfaceCreateInfoKHR #include "vulkan/vulkan_wayland.h"
{
VkStructureType sType;
const void* pNext;
VkXlibSurfaceCreateFlagsKHR flags;
X11::Display* dpy;
X11::Window window;
} VkXlibSurfaceCreateInfoKHR;
typedef VkResult (VKAPI_PTR *PFN_vkCreateXlibSurfaceKHR)(VkInstance instance, const VkXlibSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface); // Export extension from volk
typedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, X11::Display* dpy, X11::VisualID visualID);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkCreateXlibSurfaceKHR(
VkInstance instance,
const VkXlibSurfaceCreateInfoKHR* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSurfaceKHR* pSurface);
VKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceXlibPresentationSupportKHR(
VkPhysicalDevice physicalDevice,
uint32_t queueFamilyIndex,
Display* dpy,
VisualID visualID);
#endif
#ifdef __cplusplus
}
#endif
//
// Export X11 surface extension from volk
extern PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR; extern PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR;
extern PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR; extern PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR;
extern PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR;
extern PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR;
void LinuxVulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers) void LinuxVulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers)
{ {
extensions.Add(VK_KHR_SURFACE_EXTENSION_NAME); extensions.Add(VK_KHR_SURFACE_EXTENSION_NAME);
extensions.Add(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); extensions.Add(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
extensions.Add(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
} }
void LinuxVulkanPlatform::CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface) void LinuxVulkanPlatform::CreateSurface(Window* window, GPUDeviceVulkan* device, VkInstance instance, VkSurfaceKHR* surface)
{ {
#if !PLATFORM_SDL
void* windowHandle = window->GetNativePtr();
VkXlibSurfaceCreateInfoKHR surfaceCreateInfo; VkXlibSurfaceCreateInfoKHR surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR); RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR);
surfaceCreateInfo.dpy = (X11::Display*)LinuxPlatform::GetXDisplay(); surfaceCreateInfo.dpy = (X11::Display*)Platform::GetXDisplay();
surfaceCreateInfo.window = (X11::Window)windowHandle; surfaceCreateInfo.window = (X11::Window)windowHandle;
VALIDATE_VULKAN_RESULT(vkCreateXlibSurfaceKHR(instance, &surfaceCreateInfo, nullptr, surface)); VALIDATE_VULKAN_RESULT(vkCreateXlibSurfaceKHR(instance, &surfaceCreateInfo, nullptr, surface));
#else
SDLWindow* sdlWindow = static_cast<Window*>(window);
void* windowHandle = window->GetNativePtr();
if (SDLPlatform::UsesWayland())
{
VkWaylandSurfaceCreateInfoKHR surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR);
surfaceCreateInfo.display = (wl_display*)sdlWindow->GetWaylandDisplay();
surfaceCreateInfo.surface = (wl_surface*)windowHandle;
VALIDATE_VULKAN_RESULT(vkCreateWaylandSurfaceKHR(instance, &surfaceCreateInfo, nullptr, surface));
}
else if (SDLPlatform::UsesX11())
{
VkXlibSurfaceCreateInfoKHR surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR);
surfaceCreateInfo.dpy = (X11::Display*)sdlWindow->GetX11Display();
surfaceCreateInfo.window = (X11::Window)windowHandle;
VALIDATE_VULKAN_RESULT(vkCreateXlibSurfaceKHR(instance, &surfaceCreateInfo, nullptr, surface));
}
#endif
} }
#endif #endif

View File

@@ -12,6 +12,8 @@
// Prevent wierd error 'Invalid VkValidationCacheEXT Object' // Prevent wierd error 'Invalid VkValidationCacheEXT Object'
#define VULKAN_USE_VALIDATION_CACHE 0 #define VULKAN_USE_VALIDATION_CACHE 0
class GPUDeviceVulkan;
/// <summary> /// <summary>
/// The implementation for the Vulkan API support for Linux platform. /// The implementation for the Vulkan API support for Linux platform.
/// </summary> /// </summary>
@@ -19,7 +21,7 @@ class LinuxVulkanPlatform : public VulkanPlatformBase
{ {
public: public:
static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers); static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers);
static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* outSurface); static void CreateSurface(Window* window, GPUDeviceVulkan* device, VkInstance instance, VkSurfaceKHR* outSurface);
}; };
typedef LinuxVulkanPlatform VulkanPlatform; typedef LinuxVulkanPlatform VulkanPlatform;

View File

@@ -4,21 +4,40 @@
#include "MacVulkanPlatform.h" #include "MacVulkanPlatform.h"
#include "../RenderToolsVulkan.h" #include "../RenderToolsVulkan.h"
#include "Engine/Platform/Window.h"
#include "Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h"
#include <Cocoa/Cocoa.h> #include <Cocoa/Cocoa.h>
#include <QuartzCore/CAMetalLayer.h>
void MacVulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers) void MacVulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers)
{ {
extensions.Add(VK_KHR_SURFACE_EXTENSION_NAME); extensions.Add(VK_KHR_SURFACE_EXTENSION_NAME);
extensions.Add(VK_MVK_MACOS_SURFACE_EXTENSION_NAME); extensions.Add(VK_MVK_MACOS_SURFACE_EXTENSION_NAME);
extensions.Add(VK_EXT_METAL_SURFACE_EXTENSION_NAME);
} }
void MacVulkanPlatform::CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface) void MacVulkanPlatform::CreateSurface(Window* window, GPUDeviceVulkan* device, VkInstance instance, VkSurfaceKHR* surface)
{ {
NSWindow* window = (NSWindow*)windowHandle; void* windowHandle = window->GetNativePtr();
NSWindow* nswindow = (NSWindow*)windowHandle;
#if PLATFORM_SDL
nswindow.contentView.wantsLayer = YES;
nswindow.contentView.layer = [CAMetalLayer layer];
#endif
if (device->InstanceExtensions.Contains(VK_EXT_METAL_SURFACE_EXTENSION_NAME))
{
VkMetalSurfaceCreateInfoEXT surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT);
surfaceCreateInfo.pLayer = (CAMetalLayer*)nswindow.contentView.layer;
VALIDATE_VULKAN_RESULT(vkCreateMetalSurfaceEXT(instance, &surfaceCreateInfo, nullptr, surface));
}
else
{
VkMacOSSurfaceCreateInfoMVK surfaceCreateInfo; VkMacOSSurfaceCreateInfoMVK surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK); RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK);
surfaceCreateInfo.pView = (void*)window.contentView; surfaceCreateInfo.pView = (void*)nswindow.contentView;
VALIDATE_VULKAN_RESULT(vkCreateMacOSSurfaceMVK(instance, &surfaceCreateInfo, nullptr, surface)); VALIDATE_VULKAN_RESULT(vkCreateMacOSSurfaceMVK(instance, &surfaceCreateInfo, nullptr, surface));
}
} }
#endif #endif

View File

@@ -11,6 +11,8 @@
// General/Validation Error:0 VK_ERROR_INITIALIZATION_FAILED: Could not create MTLCounterSampleBuffer for query pool of type VK_QUERY_TYPE_TIMESTAMP. Reverting to emulated behavior. (Error code 0): Cannot allocate sample buffer // General/Validation Error:0 VK_ERROR_INITIALIZATION_FAILED: Could not create MTLCounterSampleBuffer for query pool of type VK_QUERY_TYPE_TIMESTAMP. Reverting to emulated behavior. (Error code 0): Cannot allocate sample buffer
#define VULKAN_USE_QUERIES 0 #define VULKAN_USE_QUERIES 0
class GPUDeviceVulkan;
/// <summary> /// <summary>
/// The implementation for the Vulkan API support for Mac platform. /// The implementation for the Vulkan API support for Mac platform.
/// </summary> /// </summary>
@@ -18,7 +20,7 @@ class MacVulkanPlatform : public VulkanPlatformBase
{ {
public: public:
static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers); static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers);
static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* outSurface); static void CreateSurface(Window* window, GPUDeviceVulkan* device, VkInstance instance, VkSurfaceKHR* outSurface);
}; };
typedef MacVulkanPlatform VulkanPlatform; typedef MacVulkanPlatform VulkanPlatform;

View File

@@ -6,6 +6,7 @@
#include "../RenderToolsVulkan.h" #include "../RenderToolsVulkan.h"
#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUDevice.h"
#include "Engine/Platform/Window.h"
void Win32VulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers) void Win32VulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers)
{ {
@@ -13,8 +14,9 @@ void Win32VulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions,
extensions.Add(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); extensions.Add(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
} }
void Win32VulkanPlatform::CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface) void Win32VulkanPlatform::CreateSurface(Window* window, GPUDeviceVulkan* device, VkInstance instance, VkSurfaceKHR* surface)
{ {
void* windowHandle = window->GetNativePtr();
VkWin32SurfaceCreateInfoKHR surfaceCreateInfo; VkWin32SurfaceCreateInfoKHR surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR); RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR);
surfaceCreateInfo.hinstance = GetModuleHandle(nullptr); surfaceCreateInfo.hinstance = GetModuleHandle(nullptr);

View File

@@ -10,6 +10,8 @@
#define VULKAN_USE_PLATFORM_WIN32_KHX 1 #define VULKAN_USE_PLATFORM_WIN32_KHX 1
#define VULKAN_USE_CREATE_WIN32_SURFACE 1 #define VULKAN_USE_CREATE_WIN32_SURFACE 1
class GPUDeviceVulkan;
/// <summary> /// <summary>
/// The implementation for the Vulkan API support for Win32 platform. /// The implementation for the Vulkan API support for Win32 platform.
/// </summary> /// </summary>
@@ -17,7 +19,7 @@ class Win32VulkanPlatform : public VulkanPlatformBase
{ {
public: public:
static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers); static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers);
static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface); static void CreateSurface(Window* window, GPUDeviceVulkan* device, VkInstance instance, VkSurfaceKHR* surface);
}; };
typedef Win32VulkanPlatform VulkanPlatform; typedef Win32VulkanPlatform VulkanPlatform;

View File

@@ -5,6 +5,7 @@
#include "iOSVulkanPlatform.h" #include "iOSVulkanPlatform.h"
#include "../RenderToolsVulkan.h" #include "../RenderToolsVulkan.h"
#include "Engine/Core/Delegate.h" #include "Engine/Core/Delegate.h"
#include "Engine/Platform/Window.h"
#include <UIKit/UIKit.h> #include <UIKit/UIKit.h>
void iOSVulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers) void iOSVulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers)
@@ -13,8 +14,9 @@ void iOSVulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Ar
extensions.Add(VK_MVK_IOS_SURFACE_EXTENSION_NAME); extensions.Add(VK_MVK_IOS_SURFACE_EXTENSION_NAME);
} }
void iOSVulkanPlatform::CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface) void iOSVulkanPlatform::CreateSurface(Window* window, GPUDeviceVulkan* device, VkInstance instance, VkSurfaceKHR* surface)
{ {
void* windowHandle = window->GetNativePtr();
// Create surface on a main UI Thread // Create surface on a main UI Thread
Function<void()> func = [&windowHandle, &instance, &surface]() Function<void()> func = [&windowHandle, &instance, &surface]()
{ {

View File

@@ -11,6 +11,8 @@
// General/Validation Error:0 VK_ERROR_INITIALIZATION_FAILED: Could not create MTLCounterSampleBuffer for query pool of type VK_QUERY_TYPE_TIMESTAMP. Reverting to emulated behavior. (Error code 0): Cannot allocate sample buffer // General/Validation Error:0 VK_ERROR_INITIALIZATION_FAILED: Could not create MTLCounterSampleBuffer for query pool of type VK_QUERY_TYPE_TIMESTAMP. Reverting to emulated behavior. (Error code 0): Cannot allocate sample buffer
#define VULKAN_USE_QUERIES 0 #define VULKAN_USE_QUERIES 0
class GPUDeviceVulkan;
/// <summary> /// <summary>
/// The implementation for the Vulkan API support for iOS platform. /// The implementation for the Vulkan API support for iOS platform.
/// </summary> /// </summary>
@@ -18,7 +20,7 @@ class iOSVulkanPlatform : public VulkanPlatformBase
{ {
public: public:
static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers); static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers);
static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* outSurface); static void CreateSurface(Window* window, GPUDeviceVulkan* device, VkInstance instance, VkSurfaceKHR* outSurface);
}; };
typedef iOSVulkanPlatform VulkanPlatform; typedef iOSVulkanPlatform VulkanPlatform;

View File

@@ -78,6 +78,7 @@ Delegate<const Float2&, MouseButton> Input::MouseUp;
Delegate<const Float2&, MouseButton> Input::MouseDoubleClick; Delegate<const Float2&, MouseButton> Input::MouseDoubleClick;
Delegate<const Float2&, float> Input::MouseWheel; Delegate<const Float2&, float> Input::MouseWheel;
Delegate<const Float2&> Input::MouseMove; Delegate<const Float2&> Input::MouseMove;
Delegate<const Float2&> Input::MouseMoveRelative;
Action Input::MouseLeave; Action Input::MouseLeave;
Delegate<const Float2&, int32> Input::TouchDown; Delegate<const Float2&, int32> Input::TouchDown;
Delegate<const Float2&, int32> Input::TouchMove; Delegate<const Float2&, int32> Input::TouchMove;
@@ -208,6 +209,14 @@ void Mouse::OnMouseMove(const Float2& position, Window* target)
e.MouseData.Position = position; e.MouseData.Position = position;
} }
void Mouse::OnMouseMoveRelative(const Float2& positionRelative, Window* target)
{
Event& e = _queue.AddOne();
e.Type = EventType::MouseMoveRelative;
e.Target = target;
e.MouseMovementData.PositionRelative = positionRelative;
}
void Mouse::OnMouseLeave(Window* target) void Mouse::OnMouseLeave(Window* target)
{ {
Event& e = _queue.AddOne(); Event& e = _queue.AddOne();
@@ -273,6 +282,11 @@ bool Mouse::Update(EventQueue& queue)
_state.MousePosition = e.MouseData.Position; _state.MousePosition = e.MouseData.Position;
break; break;
} }
case EventType::MouseMoveRelative:
{
_state.MousePosition += e.MouseMovementData.PositionRelative;
break;
}
case EventType::MouseLeave: case EventType::MouseLeave:
{ {
break; break;
@@ -899,11 +913,10 @@ void InputService::Update()
WindowsManager::WindowsLocker.Unlock(); WindowsManager::WindowsLocker.Unlock();
// Send input events for the focused window // Send input events for the focused window
WindowsManager::WindowsLocker.Lock();
for (const auto& e : InputEvents) for (const auto& e : InputEvents)
{ {
auto window = e.Target ? e.Target : defaultWindow; auto window = e.Target ? e.Target : defaultWindow;
if (!window || !WindowsManager::Windows.Contains(window)) if (!window || window->IsClosed())
continue; continue;
switch (e.Type) switch (e.Type)
{ {
@@ -933,6 +946,9 @@ void InputService::Update()
case InputDevice::EventType::MouseMove: case InputDevice::EventType::MouseMove:
window->OnMouseMove(window->ScreenToClient(e.MouseData.Position)); window->OnMouseMove(window->ScreenToClient(e.MouseData.Position));
break; break;
case InputDevice::EventType::MouseMoveRelative:
window->OnMouseMoveRelative(e.MouseMovementData.PositionRelative);
break;
case InputDevice::EventType::MouseLeave: case InputDevice::EventType::MouseLeave:
window->OnMouseLeave(); window->OnMouseLeave();
break; break;
@@ -948,7 +964,6 @@ void InputService::Update()
break; break;
} }
} }
WindowsManager::WindowsLocker.Unlock();
// Skip if game has no focus to handle the input // Skip if game has no focus to handle the input
if (!Engine::HasGameViewportFocus()) if (!Engine::HasGameViewportFocus())
@@ -989,6 +1004,9 @@ void InputService::Update()
case InputDevice::EventType::MouseMove: case InputDevice::EventType::MouseMove:
Input::MouseMove(e.MouseData.Position); Input::MouseMove(e.MouseData.Position);
break; break;
case InputDevice::EventType::MouseMoveRelative:
Input::MouseMoveRelative(e.MouseMovementData.PositionRelative);
break;
case InputDevice::EventType::MouseLeave: case InputDevice::EventType::MouseLeave:
Input::MouseLeave(); Input::MouseLeave();
break; break;
@@ -1202,6 +1220,7 @@ void InputService::Update()
} }
} }
#if !PLATFORM_SDL
// Lock mouse if need to // Lock mouse if need to
const auto lockMode = Screen::GetCursorLock(); const auto lockMode = Screen::GetCursorLock();
if (lockMode == CursorLockMode::Locked) if (lockMode == CursorLockMode::Locked)
@@ -1210,6 +1229,7 @@ void InputService::Update()
Screen::ScreenToGameViewport(Float2::Zero); Screen::ScreenToGameViewport(Float2::Zero);
Input::SetMousePosition(pos); Input::SetMousePosition(pos);
} }
#endif
// Send events for the active actions and axes (send events only in play mode) // Send events for the active actions and axes (send events only in play mode)
if (!Time::GetGamePaused()) if (!Time::GetGamePaused())

View File

@@ -108,6 +108,11 @@ public:
/// </summary> /// </summary>
API_EVENT() static Delegate<const Float2&> MouseMove; API_EVENT() static Delegate<const Float2&> MouseMove;
/// <summary>
/// Event fired when mouse moves while in relative mode.
/// </summary>
API_EVENT() static Delegate<const Float2&> MouseMoveRelative;
/// <summary> /// <summary>
/// Event fired when mouse leaves window. /// Event fired when mouse leaves window.
/// </summary> /// </summary>

View File

@@ -25,6 +25,7 @@ public:
MouseDoubleClick, MouseDoubleClick,
MouseWheel, MouseWheel,
MouseMove, MouseMove,
MouseMoveRelative,
MouseLeave, MouseLeave,
TouchDown, TouchDown,
TouchMove, TouchMove,
@@ -54,6 +55,11 @@ public:
Float2 Position; Float2 Position;
} MouseData; } MouseData;
struct
{
Float2 PositionRelative;
} MouseMovementData;
struct struct
{ {
float WheelDelta; float WheelDelta;

View File

@@ -46,12 +46,14 @@ public:
protected: protected:
State _state; State _state;
State _prevState; State _prevState;
bool _relativeMode;
explicit Mouse() explicit Mouse()
: InputDevice(SpawnParams(Guid::New(), TypeInitializer), TEXT("Mouse")) : InputDevice(SpawnParams(Guid::New(), TypeInitializer), TEXT("Mouse"))
{ {
_state.Clear(); _state.Clear();
_prevState.Clear(); _prevState.Clear();
_relativeMode = false;
} }
public: public:
@@ -114,6 +116,16 @@ public:
return !_state.MouseButtons[static_cast<int32>(button)] && _prevState.MouseButtons[static_cast<int32>(button)]; return !_state.MouseButtons[static_cast<int32>(button)] && _prevState.MouseButtons[static_cast<int32>(button)];
} }
/// <summary>
/// Gets the current state of mouse relative mode.
/// </summary>
/// <param name="window">The window to check against, or null to check for any window.</param>
/// <returns>True if mouse is in relative mode, otherwise false.</returns>
API_FUNCTION() virtual bool IsRelative(Window* window = nullptr) const
{
return _relativeMode;
}
public: public:
/// <summary> /// <summary>
/// Sets the mouse position. /// Sets the mouse position.
@@ -121,6 +133,17 @@ public:
/// <param name="newPosition">The new position.</param> /// <param name="newPosition">The new position.</param>
virtual void SetMousePosition(const Float2& newPosition) = 0; virtual void SetMousePosition(const Float2& newPosition) = 0;
/// <summary>
/// Sets the mouse relative mode state. While enabled, the mouse movement tracking becomes more accurate.
/// The cursor will be hidden while in relative mode.
/// </summary>
/// <param name="relativeMode">The new relative mode state.</param>
/// <param name="window">The window.</param>
virtual void SetRelativeMode(bool relativeMode, Window* window)
{
_relativeMode = relativeMode;
}
/// <summary> /// <summary>
/// Called when mouse cursor gets moved by the application. Invalidates the previous cached mouse position to prevent mouse jitter when locking the cursor programmatically. /// Called when mouse cursor gets moved by the application. Invalidates the previous cached mouse position to prevent mouse jitter when locking the cursor programmatically.
/// </summary> /// </summary>
@@ -158,6 +181,13 @@ public:
/// <param name="target">The target window to receive this event, otherwise input system will pick the window automatically.</param> /// <param name="target">The target window to receive this event, otherwise input system will pick the window automatically.</param>
void OnMouseMove(const Float2& position, Window* target = nullptr); void OnMouseMove(const Float2& position, Window* target = nullptr);
/// <summary>
/// Called when mouse moves in relative mode.
/// </summary>
/// <param name="positionRelative">The mouse position change.</param>
/// <param name="target">The target window to receive this event, otherwise input system will pick the window automatically.</param>
void OnMouseMoveRelative(const Float2& positionRelative, Window* target = nullptr);
/// <summary> /// <summary>
/// Called when mouse leaves the input source area. /// Called when mouse leaves the input source area.
/// </summary> /// </summary>

View File

@@ -0,0 +1,251 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Config.h"
/// <summary>
/// Window closing reasons.
/// </summary>
API_ENUM() enum class ClosingReason
{
/// <summary>
/// The unknown.
/// </summary>
Unknown = 0,
/// <summary>
/// The user.
/// </summary>
User,
/// <summary>
/// The engine exit.
/// </summary>
EngineExit,
/// <summary>
/// The close event.
/// </summary>
CloseEvent,
};
/// <summary>
/// Types of default cursors.
/// </summary>
API_ENUM() enum class CursorType
{
/// <summary>
/// The default.
/// </summary>
Default = 0,
/// <summary>
/// The cross.
/// </summary>
Cross,
/// <summary>
/// The hand.
/// </summary>
Hand,
/// <summary>
/// The help icon
/// </summary>
Help,
/// <summary>
/// The I beam.
/// </summary>
IBeam,
/// <summary>
/// The blocking image.
/// </summary>
No,
/// <summary>
/// The wait.
/// </summary>
Wait,
/// <summary>
/// The size all sides.
/// </summary>
SizeAll,
/// <summary>
/// The size NE-SW.
/// </summary>
SizeNESW,
/// <summary>
/// The size NS.
/// </summary>
SizeNS,
/// <summary>
/// The size NW-SE.
/// </summary>
SizeNWSE,
/// <summary>
/// The size WE.
/// </summary>
SizeWE,
/// <summary>
/// The cursor is hidden.
/// </summary>
Hidden,
MAX
};
/// <summary>
/// Data drag and drop effects.
/// </summary>
API_ENUM() enum class DragDropEffect
{
/// <summary>
/// The none.
/// </summary>
None = 0,
/// <summary>
/// The copy.
/// </summary>
Copy,
/// <summary>
/// The move.
/// </summary>
Move,
/// <summary>
/// The link.
/// </summary>
Link,
};
/// <summary>
/// Window hit test codes. Note: they are 1:1 mapping for Win32 values.
/// </summary>
API_ENUM() enum class WindowHitCodes
{
/// <summary>
/// The transparent area.
/// </summary>
Transparent = -1,
/// <summary>
/// The no hit.
/// </summary>
NoWhere = 0,
/// <summary>
/// The client area.
/// </summary>
Client = 1,
/// <summary>
/// The caption area.
/// </summary>
Caption = 2,
/// <summary>
/// The system menu.
/// </summary>
SystemMenu = 3,
/// <summary>
/// The grow box
/// </summary>
GrowBox = 4,
/// <summary>
/// The menu.
/// </summary>
Menu = 5,
/// <summary>
/// The horizontal scroll.
/// </summary>
HScroll = 6,
/// <summary>
/// The vertical scroll.
/// </summary>
VScroll = 7,
/// <summary>
/// The minimize button.
/// </summary>
MinButton = 8,
/// <summary>
/// The maximize button.
/// </summary>
MaxButton = 9,
/// <summary>
/// The left side;
/// </summary>
Left = 10,
/// <summary>
/// The right side.
/// </summary>
Right = 11,
/// <summary>
/// The top side.
/// </summary>
Top = 12,
/// <summary>
/// The top left corner.
/// </summary>
TopLeft = 13,
/// <summary>
/// The top right corner.
/// </summary>
TopRight = 14,
/// <summary>
/// The bottom side.
/// </summary>
Bottom = 15,
/// <summary>
/// The bottom left corner.
/// </summary>
BottomLeft = 16,
/// <summary>
/// The bottom right corner.
/// </summary>
BottomRight = 17,
/// <summary>
/// The border.
/// </summary>
Border = 18,
/// <summary>
/// The object.
/// </summary>
Object = 19,
/// <summary>
/// The close button.
/// </summary>
Close = 20,
/// <summary>
/// The help button.
/// </summary>
Help = 21,
};

View File

@@ -45,6 +45,7 @@ static_assert(sizeof(double) == 8, "Invalid double type size.");
static_assert((PLATFORM_THREADS_LIMIT & (PLATFORM_THREADS_LIMIT - 1)) == 0, "Threads limit must be power of two."); static_assert((PLATFORM_THREADS_LIMIT & (PLATFORM_THREADS_LIMIT - 1)) == 0, "Threads limit must be power of two.");
static_assert(PLATFORM_THREADS_LIMIT % 4 == 0, "Threads limit must be multiple of 4."); static_assert(PLATFORM_THREADS_LIMIT % 4 == 0, "Threads limit must be multiple of 4.");
const Char* PlatformBase::ApplicationClassName = TEXT("FlaxWindow");
float PlatformBase::CustomDpiScale = 1.0f; float PlatformBase::CustomDpiScale = 1.0f;
Array<User*, FixedAllocation<8>> PlatformBase::Users; Array<User*, FixedAllocation<8>> PlatformBase::Users;
Delegate<User*> PlatformBase::UserAdded; Delegate<User*> PlatformBase::UserAdded;
@@ -261,6 +262,15 @@ PlatformType PlatformBase::GetPlatformType()
return PLATFORM_TYPE; return PLATFORM_TYPE;
} }
#if !PLATFORM_SDL
String PlatformBase::GetDisplayServer()
{
return String::Empty;
}
#endif
bool PlatformBase::Is64BitApp() bool PlatformBase::Is64BitApp()
{ {
#if PLATFORM_64BITS #if PLATFORM_64BITS

View File

@@ -190,6 +190,13 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(PlatformBase);
/// </summary> /// </summary>
static void Exit(); static void Exit();
public:
/// <summary>
/// Application windows class name.
/// </summary>
static const Char* ApplicationClassName;
public: public:
/// <summary> /// <summary>
/// Copy memory region /// Copy memory region
@@ -362,6 +369,11 @@ public:
/// </summary> /// </summary>
API_PROPERTY() static PlatformType GetPlatformType(); API_PROPERTY() static PlatformType GetPlatformType();
/// <summary>
/// Returns the display server name on Linux.
/// </summary>
API_PROPERTY() static String GetDisplayServer() = delete;
/// <summary> /// <summary>
/// Returns true if is running 64 bit application (otherwise 32 bit). It's compile-time constant. /// Returns true if is running 64 bit application (otherwise 32 bit). It's compile-time constant.
/// </summary> /// </summary>

View File

@@ -0,0 +1,42 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Types/BaseTypes.h"
#include "Engine/Core/Math/Color32.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Core/Delegate.h"
API_INJECT_CODE(cpp, "#include \"Engine/Platform/ScreenUtilities.h\"");
/// <summary>
/// Platform-dependent screen utilities.
/// </summary>
API_CLASS(Static, Name="ScreenUtilities", Tag="NativeInvokeUseName")
class FLAXENGINE_API ScreenUtilitiesBase
{
public:
static struct FLAXENGINE_API ScriptingTypeInitializer TypeInitializer;
/// <summary>
/// Gets the pixel color at the specified coordinates.
/// </summary>
/// <param name="pos">Screen-space coordinate to read.</param>
/// <returns>Pixel color at the specified coordinates.</returns>
API_FUNCTION() static Color32 GetColorAt(const Float2& pos)
{
return Color32::Transparent;
}
/// <summary>
/// Starts async color picking. Color will be returned through PickColorDone event when the actions ends (user selected the final color with a mouse). When action is active, GetColorAt can be used to read the current value.
/// </summary>
API_FUNCTION() static void PickColor()
{
}
/// <summary>
/// Called when PickColor action is finished.
/// </summary>
API_EVENT() static Delegate<Color32> PickColorDone;
};

View File

@@ -106,7 +106,7 @@ WindowBase::WindowBase(const CreateWindowSettings& settings)
if (settings.StartPosition == WindowStartPosition::CenterParent if (settings.StartPosition == WindowStartPosition::CenterParent
|| settings.StartPosition == WindowStartPosition::CenterScreen) || settings.StartPosition == WindowStartPosition::CenterScreen)
{ {
Rectangle parentBounds = Rectangle(Float2::Zero, Platform::GetDesktopSize()); Rectangle parentBounds = Platform::GetMonitorBounds(Float2::Minimum);
if (settings.Parent != nullptr && settings.StartPosition == WindowStartPosition::CenterParent) if (settings.Parent != nullptr && settings.StartPosition == WindowStartPosition::CenterParent)
parentBounds = settings.Parent->GetClientBounds(); parentBounds = settings.Parent->GetClientBounds();
@@ -164,6 +164,15 @@ void WindowBase::SetIsVisible(bool isVisible)
} }
} }
bool WindowBase::IsAlwaysOnTop() const
{
return false;
}
void WindowBase::SetIsAlwaysOnTop(bool isAlwaysOnTop)
{
}
String WindowBase::ToString() const String WindowBase::ToString() const
{ {
return GetTitle(); return GetTitle();
@@ -257,6 +266,13 @@ void WindowBase::OnMouseMove(const Float2& mousePosition)
INVOKE_EVENT_PARAMS_1(OnMouseMove, (void*)&mousePosition); INVOKE_EVENT_PARAMS_1(OnMouseMove, (void*)&mousePosition);
} }
void WindowBase::OnMouseMoveRelative(const Float2& mousePositionRelative)
{
PROFILE_CPU_NAMED("GUI.OnMouseMoveRelative");
MouseMoveRelative(mousePositionRelative);
INVOKE_EVENT_PARAMS_1(OnMouseMoveRelative, (void*)&mousePositionRelative);
}
void WindowBase::OnMouseLeave() void WindowBase::OnMouseLeave()
{ {
PROFILE_CPU_NAMED("GUI.OnMouseLeave"); PROFILE_CPU_NAMED("GUI.OnMouseLeave");

View File

@@ -8,6 +8,7 @@
#include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Input/KeyboardKeys.h" #include "Engine/Input/KeyboardKeys.h"
#include "Engine/Input/Enums.h" #include "Engine/Input/Enums.h"
#include "Enums.h"
class Input; class Input;
class Engine; class Engine;
@@ -17,252 +18,6 @@ class GPUSwapChain;
class TextureData; class TextureData;
class IGuiData; class IGuiData;
/// <summary>
/// Window closing reasons.
/// </summary>
API_ENUM() enum class ClosingReason
{
/// <summary>
/// The unknown.
/// </summary>
Unknown = 0,
/// <summary>
/// The user.
/// </summary>
User,
/// <summary>
/// The engine exit.
/// </summary>
EngineExit,
/// <summary>
/// The close event.
/// </summary>
CloseEvent,
};
/// <summary>
/// Types of default cursors.
/// </summary>
API_ENUM() enum class CursorType
{
/// <summary>
/// The default.
/// </summary>
Default = 0,
/// <summary>
/// The cross.
/// </summary>
Cross,
/// <summary>
/// The hand.
/// </summary>
Hand,
/// <summary>
/// The help icon
/// </summary>
Help,
/// <summary>
/// The I beam.
/// </summary>
IBeam,
/// <summary>
/// The blocking image.
/// </summary>
No,
/// <summary>
/// The wait.
/// </summary>
Wait,
/// <summary>
/// The size all sides.
/// </summary>
SizeAll,
/// <summary>
/// The size NE-SW.
/// </summary>
SizeNESW,
/// <summary>
/// The size NS.
/// </summary>
SizeNS,
/// <summary>
/// The size NW-SE.
/// </summary>
SizeNWSE,
/// <summary>
/// The size WE.
/// </summary>
SizeWE,
/// <summary>
/// The cursor is hidden.
/// </summary>
Hidden,
MAX
};
/// <summary>
/// Data drag and drop effects.
/// </summary>
API_ENUM() enum class DragDropEffect
{
/// <summary>
/// The none.
/// </summary>
None = 0,
/// <summary>
/// The copy.
/// </summary>
Copy,
/// <summary>
/// The move.
/// </summary>
Move,
/// <summary>
/// The link.
/// </summary>
Link,
};
/// <summary>
/// Window hit test codes. Note: they are 1:1 mapping for Win32 values.
/// </summary>
API_ENUM() enum class WindowHitCodes
{
/// <summary>
/// The transparent area.
/// </summary>
Transparent = -1,
/// <summary>
/// The no hit.
/// </summary>
NoWhere = 0,
/// <summary>
/// The client area.
/// </summary>
Client = 1,
/// <summary>
/// The caption area.
/// </summary>
Caption = 2,
/// <summary>
/// The system menu.
/// </summary>
SystemMenu = 3,
/// <summary>
/// The grow box
/// </summary>
GrowBox = 4,
/// <summary>
/// The menu.
/// </summary>
Menu = 5,
/// <summary>
/// The horizontal scroll.
/// </summary>
HScroll = 6,
/// <summary>
/// The vertical scroll.
/// </summary>
VScroll = 7,
/// <summary>
/// The minimize button.
/// </summary>
MinButton = 8,
/// <summary>
/// The maximize button.
/// </summary>
MaxButton = 9,
/// <summary>
/// The left side;
/// </summary>
Left = 10,
/// <summary>
/// The right side.
/// </summary>
Right = 11,
/// <summary>
/// The top side.
/// </summary>
Top = 12,
/// <summary>
/// The top left corner.
/// </summary>
TopLeft = 13,
/// <summary>
/// The top right corner.
/// </summary>
TopRight = 14,
/// <summary>
/// The bottom side.
/// </summary>
Bottom = 15,
/// <summary>
/// The bottom left corner.
/// </summary>
BottomLeft = 16,
/// <summary>
/// The bottom right corner.
/// </summary>
BottomRight = 17,
/// <summary>
/// The border.
/// </summary>
Border = 18,
/// <summary>
/// The object.
/// </summary>
Object = 19,
/// <summary>
/// The close button.
/// </summary>
Close = 20,
/// <summary>
/// The help button.
/// </summary>
Help = 21,
};
API_INJECT_CODE(cpp, "#include \"Engine/Platform/Window.h\""); API_INJECT_CODE(cpp, "#include \"Engine/Platform/Window.h\"");
/// <summary> /// <summary>
@@ -290,6 +45,7 @@ protected:
bool _isHorizontalFlippingMouse = false; bool _isHorizontalFlippingMouse = false;
bool _isVerticalFlippingMouse = false; bool _isVerticalFlippingMouse = false;
bool _isClippingCursor = false; bool _isClippingCursor = false;
bool _restoreRelativeMode = false;
explicit WindowBase(const CreateWindowSettings& settings); explicit WindowBase(const CreateWindowSettings& settings);
virtual ~WindowBase(); virtual ~WindowBase();
@@ -402,6 +158,17 @@ public:
return _maximized; return _maximized;
} }
/// <summary>
/// Gets a value that indicates whether a window is always on top of other windows.
/// </summary>
API_PROPERTY() virtual bool IsAlwaysOnTop() const;
/// <summary>
/// Sets a value that indicates whether a window is always on top of other windows.
/// </summary>
/// <param name="isAlwaysOnTop">True if always on top.</param>
API_PROPERTY() virtual void SetIsAlwaysOnTop(bool isAlwaysOnTop);
/// <summary> /// <summary>
/// Gets the native window handle. /// Gets the native window handle.
/// </summary> /// </summary>
@@ -663,7 +430,7 @@ public:
public: public:
/// <summary> /// <summary>
/// Starts drag and drop operation /// Starts a drag and drop operation.
/// </summary> /// </summary>
/// <param name="data">The data.</param> /// <param name="data">The data.</param>
/// <returns>The result.</returns> /// <returns>The result.</returns>
@@ -672,6 +439,18 @@ public:
return DragDropEffect::None; return DragDropEffect::None;
} }
/// <summary>
/// Starts a window drag and drop operation.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="offset">The offset for positioning the window from cursor.</param>
/// <param name="dragSourceWindow">The window where dragging started.</param>
/// <returns>The result.</returns>
API_FUNCTION() virtual DragDropEffect DoDragDrop(const StringView& data, const Float2& offset, Window* dragSourceWindow)
{
return DragDropEffect::None;
}
/// <summary> /// <summary>
/// Starts the mouse tracking. /// Starts the mouse tracking.
/// </summary> /// </summary>
@@ -742,6 +521,17 @@ public:
{ {
} }
/// <summary>
/// Gets the mouse position in window coordinates.
/// </summary>
API_PROPERTY() virtual Float2 GetMousePosition() const;
/// <summary>
/// Sets the mouse position in window coordinates.
/// </summary>
/// <param name="position">Mouse position to set on</param>
API_PROPERTY() virtual void SetMousePosition(const Float2& position) const;
/// <summary> /// <summary>
/// Gets the mouse cursor. /// Gets the mouse cursor.
/// </summary> /// </summary>
@@ -837,6 +627,12 @@ public:
MouseDelegate MouseMove; MouseDelegate MouseMove;
void OnMouseMove(const Float2& mousePosition); void OnMouseMove(const Float2& mousePosition);
/// <summary>
/// Event fired when mouse moves in relative mode.
/// </summary>
MouseDelegate MouseMoveRelative;
void OnMouseMoveRelative(const Float2& mousePositionRelative);
/// <summary> /// <summary>
/// Event fired when mouse leaves window. /// Event fired when mouse leaves window.
/// </summary> /// </summary>
@@ -932,16 +728,6 @@ public:
API_FUNCTION() bool GetKeyUp(KeyboardKeys key) const; API_FUNCTION() bool GetKeyUp(KeyboardKeys key) const;
public: public:
/// <summary>
/// Gets the mouse position in window coordinates.
/// </summary>
API_PROPERTY() Float2 GetMousePosition() const;
/// <summary>
/// Sets the mouse position in window coordinates.
/// </summary>
/// <param name="position">Mouse position to set on</param>
API_PROPERTY() void SetMousePosition(const Float2& position) const;
/// <summary> /// <summary>
/// Gets the mouse position change during the last frame. /// Gets the mouse position change during the last frame.

View File

@@ -2,7 +2,9 @@
#pragma once #pragma once
#if PLATFORM_WINDOWS #if PLATFORM_SDL && PLATFORM_LINUX
#include "SDL/SDLClipboard.h"
#elif PLATFORM_WINDOWS
#include "Windows/WindowsClipboard.h" #include "Windows/WindowsClipboard.h"
#elif PLATFORM_LINUX #elif PLATFORM_LINUX
#include "Linux/LinuxClipboard.h" #include "Linux/LinuxClipboard.h"

View File

@@ -11,7 +11,7 @@ namespace FlaxEngine
{ {
Position = new Float2(100, 100), Position = new Float2(100, 100),
Size = new Float2(640, 480), Size = new Float2(640, 480),
MinimumSize = Float2.One, MinimumSize = new Float2(24, 24),
MaximumSize = Float2.Zero, // Unlimited size MaximumSize = Float2.Zero, // Unlimited size
StartPosition = WindowStartPosition.CenterParent, StartPosition = WindowStartPosition.CenterParent,
HasBorder = true, HasBorder = true,
@@ -21,7 +21,7 @@ namespace FlaxEngine
AllowMinimize = true, AllowMinimize = true,
AllowMaximize = true, AllowMaximize = true,
AllowDragAndDrop = true, AllowDragAndDrop = true,
IsRegularWindow = true, Type = WindowType.Regular,
HasSizingFrame = true, HasSizingFrame = true,
ShowAfterFirstPaint = true, ShowAfterFirstPaint = true,
}; };

View File

@@ -26,6 +26,32 @@ API_ENUM() enum class WindowStartPosition
Manual, Manual,
}; };
/// <summary>
/// Specifies the type of the window.
/// </summary>
API_ENUM() enum class WindowType
{
/// <summary>
/// Regular window.
/// </summary>
Regular,
/// <summary>
/// Utility window.
/// </summary>
Utility,
/// <summary>
/// Tooltip window.
/// </summary>
Tooltip,
/// <summary>
/// Popup window.
/// </summary>
Popup,
};
/// <summary> /// <summary>
/// Settings for new window. /// Settings for new window.
/// </summary> /// </summary>
@@ -119,9 +145,15 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(CreateWindowSettings);
API_FIELD() bool IsTopmost = false; API_FIELD() bool IsTopmost = false;
/// <summary> /// <summary>
/// True if it's a regular window, false for tooltips, contextmenu and other utility windows. /// True if it's a regular window, false for tooltips, context menu and other utility windows.
/// </summary> /// </summary>
API_FIELD() bool IsRegularWindow = true; API_FIELD() DEPRECATED("Use Type instead") bool IsRegularWindow = true;
/// <summary>
/// The type of window. The type affects the behaviour of the window in system level.
/// Note: Tooltip and Popup windows require Parent to be set.
/// </summary>
API_FIELD() WindowType Type = WindowType::Regular;
/// <summary> /// <summary>
/// Enable/disable window sizing frame. /// Enable/disable window sizing frame.

View File

@@ -140,6 +140,9 @@ API_ENUM() enum class ArchitectureType
#if !defined(PLATFORM_SWITCH) #if !defined(PLATFORM_SWITCH)
#define PLATFORM_SWITCH 0 #define PLATFORM_SWITCH 0
#endif #endif
#if !defined(PLATFORM_SDL)
#define PLATFORM_SDL 0
#endif
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS
#include "Windows/WindowsDefines.h" #include "Windows/WindowsDefines.h"

View File

@@ -30,7 +30,6 @@ inline bool operator==(const APP_LOCAL_DEVICE_ID& l, const APP_LOCAL_DEVICE_ID&
return Platform::MemoryCompare(&l, &r, sizeof(APP_LOCAL_DEVICE_ID)) == 0; return Platform::MemoryCompare(&l, &r, sizeof(APP_LOCAL_DEVICE_ID)) == 0;
} }
const Char* GDKPlatform::ApplicationWindowClass = TEXT("FlaxWindow");
void* GDKPlatform::Instance = nullptr; void* GDKPlatform::Instance = nullptr;
Delegate<> GDKPlatform::Suspended; Delegate<> GDKPlatform::Suspended;
Delegate<> GDKPlatform::Resumed; Delegate<> GDKPlatform::Resumed;
@@ -317,7 +316,7 @@ void GDKPlatform::PreInit(void* hInstance)
windowsClass.style = CS_HREDRAW | CS_VREDRAW; windowsClass.style = CS_HREDRAW | CS_VREDRAW;
windowsClass.lpfnWndProc = WndProc; windowsClass.lpfnWndProc = WndProc;
windowsClass.hInstance = (HINSTANCE)Instance; windowsClass.hInstance = (HINSTANCE)Instance;
windowsClass.lpszClassName = ApplicationWindowClass; windowsClass.lpszClassName = ApplicationClassName;
if (!RegisterClassW(&windowsClass)) if (!RegisterClassW(&windowsClass))
{ {
Error(TEXT("Window class registration failed!")); Error(TEXT("Window class registration failed!"));
@@ -477,7 +476,7 @@ void GDKPlatform::Exit()
if (PlmSignalResume) if (PlmSignalResume)
CloseHandle(PlmSignalResume); CloseHandle(PlmSignalResume);
UnregisterClassW(ApplicationWindowClass, nullptr); UnregisterClassW(ApplicationClassName, nullptr);
XGameRuntimeUninitialize(); XGameRuntimeUninitialize();
} }

View File

@@ -13,11 +13,6 @@ class FLAXENGINE_API GDKPlatform : public Win32Platform
{ {
public: public:
/// <summary>
/// Win32 application windows class name.
/// </summary>
static const Char* ApplicationWindowClass;
/// <summary> /// <summary>
/// Handle to Win32 application instance. /// Handle to Win32 application instance.
/// </summary> /// </summary>

View File

@@ -71,7 +71,7 @@ GDKWindow::GDKWindow(const CreateWindowSettings& settings)
// Creating the window // Creating the window
_handle = CreateWindowExW( _handle = CreateWindowExW(
exStyle, exStyle,
Platform::ApplicationWindowClass, Platform::ApplicationClassName,
settings.Title.GetText(), settings.Title.GetText(),
style, style,
x, x,

View File

@@ -21,6 +21,7 @@ namespace X11
#include <X11/Xresource.h> #include <X11/Xresource.h>
#include <X11/extensions/Xinerama.h> #include <X11/extensions/Xinerama.h>
#include <X11/Xcursor/Xcursor.h> #include <X11/Xcursor/Xcursor.h>
#include <X11/extensions/Xfixes.h>
} }
// Helper macros // Helper macros

View File

@@ -97,7 +97,9 @@ X11::Cursor Cursors[(int32)CursorType::MAX];
X11::XcursorImage* CursorsImg[(int32)CursorType::MAX]; X11::XcursorImage* CursorsImg[(int32)CursorType::MAX];
Dictionary<StringAnsi, X11::KeyCode> KeyNameMap; Dictionary<StringAnsi, X11::KeyCode> KeyNameMap;
Array<KeyboardKeys> KeyCodeMap; Array<KeyboardKeys> KeyCodeMap;
Delegate<void*> LinuxPlatform::xEventRecieved; #if !PLATFORM_SDL
Delegate<void*> LinuxPlatform::xEventReceived;
#endif
Window* MouseTrackingWindow = nullptr; Window* MouseTrackingWindow = nullptr;
// Message boxes configuration // Message boxes configuration
@@ -655,7 +657,11 @@ static int X11_MessageBoxLoop(MessageBoxData* data)
return 0; return 0;
} }
#if !PLATFORM_SDL
DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon) DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon)
#else
DialogResult MessageBox::ShowFallback(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon)
#endif
{ {
if (CommandLine::Options.Headless.IsTrue()) if (CommandLine::Options.Headless.IsTrue())
return DialogResult::None; return DialogResult::None;
@@ -842,6 +848,8 @@ DialogResult MessageBox::Show(Window* parent, const StringView& text, const Stri
return data.resultButtonIndex == -1 ? DialogResult::None : data.buttons[data.resultButtonIndex].result; return data.resultButtonIndex == -1 ? DialogResult::None : data.buttons[data.resultButtonIndex].result;
} }
#if !PLATFORM_SDL
int X11ErrorHandler(X11::Display* display, X11::XErrorEvent* event) int X11ErrorHandler(X11::Display* display, X11::XErrorEvent* event)
{ {
if (event->error_code == 5) if (event->error_code == 5)
@@ -852,6 +860,8 @@ int X11ErrorHandler(X11::Display* display, X11::XErrorEvent* event)
return 0; return 0;
} }
#endif
int32 CalculateDpi() int32 CalculateDpi()
{ {
int dpi = 96; int dpi = 96;
@@ -1207,17 +1217,20 @@ public:
} }
}; };
#if !PLATFORM_SDL
struct Property struct Property
{ {
unsigned char* data; unsigned char* data;
int format, nitems; int format, nitems;
X11::Atom type; X11::Atom type;
}; };
#endif
namespace Impl namespace Impl
{ {
LinuxKeyboard* Keyboard; LinuxKeyboard* Keyboard;
LinuxMouse* Mouse; LinuxMouse* Mouse;
#if !PLATFORM_SDL
StringAnsi ClipboardText; StringAnsi ClipboardText;
void ClipboardGetText(String& result, X11::Atom source, X11::Atom atom, X11::Window window) void ClipboardGetText(String& result, X11::Atom source, X11::Atom atom, X11::Window window)
@@ -1332,6 +1345,7 @@ namespace Impl
X11::XQueryPointer(display, w, &wtmp, &child, &tmp, &tmp, &tmp, &tmp, &utmp); X11::XQueryPointer(display, w, &wtmp, &child, &tmp, &tmp, &tmp, &tmp, &utmp);
return FindAppWindow(display, child); return FindAppWindow(display, child);
} }
#endif
Dictionary<String, String> LoadConfigFile(StringView path) Dictionary<String, String> LoadConfigFile(StringView path)
{ {
@@ -1361,6 +1375,7 @@ namespace Impl
} }
} }
#if !PLATFORM_SDL
class LinuxDropFilesData : public IGuiData class LinuxDropFilesData : public IGuiData
{ {
public: public:
@@ -1398,7 +1413,7 @@ public:
} }
}; };
DragDropEffect LinuxWindow::DoDragDrop(const StringView& data) DragDropEffect Window::DoDragDrop(const StringView& data)
{ {
if (CommandLine::Options.Headless.IsTrue()) if (CommandLine::Options.Headless.IsTrue())
return DragDropEffect::None; return DragDropEffect::None;
@@ -1412,13 +1427,14 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
StringAnsi dataAnsi(data); StringAnsi dataAnsi(data);
LinuxDropTextData dropData; LinuxDropTextData dropData;
dropData.Text = data; dropData.Text = data;
unsigned long mainWindow = _window;
// Begin dragging // Begin dragging
auto screen = X11::XDefaultScreen(xDisplay); auto screen = X11::XDefaultScreen(xDisplay);
auto rootWindow = X11::XRootWindow(xDisplay, screen); auto rootWindow = X11::XRootWindow(xDisplay, screen);
if (X11::XGrabPointer(xDisplay, _window, 1, Button1MotionMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, rootWindow, cursorWrong, CurrentTime) != GrabSuccess) if (X11::XGrabPointer(xDisplay, mainWindow, 1, Button1MotionMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, rootWindow, cursorWrong, CurrentTime) != GrabSuccess)
return DragDropEffect::None; return DragDropEffect::None;
X11::XSetSelectionOwner(xDisplay, xAtomXdndSelection, _window, CurrentTime); X11::XSetSelectionOwner(xDisplay, xAtomXdndSelection, mainWindow, CurrentTime);
// Process events // Process events
X11::XEvent event; X11::XEvent event;
@@ -1526,7 +1542,7 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
m.window = previousWindow; m.window = previousWindow;
m.message_type = xAtomXdndLeave; m.message_type = xAtomXdndLeave;
m.format = 32; m.format = 32;
m.data.l[0] = _window; m.data.l[0] = mainWindow;
m.data.l[1] = 0; m.data.l[1] = 0;
m.data.l[2] = 0; m.data.l[2] = 0;
m.data.l[3] = 0; m.data.l[3] = 0;
@@ -1555,7 +1571,7 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
m.window = window; m.window = window;
m.message_type = xAtomXdndEnter; m.message_type = xAtomXdndEnter;
m.format = 32; m.format = 32;
m.data.l[0] = _window; m.data.l[0] = mainWindow;
m.data.l[1] = Math::Min(5, version) << 24 | (formats.Count() > 3); m.data.l[1] = Math::Min(5, version) << 24 | (formats.Count() > 3);
m.data.l[2] = formats.Count() > 0 ? formats[0] : 0; m.data.l[2] = formats.Count() > 0 ? formats[0] : 0;
m.data.l[3] = formats.Count() > 1 ? formats[1] : 0; m.data.l[3] = formats.Count() > 1 ? formats[1] : 0;
@@ -1590,7 +1606,7 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
m.window = window; m.window = window;
m.message_type = xAtomXdndPosition; m.message_type = xAtomXdndPosition;
m.format = 32; m.format = 32;
m.data.l[0] = _window; m.data.l[0] = mainWindow;
m.data.l[1] = 0; m.data.l[1] = 0;
m.data.l[2] = (x << 16) | y; m.data.l[2] = (x << 16) | y;
m.data.l[3] = CurrentTime; m.data.l[3] = CurrentTime;
@@ -1633,7 +1649,7 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
m.window = previousWindow; m.window = previousWindow;
m.message_type = xAtomXdndDrop; m.message_type = xAtomXdndDrop;
m.format = 32; m.format = 32;
m.data.l[0] = _window; m.data.l[0] = mainWindow;
m.data.l[1] = 0; m.data.l[1] = 0;
m.data.l[2] = CurrentTime; m.data.l[2] = CurrentTime;
m.data.l[3] = 0; m.data.l[3] = 0;
@@ -1680,7 +1696,7 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
m.window = previousWindow; m.window = previousWindow;
m.message_type = xAtomXdndLeave; m.message_type = xAtomXdndLeave;
m.format = 32; m.format = 32;
m.data.l[0] = _window; m.data.l[0] = mainWindow;
m.data.l[1] = 0; m.data.l[1] = 0;
m.data.l[2] = 0; m.data.l[2] = 0;
m.data.l[3] = 0; m.data.l[3] = 0;
@@ -1706,7 +1722,7 @@ void LinuxClipboard::SetText(const StringView& text)
{ {
if (CommandLine::Options.Headless.IsTrue()) if (CommandLine::Options.Headless.IsTrue())
return; return;
auto mainWindow = (LinuxWindow*)Engine::MainWindow; auto mainWindow = Engine::MainWindow;
if (!mainWindow) if (!mainWindow)
return; return;
X11::Window window = (X11::Window)mainWindow->GetNativePtr(); X11::Window window = (X11::Window)mainWindow->GetNativePtr();
@@ -1729,7 +1745,7 @@ String LinuxClipboard::GetText()
if (CommandLine::Options.Headless.IsTrue()) if (CommandLine::Options.Headless.IsTrue())
return String::Empty; return String::Empty;
String result; String result;
auto mainWindow = (LinuxWindow*)Engine::MainWindow; auto mainWindow = Engine::MainWindow;
if (!mainWindow) if (!mainWindow)
return result; return result;
X11::Window window = (X11::Window)mainWindow->GetNativePtr(); X11::Window window = (X11::Window)mainWindow->GetNativePtr();
@@ -1758,6 +1774,8 @@ Array<String> LinuxClipboard::GetFiles()
return Array<String>(); return Array<String>();
} }
#endif
void* LinuxPlatform::GetXDisplay() void* LinuxPlatform::GetXDisplay()
{ {
return xDisplay; return xDisplay;
@@ -2090,6 +2108,7 @@ bool LinuxPlatform::Init()
UnixGetMacAddress(MacAddress); UnixGetMacAddress(MacAddress);
#if !PLATFORM_SDL
// Get user locale string // Get user locale string
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
const char* locale = setlocale(LC_CTYPE, NULL); const char* locale = setlocale(LC_CTYPE, NULL);
@@ -2099,6 +2118,7 @@ bool LinuxPlatform::Init()
UserLocale.Replace('_', '-'); UserLocale.Replace('_', '-');
if (UserLocale == TEXT("C")) if (UserLocale == TEXT("C"))
UserLocale = TEXT("en"); UserLocale = TEXT("en");
#endif
// Get computer name string // Get computer name string
gethostname(buffer, UNIX_APP_BUFF_SIZE); gethostname(buffer, UNIX_APP_BUFF_SIZE);
@@ -2142,7 +2162,11 @@ bool LinuxPlatform::Init()
// Skip setup if running in headless mode (X11 might not be available on servers) // Skip setup if running in headless mode (X11 might not be available on servers)
if (CommandLine::Options.Headless.IsTrue()) if (CommandLine::Options.Headless.IsTrue())
return false; return false;
#if PLATFORM_SDL
xDisplay = X11::XOpenDisplay(nullptr);
#endif
#if !PLATFORM_SDL
X11::XInitThreads(); X11::XInitThreads();
xDisplay = X11::XOpenDisplay(nullptr); xDisplay = X11::XOpenDisplay(nullptr);
@@ -2281,7 +2305,7 @@ bool LinuxPlatform::Init()
Input::Mouse = Impl::Mouse = New<LinuxMouse>(); Input::Mouse = Impl::Mouse = New<LinuxMouse>();
Input::Keyboard = Impl::Keyboard = New<LinuxKeyboard>(); Input::Keyboard = Impl::Keyboard = New<LinuxKeyboard>();
LinuxInput::Init(); LinuxInput::Init();
#endif
return false; return false;
} }
@@ -2291,6 +2315,7 @@ void LinuxPlatform::BeforeRun()
void LinuxPlatform::Tick() void LinuxPlatform::Tick()
{ {
#if !PLATFORM_SDL
UnixPlatform::Tick(); UnixPlatform::Tick();
LinuxInput::UpdateState(); LinuxInput::UpdateState();
@@ -2307,9 +2332,9 @@ void LinuxPlatform::Tick()
continue; continue;
// External event handling // External event handling
xEventRecieved(&event); xEventReceived(&event);
LinuxWindow* window; Window* window;
switch (event.type) switch (event.type)
{ {
case ClientMessage: case ClientMessage:
@@ -2641,6 +2666,7 @@ void LinuxPlatform::Tick()
} }
//X11::XFlush(xDisplay); //X11::XFlush(xDisplay);
#endif
} }
void LinuxPlatform::BeforeExit() void LinuxPlatform::BeforeExit()
@@ -2649,6 +2675,7 @@ void LinuxPlatform::BeforeExit()
void LinuxPlatform::Exit() void LinuxPlatform::Exit()
{ {
#if !PLATFORM_SDL
for (int32 i = 0; i < (int32)CursorType::MAX; i++) for (int32 i = 0; i < (int32)CursorType::MAX; i++)
{ {
if (Cursors[i]) if (Cursors[i])
@@ -2674,6 +2701,7 @@ void LinuxPlatform::Exit()
X11::XCloseDisplay(xDisplay); X11::XCloseDisplay(xDisplay);
xDisplay = nullptr; xDisplay = nullptr;
} }
#endif
} }
String LinuxPlatform::GetSystemName() String LinuxPlatform::GetSystemName()
@@ -2695,6 +2723,7 @@ Version LinuxPlatform::GetSystemVersion()
return Version(0, 0); return Version(0, 0);
} }
#if !PLATFORM_SDL
int32 LinuxPlatform::GetDpi() int32 LinuxPlatform::GetDpi()
{ {
return SystemDpi; return SystemDpi;
@@ -2704,6 +2733,7 @@ String LinuxPlatform::GetUserLocaleName()
{ {
return UserLocale; return UserLocale;
} }
#endif
String LinuxPlatform::GetComputerName() String LinuxPlatform::GetComputerName()
{ {
@@ -2911,10 +2941,12 @@ bool LinuxPlatform::SetWorkingDirectory(const String& path)
return chdir(StringAsANSI<>(*path).Get()) != 0; return chdir(StringAsANSI<>(*path).Get()) != 0;
} }
#if !PLATFORM_SDL
Window* LinuxPlatform::CreateWindow(const CreateWindowSettings& settings) Window* LinuxPlatform::CreateWindow(const CreateWindowSettings& settings)
{ {
return New<LinuxWindow>(settings); return New<LinuxWindow>(settings);
} }
#endif
extern char **environ; extern char **environ;

View File

@@ -36,7 +36,7 @@ public:
/// <summary> /// <summary>
/// An event that is fired when an XEvent is received during platform tick. /// An event that is fired when an XEvent is received during platform tick.
/// </summary> /// </summary>
static Delegate<void*> xEventRecieved; static Delegate<void*> xEventReceived;
public: public:
@@ -123,8 +123,10 @@ public:
static void Tick(); static void Tick();
static void BeforeExit(); static void BeforeExit();
static void Exit(); static void Exit();
#if !PLATFORM_SDL
static int32 GetDpi(); static int32 GetDpi();
static String GetUserLocaleName(); static String GetUserLocaleName();
#endif
static String GetComputerName(); static String GetComputerName();
static bool GetHasFocus(); static bool GetHasFocus();
static bool CanOpenUrl(const StringView& url); static bool CanOpenUrl(const StringView& url);
@@ -139,7 +141,9 @@ public:
static Guid GetUniqueDeviceId(); static Guid GetUniqueDeviceId();
static String GetWorkingDirectory(); static String GetWorkingDirectory();
static bool SetWorkingDirectory(const String& path); static bool SetWorkingDirectory(const String& path);
#if !PLATFORM_SDL
static Window* CreateWindow(const CreateWindowSettings& settings); static Window* CreateWindow(const CreateWindowSettings& settings);
#endif
static void GetEnvironmentVariables(Dictionary<String, String, HeapAllocation>& result); static void GetEnvironmentVariables(Dictionary<String, String, HeapAllocation>& result);
static bool GetEnvironmentVariable(const String& name, String& value); static bool GetEnvironmentVariable(const String& name, String& value);
static bool SetEnvironmentVariable(const String& name, const String& value); static bool SetEnvironmentVariable(const String& name, const String& value);

View File

@@ -0,0 +1,94 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#if PLATFORM_LINUX
#include "Engine/Platform/Types.h"
#include "Engine/Platform/ScreenUtilities.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Core/Delegate.h"
#include "Engine/Core/Log.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Platform/Linux/LinuxPlatform.h"
#include "Engine/Platform/Linux/IncludeX11.h"
Delegate<Color32> ScreenUtilitiesBase::PickColorDone;
Color32 LinuxScreenUtilities::GetColorAt(const Float2& pos)
{
X11::Display* display = (X11::Display*)Platform::GetXDisplay();
if (display)
{
int defaultScreen = X11::XDefaultScreen(display);
X11::Window rootWindow = X11::XRootWindow(display, defaultScreen);
X11::XImage* image = X11::XGetImage(display, rootWindow, (int)pos.X, (int)pos.Y, 1, 1, AllPlanes, XYPixmap);
if (image)
{
X11::XColor color;
color.pixel = XGetPixel(image, 0, 0);
X11::XFree(image);
X11::XQueryColor(display, X11::XDefaultColormap(display, defaultScreen), &color);
Color32 outputColor;
outputColor.R = color.red / 256;
outputColor.G = color.green / 256;
outputColor.B = color.blue / 256;
outputColor.A = 255;
return outputColor;
}
else
{
// XWayland doesn't support XGetImage...
// TODO: Fallback to Wayland implementation here?
return Color32::Black;
}
}
else
{
// TODO: Wayland
ASSERT(false);
}
}
void OnScreenUtilsXEventCallback(void* eventPtr)
{
X11::XEvent* event = (X11::XEvent*) eventPtr;
X11::Display* display = (X11::Display*)Platform::GetXDisplay();
if (event->type == ButtonPress)
{
const Float2 cursorPos = Platform::GetMousePosition();
const Color32 colorPicked = ScreenUtilities::GetColorAt(cursorPos);
X11::XUngrabPointer(display, CurrentTime);
ScreenUtilities::PickColorDone(colorPicked);
LinuxPlatform::xEventReceived.Unbind(OnScreenUtilsXEventCallback);
}
}
void LinuxScreenUtilities::PickColor()
{
PROFILE_CPU();
X11::Display* display = (X11::Display*)Platform::GetXDisplay();
if (display)
{
X11::Window rootWindow = X11::XRootWindow(display, X11::XDefaultScreen(display));
X11::Cursor cursor = XCreateFontCursor(display, 130);
int grabbedPointer = X11::XGrabPointer(display, rootWindow, 0, ButtonPressMask, GrabModeAsync, GrabModeAsync, rootWindow, cursor, CurrentTime);
if (grabbedPointer != GrabSuccess)
{
LOG(Error, "Failed to grab cursor for events.");
X11::XFreeCursor(display, cursor);
return;
}
X11::XFreeCursor(display, cursor);
LinuxPlatform::xEventReceived.Bind(OnScreenUtilsXEventCallback);
}
else
{
// TODO: Wayland
ASSERT(false);
}
}
#endif

View File

@@ -0,0 +1,25 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#if PLATFORM_LINUX
#include "Engine/Platform/Base/ScreenUtilitiesBase.h"
#include "Engine/Core/Types/BaseTypes.h"
#include "Engine/Core/Math/Color32.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Core/Delegate.h"
/// <summary>
/// Platform-dependent screen utilities.
/// </summary>
class FLAXENGINE_API LinuxScreenUtilities : public ScreenUtilitiesBase
{
public:
// [ScreenUtilitiesBase]
static Color32 GetColorAt(const Float2& pos);
static void PickColor();
};
#endif

View File

@@ -1,6 +1,6 @@
// Copyright (c) Wojciech Figat. All rights reserved. // Copyright (c) Wojciech Figat. All rights reserved.
#if PLATFORM_LINUX #if PLATFORM_LINUX && !PLATFORM_SDL
#include "../Window.h" #include "../Window.h"
#include "Engine/Input/Input.h" #include "Engine/Input/Input.h"
@@ -616,7 +616,7 @@ void LinuxWindow::OnButtonPress(void* event)
} }
// Handle double-click // Handle double-click
if (buttonEvent->button == Button1) if (buttonEvent->button == Button1 && !Input::Mouse->IsRelative())
{ {
if ( if (
buttonEvent->time < (MouseLastButtonPressTime + MouseDoubleClickTime) && buttonEvent->time < (MouseLastButtonPressTime + MouseDoubleClickTime) &&

View File

@@ -273,6 +273,7 @@ bool MacPlatform::Init()
CFRelease(computerName); CFRelease(computerName);
} }
#if !PLATFORM_SDL
// Find the maximum scale of the display to handle high-dpi displays scaling factor // Find the maximum scale of the display to handle high-dpi displays scaling factor
{ {
NSArray* screenArray = [NSScreen screens]; NSArray* screenArray = [NSScreen screens];
@@ -297,6 +298,7 @@ bool MacPlatform::Init()
Input::Mouse = New<MacMouse>(); Input::Mouse = New<MacMouse>();
Input::Keyboard = New<MacKeyboard>(); Input::Keyboard = New<MacKeyboard>();
#endif
return false; return false;
} }
@@ -423,11 +425,15 @@ String MacPlatform::GetMainDirectory()
return path; return path;
} }
#if !PLATFORM_SDL
Window* MacPlatform::CreateWindow(const CreateWindowSettings& settings) Window* MacPlatform::CreateWindow(const CreateWindowSettings& settings)
{ {
return New<MacWindow>(settings); return New<MacWindow>(settings);
} }
#endif
int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) int32 MacPlatform::CreateProcess(CreateProcessSettings& settings)
{ {
LOG(Info, "Command: {0} {1}", settings.FileName, settings.Arguments); LOG(Info, "Command: {0} {1}", settings.FileName, settings.Arguments);

View File

@@ -0,0 +1,29 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#if USE_EDITOR && PLATFORM_MAC
#include "Engine/Platform/Types.h"
#include "Engine/Platform/ScreenUtilities.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Core/Delegate.h"
#include "Engine/Core/Log.h"
#include <Cocoa/Cocoa.h>
#include <AppKit/AppKit.h>
Delegate<Color32> ScreenUtilitiesBase::PickColorDone;
Color32 MacScreenUtilities::GetColorAt(const Float2& pos)
{
// TODO: implement ScreenUtilities for macOS
return { 0, 0, 0, 255 };
}
void MacScreenUtilities::PickColor()
{
// This is what C# calls to start the color picking sequence
// This should stop mouse clicks from working for one click, and that click is on the selected color
// There is a class called NSColorSample that might implement that for you, but maybe not.
}
#endif

View File

@@ -0,0 +1,25 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#if USE_EDITOR && PLATFORM_MAC
#include "Engine/Platform/Base/ScreenUtilitiesBase.h"
#include "Engine/Core/Types/BaseTypes.h"
#include "Engine/Core/Math/Color32.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Core/Delegate.h"
/// <summary>
/// Platform-dependent screen utilities.
/// </summary>
class FLAXENGINE_API MacScreenUtilities : public ScreenUtilitiesBase
{
public:
// [ScreenUtilitiesBase]
static Color32 GetColorAt(const Float2& pos);
static void PickColor();
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
// Copyright (c) Wojciech Figat. All rights reserved. // Copyright (c) Wojciech Figat. All rights reserved.
#if PLATFORM_MAC #if PLATFORM_MAC && !PLATFORM_SDL
#include "../Window.h" #include "../Window.h"
#include "Engine/Platform/Apple/AppleUtils.h" #include "Engine/Platform/Apple/AppleUtils.h"
@@ -507,7 +507,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
Float2 mousePos = GetMousePosition(Window, event); Float2 mousePos = GetMousePosition(Window, event);
mousePos = Window->ClientToScreen(mousePos); mousePos = Window->ClientToScreen(mousePos);
MouseButton mouseButton = MouseButton::Left; MouseButton mouseButton = MouseButton::Left;
if ([event clickCount] == 2) if ([event clickCount] == 2 && !Input::Mouse->IsRelative())
Input::Mouse->OnMouseDoubleClick(mousePos, mouseButton, Window); Input::Mouse->OnMouseDoubleClick(mousePos, mouseButton, Window);
else else
Input::Mouse->OnMouseDown(mousePos, mouseButton, Window); Input::Mouse->OnMouseDown(mousePos, mouseButton, Window);
@@ -544,7 +544,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
if (IsWindowInvalid(Window)) return; if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event); Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton = MouseButton::Right; MouseButton mouseButton = MouseButton::Right;
if ([event clickCount] == 2) if ([event clickCount] == 2 && !Input::Mouse->IsRelative())
Input::Mouse->OnMouseDoubleClick(Window->ClientToScreen(mousePos), mouseButton, Window); Input::Mouse->OnMouseDoubleClick(Window->ClientToScreen(mousePos), mouseButton, Window);
else else
Input::Mouse->OnMouseDown(Window->ClientToScreen(mousePos), mouseButton, Window); Input::Mouse->OnMouseDown(Window->ClientToScreen(mousePos), mouseButton, Window);
@@ -582,7 +582,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
default: default:
return; return;
} }
if ([event clickCount] == 2) if ([event clickCount] == 2 && !Input::Mouse->IsRelative())
Input::Mouse->OnMouseDoubleClick(Window->ClientToScreen(mousePos), mouseButton, Window); Input::Mouse->OnMouseDoubleClick(Window->ClientToScreen(mousePos), mouseButton, Window);
else else
Input::Mouse->OnMouseDown(Window->ClientToScreen(mousePos), mouseButton, Window); Input::Mouse->OnMouseDown(Window->ClientToScreen(mousePos), mouseButton, Window);

View File

@@ -237,4 +237,9 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(MessageBox);
/// <param name="icon">One of the MessageBoxIcon values that specifies which icon to display in the message box.</param> /// <param name="icon">One of the MessageBoxIcon values that specifies which icon to display in the message box.</param>
/// <returns>The message box dialog result.</returns> /// <returns>The message box dialog result.</returns>
API_FUNCTION() static DialogResult Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon); API_FUNCTION() static DialogResult Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon);
private:
#if PLATFORM_SDL && PLATFORM_LINUX
static DialogResult ShowFallback(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon);
#endif
}; };

View File

@@ -89,6 +89,21 @@ public class Platform : EngineModule
break; break;
default: throw new InvalidPlatformException(options.Platform.Target); default: throw new InvalidPlatformException(options.Platform.Target);
} }
if (EngineConfiguration.WithSDL(options))
{
switch (options.Platform.Target)
{
case TargetPlatform.Windows:
case TargetPlatform.Linux:
case TargetPlatform.Mac:
options.PublicDependencies.Add("SDL");
options.SourcePaths.Add(Path.Combine(FolderPath, "SDL"));
break;
}
if (options.Platform.Target == TargetPlatform.Linux)
options.PublicDependencies.Add("Wayland");
}
if (options.Target.IsEditor) if (options.Target.IsEditor)
{ {
// Include platform settings headers // Include platform settings headers

View File

@@ -8,7 +8,9 @@
#include "Types.h" #include "Types.h"
#include "Defines.h" #include "Defines.h"
#if PLATFORM_WINDOWS #if PLATFORM_SDL
#include "SDL/SDLPlatform.h"
#elif PLATFORM_WINDOWS
#include "Windows/WindowsPlatform.h" #include "Windows/WindowsPlatform.h"
#elif PLATFORM_UWP #elif PLATFORM_UWP
#include "UWP/UWPPlatform.h" #include "UWP/UWPPlatform.h"

View File

@@ -0,0 +1,26 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#if PLATFORM_SDL && PLATFORM_LINUX
#include "Engine/Platform/Base/ClipboardBase.h"
/// <summary>
/// SDL implementation of the clipboard service for Linux platform.
/// </summary>
class FLAXENGINE_API SDLClipboard : public ClipboardBase
{
public:
// [ClipboardBase]
static void Clear();
static void SetText(const StringView& text);
static void SetRawData(const Span<byte>& data);
static void SetFiles(const Array<String>& files);
static String GetText();
static Array<byte> GetRawData();
static Array<String> GetFiles();
};
#endif

View File

@@ -0,0 +1,836 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#if PLATFORM_SDL
#include "SDLInput.h"
#include "SDLWindow.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Core/Log.h"
#include "Engine/Input/Input.h"
#include "Engine/Input/Mouse.h"
#include "Engine/Input/Keyboard.h"
#include "Engine/Input/Gamepad.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/Screen.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#include "Editor/Managed/ManagedEditor.h"
#endif
#include <SDL3/SDL_events.h>
class SDLMouse;
class SDLKeyboard;
class SDLGamepad;
// TODO: Turn these into customizable values
#define TRIGGER_THRESHOLD 30
#define LEFT_STICK_THRESHOLD 7849
#define RIGHT_STICK_THRESHOLD 8689
namespace SDLInputImpl
{
SDLMouse* Mouse = nullptr;
SDLKeyboard* Keyboard = nullptr;
Dictionary<SDL_JoystickID, SDLGamepad*> Gamepads;
}
static const KeyboardKeys SDL_TO_FLAX_KEYS_MAP[] =
{
KeyboardKeys::None, // SDL_SCANCODE_UNKNOWN
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::A,
KeyboardKeys::B,
KeyboardKeys::C,
KeyboardKeys::D,
KeyboardKeys::E,
KeyboardKeys::F,
KeyboardKeys::G,
KeyboardKeys::H,
KeyboardKeys::I,
KeyboardKeys::J,
KeyboardKeys::K,
KeyboardKeys::L,
KeyboardKeys::M,
KeyboardKeys::N,
KeyboardKeys::O,
KeyboardKeys::P,
KeyboardKeys::Q,
KeyboardKeys::R,
KeyboardKeys::S,
KeyboardKeys::T,
KeyboardKeys::U,
KeyboardKeys::V,
KeyboardKeys::W,
KeyboardKeys::X,
KeyboardKeys::Y,
KeyboardKeys::Z, // 29
KeyboardKeys::Alpha1,
KeyboardKeys::Alpha2,
KeyboardKeys::Alpha3,
KeyboardKeys::Alpha4,
KeyboardKeys::Alpha5,
KeyboardKeys::Alpha6,
KeyboardKeys::Alpha7,
KeyboardKeys::Alpha8,
KeyboardKeys::Alpha9,
KeyboardKeys::Alpha0, // 39
KeyboardKeys::Return,
KeyboardKeys::Escape,
KeyboardKeys::Backspace,
KeyboardKeys::Tab,
KeyboardKeys::Spacebar,
KeyboardKeys::Minus,
KeyboardKeys::None, //KeyboardKeys::Equals, // ?
KeyboardKeys::LeftBracket,
KeyboardKeys::RightBracket,
KeyboardKeys::Backslash, // SDL_SCANCODE_BACKSLASH ?
KeyboardKeys::Oem102, // SDL_SCANCODE_NONUSHASH ?
KeyboardKeys::Colon, // SDL_SCANCODE_SEMICOLON ?
KeyboardKeys::Quote, // SDL_SCANCODE_APOSTROPHE
KeyboardKeys::BackQuote, // SDL_SCANCODE_GRAVE
KeyboardKeys::Comma,
KeyboardKeys::Period,
KeyboardKeys::Slash,
KeyboardKeys::Capital,
KeyboardKeys::F1,
KeyboardKeys::F2,
KeyboardKeys::F3,
KeyboardKeys::F4,
KeyboardKeys::F5,
KeyboardKeys::F6,
KeyboardKeys::F7,
KeyboardKeys::F8,
KeyboardKeys::F9,
KeyboardKeys::F10,
KeyboardKeys::F11,
KeyboardKeys::F12,
KeyboardKeys::PrintScreen,
KeyboardKeys::Scroll,
KeyboardKeys::Pause,
KeyboardKeys::Insert,
KeyboardKeys::Home,
KeyboardKeys::PageUp,
KeyboardKeys::Delete,
KeyboardKeys::End,
KeyboardKeys::PageDown,
KeyboardKeys::ArrowRight,
KeyboardKeys::ArrowLeft,
KeyboardKeys::ArrowDown,
KeyboardKeys::ArrowUp,
KeyboardKeys::Numlock,
KeyboardKeys::NumpadDivide,
KeyboardKeys::NumpadMultiply,
KeyboardKeys::NumpadSubtract,
KeyboardKeys::NumpadAdd,
KeyboardKeys::Return, // SDL_SCANCODE_KP_ENTER ?
KeyboardKeys::Numpad1,
KeyboardKeys::Numpad2,
KeyboardKeys::Numpad3,
KeyboardKeys::Numpad4,
KeyboardKeys::Numpad5,
KeyboardKeys::Numpad6,
KeyboardKeys::Numpad7,
KeyboardKeys::Numpad8,
KeyboardKeys::Numpad9,
KeyboardKeys::Numpad0, //98
KeyboardKeys::NumpadDecimal, // SDL_SCANCODE_KP_PERIOD
KeyboardKeys::Backslash, // SDL_SCANCODE_NONUSBACKSLASH ?
KeyboardKeys::Applications,
KeyboardKeys::Sleep, // SDL_SCANCODE_POWER ?
KeyboardKeys::None, // SDL_SCANCODE_KP_EQUALS ?
KeyboardKeys::F13,
KeyboardKeys::F14,
KeyboardKeys::F15,
KeyboardKeys::F16,
KeyboardKeys::F17,
KeyboardKeys::F18,
KeyboardKeys::F19,
KeyboardKeys::F20,
KeyboardKeys::F21,
KeyboardKeys::F22,
KeyboardKeys::F23,
KeyboardKeys::F24,
KeyboardKeys::Execute,
KeyboardKeys::Help,
KeyboardKeys::LeftMenu, // SDL_SCANCODE_MENU ?
KeyboardKeys::Select,
KeyboardKeys::None, // SDL_SCANCODE_STOP
KeyboardKeys::None, // SDL_SCANCODE_AGAIN
KeyboardKeys::None, // SDL_SCANCODE_UNDO
KeyboardKeys::None, // SDL_SCANCODE_CUT
KeyboardKeys::None, // SDL_SCANCODE_COPY
KeyboardKeys::None, // SDL_SCANCODE_PASTE
KeyboardKeys::None, // SDL_SCANCODE_FIND
KeyboardKeys::None, // SDL_SCANCODE_MUTE
KeyboardKeys::None, // SDL_SCANCODE_VOLUMEUP
KeyboardKeys::None, // SDL_SCANCODE_VOLUMEDOWN
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::NumpadSeparator, // SDL_SCANCODE_KP_COMMA ?
KeyboardKeys::None, // SDL_SCANCODE_KP_EQUALSAS400
KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL1
KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL2
KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL3
KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL4
KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL5
KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL6
KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL7
KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL8
KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL9
KeyboardKeys::None, // SDL_SCANCODE_LANG1
KeyboardKeys::None, // SDL_SCANCODE_LANG2
KeyboardKeys::None, // SDL_SCANCODE_LANG3
KeyboardKeys::None, // SDL_SCANCODE_LANG4
KeyboardKeys::None, // SDL_SCANCODE_LANG5
KeyboardKeys::None, // SDL_SCANCODE_LANG6
KeyboardKeys::None, // SDL_SCANCODE_LANG7
KeyboardKeys::None, // SDL_SCANCODE_LANG8
KeyboardKeys::None, // SDL_SCANCODE_LANG9
KeyboardKeys::None, // SDL_SCANCODE_ALTERASE
KeyboardKeys::None, // SDL_SCANCODE_SYSREQ
KeyboardKeys::None, // SDL_SCANCODE_CANCEL
KeyboardKeys::Clear, // SDL_SCANCODE_CLEAR
KeyboardKeys::None, // SDL_SCANCODE_PRIOR
KeyboardKeys::None, // SDL_SCANCODE_RETURN2
KeyboardKeys::None, // SDL_SCANCODE_SEPARATOR
KeyboardKeys::None, // SDL_SCANCODE_OUT
KeyboardKeys::None, // SDL_SCANCODE_OPER
KeyboardKeys::None, // SDL_SCANCODE_CLEARAGAIN
KeyboardKeys::None, // SDL_SCANCODE_CRSEL
KeyboardKeys::None, // SDL_SCANCODE_EXSEL
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None, // SDL_SCANCODE_KP_00
KeyboardKeys::None, // SDL_SCANCODE_KP_000
KeyboardKeys::None, // SDL_SCANCODE_THOUSANDSSEPARATOR
KeyboardKeys::None, // SDL_SCANCODE_DECIMALSEPARATOR
KeyboardKeys::None, // SDL_SCANCODE_CURRENCYUNIT
KeyboardKeys::None, // SDL_SCANCODE_CURRENCYSUBUNIT
KeyboardKeys::None, // SDL_SCANCODE_KP_LEFTPAREN = 182,
KeyboardKeys::None, // SDL_SCANCODE_KP_RIGHTPAREN = 183,
KeyboardKeys::None, // SDL_SCANCODE_KP_LEFTBRACE = 184,
KeyboardKeys::None, // SDL_SCANCODE_KP_RIGHTBRACE = 185,
KeyboardKeys::None, // SDL_SCANCODE_KP_TAB = 186,
KeyboardKeys::None, // SDL_SCANCODE_KP_BACKSPACE = 187,
KeyboardKeys::None, // SDL_SCANCODE_KP_A = 188,
KeyboardKeys::None, // SDL_SCANCODE_KP_B = 189,
KeyboardKeys::None, // SDL_SCANCODE_KP_C = 190,
KeyboardKeys::None, // SDL_SCANCODE_KP_D = 191,
KeyboardKeys::None, // SDL_SCANCODE_KP_E = 192,
KeyboardKeys::None, // SDL_SCANCODE_KP_F = 193,
KeyboardKeys::None, // SDL_SCANCODE_KP_XOR = 194,
KeyboardKeys::None, // SDL_SCANCODE_KP_POWER = 195,
KeyboardKeys::None, // SDL_SCANCODE_KP_PERCENT = 196,
KeyboardKeys::None, // SDL_SCANCODE_KP_LESS = 197,
KeyboardKeys::None, // SDL_SCANCODE_KP_GREATER = 198,
KeyboardKeys::None, // SDL_SCANCODE_KP_AMPERSAND = 199,
KeyboardKeys::None, // SDL_SCANCODE_KP_DBLAMPERSAND = 200,
KeyboardKeys::None, // SDL_SCANCODE_KP_VERTICALBAR = 201,
KeyboardKeys::None, // SDL_SCANCODE_KP_DBLVERTICALBAR = 202,
KeyboardKeys::None, // SDL_SCANCODE_KP_COLON = 203,
KeyboardKeys::None, // SDL_SCANCODE_KP_HASH = 204,
KeyboardKeys::None, // SDL_SCANCODE_KP_SPACE = 205,
KeyboardKeys::None, // SDL_SCANCODE_KP_AT = 206,
KeyboardKeys::None, // SDL_SCANCODE_KP_EXCLAM = 207,
KeyboardKeys::None, // SDL_SCANCODE_KP_MEMSTORE = 208,
KeyboardKeys::None, // SDL_SCANCODE_KP_MEMRECALL = 209,
KeyboardKeys::None, // SDL_SCANCODE_KP_MEMCLEAR = 210,
KeyboardKeys::None, // SDL_SCANCODE_KP_MEMADD = 211,
KeyboardKeys::None, // SDL_SCANCODE_KP_MEMSUBTRACT = 212,
KeyboardKeys::None, // SDL_SCANCODE_KP_MEMMULTIPLY = 213,
KeyboardKeys::None, // SDL_SCANCODE_KP_MEMDIVIDE = 214,
KeyboardKeys::None, // SDL_SCANCODE_KP_PLUSMINUS = 215,
KeyboardKeys::None, // SDL_SCANCODE_KP_CLEAR = 216,
KeyboardKeys::None, // SDL_SCANCODE_KP_CLEARENTRY = 217,
KeyboardKeys::None, // SDL_SCANCODE_KP_BINARY = 218,
KeyboardKeys::None, // SDL_SCANCODE_KP_OCTAL = 219,
KeyboardKeys::None, // SDL_SCANCODE_KP_DECIMAL = 220,
KeyboardKeys::None, // SDL_SCANCODE_KP_HEXADECIMAL = 221,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::Control, // SDL_SCANCODE_LCTRL = 224,
KeyboardKeys::Shift, // SDL_SCANCODE_LSHIFT = 225,
KeyboardKeys::Alt, // SDL_SCANCODE_LALT = 226,
KeyboardKeys::LeftMenu, // SDL_SCANCODE_LGUI = 227,
KeyboardKeys::Control, // SDL_SCANCODE_RCTRL = 228,
KeyboardKeys::Shift, // SDL_SCANCODE_RSHIFT = 229,
KeyboardKeys::Alt, // SDL_SCANCODE_RALT = 230,
KeyboardKeys::RightMenu, // SDL_SCANCODE_RGUI = 231,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::None,
KeyboardKeys::Modechange, // SDL_SCANCODE_MODE
KeyboardKeys::Sleep, // SDL_SCANCODE_SLEEP
KeyboardKeys::None, // SDL_SCANCODE_WAKE
KeyboardKeys::None, // SDL_SCANCODE_CHANNEL_INCREMENT = 260,
KeyboardKeys::None, // SDL_SCANCODE_CHANNEL_DECREMENT = 261,
KeyboardKeys::None, // SDL_SCANCODE_MEDIA_PLAY = 262,
KeyboardKeys::None, // SDL_SCANCODE_MEDIA_PAUSE = 263,
KeyboardKeys::None, // SDL_SCANCODE_MEDIA_RECORD = 264,
KeyboardKeys::None, // SDL_SCANCODE_MEDIA_FAST_FORWARD = 265,
KeyboardKeys::None, // SDL_SCANCODE_MEDIA_REWIND = 266,
KeyboardKeys::MediaNextTrack, // SDL_SCANCODE_MEDIA_NEXT_TRACK = 267,
KeyboardKeys::MediaPrevTrack, // SDL_SCANCODE_MEDIA_PREVIOUS_TRACK = 268,
KeyboardKeys::MediaStop, // SDL_SCANCODE_MEDIA_STOP = 269,
KeyboardKeys::None, // SDL_SCANCODE_MEDIA_EJECT = 270,
KeyboardKeys::MediaPlayPause, // SDL_SCANCODE_MEDIA_PLAY_PAUSE = 271,
KeyboardKeys::None, // SDL_SCANCODE_MEDIA_SELECT = 272,
KeyboardKeys::None, // SDL_SCANCODE_AC_NEW = 273,
KeyboardKeys::None, // SDL_SCANCODE_AC_OPEN = 274,
KeyboardKeys::None, // SDL_SCANCODE_AC_CLOSE = 275,
KeyboardKeys::None, // SDL_SCANCODE_AC_EXIT = 276,
KeyboardKeys::None, // SDL_SCANCODE_AC_SAVE = 277,
KeyboardKeys::None, // SDL_SCANCODE_AC_PRINT = 278,
KeyboardKeys::None, // SDL_SCANCODE_AC_PROPERTIES = 279,
KeyboardKeys::None, // SDL_SCANCODE_AC_SEARCH = 280,
KeyboardKeys::None, // SDL_SCANCODE_AC_HOME = 281,
KeyboardKeys::None, // SDL_SCANCODE_AC_BACK = 282,
KeyboardKeys::None, // SDL_SCANCODE_AC_FORWARD = 283,
KeyboardKeys::None, // SDL_SCANCODE_AC_STOP = 284,
KeyboardKeys::None, // SDL_SCANCODE_AC_REFRESH = 285,
KeyboardKeys::None, // SDL_SCANCODE_AC_BOOKMARKS = 286,
KeyboardKeys::None, // SDL_SCANCODE_SOFTLEFT = 287,
KeyboardKeys::None, // SDL_SCANCODE_SOFTRIGHT = 288,
KeyboardKeys::None, // SDL_SCANCODE_CALL = 289,
KeyboardKeys::None, // SDL_SCANCODE_ENDCALL = 290
};
/// <summary>
/// Implementation of the keyboard device for Windows platform.
/// </summary>
/// <seealso cref="Keyboard" />
class SDLKeyboard : public Keyboard
{
public:
/// <summary>
/// Initializes a new instance of the <see cref="SDLKeyboard"/> class.
/// </summary>
explicit SDLKeyboard()
: Keyboard()
{
}
public:
};
/// <summary>
/// Implementation of the mouse device for SDL platform.
/// </summary>
/// <seealso cref="Mouse" />
class SDLMouse : public Mouse
{
private:
Float2 _oldPosition = Float2::Zero;
Window* _relativeModeWindow = nullptr;
const SDL_Rect* _oldScreenRect = nullptr;
public:
/// <summary>
/// Initializes a new instance of the <see cref="SDLMouse"/> class.
/// </summary>
explicit SDLMouse()
: Mouse()
{
}
public:
/// <summary>
/// Returns the previous known position of the mouse before entering relative mode.
/// </summary>
Float2 GetOldMousePosition() const
{
ASSERT(_relativeModeWindow != nullptr);
return _relativeModeWindow->ClientToScreen(_oldPosition);
}
// [Mouse]
void SetMousePosition(const Float2& screenPosition) final override
{
#if USE_EDITOR
auto window = Editor::Managed->GetGameWindow();
if (window == nullptr)
window = Engine::MainWindow;
#else
auto window = Engine::MainWindow;
#endif
Float2 position = window->ScreenToClient(screenPosition);
SDL_WarpMouseInWindow(static_cast<SDLWindow*>(window)->_window, position.X, position.Y);
OnMouseMoved(screenPosition);
}
void SetRelativeMode(bool relativeMode, Window* window) final override
{
ASSERT(window != nullptr);
if (relativeMode == _relativeMode)
return;
auto windowHandle = static_cast<SDLWindow*>(window)->_window;
if (relativeMode)
{
_relativeModeWindow = window;
SDL_GetMouseState(&_oldPosition.X, &_oldPosition.Y);
if (!SDL_CursorVisible())
{
// Trap the cursor in current location
SDL_Rect clipRect = { (int)_oldPosition.X, (int)_oldPosition.Y, 1, 1 };
_oldScreenRect = SDL_GetWindowMouseRect(windowHandle);
SDL_SetWindowMouseRect(windowHandle, &clipRect);
}
}
else
{
if (_relativeModeWindow != window)
{
// FIXME: When floating game window is focused and editor viewport activated, the relative mode gets stuck
return;
}
SDL_SetWindowMouseRect(windowHandle, nullptr);//oldScreenRect);
SDL_WarpMouseInWindow(windowHandle, _oldPosition.X, _oldPosition.Y);
_oldScreenRect = nullptr;
_relativeModeWindow = nullptr;
}
Mouse::SetRelativeMode(relativeMode, window);
if (!SDL_SetWindowRelativeMouseMode(windowHandle, relativeMode))
LOG(Error, "Failed to set mouse relative mode: {0}", String(SDL_GetError()));
#if PLATFORM_MAC
// Warping right before entering relative mode seems to generate motion event for relative mode
SDL_PumpEvents();
SDL_FlushEvent(SDL_EVENT_MOUSE_MOTION);
#endif
}
bool IsRelative(Window* window) const override
{
if (window == nullptr)
return _relativeMode;
return _relativeModeWindow == window && _relativeMode;
}
};
/// <summary>
/// Implementation of the gamepad device for SDL platform.
/// </summary>
/// <seealso cref="Gamepad" />
class SDLGamepad : public Gamepad
{
private:
SDL_Gamepad* _gamepad = nullptr;
SDL_JoystickID _instanceId;
public:
/// <summary>
/// Initializes a new instance of the <see cref="SDLGamepad"/> class.
/// </summary>
/// <param name="userIndex">The joystick.</param>
explicit SDLGamepad(SDL_JoystickID instanceId);
/// <summary>
/// Finalizes an instance of the <see cref="SDLGamepad"/> class.
/// </summary>
~SDLGamepad();
private:
SDLGamepad(SDL_Gamepad* gamepad, SDL_JoystickID instanceId);
public:
static SDLGamepad* GetGamepadById(SDL_JoystickID id)
{
SDLGamepad* gamepad = nullptr;
SDLInputImpl::Gamepads.TryGet(id, gamepad);
return gamepad;
}
SDL_JoystickID GetJoystickInstanceId() const
{
return _instanceId;
}
void OnAxisMotion(SDL_GamepadAxis axis, int16 value);
void OnButtonState(SDL_GamepadButton axis, bool pressed);
// [Gamepad]
void SetVibration(const GamepadVibrationState& state) override;
protected:
// [Gamepad]
bool UpdateState() override;
};
void SDLInput::Init()
{
Input::Mouse = SDLInputImpl::Mouse = New<SDLMouse>();
Input::Keyboard = SDLInputImpl::Keyboard = New<SDLKeyboard>();
}
void SDLInput::Update()
{
}
float NormalizeAxisValue(const int16 axisVal)
{
// Normalize [-32768..32767] -> [-1..1]
const float norm = axisVal <= 0 ? 32768.0f : 32767.0f;
return float(axisVal) / norm;
}
bool SDLInput::HandleEvent(SDLWindow* window, SDL_Event& event)
{
switch (event.type)
{
case SDL_EVENT_MOUSE_MOTION:
{
if (Input::Mouse->IsRelative())
{
const Float2 mouseDelta(event.motion.xrel, event.motion.yrel);
Input::Mouse->OnMouseMoveRelative(mouseDelta, window);
}
else
{
const Float2 mousePos = window->ClientToScreen({ event.motion.x, event.motion.y });
Input::Mouse->OnMouseMove(mousePos, window);
}
return true;
}
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
{
Input::Mouse->OnMouseLeave(window);
return true;
}
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
{
Float2 mousePos = window->ClientToScreen({ event.button.x, event.button.y });
// In case of activating the window from non-focused state, we might not have received the motion event yet
if (!Input::Mouse->IsRelative() && event.button.down)
Input::Mouse->OnMouseMove(mousePos, window);
MouseButton button = MouseButton::None;
if (event.button.button == SDL_BUTTON_LEFT)
button = MouseButton::Left;
else if (event.button.button == SDL_BUTTON_RIGHT)
button = MouseButton::Right;
else if (event.button.button == SDL_BUTTON_MIDDLE)
button = MouseButton::Middle;
else if (event.button.button == SDL_BUTTON_X1)
button = MouseButton::Extended1;
else if (event.button.button == SDL_BUTTON_X2)
button = MouseButton::Extended2;
if (Input::Mouse->IsRelative())
{
// Use the previous visible mouse position here, the event or global
// mouse position would cause input to trigger in other editor windows.
mousePos = SDLInputImpl::Mouse->GetOldMousePosition();
}
if (!event.button.down)
Input::Mouse->OnMouseUp(mousePos, button, window);
// Prevent sending multiple mouse down event when double-clicking UI elements
else if (event.button.clicks % 2 == 1)
Input::Mouse->OnMouseDown(mousePos, button, window);
else
Input::Mouse->OnMouseDoubleClick(mousePos, button, window);
return true;
}
case SDL_EVENT_MOUSE_WHEEL:
{
Float2 mousePos = window->ClientToScreen({ event.wheel.mouse_x, event.wheel.mouse_y });
const float delta = event.wheel.y;
if (Input::Mouse->IsRelative())
{
// Use the previous visible mouse position here, the event or global
// mouse position would cause input to trigger in other editor windows.
mousePos = SDLInputImpl::Mouse->GetOldMousePosition();
}
Input::Mouse->OnMouseWheel(mousePos, delta, window);
return true;
}
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
{
// TODO: scancode support
KeyboardKeys key = SDL_TO_FLAX_KEYS_MAP[event.key.scancode];
//event.key.mod
if (!event.key.down)
Input::Keyboard->OnKeyUp(key, window);
else
Input::Keyboard->OnKeyDown(key, window);
return true;
}
case SDL_EVENT_TEXT_EDITING:
{
auto edit = event.edit;
return true;
}
case SDL_EVENT_TEXT_INPUT:
{
String text(event.text.text);
for (int i = 0; i < text.Length(); i++)
{
Input::Keyboard->OnCharInput(text[i], window);
}
return true;
}
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
{
SDLGamepad* gamepad = SDLGamepad::GetGamepadById(event.gaxis.which);
SDL_GamepadAxis axis = (SDL_GamepadAxis)event.gaxis.axis;
gamepad->OnAxisMotion(axis, event.gaxis.value);
break;
}
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
{
SDLGamepad* gamepad = SDLGamepad::GetGamepadById(event.gbutton.which);
SDL_GamepadButton button = (SDL_GamepadButton)event.gbutton.button;
gamepad->OnButtonState(button, event.gbutton.down);
break;
}
case SDL_EVENT_GAMEPAD_ADDED:
{
Input::Gamepads.Add(New<SDLGamepad>(event.gdevice.which));
Input::OnGamepadsChanged();
break;
}
case SDL_EVENT_GAMEPAD_REMOVED:
{
for (int i = 0; i < Input::Gamepads.Count(); i++)
{
SDLGamepad* gamepad = static_cast<SDLGamepad*>(Input::Gamepads[i]);
if (gamepad->GetJoystickInstanceId() == event.gdevice.which)
{
Input::Gamepads[i]->DeleteObject();
Input::Gamepads.RemoveAtKeepOrder(i);
Input::OnGamepadsChanged();
break;
}
}
break;
}
case SDL_EVENT_GAMEPAD_REMAPPED:
{
auto ev = event.gdevice;
LOG(Info, "TODO: SDL_EVENT_GAMEPAD_REMAPPED");
break;
}
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
{
LOG(Info, "TODO: SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN");
break;
}
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
{
LOG(Info, "TODO: SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION");
break;
}
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
{
LOG(Info, "TODO: SDL_EVENT_GAMEPAD_TOUCHPAD_UP");
break;
}
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
{
LOG(Info, "TODO: SDL_EVENT_GAMEPAD_SENSOR_UPDATE");
break;
}
case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED:
{
auto ev = event.gdevice;
LOG(Info, "TODO: SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED");
break;
}
}
return false;
}
Guid GetGamepadGuid(SDL_JoystickID instanceId)
{
SDL_GUID joystickGuid = SDL_GetGamepadGUIDForID(instanceId);
Guid guid;
Platform::MemoryCopy(&guid.Raw, joystickGuid.data, sizeof(uint8) * 16);
return guid;
}
SDLGamepad::SDLGamepad(SDL_JoystickID instanceId)
: SDLGamepad(SDL_OpenGamepad(instanceId), instanceId)
{
LOG(Info, "Gamepad connected: {}", String(SDL_GetGamepadName(_gamepad)));
}
SDLGamepad::SDLGamepad(SDL_Gamepad* gamepad, SDL_JoystickID instanceId)
: Gamepad(GetGamepadGuid(instanceId), String(SDL_GetGamepadName(gamepad)))
, _gamepad(gamepad)
, _instanceId(instanceId)
{
SDLInputImpl::Gamepads.Add(_instanceId, this);
}
SDLGamepad::~SDLGamepad()
{
SDL_CloseGamepad(_gamepad);
SDLInputImpl::Gamepads.Remove(_instanceId);
}
void SDLGamepad::SetVibration(const GamepadVibrationState& state)
{
Gamepad::SetVibration(state);
LOG(Info, "TODO: SDLGamepad::SetVibration");
}
bool SDLGamepad::UpdateState()
{
return false;
}
void SDLGamepad::OnAxisMotion(SDL_GamepadAxis sdlAxis, int16 value)
{
GamepadAxis axis;
int16 deadzone = 1; // SDL reports -1 for centered axis?
float valueNormalized = NormalizeAxisValue(value);
switch (sdlAxis)
{
case SDL_GAMEPAD_AXIS_LEFTX:
axis = GamepadAxis::LeftStickX;
deadzone = LEFT_STICK_THRESHOLD;
_state.Buttons[(int32)GamepadButton::LeftStickLeft] = value > LEFT_STICK_THRESHOLD;
_state.Buttons[(int32)GamepadButton::LeftStickRight] = value < -LEFT_STICK_THRESHOLD;
break;
case SDL_GAMEPAD_AXIS_LEFTY:
axis = GamepadAxis::LeftStickY;
deadzone = LEFT_STICK_THRESHOLD;
_state.Buttons[(int32)GamepadButton::LeftStickUp] = value < -LEFT_STICK_THRESHOLD;
_state.Buttons[(int32)GamepadButton::LeftStickDown] = value > LEFT_STICK_THRESHOLD;
valueNormalized = -valueNormalized;
break;
case SDL_GAMEPAD_AXIS_RIGHTX:
deadzone = RIGHT_STICK_THRESHOLD;
axis = GamepadAxis::RightStickX;
_state.Buttons[(int32)GamepadButton::RightStickLeft] = value > RIGHT_STICK_THRESHOLD;
_state.Buttons[(int32)GamepadButton::RightStickRight] = value < -RIGHT_STICK_THRESHOLD;
break;
case SDL_GAMEPAD_AXIS_RIGHTY:
deadzone = RIGHT_STICK_THRESHOLD;
axis = GamepadAxis::RightStickY;
_state.Buttons[(int32)GamepadButton::RightStickUp] = value < -RIGHT_STICK_THRESHOLD;
_state.Buttons[(int32)GamepadButton::RightStickDown] = value > RIGHT_STICK_THRESHOLD;
valueNormalized = -valueNormalized;
break;
case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
deadzone = TRIGGER_THRESHOLD;
axis = GamepadAxis::LeftTrigger;
_state.Buttons[(int32)GamepadButton::LeftTrigger] = value > TRIGGER_THRESHOLD;
break;
case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
deadzone = TRIGGER_THRESHOLD;
axis = GamepadAxis::RightTrigger;
_state.Buttons[(int32)GamepadButton::RightTrigger] = value > TRIGGER_THRESHOLD;
break;
default:
return;
}
if (value <= deadzone && value >= -deadzone)
valueNormalized = 0.0f;
_state.Axis[(int32)axis] = valueNormalized;
}
void SDLGamepad::OnButtonState(SDL_GamepadButton sdlButton, bool pressed)
{
switch (sdlButton)
{
case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_SOUTH:
_state.Buttons[(int32)GamepadButton::A] = pressed;
break;
case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_EAST:
_state.Buttons[(int32)GamepadButton::B] = pressed;
break;
case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_WEST:
_state.Buttons[(int32)GamepadButton::X] = pressed;
break;
case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_NORTH:
_state.Buttons[(int32)GamepadButton::Y] = pressed;
break;
case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
_state.Buttons[(int32)GamepadButton::LeftShoulder] = pressed;
break;
case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
_state.Buttons[(int32)GamepadButton::RightShoulder] = pressed;
break;
case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_BACK:
_state.Buttons[(int32)GamepadButton::Back] = pressed;
break;
case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_START:
_state.Buttons[(int32)GamepadButton::Start] = pressed;
break;
case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_LEFT_STICK:
_state.Buttons[(int32)GamepadButton::LeftThumb] = pressed;
break;
case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_RIGHT_STICK:
_state.Buttons[(int32)GamepadButton::RightThumb] = pressed;
break;
case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_DPAD_UP:
_state.Buttons[(int32)GamepadButton::DPadUp] = pressed;
break;
case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_DPAD_DOWN:
_state.Buttons[(int32)GamepadButton::DPadDown] = pressed;
break;
case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_DPAD_LEFT:
_state.Buttons[(int32)GamepadButton::DPadLeft] = pressed;
break;
case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
_state.Buttons[(int32)GamepadButton::DPadRight] = pressed;
break;
}
}
#endif

View File

@@ -0,0 +1,22 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#if PLATFORM_SDL
class SDLWindow;
union SDL_Event;
/// <summary>
/// SDL specific implementation of the input system parts.
/// </summary>
class SDLInput
{
public:
static void Init();
static void Update();
static bool HandleEvent(SDLWindow* window, SDL_Event& event);
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,587 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "SDLInput.h"
#if PLATFORM_SDL && PLATFORM_MAC
#include "SDLWindow.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/Time.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Input/Input.h"
#include "Engine/Input/Mouse.h"
#include "Engine/Platform/IGuiData.h"
#include "Engine/Platform/MessageBox.h"
#include "Engine/Platform/Platform.h"
#include "Engine/Platform/WindowsManager.h"
#include "Engine/Platform/Base/DragDropHelper.h"
#include "Engine/Platform/SDL/SDLClipboard.h"
#include "Engine/Platform/Unix/UnixFile.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Platform/Apple/AppleUtils.h"
#include <Cocoa/Cocoa.h>
#include <AppKit/AppKit.h>
#include <QuartzCore/CAMetalLayer.h>
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_hints.h>
#include <SDL3/SDL_messagebox.h>
#include <SDL3/SDL_mouse.h>
#include <SDL3/SDL_system.h>
#include <SDL3/SDL_timer.h>
#include <SDL3/SDL_video.h>
namespace MacImpl
{
Window* DraggedWindow = nullptr;
String DraggingData = String();
Float2 DraggingPosition;
Nullable<Float2> LastMouseDragPosition;
#if USE_EDITOR
//CriticalSection MacDragLocker;
bool DraggingActive = false;
bool DraggingIgnoreEvent = false;
NSDraggingSession* MacDragSession = nullptr;
//DoDragDropJob* MacDragJob = nullptr;
int64 MacDragExitFlag = 0;
#endif
}
class MacDropData : public IGuiData
{
public:
Type CurrentType;
String AsText;
Array<String> AsFiles;
Type GetType() const override
{
return CurrentType;
}
String GetAsText() const override
{
return AsText;
}
void GetAsFiles(Array<String>* files) const override
{
files->Add(AsFiles);
}
};
bool SDLPlatform::InitInternal()
{
return false;
}
bool SDLPlatform::UsesWindows()
{
return false;
}
bool SDLPlatform::UsesWayland()
{
return false;
}
bool SDLPlatform::UsesX11()
{
return false;
}
bool SDLPlatform::EventFilterCallback(void* userdata, SDL_Event* event)
{
Window* draggedWindow = *(Window**)userdata;
if (draggedWindow == nullptr)
{
if (MacImpl::DraggingActive)
{
// Handle events during drag operation here since the normal event loop is blocked
if (event->type == SDL_EVENT_WINDOW_EXPOSED)
{
LOG(Info, "Window exposed event");
// The internal timer is sending exposed events every ~16ms
#if USE_EDITOR
// Flush any single-frame shapes to prevent memory leaking (eg. via terrain collision debug during scene drawing with PhysicsColliders or PhysicsDebug flag)
DebugDraw::UpdateContext(nullptr, 0.0f);
#endif
Engine::OnUpdate(); // For docking updates
Engine::OnDraw();
}
else
{
SDLWindow* window = SDLWindow::GetWindowFromEvent(*event);
if (window)
window->HandleEvent(*event);
// We do not receive events at steady rate to keep the engine updated...
#if USE_EDITOR
// Flush any single-frame shapes to prevent memory leaking (eg. via terrain collision debug during scene drawing with PhysicsColliders or PhysicsDebug flag)
DebugDraw::UpdateContext(nullptr, 0.0f);
#endif
Engine::OnUpdate(); // For docking updates
Engine::OnDraw();
if (event->type == SDL_EVENT_DROP_BEGIN || event->type == SDL_EVENT_DROP_FILE || event->type == SDL_EVENT_DROP_TEXT)
return true; // Filtering these event stops other following events from getting added to the queue
else
LOG(Info, "Unhandled event: {}", event->type);
}
return false;
}
return true;
}
return true;
// When the window is being dragged on Windows, the internal message loop is blocking
// the SDL event queue. We need to handle all relevant events in this event watch callback
// to ensure dragging related functionality doesn't break due to engine not getting updated.
// This also happens to fix the engine freezing during the dragging operation.
#if false
SDLWindow* window = SDLWindow::GetWindowFromEvent(*event);
if (event->type == SDL_EVENT_WINDOW_EXPOSED)
{
// The internal timer is sending exposed events every ~16ms
Engine::OnUpdate(); // For docking updates
Engine::OnDraw();
return false;
}
else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN)
{
if (window)
{
bool result = false;
window->OnLeftButtonHit(WindowHitCodes::Caption, result);
//if (result)
// return false;
window->HandleEvent(*event);
}
return false;
}
else if (event->type == SDL_EVENT_WINDOW_MOVED)
{
if (window)
window->HandleEvent(*event);
/*if (WinImpl::DraggedWindowSize != window->GetClientSize())
{
// The window size changed while dragging, most likely due to maximized window restoring back to previous size.
WinImpl::DraggedWindowMousePosition = WinImpl::DraggedWindowStartPosition + WinImpl::DraggedWindowMousePosition - window->GetClientPosition();
WinImpl::DraggedWindowStartPosition = window->GetClientPosition();
WinImpl::DraggedWindowSize = window->GetClientSize();
}
Float2 windowPosition = Float2(static_cast<float>(event->window.data1), static_cast<float>(event->window.data2));
Float2 mousePosition = WinImpl::DraggedWindowMousePosition;
// Generate mouse movement events while dragging the window around
SDL_Event mouseMovedEvent { 0 };
mouseMovedEvent.motion.type = SDL_EVENT_MOUSE_MOTION;
mouseMovedEvent.motion.windowID = SDL_GetWindowID(WinImpl::DraggedWindow->GetSDLWindow());
mouseMovedEvent.motion.timestamp = SDL_GetTicksNS();
mouseMovedEvent.motion.state = SDL_BUTTON_LEFT;
mouseMovedEvent.motion.x = mousePosition.X;
mouseMovedEvent.motion.y = mousePosition.Y;
if (window)
window->HandleEvent(mouseMovedEvent);*/
return false;
}
if (window)
window->HandleEvent(*event);
return false;
#endif
}
void SDLPlatform::PreHandleEvents()
{
SDL_SetEventFilter(EventFilterCallback, &MacImpl::DraggedWindow);
}
void SDLPlatform::PostHandleEvents()
{
SDL_SetEventFilter(EventFilterCallback, &MacImpl::DraggedWindow);
// Handle window dragging release here
if (MacImpl::DraggedWindow != nullptr)
{
Float2 mousePosition;
auto buttons = SDL_GetGlobalMouseState(&mousePosition.X, &mousePosition.Y);
bool buttonReleased = (buttons & SDL_BUTTON_MASK(SDL_BUTTON_LEFT)) == 0;
if (buttonReleased)
{
// Send simulated mouse up event
SDL_Event buttonUpEvent { 0 };
buttonUpEvent.motion.type = SDL_EVENT_MOUSE_BUTTON_UP;
buttonUpEvent.button.down = false;
buttonUpEvent.motion.windowID = SDL_GetWindowID(MacImpl::DraggedWindow->GetSDLWindow());
buttonUpEvent.motion.timestamp = SDL_GetTicksNS();
buttonUpEvent.motion.state = SDL_BUTTON_LEFT;
buttonUpEvent.button.clicks = 1;
buttonUpEvent.motion.x = mousePosition.X;
buttonUpEvent.motion.y = mousePosition.Y;
MacImpl::DraggedWindow->HandleEvent(buttonUpEvent);
MacImpl::DraggedWindow = nullptr;
}
}
}
bool SDLWindow::HandleEventInternal(SDL_Event& event)
{
switch (event.type)
{
case SDL_EVENT_WINDOW_MOVED:
{
// Quartz doesn't report any mouse events when mouse is over the caption area, send a simulated event instead...
Float2 mousePosition;
auto buttons = SDL_GetGlobalMouseState(&mousePosition.X, &mousePosition.Y);
if ((buttons & SDL_BUTTON_MASK(SDL_BUTTON_LEFT)) != 0)
{
if (MacImpl::DraggedWindow == nullptr)
{
// TODO: verify mouse position, window focus
bool result = false;
OnLeftButtonHit(WindowHitCodes::Caption, result);
if (result)
MacImpl::DraggedWindow = this;
}
else
{
Float2 mousePos = Platform::GetMousePosition();
Input::Mouse->OnMouseMove(mousePos, this);
}
}
break;
}
case SDL_EVENT_MOUSE_BUTTON_UP:
case SDL_EVENT_MOUSE_BUTTON_DOWN:
{
if (MacImpl::LastMouseDragPosition.HasValue())
{
// SDL reports wrong mouse position after dragging has ended
Float2 mouseClientPosition = ScreenToClient(MacImpl::LastMouseDragPosition.GetValue());
event.button.x = mouseClientPosition.X;
event.button.y = mouseClientPosition.Y;
}
break;
}
case SDL_EVENT_MOUSE_MOTION:
{
if (MacImpl::LastMouseDragPosition.HasValue())
MacImpl::LastMouseDragPosition.Reset();
if (MacImpl::DraggedWindow != nullptr)
return true;
break;
}
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
{
OnDragLeave(); // Check for release of mouse button too?
break;
}
//case SDL_EVENT_CLIPBOARD_UPDATE:
case SDL_EVENT_DROP_BEGIN:
case SDL_EVENT_DROP_POSITION:
case SDL_EVENT_DROP_FILE:
case SDL_EVENT_DROP_TEXT:
case SDL_EVENT_DROP_COMPLETE:
{
{
// HACK: We can't use Wayland listeners due to SDL also using them at the same time causes
// some of the events to drop and make it impossible to implement dragging on application side.
// We can get enough information through SDL_EVENT_DROP_* events to fill in the blanks for the
// drag and drop implementation.
auto dpiScale = GetDpiScale();
Float2 mousePos = Float2(event.drop.x * dpiScale, event.drop.y * dpiScale);
DragDropEffect effect = DragDropEffect::None;
String text(event.drop.data);
MacDropData dropData;
if (MacImpl::DraggingActive)
{
// We don't have the window dragging data during these events...
text = MacImpl::DraggingData;
mousePos = ScreenToClient(MacImpl::DraggingPosition);
// Ensure mouse position is updated while dragging
Input::Mouse->OnMouseMove(MacImpl::DraggingPosition, this);
MacImpl::LastMouseDragPosition = MacImpl::DraggingPosition;
}
dropData.AsText = text;
if (event.type == SDL_EVENT_DROP_BEGIN)
{
// We don't know the type of dragged data at this point, so call the events for both types
if (!MacImpl::DraggingActive)
{
dropData.CurrentType = IGuiData::Type::Files;
OnDragEnter(&dropData, mousePos, effect);
}
if (effect == DragDropEffect::None)
{
dropData.CurrentType = IGuiData::Type::Text;
OnDragEnter(&dropData, mousePos, effect);
}
}
else if (event.type == SDL_EVENT_DROP_POSITION)
{
Input::Mouse->OnMouseMove(ClientToScreen(mousePos), this);
// We don't know the type of dragged data at this point, so call the events for both types
if (!MacImpl::DraggingActive)
{
dropData.CurrentType = IGuiData::Type::Files;
OnDragOver(&dropData, mousePos, effect);
}
if (effect == DragDropEffect::None)
{
dropData.CurrentType = IGuiData::Type::Text;
OnDragOver(&dropData, mousePos, effect);
}
}
else if (event.type == SDL_EVENT_DROP_FILE)
{
text.Split('\n', dropData.AsFiles);
dropData.CurrentType = IGuiData::Type::Files;
OnDragDrop(&dropData, mousePos, effect);
}
else if (event.type == SDL_EVENT_DROP_TEXT)
{
dropData.CurrentType = IGuiData::Type::Text;
OnDragDrop(&dropData, mousePos, effect);
}
else if (event.type == SDL_EVENT_DROP_COMPLETE)
{
OnDragLeave();
if (MacImpl::DraggingActive)
{
// The previous drop events needs to be flushed to avoid processing them twice
SDL_FlushEvents(SDL_EVENT_DROP_FILE, SDL_EVENT_DROP_POSITION);
}
}
// TODO: Implement handling for feedback effect result (https://github.com/libsdl-org/SDL/issues/10448)
}
break;
}
}
return false;
}
void SDLPlatform::SetHighDpiAwarenessEnabled(bool enable)
{
// TODO: This is now called before Platform::Init, ensure the scaling is changed accordingly during Platform::Init (see ApplePlatform::SetHighDpiAwarenessEnabled)
}
inline bool IsWindowInvalid(Window* win)
{
WindowsManager::WindowsLocker.Lock();
const bool hasWindow = WindowsManager::Windows.Contains(win);
WindowsManager::WindowsLocker.Unlock();
return !hasWindow || !win;
}
Float2 GetWindowTitleSize(const SDLWindow* window)
{
Float2 size = Float2::Zero;
if (window->GetSettings().HasBorder)
{
NSRect frameStart = [(NSWindow*)window->GetNativePtr() frameRectForContentRect:NSMakeRect(0, 0, 0, 0)];
size.Y = frameStart.size.height;
}
return size * MacPlatform::ScreenScale;
}
Float2 GetMousePosition(SDLWindow* window, NSEvent* event)
{
NSRect frame = [(NSWindow*)window->GetNativePtr() frame];
NSPoint point = [event locationInWindow];
return Float2(point.x, frame.size.height - point.y) * MacPlatform::ScreenScale - GetWindowTitleSize(window);
}
Float2 GetMousePosition(SDLWindow* window, const NSPoint& point)
{
NSRect frame = [(NSWindow*)window->GetNativePtr() frame];
CGRect screenBounds = CGDisplayBounds(CGMainDisplayID());
return Float2(point.x, screenBounds.size.height - point.y) * MacPlatform::ScreenScale;
}
void GetDragDropData(const SDLWindow* window, id<NSDraggingInfo> sender, Float2& mousePos, MacDropData& dropData)
{
NSRect frame = [(NSWindow*)window->GetNativePtr() frame];
NSPoint point = [sender draggingLocation];
mousePos = Float2(point.x, frame.size.height - point.y) * MacPlatform::ScreenScale - GetWindowTitleSize(window);
NSPasteboard* pasteboard = [sender draggingPasteboard];
if ([[pasteboard types] containsObject:NSPasteboardTypeString])
{
dropData.CurrentType = IGuiData::Type::Text;
dropData.AsText = AppleUtils::ToString((CFStringRef)[pasteboard stringForType:NSPasteboardTypeString]);
}
else
{
dropData.CurrentType = IGuiData::Type::Files;
NSArray* files = [pasteboard readObjectsForClasses:@[[NSURL class]] options:nil];
for (int32 i = 0; i < [files count]; i++)
{
NSString* url = [[files objectAtIndex:i] path];
NSString* file = [NSURL URLWithString:url].path;
dropData.AsFiles.Add(AppleUtils::ToString((CFStringRef)file));
}
}
}
NSDragOperation GetDragDropOperation(DragDropEffect dragDropEffect)
{
NSDragOperation result = NSDragOperationCopy;
switch (dragDropEffect)
{
case DragDropEffect::None:
//result = NSDragOperationNone;
break;
case DragDropEffect::Copy:
result = NSDragOperationCopy;
break;
case DragDropEffect::Move:
result = NSDragOperationMove;
break;
case DragDropEffect::Link:
result = NSDragOperationLink;
break;
}
return result;
}
#undef INCLUDED_IN_SDL
@interface ClipboardDataProviderImpl : NSObject <NSPasteboardItemDataProvider, NSDraggingSource>
{
@public
SDLWindow* Window;
}
@end
@implementation ClipboardDataProviderImpl
// NSPasteboardItemDataProvider
// ---
- (void)pasteboard:(nullable NSPasteboard*)pasteboard item:(NSPasteboardItem*)item provideDataForType:(NSPasteboardType)type
{
LOG(Info, "pasteboard");
if (IsWindowInvalid(Window)) return;
[pasteboard setString:(NSString*)AppleUtils::ToString(MacImpl::DraggingData) forType:NSPasteboardTypeString];
}
- (void)pasteboardFinishedWithDataProvider:(NSPasteboard*)pasteboard
{
LOG(Info, "pasteboardFinishedWithDataProvider");
}
// NSDraggingSource
// ---
- (NSDragOperation)draggingSession:(NSDraggingSession*)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
{
if (IsWindowInvalid(Window))
return NSDragOperationNone;
switch(context)
{
case NSDraggingContextOutsideApplication:
LOG(Info, "draggingSession sourceOperationMaskForDraggingContext: outside");
return NSDragOperationCopy;
case NSDraggingContextWithinApplication:
LOG(Info, "draggingSession sourceOperationMaskForDraggingContext: inside");
return NSDragOperationCopy;
default:
LOG(Info, "draggingSession sourceOperationMaskForDraggingContext: unknown");
return NSDragOperationMove;
}
}
- (void)draggingSession:(NSDraggingSession*)session willBeginAtPoint:(NSPoint)screenPoint
{
LOG(Info, "draggingSession willBeginAtPoint");
MacImpl::DraggingPosition = GetMousePosition(Window, screenPoint);
}
- (void)draggingSession:(NSDraggingSession*)session movedToPoint:(NSPoint)screenPoint
{
//LOG(Info, "draggingSession movedToPoint");
MacImpl::DraggingPosition = GetMousePosition(Window, screenPoint);
}
- (void)draggingSession:(NSDraggingSession*)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
{
LOG(Info, "draggingSession endedAtPoint");
MacImpl::DraggingPosition = GetMousePosition(Window, screenPoint);
#if USE_EDITOR
// Stop background worker once the drag ended
if (MacImpl::MacDragSession && MacImpl::MacDragSession == session)
Platform::AtomicStore(&MacImpl::MacDragExitFlag, 1);
#endif
}
@end
DragDropEffect SDLWindow::DoDragDrop(const StringView& data)
{
NSWindow* window = (NSWindow*)_handle;
ClipboardDataProviderImpl* clipboardDataProvider = [ClipboardDataProviderImpl alloc];
clipboardDataProvider->Window = this;
// Create mouse drag event
NSEvent* event = [NSEvent
mouseEventWithType:NSEventTypeLeftMouseDragged
location:window.mouseLocationOutsideOfEventStream
modifierFlags:0
timestamp:NSApp.currentEvent.timestamp
windowNumber:window.windowNumber
context:nil
eventNumber:0
clickCount:1
pressure:1.0];
// Create drag item
NSPasteboardItem* pasteItem = [NSPasteboardItem new];
[pasteItem setDataProvider:clipboardDataProvider forTypes:[NSArray arrayWithObjects:NSPasteboardTypeString, nil]];
NSDraggingItem* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pasteItem];
[dragItem setDraggingFrame:NSMakeRect(event.locationInWindow.x, event.locationInWindow.y, 100, 100) contents:nil];
// Start dragging session
NSDraggingSession* draggingSession = [window.contentView beginDraggingSessionWithItems:[NSArray arrayWithObject:dragItem] event:event source:clipboardDataProvider];
DragDropEffect result = DragDropEffect::None;
#if USE_EDITOR
// Create background worker that will keep updating GUI (perform rendering)
ASSERT(!MacImpl::MacDragSession);
MacImpl::MacDragSession = draggingSession;
MacImpl::MacDragExitFlag = 0;
MacImpl::DraggingData = data;
MacImpl::DraggingActive = true;
while (Platform::AtomicRead(&MacImpl::MacDragExitFlag) == 0)
{
// The internal event loop will block here during the drag operation,
// events are processed in the event filter callback instead.
SDLPlatform::Tick();
Platform::Sleep(1);
}
MacImpl::DraggingActive = false;
MacImpl::DraggingData.Clear();
MacImpl::MacDragSession = nullptr;
#endif
return result;
}
DragDropEffect SDLWindow::DoDragDrop(const StringView& data, const Float2& offset, Window* dragSourceWindow)
{
Show();
return DragDropEffect::None;
}
#endif

View File

@@ -0,0 +1,258 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#if PLATFORM_SDL && PLATFORM_WINDOWS
#include "SDLPlatform.h"
#include "SDLInput.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Platform/WindowsManager.h"
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
#include "Engine/Input/Mouse.h"
#include "Engine/Core/Log.h"
#include "Engine/Engine/Engine.h"
#include <SDL3/SDL_hints.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_system.h>
#include <SDL3/SDL_timer.h>
#if USE_EDITOR
#include <oleidl.h>
#endif
namespace WinImpl
{
Window* DraggedWindow;
Float2 DraggedWindowStartPosition = Float2::Zero;
Float2 DraggedWindowMousePosition = Float2::Zero;
Float2 DraggedWindowSize = Float2::Zero;
}
// The events for releasing the mouse during window dragging are missing, handle the mouse release event here
bool SDLCALL SDLPlatform::EventMessageHook(void* userdata, MSG* msg)
{
if (msg->message == WM_NCLBUTTONDOWN)
{
Window* window = WindowsManager::GetByNativePtr(msg->hwnd);
Float2 mousePosition(static_cast<float>(static_cast<LONG>(WINDOWS_GET_X_LPARAM(msg->lParam))), static_cast<float>(static_cast<LONG>(WINDOWS_GET_Y_LPARAM(msg->lParam))));
WinImpl::DraggedWindow = window;
WinImpl::DraggedWindowStartPosition = window->GetClientPosition();
WinImpl::DraggedWindowMousePosition = mousePosition - WinImpl::DraggedWindowStartPosition;
WinImpl::DraggedWindowSize = window->GetClientSize();
bool result = false;
WindowHitCodes hit = static_cast<WindowHitCodes>(msg->wParam);
window->OnHitTest(mousePosition, hit, result);
//if (result && hit != WindowHitCodes::Caption)
// return false;
if (hit == WindowHitCodes::Caption)
{
SDL_Event event{ 0 };
event.button.type = SDL_EVENT_MOUSE_BUTTON_DOWN;
event.button.down = true;
event.button.timestamp = SDL_GetTicksNS();
event.button.windowID = SDL_GetWindowID(window->GetSDLWindow());
event.button.button = SDL_BUTTON_LEFT;
event.button.clicks = 1;
event.button.x = WinImpl::DraggedWindowMousePosition.X;
event.button.y = WinImpl::DraggedWindowMousePosition.Y;
SDL_PushEvent(&event);
}
}
return true;
}
bool SDLPlatform::InitInternal()
{
// Workaround required for handling window dragging events properly
SDL_SetWindowsMessageHook(&EventMessageHook, nullptr);
if (WindowsPlatform::Init())
return true;
return false;
}
bool SDLPlatform::EventFilterCallback(void* userdata, SDL_Event* event)
{
Window* draggedWindow = *(Window**)userdata;
if (draggedWindow == nullptr)
return true;
// When the window is being dragged on Windows, the internal message loop is blocking
// the SDL event queue. We need to handle all relevant events in this event watch callback
// to ensure dragging related functionality doesn't break due to engine not getting updated.
// This also happens to fix the engine freezing during the dragging operation.
SDLWindow* window = SDLWindow::GetWindowFromEvent(*event);
if (event->type == SDL_EVENT_WINDOW_EXPOSED)
{
// The internal timer is sending exposed events every ~16ms
Engine::OnUpdate(); // For docking updates
Engine::OnDraw();
return false;
}
else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN)
{
if (window)
{
bool result = false;
window->OnLeftButtonHit(WindowHitCodes::Caption, result);
//if (result)
// return false;
window->HandleEvent(*event);
}
return false;
}
else if (event->type == SDL_EVENT_WINDOW_MOVED)
{
if (window)
window->HandleEvent(*event);
if (WinImpl::DraggedWindowSize != window->GetClientSize())
{
// The window size changed while dragging, most likely due to maximized window restoring back to previous size.
WinImpl::DraggedWindowMousePosition = WinImpl::DraggedWindowStartPosition + WinImpl::DraggedWindowMousePosition - window->GetClientPosition();
WinImpl::DraggedWindowStartPosition = window->GetClientPosition();
WinImpl::DraggedWindowSize = window->GetClientSize();
}
Float2 windowPosition = Float2(static_cast<float>(event->window.data1), static_cast<float>(event->window.data2));
Float2 mousePosition = WinImpl::DraggedWindowMousePosition;
// Generate mouse movement events while dragging the window around
SDL_Event mouseMovedEvent { 0 };
mouseMovedEvent.motion.type = SDL_EVENT_MOUSE_MOTION;
mouseMovedEvent.motion.windowID = SDL_GetWindowID(WinImpl::DraggedWindow->GetSDLWindow());
mouseMovedEvent.motion.timestamp = SDL_GetTicksNS();
mouseMovedEvent.motion.state = SDL_BUTTON_LEFT;
mouseMovedEvent.motion.x = mousePosition.X;
mouseMovedEvent.motion.y = mousePosition.Y;
if (window)
window->HandleEvent(mouseMovedEvent);
return false;
}
if (window)
window->HandleEvent(*event);
return false;
}
void SDLPlatform::PreHandleEvents()
{
SDL_AddEventWatch(EventFilterCallback, &WinImpl::DraggedWindow);
}
void SDLPlatform::PostHandleEvents()
{
SDL_RemoveEventWatch(EventFilterCallback, &WinImpl::DraggedWindow);
// Handle window dragging release here
if (WinImpl::DraggedWindow != nullptr)
{
Float2 mousePosition;
auto buttons = SDL_GetGlobalMouseState(&mousePosition.X, &mousePosition.Y);
// Send simulated mouse up event
SDL_Event buttonUpEvent { 0 };
buttonUpEvent.motion.type = SDL_EVENT_MOUSE_BUTTON_UP;
buttonUpEvent.button.down = false;
buttonUpEvent.motion.windowID = SDL_GetWindowID(WinImpl::DraggedWindow->GetSDLWindow());
buttonUpEvent.motion.timestamp = SDL_GetTicksNS();
buttonUpEvent.motion.state = SDL_BUTTON_LEFT;
buttonUpEvent.button.clicks = 1;
buttonUpEvent.motion.x = mousePosition.X;
buttonUpEvent.motion.y = mousePosition.Y;
WinImpl::DraggedWindow->HandleEvent(buttonUpEvent);
WinImpl::DraggedWindow = nullptr;
}
}
bool SDLWindow::HandleEventInternal(SDL_Event& event)
{
switch (event.type)
{
case SDL_EVENT_WINDOW_DESTROYED:
{
#if USE_EDITOR
// Disable file dropping
if (_settings.AllowDragAndDrop)
{
const auto result = RevokeDragDrop((HWND)_handle);
if (result != S_OK)
LOG(Warning, "Window drag and drop service error: 0x{0:x}:{1}", result, 2);
}
#endif
break;
}
case SDL_EVENT_MOUSE_BUTTON_UP:
{
if (WinImpl::DraggedWindow != nullptr && WinImpl::DraggedWindow->_windowId != event.button.windowID)
{
// Send the button event to dragged window as well
Float2 mousePos = ClientToScreen({ event.button.x, event.button.y });
Float2 clientPos = WinImpl::DraggedWindow->ScreenToClient(mousePos);
SDL_Event event2 = event;
event2.button.windowID = WinImpl::DraggedWindow->_windowId;
event2.button.x = clientPos.X;
event2.button.y = clientPos.Y;
SDLInput::HandleEvent(WinImpl::DraggedWindow, event2);
}
break;
}
default:
break;
}
return false;
}
bool SDLPlatform::UsesWindows()
{
return true;
}
bool SDLPlatform::UsesWayland()
{
return false;
}
bool SDLPlatform::UsesX11()
{
return false;
}
void SDLWindow::Focus()
{
auto activateWhenRaised = SDL_GetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED);
SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, "1");
// Forcing the window to focus causes issues with opening context menus while window is maximized
//auto forceRaiseWindow = SDL_GetHint(SDL_HINT_FORCE_RAISEWINDOW);
//SDL_SetHint(SDL_HINT_FORCE_RAISEWINDOW, "1");
SDL_RaiseWindow(_window);
SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, activateWhenRaised);
//SDL_SetHint(SDL_HINT_FORCE_RAISEWINDOW, forceRaiseWindow);
}
void SDLPlatform::SetHighDpiAwarenessEnabled(bool enable)
{
// Other supported values: "permonitor", "permonitorv2"
SDL_SetHint("SDL_WINDOWS_DPI_AWARENESS", enable ? "system" : "unaware");
}
DragDropEffect SDLWindow::DoDragDrop(const StringView& data, const Float2& offset, Window* dragSourceWindow)
{
Show();
return DragDropEffect::None;
}
#endif

View File

@@ -0,0 +1,293 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#if PLATFORM_SDL
#include "SDLPlatform.h"
#include "SDLWindow.h"
#include "Engine/Core/Log.h"
#include "Engine/Input/Input.h"
#include "Engine/Input/Mouse.h"
#include "Engine/Platform/BatteryInfo.h"
#include "Engine/Platform/WindowsManager.h"
#include "Engine/Platform/SDL/SDLInput.h"
#include "Engine/Engine/Engine.h"
#include <SDL3/SDL_hints.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_misc.h>
#include <SDL3/SDL_power.h>
#include <SDL3/SDL_revision.h>
#include <SDL3/SDL_system.h>
#include <SDL3/SDL_version.h>
#include <SDL3/SDL_locale.h>
#if PLATFORM_LINUX
#include "Engine/Engine/CommandLine.h"
#endif
#define DefaultDPI 96
namespace
{
int32 SystemDpi = 96;
String UserLocale("en");
}
bool SDLPlatform::Init()
{
#if PLATFORM_LINUX
if (CommandLine::Options.X11.IsTrue())
SDL_SetHintWithPriority(SDL_HINT_VIDEO_DRIVER, "x11", SDL_HINT_OVERRIDE);
else if (CommandLine::Options.Wayland.IsTrue())
SDL_SetHintWithPriority(SDL_HINT_VIDEO_DRIVER, "wayland", SDL_HINT_OVERRIDE);
else
{
// Override the X11 preference when running in Wayland session
String waylandDisplayEnv;
if (!GetEnvironmentVariable(String("WAYLAND_DISPLAY"), waylandDisplayEnv))
SDL_SetHintWithPriority(SDL_HINT_VIDEO_DRIVER, "wayland", SDL_HINT_OVERRIDE);
}
#endif
#if PLATFORM_LINUX
// TODO: This should be read from the platform configuration (needed for desktop icon handling)
#if USE_EDITOR
SDL_SetHint(SDL_HINT_APP_ID, StringAnsi("com.FlaxEngine.FlaxEditor").Get());
#else
SDL_SetHint(SDL_HINT_APP_ID, StringAnsi("com.FlaxEngine.FlaxGame").Get());
#endif
#else
SDL_SetHint(SDL_HINT_APP_ID, StringAnsi(ApplicationClassName).Get());
#endif
SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, "0");
SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, "0");
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); // Fixes context menu focus issues when clicking unfocused menus
SDL_SetHint(SDL_HINT_WINDOWS_ERASE_BACKGROUND_MODE, "0");
SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "0"); // Already handled during platform initialization
SDL_SetHint("SDL_BORDERLESS_RESIZABLE_STYLE", "1"); // Allow borderless windows to be resizable on Windows
//SDL_SetHint("SDL_BORDERLESS_WINDOWED_STYLE", "1");
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_WARP_MOTION, "0");
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE, "1"); // Needed for tracking mode
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_CENTER, "0"); // Relative mode can be active when cursor is shown and clipped
SDL_SetHint(SDL_HINT_MOUSE_DOUBLE_CLICK_RADIUS, "8"); // Reduce the default mouse double-click radius
//SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1"); // Disables raw mouse input
SDL_SetHint(SDL_HINT_WINDOWS_RAW_KEYBOARD, "1");
SDL_SetHint(SDL_HINT_VIDEO_WAYLAND_SCALE_TO_DISPLAY, "1");
//if (InitInternal())
// return true;
if (!SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD))
Platform::Fatal(String::Format(TEXT("Failed to initialize SDL: {0}."), String(SDL_GetError())));
int localesCount = 0;
auto locales = SDL_GetPreferredLocales(&localesCount);
for (int i = 0; i < localesCount; i++)
{
auto language = StringAnsiView(locales[i]->language);
auto country = StringAnsiView(locales[i]->country);
if (language.StartsWith("en"))
{
if (country != nullptr)
UserLocale = String::Format(TEXT("{0}-{1}"), String(language), String(locales[i]->country));
else
UserLocale = String(language);
break;
}
}
SDL_free(locales);
if (InitInternal())
return true;
#if !PLATFORM_MAC
if (!UsesWayland())
{
// Disable SDL clipboard support
SDL_SetEventEnabled(SDL_EVENT_CLIPBOARD_UPDATE, false);
// Disable SDL drag and drop support
SDL_SetEventEnabled(SDL_EVENT_DROP_FILE, false);
SDL_SetEventEnabled(SDL_EVENT_DROP_TEXT, false);
SDL_SetEventEnabled(SDL_EVENT_DROP_BEGIN, false);
SDL_SetEventEnabled(SDL_EVENT_DROP_COMPLETE, false);
SDL_SetEventEnabled(SDL_EVENT_DROP_POSITION, false);
}
#endif
SDLInput::Init();
SDLWindow::Init();
SystemDpi = (int)(SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()) * DefaultDPI);
//SDL_StartTextInput(); // TODO: Call this only when text input is expected (shows virtual keyboard in some cases)
return base::Init();
}
void SDLPlatform::LogInfo()
{
base::LogInfo();
const int32 runtimeVersion = SDL_GetVersion();
LOG(Info, "Using SDL version {}.{}.{} ({}), runtime: {}.{}.{} ({})",
SDL_VERSIONNUM_MAJOR(SDL_VERSION), SDL_VERSIONNUM_MINOR(SDL_VERSION), SDL_VERSIONNUM_MICRO(SDL_VERSION), String(SDL_REVISION),
SDL_VERSIONNUM_MAJOR(runtimeVersion), SDL_VERSIONNUM_MINOR(runtimeVersion), SDL_VERSIONNUM_MICRO(runtimeVersion), String(SDL_GetRevision()));
LOG(Info, "SDL video driver: {}", String(SDL_GetCurrentVideoDriver()));
}
void SDLPlatform::Tick()
{
SDLInput::Update();
PreHandleEvents();
SDL_PumpEvents();
SDL_Event events[32];
int count = SDL_PeepEvents(events, SDL_arraysize(events), SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST);
for (int i = 0; i < count; ++i)
{
SDLWindow* window = SDLWindow::GetWindowFromEvent(events[i]);
if (window)
window->HandleEvent(events[i]);
else if (events[i].type >= SDL_EVENT_JOYSTICK_AXIS_MOTION && events[i].type <= SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED)
SDLInput::HandleEvent(nullptr, events[i]);
else
HandleEvent(events[i]);
}
PostHandleEvents();
}
bool SDLPlatform::HandleEvent(SDL_Event& event)
{
return true;
}
String SDLPlatform::GetDisplayServer()
{
#if PLATFORM_LINUX
String driver(SDL_GetCurrentVideoDriver());
if (driver.Length() > 0)
driver[0] = StringUtils::ToUpper(driver[0]);
return driver;
#else
return String::Empty;
#endif
}
BatteryInfo SDLPlatform::GetBatteryInfo()
{
BatteryInfo info;
int percentage;
SDL_PowerState powerState = SDL_GetPowerInfo(nullptr, &percentage);
if (percentage < 0)
info.BatteryLifePercent = 1.0f;
else
info.BatteryLifePercent = (float)percentage / 100.0f;
switch (powerState)
{
case SDL_POWERSTATE_CHARGING:
info.State = BatteryInfo::States::BatteryCharging;
break;
case SDL_POWERSTATE_ON_BATTERY:
info.State = BatteryInfo::States::BatteryDischarging;
break;
case SDL_POWERSTATE_CHARGED:
info.State = BatteryInfo::States::Connected;
break;
default:
info.State = BatteryInfo::States::Unknown;
}
return info;
}
int32 SDLPlatform::GetDpi()
{
return SystemDpi;
}
String SDLPlatform::GetUserLocaleName()
{
return UserLocale;
}
void SDLPlatform::OpenUrl(const StringView& url)
{
StringAnsi urlStr(url);
SDL_OpenURL(urlStr.GetText());
}
Float2 SDLPlatform::GetMousePosition()
{
#if PLATFORM_LINUX
if (UsesWayland())
{
// Wayland doesn't support reporting global mouse position,
// use the last known reported position we got from received window events.
return Input::GetMouseScreenPosition();
}
if (UsesX11())
#elif PLATFORM_LINUX || PLATFORM_MAC
{
Float2 pos;
SDL_GetGlobalMouseState(&pos.X, &pos.Y);
return pos;
}
#endif
return Input::GetMouseScreenPosition();
}
void SDLPlatform::SetMousePosition(const Float2& pos)
{
SDL_WarpMouseGlobal(pos.X, pos.Y);
}
Float2 SDLPlatform::GetDesktopSize()
{
SDL_Rect rect;
SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &rect);
return Float2(static_cast<float>(rect.w), static_cast<float>(rect.h));
}
Rectangle SDLPlatform::GetMonitorBounds(const Float2& screenPos)
{
SDL_Point point{ (int32)screenPos.X, (int32)screenPos.Y };
SDL_DisplayID display = SDL_GetDisplayForPoint(&point);
SDL_Rect rect;
SDL_GetDisplayBounds(display, &rect);
return Rectangle(static_cast<float>(rect.x), static_cast<float>(rect.y), static_cast<float>(rect.w), static_cast<float>(rect.h));
}
Rectangle SDLPlatform::GetVirtualDesktopBounds()
{
int count;
const SDL_DisplayID* displays = SDL_GetDisplays(&count);
if (displays == nullptr)
return Rectangle::Empty;
Rectangle bounds = Rectangle::Empty;
for (int i = 0; i < count; i++)
{
SDL_DisplayID display = displays[i];
SDL_Rect rect;
SDL_GetDisplayBounds(display, &rect);
bounds = Rectangle::Union(bounds, Rectangle(static_cast<float>(rect.x), static_cast<float>(rect.y), static_cast<float>(rect.w), static_cast<float>(rect.h)));
}
SDL_free((void*)displays);
return bounds;
}
Window* SDLPlatform::CreateWindow(const CreateWindowSettings& settings)
{
return New<SDLWindow>(settings);
}
#endif

View File

@@ -0,0 +1,90 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#if PLATFORM_SDL
#include "Engine/Platform/Base/Enums.h"
#if PLATFORM_WINDOWS
#include "Engine/Platform/Windows/WindowsPlatform.h"
typedef struct tagMSG MSG;
#elif PLATFORM_LINUX
#include "Engine/Platform/Linux/LinuxPlatform.h"
union _XEvent;
#elif PLATFORM_MAC
#include "Engine/Platform/Mac/MacPlatform.h"
#else
static_assert(false, "Unsupported Platform");
#endif
class SDLWindow;
union SDL_Event;
/// <summary>
/// The SDL platform implementation and application management utilities.
/// </summary>
class FLAXENGINE_API SDLPlatform
#if PLATFORM_WINDOWS
: public WindowsPlatform
{
using base = WindowsPlatform;
#elif PLATFORM_LINUX
: public LinuxPlatform
{
using base = LinuxPlatform;
#elif PLATFORM_MAC
: public MacPlatform
{
using base = MacPlatform;
#else
{
static_assert(false, "Unsupported Platform");
#endif
friend SDLWindow;
private:
static bool InitInternal();
#if PLATFORM_LINUX
static bool InitX11(void* display);
#endif
static bool HandleEvent(SDL_Event& event);
#if PLATFORM_WINDOWS
static bool EventMessageHook(void* userdata, MSG* msg);
static bool EventFilterCallback(void* userdata, SDL_Event* event);
#elif PLATFORM_LINUX
static bool X11EventHook(void* userdata, _XEvent* xevent);
#elif PLATFORM_MAC
static bool EventFilterCallback(void* userdata, SDL_Event* event);
#endif
static void PreHandleEvents();
static void PostHandleEvents();
public:
#if PLATFORM_LINUX
static void* GetXDisplay();
#endif
static bool UsesWindows();
static bool UsesWayland();
static bool UsesX11();
public:
// [PlatformBase]
static bool Init();
static void LogInfo();
static void Tick();
static String GetDisplayServer();
static void SetHighDpiAwarenessEnabled(bool enable);
static BatteryInfo GetBatteryInfo();
static int32 GetDpi();
static String GetUserLocaleName();
static void OpenUrl(const StringView& url);
static Float2 GetMousePosition();
static void SetMousePosition(const Float2& pos);
static Float2 GetDesktopSize();
static Rectangle GetMonitorBounds(const Float2& screenPos);
static Rectangle GetVirtualDesktopBounds();
static Window* CreateWindow(const CreateWindowSettings& settings);
};
#endif

View File

@@ -0,0 +1,965 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#if PLATFORM_SDL
#include "SDLWindow.h"
#include "SDLInput.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Math/Rectangle.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/GPUSwapChain.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Input/Input.h"
#include "Engine/Input/Keyboard.h"
#include "Engine/Input/Mouse.h"
#include "Engine/Platform/WindowsManager.h"
#define NOGDI
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_hints.h>
#include <SDL3/SDL_properties.h>
#include <SDL3/SDL_video.h>
#undef CreateWindow
#if PLATFORM_WINDOWS
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
#if USE_EDITOR
#include <oleidl.h>
#endif
#elif PLATFORM_LINUX
#include "Engine/Platform/Linux/IncludeX11.h"
#elif PLATFORM_MAC
#include <Cocoa/Cocoa.h>
#else
static_assert(false, "Unsupported Platform");
#endif
#define DefaultDPI 96
namespace WindowImpl
{
SDLWindow* LastEventWindow = nullptr;
static SDL_Cursor* Cursors[SDL_SYSTEM_CURSOR_COUNT] = { nullptr };
}
using namespace WindowImpl;
SDL_HitTestResult OnWindowHitTest(SDL_Window* win, const SDL_Point* area, void* data);
void GetRelativeWindowOffset(WindowType type, SDLWindow* parentWindow, Int2& positionOffset);
Int2 GetSDLWindowScreenPosition(const SDLWindow* window);
void SetSDLWindowScreenPosition(const SDLWindow* window, const Int2 position);
bool IsPopupWindow(WindowType type)
{
return type == WindowType::Popup || type == WindowType::Tooltip;
}
void* GetNativeWindowPointer(SDL_Window* window)
{
void* windowPtr;
auto props = SDL_GetWindowProperties(window);
#if PLATFORM_WINDOWS
windowPtr = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
#elif PLATFORM_LINUX
windowPtr = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr);
if (windowPtr == nullptr)
windowPtr = (void*)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
#elif PLATFORM_MAC
windowPtr = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr);
#elif PLATFORM_ANDROID
windowPtr = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, nullptr);
#elif PLATFORM_IOS
windowPtr = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, nullptr);
#else
static_assert(false, "unsupported platform");
#endif
return windowPtr;
}
void SDLWindow::Init()
{
}
SDLWindow::SDLWindow(const CreateWindowSettings& settings)
: WindowBase(settings)
, _handle(nullptr)
, _cachedClientRectangle(Rectangle())
#if PLATFORM_LINUX
, _dragOver(false)
#endif
{
Int2 clientSize(Math::TruncToInt(settings.Size.X), Math::TruncToInt(settings.Size.Y));
_clientSize = Float2(clientSize);
if (SDLPlatform::UsesWayland())
{
// The compositor seems to crash when something is rendered to the hidden popup window surface
_settings.ShowAfterFirstPaint = _showAfterFirstPaint = false;
}
uint32 flags = SDL_WINDOW_HIDDEN;
if (_settings.Type == WindowType::Utility)
flags |= SDL_WINDOW_UTILITY;
else if (_settings.Type == WindowType::Regular && !_settings.ShowInTaskbar)
flags |= SDL_WINDOW_UTILITY;
else if (_settings.Type == WindowType::Tooltip)
flags |= SDL_WINDOW_TOOLTIP;
else if (_settings.Type == WindowType::Popup)
flags |= SDL_WINDOW_POPUP_MENU;
if (!_settings.HasBorder)
flags |= SDL_WINDOW_BORDERLESS;
if (_settings.AllowInput)
flags |= SDL_WINDOW_INPUT_FOCUS;
else
flags |= SDL_WINDOW_NOT_FOCUSABLE;
if (_settings.HasSizingFrame)
flags |= SDL_WINDOW_RESIZABLE;
if (_settings.IsTopmost)
flags |= SDL_WINDOW_ALWAYS_ON_TOP;
if (_settings.SupportsTransparency)
flags |= SDL_WINDOW_TRANSPARENT;
// Disable parenting of child windows as those are always on top of the parent window and never show up in taskbar
if (_settings.Parent != nullptr && (_settings.Type != WindowType::Tooltip && _settings.Type != WindowType::Popup))
_settings.Parent = nullptr;
// The window position needs to be relative to the parent window
Int2 relativePosition(Math::TruncToInt(settings.Position.X), Math::TruncToInt(settings.Position.Y));
GetRelativeWindowOffset(_settings.Type, _settings.Parent, relativePosition);
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, flags);
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, settings.Title.ToStringAnsi().Get());
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, relativePosition.X);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, relativePosition.Y);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, clientSize.X);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, clientSize.Y);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN, true);
if ((flags & SDL_WINDOW_TOOLTIP) != 0)
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_TOOLTIP_BOOLEAN, true);
else if ((flags & SDL_WINDOW_POPUP_MENU) != 0)
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MENU_BOOLEAN, true);
if (_settings.Parent != nullptr)
SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, _settings.Parent->_window);
_window = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
if (_window == nullptr)
Platform::Fatal(String::Format(TEXT("Cannot create SDL window: {0}"), String(SDL_GetError())));
_windowId = SDL_GetWindowID(_window);
_handle = GetNativeWindowPointer(_window);
ASSERT(_handle != nullptr);
SDL_DisplayID display = SDL_GetDisplayForWindow(_window);
_dpiScale = SDL_GetWindowDisplayScale(_window);
_dpi = Math::TruncToInt(_dpiScale * DefaultDPI);
Int2 minimumSize(Math::TruncToInt(_settings.MinimumSize.X) , Math::TruncToInt(_settings.MinimumSize.Y));
Int2 maximumSize(Math::TruncToInt(_settings.MaximumSize.X) , Math::TruncToInt(_settings.MaximumSize.Y));
SDL_SetWindowMinimumSize(_window, minimumSize.X, minimumSize.Y);
#if PLATFORM_MAC
// BUG: The maximum size is not enforced correctly, set it to real high value instead
if (maximumSize.X == 0)
maximumSize.X = 999999;
if (maximumSize.Y == 0)
maximumSize.Y = 999999;
#endif
SDL_SetWindowMaximumSize(_window, maximumSize.X, maximumSize.Y);
SDL_SetWindowHitTest(_window, &OnWindowHitTest, this);
InitSwapChain();
#if USE_EDITOR
// Enable file drag & drop support
if (_settings.AllowDragAndDrop)
{
#if PLATFORM_WINDOWS
const auto result = RegisterDragDrop((HWND)_handle, (LPDROPTARGET)(Windows::IDropTarget*)this);
if (result != S_OK)
{
LOG(Warning, "Window drag and drop service error: 0x{0:x}:{1}", result, 1);
}
#elif PLATFORM_LINUX
auto xDisplay = (X11::Display*)GetX11Display();
if (xDisplay)
{
auto xdndVersion = 5;
auto xdndAware = X11::XInternAtom(xDisplay, "XdndAware", 0);
if (xdndAware != 0)
X11::XChangeProperty(xDisplay, (X11::Window)_handle, xdndAware, (X11::Atom)4, 32, PropModeReplace, (unsigned char*)&xdndVersion, 1);
}
#elif PLATFORM_MAC
NSWindow* win = ((NSWindow*)_handle);
NSView* view = win.contentView;
[win unregisterDraggedTypes];
[win registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString, (NSString*)kUTTypeFileURL, (NSString*)kUTTypeUTF8PlainText]];
#endif
}
#endif
LastEventWindow = this;
#if PLATFORM_LINUX
// Initialize using the shared Display instance from SDL
if (SDLPlatform::UsesX11() && SDLPlatform::GetXDisplay() == nullptr)
SDLPlatform::InitX11(GetX11Display());
// Window focus changes breaks the text input for some reason, just keep it enabled for good
if (SDLPlatform::UsesX11() && _settings.AllowInput)
SDL_StartTextInput(_window);
#endif
}
SDL_Window* SDLWindow::GetSDLWindow() const
{
return _window;
}
#if PLATFORM_LINUX
void* SDLWindow::GetWaylandDisplay() const
{
return SDL_GetPointerProperty(SDL_GetWindowProperties(_window), SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr);
}
void* SDLWindow::GetX11Display() const
{
return SDL_GetPointerProperty(SDL_GetWindowProperties(_window), SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr);
}
#endif
SDLWindow::~SDLWindow()
{
if (LastEventWindow == this)
LastEventWindow = nullptr;
if (_window == nullptr)
return;
if (Input::Mouse != nullptr && Input::Mouse->IsRelative(this))
Input::Mouse->SetRelativeMode(false, this);
SDL_StopTextInput(_window);
SDL_DestroyWindow(_window);
_window = nullptr;
_handle = nullptr;
_windowId = 0;
_visible = false;
}
SDL_HitTestResult OnWindowHitTest(SDL_Window* win, const SDL_Point* area, void* data)
{
SDLWindow* window = (SDLWindow*)data;
if (window->IsFullscreen())
return SDL_HITTEST_NORMAL;
Float2 clientPosition = Float2(static_cast<float>(area->x), static_cast<float>(area->y));
Float2 screenPosition = window->ClientToScreen(clientPosition);
WindowHitCodes hit = WindowHitCodes::Client;
bool handled = false;
window->OnHitTest(screenPosition, hit, handled);
if (!handled)
{
int margin = window->GetSettings().HasBorder ? 0 : 0;
auto size = window->GetClientSize();
//if (clientPosition.Y < 0)
// return SDL_HITTEST_DRAGGABLE;
if (clientPosition.Y < margin && clientPosition.X < margin)
return SDL_HITTEST_RESIZE_TOPLEFT;
else if (clientPosition.Y < margin && clientPosition.X > size.X - margin)
return SDL_HITTEST_RESIZE_TOPRIGHT;
else if (clientPosition.Y < margin)
return SDL_HITTEST_RESIZE_TOP;
else if (clientPosition.X < margin && clientPosition.Y > size.Y - margin)
return SDL_HITTEST_RESIZE_BOTTOMLEFT;
else if (clientPosition.X < margin)
return SDL_HITTEST_RESIZE_LEFT;
else if (clientPosition.X > size.X - margin && clientPosition.Y > size.Y - margin)
return SDL_HITTEST_RESIZE_BOTTOMRIGHT;
else if (clientPosition.X > size.X - margin)
return SDL_HITTEST_RESIZE_RIGHT;
else if (clientPosition.Y > size.Y - margin)
return SDL_HITTEST_RESIZE_BOTTOM;
else
return SDL_HITTEST_NORMAL;
}
switch (hit)
{
case WindowHitCodes::Caption:
return SDL_HITTEST_DRAGGABLE;
case WindowHitCodes::TopLeft:
return SDL_HITTEST_RESIZE_TOPLEFT;
case WindowHitCodes::Top:
return SDL_HITTEST_RESIZE_TOP;
case WindowHitCodes::TopRight:
return SDL_HITTEST_RESIZE_TOPRIGHT;
case WindowHitCodes::Right:
return SDL_HITTEST_RESIZE_RIGHT;
case WindowHitCodes::BottomRight:
return SDL_HITTEST_RESIZE_BOTTOMRIGHT;
case WindowHitCodes::Bottom:
return SDL_HITTEST_RESIZE_BOTTOM;
case WindowHitCodes::BottomLeft:
return SDL_HITTEST_RESIZE_BOTTOMLEFT;
case WindowHitCodes::Left:
return SDL_HITTEST_RESIZE_LEFT;
default:
return SDL_HITTEST_NORMAL;
}
}
SDLWindow* SDLWindow::GetWindowFromEvent(const SDL_Event& event)
{
SDL_Window* window = SDL_GetWindowFromEvent(&event);
if (window == nullptr)
return nullptr;
if (LastEventWindow == nullptr || window != LastEventWindow->_window)
LastEventWindow = GetWindowWithSDLWindow(window);
return LastEventWindow;
}
SDLWindow* SDLWindow::GetWindowWithSDLWindow(SDL_Window* window)
{
SDLWindow* found = nullptr;
WindowsManager::WindowsLocker.Lock();
for (auto win : WindowsManager::Windows)
{
if (win->_window == window)
{
found = win;
break;
}
}
WindowsManager::WindowsLocker.Unlock();
return found;
}
void SDLWindow::HandleEvent(SDL_Event& event)
{
if (_isClosing)
return;
// Platform specific event handling
if (HandleEventInternal(event))
return;
switch (event.type)
{
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
{
Close(ClosingReason::User);
return;
}
case SDL_EVENT_WINDOW_DESTROYED:
{
// Quit
#if PLATFORM_WINDOWS
PostQuitMessage(0);
#endif
return;
}
case SDL_EVENT_MOUSE_MOTION:
{
if (_isTrackingMouse && _isUsingMouseOffset)
{
Float2 delta(event.motion.xrel, event.motion.yrel);
_trackingMouseOffset += delta;
}
break;
}
case SDL_EVENT_KEY_DOWN:
{
if (event.key.scancode == SDL_SCANCODE_RETURN && Input::Keyboard->GetKey(KeyboardKeys::Alt))
{
LOG(Info, "Alt+Enter pressed");
SetIsFullscreen(!IsFullscreen());
return;
}
break;
}
case SDL_EVENT_WINDOW_MOVED:
{
_cachedClientRectangle.Location = Float2(static_cast<float>(event.window.data1), static_cast<float>(event.window.data2));
return;
}
case SDL_EVENT_WINDOW_HIT_TEST:
{
return;
}
case SDL_EVENT_WINDOW_MINIMIZED:
{
_minimized = true;
_maximized = false;
return;
}
case SDL_EVENT_WINDOW_MAXIMIZED:
{
_minimized = false;
_maximized = true;
CheckForWindowResize();
return;
}
case SDL_EVENT_WINDOW_RESTORED:
{
if (_maximized)
{
_maximized = false;
// We assume SDL_EVENT_WINDOW_RESIZED is called right afterwards, no need to check for resize here
//CheckForWindowResize();
}
else if (_minimized)
{
_minimized = false;
CheckForWindowResize();
}
return;
}
case SDL_EVENT_WINDOW_RESIZED:
{
int32 width = event.window.data1;
int32 height = event.window.data2;
_clientSize = Float2(static_cast<float>(width), static_cast<float>(height));
_cachedClientRectangle.Size = _clientSize;
// Check if window size has been changed
if (width > 0 && height > 0 && (_swapChain == nullptr || width != _swapChain->GetWidth() || height != _swapChain->GetHeight()))
OnResize(width, height);
return;
}
case SDL_EVENT_WINDOW_FOCUS_GAINED:
{
OnGotFocus();
if (_settings.AllowInput && !SDLPlatform::UsesX11())
SDL_StartTextInput(_window);
if (_isClippingCursor)
{
// The relative mode needs to be disabled for clipping to take effect
bool inRelativeMode = Input::Mouse->IsRelative(this) || _restoreRelativeMode;
if (inRelativeMode)
Input::Mouse->SetRelativeMode(false, this);
// Restore previous clipping region
SDL_Rect rect{ (int)_clipCursorRect.GetX(), (int)_clipCursorRect.GetY(), (int)_clipCursorRect.GetWidth(), (int)_clipCursorRect.GetHeight() };
SDL_SetWindowMouseRect(_window, &rect);
if (inRelativeMode)
Input::Mouse->SetRelativeMode(true, this);
}
else if (_restoreRelativeMode)
Input::Mouse->SetRelativeMode(true, this);
_restoreRelativeMode = false;
return;
}
case SDL_EVENT_WINDOW_FOCUS_LOST:
{
if (_settings.AllowInput && !SDLPlatform::UsesX11())
SDL_StopTextInput(_window);
if (_isClippingCursor)
SDL_SetWindowMouseRect(_window, nullptr);
if (Input::Mouse->IsRelative(this))
{
Input::Mouse->SetRelativeMode(false, this);
_restoreRelativeMode = true;
}
OnLostFocus();
return;
}
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
{
float scale = SDL_GetWindowDisplayScale(_window);
if (scale > 0.0f && _dpiScale != scale)
{
float oldScale = _dpiScale;
_dpiScale = scale;
_dpi = static_cast<int>(_dpiScale * DefaultDPI);
int w = static_cast<int>(_cachedClientRectangle.GetWidth() * (scale / oldScale));
int h = static_cast<int>(_cachedClientRectangle.GetHeight() * (scale / oldScale));
_cachedClientRectangle.Size = Float2(static_cast<float>(w), static_cast<float>(h));
SDL_SetWindowSize(_window, w, h);
// TODO: Recalculate fonts
}
return;
}
default:
break;
}
if (_settings.AllowInput)
{
if (SDLInput::HandleEvent(this, event))
return;
}
}
void* SDLWindow::GetNativePtr() const
{
return _handle;
}
void SDLWindow::Show()
{
if (_visible)
return;
if (_showAfterFirstPaint)
{
if (RenderTask)
RenderTask->Enabled = true;
return;
}
SDL_ShowWindow(_window);
if (_settings.AllowInput && _settings.ActivateWhenFirstShown)
Focus();
else if (_settings.Parent == nullptr)
BringToFront();
// Reused top-most windows doesn't stay on top for some reason
if (_settings.IsTopmost && !IsPopupWindow(_settings.Type))
SetIsAlwaysOnTop(true);
if (_isTrackingMouse)
{
if (!SDL_CaptureMouse(true))
{
if (!SDLPlatform::UsesWayland()) // Suppress "That operation is not supported" errors
LOG(Warning, "SDL_CaptureMouse: {0}", String(SDL_GetError()));
}
}
WindowBase::Show();
}
void SDLWindow::Hide()
{
if (!_visible)
return;
SDL_HideWindow(_window);
WindowBase::Hide();
}
void SDLWindow::Minimize()
{
if (!_settings.AllowMinimize)
return;
SDL_MinimizeWindow(_window);
}
void SDLWindow::Maximize()
{
if (!_settings.AllowMaximize)
return;
SDL_MaximizeWindow(_window);
}
void SDLWindow::SetBorderless(bool isBorderless, bool maximized)
{
if (IsFullscreen())
SetIsFullscreen(false);
// Fixes issue of borderless window not going full screen
if (IsMaximized())
Restore();
_settings.HasBorder = !isBorderless;
BringToFront();
SDL_SetWindowBordered(_window, !isBorderless ? true : false);
if (maximized)
Maximize();
else
Focus();
CheckForWindowResize();
}
void SDLWindow::Restore()
{
SDL_RestoreWindow(_window);
}
bool SDLWindow::IsClosed() const
{
return WindowBase::IsClosed() || _handle == nullptr;
}
bool SDLWindow::IsForegroundWindow() const
{
SDL_WindowFlags flags = SDL_GetWindowFlags(_window);
return (flags & SDL_WINDOW_INPUT_FOCUS) != 0;
}
void SDLWindow::BringToFront(bool force)
{
SDL_RaiseWindow(_window);
}
void SDLWindow::SetClientBounds(const Rectangle& clientArea)
{
Int2 newPos = Int2(clientArea.GetTopLeft());
int newW = static_cast<int>(clientArea.GetWidth());
int newH = static_cast<int>(clientArea.GetHeight());
SetSDLWindowScreenPosition(this, newPos);
SDL_SetWindowSize(_window, newW, newH);
}
void GetRelativeWindowOffset(WindowType type, SDLWindow* parentWindow, Int2& positionOffset)
{
if (!IsPopupWindow(type))
return;
SDLWindow* window = parentWindow;
while (window != nullptr)
{
Int2 parentPosition;
SDL_GetWindowPosition(window->GetSDLWindow(), &parentPosition.X, &parentPosition.Y);
positionOffset -= parentPosition;
if (!IsPopupWindow(window->GetSettings().Type))
break;
window = window->GetSettings().Parent;
}
}
Int2 GetSDLWindowScreenPosition(const SDLWindow* window)
{
Int2 relativeOffset(0, 0);
GetRelativeWindowOffset(window->GetSettings().Type, window->GetSettings().Parent, relativeOffset);
Int2 position;
SDL_GetWindowPosition(window->GetSDLWindow(), &position.X, &position.Y);
return position - relativeOffset;
}
void SetSDLWindowScreenPosition(const SDLWindow* window, const Int2 position)
{
Int2 relativePosition = position;
GetRelativeWindowOffset(window->GetSettings().Type, window->GetSettings().Parent, relativePosition);
SDL_SetWindowPosition(window->GetSDLWindow(), relativePosition.X, relativePosition.Y);
}
void SDLWindow::SetPosition(const Float2& position)
{
Int2 topLeftBorder;
SDL_GetWindowBordersSize(_window, &topLeftBorder.Y, &topLeftBorder.X, nullptr, nullptr);
Int2 screenPosition(static_cast<int>(position.X), static_cast<int>(position.Y));
screenPosition += topLeftBorder;
if (false && SDLPlatform::UsesX11())
{
// TODO: is this needed?
auto monitorBounds = Platform::GetMonitorBounds(Float2::Minimum);
screenPosition += Int2(monitorBounds.GetTopLeft());
}
SetSDLWindowScreenPosition(this, screenPosition);
}
void SDLWindow::SetClientPosition(const Float2& position)
{
SetSDLWindowScreenPosition(this, Int2(position));
}
void SDLWindow::SetIsFullscreen(bool isFullscreen)
{
SDL_SetWindowFullscreen(_window, isFullscreen ? true : false);
if (!isFullscreen)
{
// The window is set to always-on-top for some reason when leaving fullscreen
SetIsAlwaysOnTop(false);
}
WindowBase::SetIsFullscreen(isFullscreen);
}
bool SDLWindow::IsAlwaysOnTop() const
{
SDL_WindowFlags flags = SDL_GetWindowFlags(_window);
return (flags & SDL_WINDOW_ALWAYS_ON_TOP) != 0;
}
void SDLWindow::SetIsAlwaysOnTop(bool isAlwaysOnTop)
{
if (!SDL_SetWindowAlwaysOnTop(_window, isAlwaysOnTop))
LOG(Warning, "SDL_SetWindowAlwaysOnTop failed: {0}", String(SDL_GetError()));
// Not sure if this should change _settings.IsTopmost to reflect the new value?
}
Float2 SDLWindow::GetPosition() const
{
Int2 topLeftBorder;
SDL_GetWindowBordersSize(_window, &topLeftBorder.Y, &topLeftBorder.X, nullptr, nullptr);
Int2 position = GetSDLWindowScreenPosition(this);
position -= topLeftBorder;
return Float2(static_cast<float>(position.X), static_cast<float>(position.Y));
}
Float2 SDLWindow::GetSize() const
{
int top, left, bottom, right;
SDL_GetWindowBordersSize(_window, &top, &left, &bottom, &right);
int w, h;
SDL_GetWindowSizeInPixels(_window, &w, &h);
return Float2(static_cast<float>(w + left + right), static_cast<float>(h + top + bottom));
}
Float2 SDLWindow::GetClientSize() const
{
int w, h;
SDL_GetWindowSizeInPixels(_window, &w, &h);
return Float2(static_cast<float>(w), static_cast<float>(h));;
}
Float2 SDLWindow::ScreenToClient(const Float2& screenPos) const
{
Int2 position = GetSDLWindowScreenPosition(this);
return screenPos - Float2(static_cast<float>(position.X), static_cast<float>(position.Y));
}
Float2 SDLWindow::ClientToScreen(const Float2& clientPos) const
{
Int2 position = GetSDLWindowScreenPosition(this);
return clientPos + Float2(static_cast<float>(position.X), static_cast<float>(position.Y));
}
void SDLWindow::FlashWindow()
{
SDL_FlashWindow(_window, SDL_FLASH_UNTIL_FOCUSED);
}
float SDLWindow::GetOpacity() const
{
float opacity = SDL_GetWindowOpacity(_window);
if (opacity < 0.0f)
{
LOG(Warning, "SDL_GetWindowOpacity failed: {0}", String(SDL_GetError()));
opacity = 1.0f;
}
return opacity;
}
void SDLWindow::SetOpacity(const float opacity)
{
if (!SDL_SetWindowOpacity(_window, opacity))
LOG(Warning, "SDL_SetWindowOpacity failed: {0}", String(SDL_GetError()));
}
#if !PLATFORM_WINDOWS
void SDLWindow::Focus()
{
SDL_RaiseWindow(_window);
}
#endif
String SDLWindow::GetTitle() const
{
return String(SDL_GetWindowTitle(_window));
}
void SDLWindow::SetTitle(const StringView& title)
{
SDL_SetWindowTitle(_window, title.ToStringAnsi().Get());
}
void SDLWindow::StartTrackingMouse(bool useMouseScreenOffset)
{
if (_isTrackingMouse)
return;
_isTrackingMouse = true;
_trackingMouseOffset = Float2::Zero;
_isUsingMouseOffset = useMouseScreenOffset;
if (_visible)
{
if (!SDL_CaptureMouse(true))
{
if (!SDLPlatform::UsesWayland()) // Suppress "That operation is not supported" errors
LOG(Warning, "SDL_CaptureMouse: {0}", String(SDL_GetError()));
}
// For viewport camera mouse tracking we want to use relative mode for best precision
if (_cursor == CursorType::Hidden)
Input::Mouse->SetRelativeMode(true, this);
}
}
void SDLWindow::EndTrackingMouse()
{
if (!_isTrackingMouse)
return;
_isTrackingMouse = false;
_isHorizontalFlippingMouse = false;
_isVerticalFlippingMouse = false;
if (!SDL_CaptureMouse(false))
{
if (!SDLPlatform::UsesWayland()) // Suppress "That operation is not supported" errors
LOG(Warning, "SDL_CaptureMouse: {0}", String(SDL_GetError()));
}
Input::Mouse->SetRelativeMode(false, this);
}
void SDLWindow::StartClippingCursor(const Rectangle& bounds)
{
if (!IsFocused())
return;
// The cursor is not fully constrained when positioned outside the clip region
SetMousePosition(bounds.GetCenter());
_isClippingCursor = true;
SDL_Rect rect{ (int)bounds.GetX(), (int)bounds.GetY(), (int)bounds.GetWidth(), (int)bounds.GetHeight() };
SDL_SetWindowMouseRect(_window, &rect);
_clipCursorRect = bounds;
}
void SDLWindow::EndClippingCursor()
{
if (!_isClippingCursor)
return;
_isClippingCursor = false;
SDL_SetWindowMouseRect(_window, nullptr);
}
void SDLWindow::SetMousePosition(const Float2& position) const
{
if (!_settings.AllowInput || !_focused)
return;
SDL_WarpMouseInWindow(_window, position.X, position.Y);
Float2 screenPosition = ClientToScreen(position);
Input::Mouse->OnMouseMoved(screenPosition);
}
void SDLWindow::SetCursor(CursorType type)
{
CursorType oldCursor = _cursor;
WindowBase::SetCursor(type);
if (oldCursor != type)
SDLWindow::UpdateCursor();
}
void SDLWindow::CheckForWindowResize()
{
return;
// Cache client size
_clientSize = GetClientSize();
int32 width = (int32)(_clientSize.X);
int32 height = (int32)(_clientSize.Y);
// Check for windows maximized size and see if it needs to adjust position if needed
if (_maximized)
{
// Pick the current monitor data for sizing
SDL_Rect rect;
auto displayId = SDL_GetDisplayForWindow(_window);
SDL_GetDisplayUsableBounds(displayId, &rect);
if (width > rect.w && height > rect.h)
{
width = rect.w;
height = rect.h;
SDL_SetWindowSize(_window, width, height);
}
}
// Check if window size has been changed
if (width > 0 && height > 0 && (_swapChain == nullptr || width != _swapChain->GetWidth() || height != _swapChain->GetHeight()))
OnResize(width, height);
}
void SDLWindow::UpdateCursor()
{
if (_cursor == CursorType::Hidden)
{
SDL_HideCursor();
if (_isTrackingMouse)
Input::Mouse->SetRelativeMode(true, this);
return;
}
SDL_ShowCursor();
//if (_isTrackingMouse)
// Input::Mouse->SetRelativeMode(false, this);
int32 index = SDL_SYSTEM_CURSOR_DEFAULT;
switch (_cursor)
{
case CursorType::Cross:
index = SDL_SYSTEM_CURSOR_CROSSHAIR;
break;
case CursorType::Hand:
index = SDL_SYSTEM_CURSOR_POINTER;
break;
case CursorType::Help:
//index = SDL_SYSTEM_CURSOR_DEFAULT;
break;
case CursorType::IBeam:
index = SDL_SYSTEM_CURSOR_TEXT;
break;
case CursorType::No:
index = SDL_SYSTEM_CURSOR_NOT_ALLOWED;
break;
case CursorType::Wait:
index = SDL_SYSTEM_CURSOR_WAIT;
break;
case CursorType::SizeAll:
index = SDL_SYSTEM_CURSOR_MOVE;
break;
case CursorType::SizeNESW:
index = SDL_SYSTEM_CURSOR_NESW_RESIZE;
break;
case CursorType::SizeNS:
index = SDL_SYSTEM_CURSOR_NS_RESIZE;
break;
case CursorType::SizeNWSE:
index = SDL_SYSTEM_CURSOR_NWSE_RESIZE;
break;
case CursorType::SizeWE:
index = SDL_SYSTEM_CURSOR_EW_RESIZE;
break;
case CursorType::Default:
default:
break;
}
if (Cursors[index] == nullptr)
Cursors[index] = SDL_CreateSystemCursor(static_cast<SDL_SystemCursor>(index));
SDL_SetCursor(Cursors[index]);
}
#endif

View File

@@ -0,0 +1,128 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#if PLATFORM_SDL
#include "Engine/Platform/Base/WindowBase.h"
struct SDL_Window;
union SDL_Event;
#if PLATFORM_LINUX
class MessageBox;
#endif
/// <summary>
/// Implementation of the window class for SDL platform
/// </summary>
class FLAXENGINE_API SDLWindow : public WindowBase
#if USE_EDITOR && PLATFORM_WINDOWS
, public Windows::IDropTarget
#endif
{
friend SDLPlatform;
friend class SDLMouse;
#if PLATFORM_LINUX
friend LinuxPlatform;
friend MessageBox;
#endif
private:
void* _handle; // Opaque, platform specific window handle
#if USE_EDITOR && PLATFORM_WINDOWS
Windows::ULONG _refCount;
#endif
#if PLATFORM_LINUX
bool _dragOver;
#endif
SDL_Window* _window;
uint32 _windowId;
Rectangle _clipCursorRect;
Rectangle _cachedClientRectangle;
public:
static void Init();
public:
/// <summary>
/// Initializes a new instance of the <see cref="SDLWindow"/> class.
/// </summary>
/// <param name="settings">The initial window settings.</param>
SDLWindow(const CreateWindowSettings& settings);
/// <summary>
/// Finalizes an instance of the <see cref="SDLWindow"/> class.
/// </summary>
~SDLWindow();
private:
static SDLWindow* GetWindowFromEvent(const SDL_Event& event);
static SDLWindow* GetWindowWithSDLWindow(SDL_Window* window);
void HandleEvent(SDL_Event& event);
bool HandleEventInternal(SDL_Event& event);
void CheckForWindowResize();
void UpdateCursor();
public:
SDL_Window* GetSDLWindow() const;
#if PLATFORM_LINUX
void* GetWaylandDisplay() const;
void* GetX11Display() const;
#endif
// [WindowBase]
void* GetNativePtr() const override;
void Show() override;
void Hide() override;
void Minimize() override;
void Maximize() override;
void SetBorderless(bool isBorderless, bool maximized = false) override;
void Restore() override;
bool IsClosed() const override;
bool IsForegroundWindow() const override;
void BringToFront(bool force = false) override;
void SetClientBounds(const Rectangle& clientArea) override;
void SetPosition(const Float2& position) override;
void SetClientPosition(const Float2& position) override;
void SetIsFullscreen(bool isFullscreen) override;
bool IsAlwaysOnTop() const override;
void SetIsAlwaysOnTop(bool isAlwaysOnTop) override;
Float2 GetPosition() const override;
Float2 GetSize() const override;
Float2 GetClientSize() const override;
Float2 ScreenToClient(const Float2& screenPos) const override;
Float2 ClientToScreen(const Float2& clientPos) const override;
void FlashWindow() override;
float GetOpacity() const override;
void SetOpacity(float opacity) override;
void Focus() override;
String GetTitle() const override;
void SetTitle(const StringView& title) override;
DragDropEffect DoDragDrop(const StringView& data) override;
DragDropEffect DoDragDrop(const StringView& data, const Float2& offset, Window* dragSourceWindow) override;
void StartTrackingMouse(bool useMouseScreenOffset) override;
void EndTrackingMouse() override;
void StartClippingCursor(const Rectangle& bounds) override;
void EndClippingCursor() override;
void SetMousePosition(const Float2& position) const override;
void SetCursor(CursorType type) override;
#if USE_EDITOR && PLATFORM_WINDOWS
// [IUnknown]
Windows::HRESULT __stdcall QueryInterface(const Windows::IID& id, void** ppvObject) override;
Windows::ULONG __stdcall AddRef() override;
Windows::ULONG __stdcall Release() override;
// [Windows::IDropTarget]
Windows::HRESULT __stdcall DragEnter(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) override;
Windows::HRESULT __stdcall DragOver(Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) override;
Windows::HRESULT __stdcall DragLeave() override;
Windows::HRESULT __stdcall Drop(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) override;
#endif
#if PLATFORM_LINUX
DragDropEffect DoDragDropWayland(const StringView& data, Window* dragSourceWindow = nullptr, Float2 dragOffset = Float2::Zero);
DragDropEffect DoDragDropX11(const StringView& data);
#endif
};
#endif

View File

@@ -0,0 +1,15 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#if PLATFORM_WINDOWS
#include "Windows/WindowsScreenUtilities.h"
#elif PLATFORM_LINUX
#include "Linux/LinuxScreenUtilities.h"
#elif PLATFORM_MAC
#include "Mac/MacScreenUtilities.h"
#else
#include "Base/ScreenUtilitiesBase.h"
#endif
#include "Types.h"

View File

@@ -4,8 +4,6 @@
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS
class WindowsClipboard;
typedef WindowsClipboard Clipboard;
class Win32CriticalSection; class Win32CriticalSection;
typedef Win32CriticalSection CriticalSection; typedef Win32CriticalSection CriticalSection;
class Win32ConditionVariable; class Win32ConditionVariable;
@@ -16,21 +14,25 @@ class WindowsFileSystemWatcher;
typedef WindowsFileSystemWatcher FileSystemWatcher; typedef WindowsFileSystemWatcher FileSystemWatcher;
class Win32File; class Win32File;
typedef Win32File File; typedef Win32File File;
class WindowsPlatform;
typedef WindowsPlatform Platform;
class Win32Thread; class Win32Thread;
typedef Win32Thread Thread; typedef Win32Thread Thread;
class WindowsClipboard;
typedef WindowsClipboard Clipboard;
#if !PLATFORM_SDL
class WindowsPlatform;
typedef WindowsPlatform Platform;
class WindowsWindow; class WindowsWindow;
typedef WindowsWindow Window; typedef WindowsWindow Window;
#endif
class Win32Network; class Win32Network;
typedef Win32Network Network; typedef Win32Network Network;
class UserBase; class UserBase;
typedef UserBase User; typedef UserBase User;
class WindowsScreenUtilities;
typedef WindowsScreenUtilities ScreenUtilities;
#elif PLATFORM_UWP #elif PLATFORM_UWP
class ClipboardBase;
typedef ClipboardBase Clipboard;
class Win32CriticalSection; class Win32CriticalSection;
typedef Win32CriticalSection CriticalSection; typedef Win32CriticalSection CriticalSection;
class Win32ConditionVariable; class Win32ConditionVariable;
@@ -41,21 +43,23 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher; typedef FileSystemWatcherBase FileSystemWatcher;
class Win32File; class Win32File;
typedef Win32File File; typedef Win32File File;
class UWPPlatform;
typedef UWPPlatform Platform;
class Win32Thread; class Win32Thread;
typedef Win32Thread Thread; typedef Win32Thread Thread;
class ClipboardBase;
typedef ClipboardBase Clipboard;
class UWPPlatform;
typedef UWPPlatform Platform;
class UWPWindow; class UWPWindow;
typedef UWPWindow Window; typedef UWPWindow Window;
class Win32Network; class Win32Network;
typedef Win32Network Network; typedef Win32Network Network;
class UserBase; class UserBase;
typedef UserBase User; typedef UserBase User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#elif PLATFORM_LINUX #elif PLATFORM_LINUX
class LinuxClipboard;
typedef LinuxClipboard Clipboard;
class UnixCriticalSection; class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection; typedef UnixCriticalSection CriticalSection;
class UnixConditionVariable; class UnixConditionVariable;
@@ -66,21 +70,25 @@ class LinuxFileSystemWatcher;
typedef LinuxFileSystemWatcher FileSystemWatcher; typedef LinuxFileSystemWatcher FileSystemWatcher;
class UnixFile; class UnixFile;
typedef UnixFile File; typedef UnixFile File;
class LinuxPlatform;
typedef LinuxPlatform Platform;
class LinuxThread; class LinuxThread;
typedef LinuxThread Thread; typedef LinuxThread Thread;
#if !PLATFORM_SDL
class LinuxClipboard;
typedef LinuxClipboard Clipboard;
class LinuxPlatform;
typedef LinuxPlatform Platform;
class LinuxWindow; class LinuxWindow;
typedef LinuxWindow Window; typedef LinuxWindow Window;
#endif
class UnixNetwork; class UnixNetwork;
typedef UnixNetwork Network; typedef UnixNetwork Network;
class UserBase; class UserBase;
typedef UserBase User; typedef UserBase User;
class LinuxScreenUtilities;
typedef LinuxScreenUtilities ScreenUtilities;
#elif PLATFORM_PS4 #elif PLATFORM_PS4
class ClipboardBase;
typedef ClipboardBase Clipboard;
class UnixCriticalSection; class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection; typedef UnixCriticalSection CriticalSection;
class UnixConditionVariable; class UnixConditionVariable;
@@ -91,21 +99,23 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher; typedef FileSystemWatcherBase FileSystemWatcher;
class UnixFile; class UnixFile;
typedef UnixFile File; typedef UnixFile File;
class PS4Platform;
typedef PS4Platform Platform;
class PS4Thread; class PS4Thread;
typedef PS4Thread Thread; typedef PS4Thread Thread;
class ClipboardBase;
typedef ClipboardBase Clipboard;
class PS4Platform;
typedef PS4Platform Platform;
class PS4Window; class PS4Window;
typedef PS4Window Window; typedef PS4Window Window;
class PS4Network; class PS4Network;
typedef PS4Network Network; typedef PS4Network Network;
class PS4User; class PS4User;
typedef PS4User User; typedef PS4User User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#elif PLATFORM_PS5 #elif PLATFORM_PS5
class ClipboardBase;
typedef ClipboardBase Clipboard;
class UnixCriticalSection; class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection; typedef UnixCriticalSection CriticalSection;
class UnixConditionVariable; class UnixConditionVariable;
@@ -116,21 +126,23 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher; typedef FileSystemWatcherBase FileSystemWatcher;
class UnixFile; class UnixFile;
typedef UnixFile File; typedef UnixFile File;
class PS5Platform; class ClipboardBase;
typedef PS5Platform Platform; typedef ClipboardBase Clipboard;
class PS5Thread; class PS5Thread;
typedef PS5Thread Thread; typedef PS5Thread Thread;
class PS5Platform;
typedef PS5Platform Platform;
class PS5Window; class PS5Window;
typedef PS5Window Window; typedef PS5Window Window;
class PS5Network; class PS5Network;
typedef PS5Network Network; typedef PS5Network Network;
class PS5User; class PS5User;
typedef PS5User User; typedef PS5User User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#elif PLATFORM_XBOX_ONE #elif PLATFORM_XBOX_ONE
class ClipboardBase;
typedef ClipboardBase Clipboard;
class Win32CriticalSection; class Win32CriticalSection;
typedef Win32CriticalSection CriticalSection; typedef Win32CriticalSection CriticalSection;
class Win32ConditionVariable; class Win32ConditionVariable;
@@ -141,21 +153,23 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher; typedef FileSystemWatcherBase FileSystemWatcher;
class Win32File; class Win32File;
typedef Win32File File; typedef Win32File File;
class XboxOnePlatform;
typedef XboxOnePlatform Platform;
class Win32Thread; class Win32Thread;
typedef Win32Thread Thread; typedef Win32Thread Thread;
class ClipboardBase;
typedef ClipboardBase Clipboard;
class XboxOnePlatform;
typedef XboxOnePlatform Platform;
class GDKWindow; class GDKWindow;
typedef GDKWindow Window; typedef GDKWindow Window;
class Win32Network; class Win32Network;
typedef Win32Network Network; typedef Win32Network Network;
class GDKUser; class GDKUser;
typedef GDKUser User; typedef GDKUser User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#elif PLATFORM_XBOX_SCARLETT #elif PLATFORM_XBOX_SCARLETT
class ClipboardBase;
typedef ClipboardBase Clipboard;
class Win32CriticalSection; class Win32CriticalSection;
typedef Win32CriticalSection CriticalSection; typedef Win32CriticalSection CriticalSection;
class Win32ConditionVariable; class Win32ConditionVariable;
@@ -166,21 +180,23 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher; typedef FileSystemWatcherBase FileSystemWatcher;
class Win32File; class Win32File;
typedef Win32File File; typedef Win32File File;
class XboxScarlettPlatform;
typedef XboxScarlettPlatform Platform;
class Win32Thread; class Win32Thread;
typedef Win32Thread Thread; typedef Win32Thread Thread;
class ClipboardBase;
typedef ClipboardBase Clipboard;
class XboxScarlettPlatform;
typedef XboxScarlettPlatform Platform;
class GDKWindow; class GDKWindow;
typedef GDKWindow Window; typedef GDKWindow Window;
class Win32Network; class Win32Network;
typedef Win32Network Network; typedef Win32Network Network;
class GDKUser; class GDKUser;
typedef GDKUser User; typedef GDKUser User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#elif PLATFORM_ANDROID #elif PLATFORM_ANDROID
class ClipboardBase;
typedef ClipboardBase Clipboard;
class UnixCriticalSection; class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection; typedef UnixCriticalSection CriticalSection;
class UnixConditionVariable; class UnixConditionVariable;
@@ -191,21 +207,23 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher; typedef FileSystemWatcherBase FileSystemWatcher;
class AndroidFile; class AndroidFile;
typedef AndroidFile File; typedef AndroidFile File;
class AndroidPlatform;
typedef AndroidPlatform Platform;
class AndroidThread; class AndroidThread;
typedef AndroidThread Thread; typedef AndroidThread Thread;
class ClipboardBase;
typedef ClipboardBase Clipboard;
class AndroidPlatform;
typedef AndroidPlatform Platform;
class AndroidWindow; class AndroidWindow;
typedef AndroidWindow Window; typedef AndroidWindow Window;
class UnixNetwork; class UnixNetwork;
typedef UnixNetwork Network; typedef UnixNetwork Network;
class UserBase; class UserBase;
typedef UserBase User; typedef UserBase User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#elif PLATFORM_SWITCH #elif PLATFORM_SWITCH
class ClipboardBase;
typedef ClipboardBase Clipboard;
class SwitchCriticalSection; class SwitchCriticalSection;
typedef SwitchCriticalSection CriticalSection; typedef SwitchCriticalSection CriticalSection;
class SwitchConditionVariable; class SwitchConditionVariable;
@@ -216,21 +234,23 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher; typedef FileSystemWatcherBase FileSystemWatcher;
class SwitchFile; class SwitchFile;
typedef SwitchFile File; typedef SwitchFile File;
class SwitchPlatform;
typedef SwitchPlatform Platform;
class SwitchThread; class SwitchThread;
typedef SwitchThread Thread; typedef SwitchThread Thread;
class ClipboardBase;
typedef ClipboardBase Clipboard;
class SwitchPlatform;
typedef SwitchPlatform Platform;
class SwitchWindow; class SwitchWindow;
typedef SwitchWindow Window; typedef SwitchWindow Window;
class SwitchNetwork; class SwitchNetwork;
typedef SwitchNetwork Network; typedef SwitchNetwork Network;
class SwitchUser; class SwitchUser;
typedef SwitchUser User; typedef SwitchUser User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#elif PLATFORM_MAC #elif PLATFORM_MAC
class MacClipboard;
typedef MacClipboard Clipboard;
class UnixCriticalSection; class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection; typedef UnixCriticalSection CriticalSection;
class UnixConditionVariable; class UnixConditionVariable;
@@ -241,21 +261,25 @@ class MacFileSystemWatcher;
typedef MacFileSystemWatcher FileSystemWatcher; typedef MacFileSystemWatcher FileSystemWatcher;
class UnixFile; class UnixFile;
typedef UnixFile File; typedef UnixFile File;
class MacPlatform;
typedef MacPlatform Platform;
class AppleThread; class AppleThread;
typedef AppleThread Thread; typedef AppleThread Thread;
class MacClipboard;
typedef MacClipboard Clipboard;
#if !PLATFORM_SDL
class MacPlatform;
typedef MacPlatform Platform;
class MacWindow; class MacWindow;
typedef MacWindow Window; typedef MacWindow Window;
#endif
class UnixNetwork; class UnixNetwork;
typedef UnixNetwork Network; typedef UnixNetwork Network;
class UserBase; class UserBase;
typedef UserBase User; typedef UserBase User;
class MacScreenUtilities;
typedef MacScreenUtilities ScreenUtilities;
#elif PLATFORM_IOS #elif PLATFORM_IOS
class ClipboardBase;
typedef ClipboardBase Clipboard;
class UnixCriticalSection; class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection; typedef UnixCriticalSection CriticalSection;
class UnixConditionVariable; class UnixConditionVariable;
@@ -266,19 +290,34 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher; typedef FileSystemWatcherBase FileSystemWatcher;
class iOSFile; class iOSFile;
typedef iOSFile File; typedef iOSFile File;
class iOSPlatform;
typedef iOSPlatform Platform;
class AppleThread; class AppleThread;
typedef AppleThread Thread; typedef AppleThread Thread;
class ClipboardBase;
typedef ClipboardBase Clipboard;
class iOSPlatform;
typedef iOSPlatform Platform;
class iOSWindow; class iOSWindow;
typedef iOSWindow Window; typedef iOSWindow Window;
class UnixNetwork; class UnixNetwork;
typedef UnixNetwork Network; typedef UnixNetwork Network;
class UserBase; class UserBase;
typedef UserBase User; typedef UserBase User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#else #else
#error Missing Types implementation! #error Missing Types implementation!
#endif #endif
#if PLATFORM_SDL
#if PLATFORM_LINUX
class SDLClipboard;
typedef SDLClipboard Clipboard;
#endif
class SDLPlatform;
typedef SDLPlatform Platform;
class SDLWindow;
typedef SDLWindow Window;
#endif

View File

@@ -17,31 +17,37 @@ namespace FlaxEngine
/// <summary> /// <summary>
/// Perform window hit test delegate. /// Perform window hit test delegate.
/// </summary> /// </summary>
/// <param name="mouse">The mouse position. The coordinate is relative to the upper-left corner of the screen. Use <see cref="ScreenToClient"/> to convert position into client space coordinates.</param> /// <param name="mousePosition">The mouse position. The coordinate is relative to the upper-left corner of the screen. Use <see cref="ScreenToClient"/> to convert position into client space coordinates.</param>
/// <returns>Hit result.</returns> /// <returns>Hit result.</returns>
public delegate WindowHitCodes HitTestDelegate(ref Float2 mouse); public delegate WindowHitCodes HitTestDelegate(ref Float2 mousePosition);
/// <summary> /// <summary>
/// Perform mouse buttons action. /// Perform mouse buttons action.
/// </summary> /// </summary>
/// <param name="mouse">The mouse position.</param> /// <param name="mousePosition">The mouse position.</param>
/// <param name="button">The mouse buttons state.</param> /// <param name="button">The mouse buttons state.</param>
/// <param name="handled">The flag that indicated that event has been handled by the custom code and should not be passed further. By default it is set to false.</param> /// <param name="handled">The flag that indicated that event has been handled by the custom code and should not be passed further. By default it is set to false.</param>
public delegate void MouseButtonDelegate(ref Float2 mouse, MouseButton button, ref bool handled); public delegate void MouseButtonDelegate(ref Float2 mousePosition, MouseButton button, ref bool handled);
/// <summary> /// <summary>
/// Perform mouse move action. /// Perform mouse move action.
/// </summary> /// </summary>
/// <param name="mouse">The mouse position.</param> /// <param name="mousePosition">The mouse position.</param>
public delegate void MouseMoveDelegate(ref Float2 mouse); public delegate void MouseMoveDelegate(ref Float2 mousePosition);
/// <summary>
/// Perform mouse move action in relative mode.
/// </summary>
/// <param name="mouseMotion">The relative mouse motion.</param>
public delegate void MouseMoveRelativeDelegate(ref Float2 mouseMotion);
/// <summary> /// <summary>
/// Perform mouse wheel action. /// Perform mouse wheel action.
/// </summary> /// </summary>
/// <param name="mouse">The mouse position.</param> /// <param name="mousePosition">The mouse position.</param>
/// <param name="delta">The mouse wheel move delta (can be positive or negative; normalized to [-1;1] range).</param> /// <param name="delta">The mouse wheel move delta (can be positive or negative; normalized to [-1;1] range).</param>
/// <param name="handled">The flag that indicated that event has been handled by the custom code and should not be passed further. By default it is set to false.</param> /// <param name="handled">The flag that indicated that event has been handled by the custom code and should not be passed further. By default it is set to false.</param>
public delegate void MouseWheelDelegate(ref Float2 mouse, float delta, ref bool handled); public delegate void MouseWheelDelegate(ref Float2 mousePosition, float delta, ref bool handled);
/// <summary> /// <summary>
/// Perform touch action. /// Perform touch action.
@@ -99,10 +105,15 @@ namespace FlaxEngine
public event MouseWheelDelegate MouseWheel; public event MouseWheelDelegate MouseWheel;
/// <summary> /// <summary>
/// Event fired when mouse moves /// Event fired when mouse moves.
/// </summary> /// </summary>
public event MouseMoveDelegate MouseMove; public event MouseMoveDelegate MouseMove;
/// <summary>
/// Event fired when mouse moves in relative mode.
/// </summary>
public event MouseMoveRelativeDelegate MouseMoveRelative;
/// <summary> /// <summary>
/// Event fired when mouse leaves window. /// Event fired when mouse leaves window.
/// </summary> /// </summary>
@@ -274,6 +285,12 @@ namespace FlaxEngine
GUI.OnMouseMove(pos); GUI.OnMouseMove(pos);
} }
internal void Internal_OnMouseMoveRelative(ref Float2 mouseMotion)
{
MouseMoveRelative?.Invoke(ref mouseMotion);
GUI.OnMouseMoveRelative(mouseMotion);
}
internal void Internal_OnMouseLeave() internal void Internal_OnMouseLeave()
{ {
MouseLeave?.Invoke(); MouseLeave?.Invoke();

View File

@@ -2,7 +2,9 @@
#pragma once #pragma once
#if PLATFORM_WINDOWS #if PLATFORM_SDL
#include "SDL/SDLWindow.h"
#elif PLATFORM_WINDOWS
#include "Windows/WindowsWindow.h" #include "Windows/WindowsWindow.h"
#elif PLATFORM_UWP #elif PLATFORM_UWP
#include "UWP/UWPWindow.h" #include "UWP/UWPWindow.h"

View File

@@ -2,6 +2,7 @@
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS
#include "WindowsWindow.h"
#include "WindowsFileSystem.h" #include "WindowsFileSystem.h"
#include "Engine/Platform/File.h" #include "Engine/Platform/File.h"
#include "Engine/Platform/Window.h" #include "Engine/Platform/Window.h"
@@ -317,7 +318,7 @@ bool WindowsFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const Strin
if (SUCCEEDED(SHCreateItemFromParsingName(initialDirectory.Get(), NULL, IID_PPV_ARGS(&defaultFolder)))) if (SUCCEEDED(SHCreateItemFromParsingName(initialDirectory.Get(), NULL, IID_PPV_ARGS(&defaultFolder))))
fd->SetFolder(defaultFolder); fd->SetFolder(defaultFolder);
HWND hwndOwner = parentWindow ? parentWindow->GetHWND() : NULL; HWND hwndOwner = parentWindow ? (HWND)parentWindow->GetNativePtr() : NULL;
if (SUCCEEDED(fd->Show(hwndOwner))) if (SUCCEEDED(fd->Show(hwndOwner)))
{ {
ComPtr<IShellItem> si; ComPtr<IShellItem> si;

View File

@@ -1,6 +1,6 @@
// Copyright (c) Wojciech Figat. All rights reserved. // Copyright (c) Wojciech Figat. All rights reserved.
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS && !PLATFORM_SDL
#include "WindowsInput.h" #include "WindowsInput.h"
#include "WindowsWindow.h" #include "WindowsWindow.h"
@@ -265,19 +265,38 @@ bool WindowsMouse::WndProc(Window* window, const UINT msg, WPARAM wParam, LPARAM
} }
case WM_LBUTTONDBLCLK: case WM_LBUTTONDBLCLK:
{ {
if (!Input::Mouse->IsRelative())
OnMouseDoubleClick(mousePos, MouseButton::Left, window); OnMouseDoubleClick(mousePos, MouseButton::Left, window);
else
OnMouseDown(mousePos, MouseButton::Left, window);
result = true; result = true;
break; break;
} }
case WM_RBUTTONDBLCLK: case WM_RBUTTONDBLCLK:
{ {
if (!Input::Mouse->IsRelative())
OnMouseDoubleClick(mousePos, MouseButton::Right, window); OnMouseDoubleClick(mousePos, MouseButton::Right, window);
else
OnMouseDown(mousePos, MouseButton::Right, window);
result = true; result = true;
break; break;
} }
case WM_MBUTTONDBLCLK: case WM_MBUTTONDBLCLK:
{ {
if (!Input::Mouse->IsRelative())
OnMouseDoubleClick(mousePos, MouseButton::Middle, window); OnMouseDoubleClick(mousePos, MouseButton::Middle, window);
else
OnMouseDown(mousePos, MouseButton::Middle, window);
result = true;
break;
}
case WM_XBUTTONDBLCLK:
{
const auto button = (HIWORD(wParam) & XBUTTON1) ? MouseButton::Extended1 : MouseButton::Extended2;
if (!Input::Mouse->IsRelative())
OnMouseDoubleClick(mousePos, button, window);
else
OnMouseDown(mousePos, button, window);
result = true; result = true;
break; break;
} }

View File

@@ -4,6 +4,8 @@
#include "Engine/Platform/Platform.h" #include "Engine/Platform/Platform.h"
#include "Engine/Platform/Window.h" #include "Engine/Platform/Window.h"
#include "Engine/Platform/Windows/WindowsInput.h"
#include "Engine/Platform/Windows/WindowsWindow.h"
#include "Engine/Platform/FileSystem.h" #include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/CreateWindowSettings.h" #include "Engine/Platform/CreateWindowSettings.h"
#include "Engine/Platform/CreateProcessSettings.h" #include "Engine/Platform/CreateProcessSettings.h"
@@ -34,7 +36,6 @@
#define CLR_EXCEPTION 0xE0434352 #define CLR_EXCEPTION 0xE0434352
#define VCPP_EXCEPTION 0xE06D7363 #define VCPP_EXCEPTION 0xE06D7363
const Char* WindowsPlatform::ApplicationWindowClass = TEXT("FlaxWindow");
void* WindowsPlatform::Instance = nullptr; void* WindowsPlatform::Instance = nullptr;
#if CRASH_LOG_ENABLE || TRACY_ENABLE #if CRASH_LOG_ENABLE || TRACY_ENABLE
@@ -257,6 +258,8 @@ void GetWindowsVersion(String& windowsName, int32& versionMajor, int32& versionM
RegCloseKey(hKey); RegCloseKey(hKey);
} }
#if !PLATFORM_SDL
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{ {
// Find window to process that message // Find window to process that message
@@ -274,6 +277,8 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
return DefWindowProc(hwnd, msg, wParam, lParam); return DefWindowProc(hwnd, msg, wParam, lParam);
} }
#endif
long __stdcall WindowsPlatform::SehExceptionHandler(EXCEPTION_POINTERS* ep) long __stdcall WindowsPlatform::SehExceptionHandler(EXCEPTION_POINTERS* ep)
{ {
if (ep->ExceptionRecord->ExceptionCode == CLR_EXCEPTION) if (ep->ExceptionRecord->ExceptionCode == CLR_EXCEPTION)
@@ -438,11 +443,12 @@ DialogResult MessageBox::Show(Window* parent, const StringView& text, const Stri
flags |= MB_ICONHAND; flags |= MB_ICONHAND;
break; break;
case MessageBoxIcon::Information: case MessageBoxIcon::Information:
case MessageBoxIcon::Question:
flags |= MB_ICONINFORMATION; flags |= MB_ICONINFORMATION;
break; break;
case MessageBoxIcon::Question: //case MessageBoxIcon::Question:
flags |= MB_ICONQUESTION; // flags |= MB_ICONQUESTION;
break; // break;
case MessageBoxIcon::Stop: case MessageBoxIcon::Stop:
flags |= MB_ICONSTOP; flags |= MB_ICONSTOP;
break; break;
@@ -520,6 +526,7 @@ void WindowsPlatform::PreInit(void* hInstance)
// Disable the process from being showing "ghosted" while not responding messages during slow tasks // Disable the process from being showing "ghosted" while not responding messages during slow tasks
DisableProcessWindowsGhosting(); DisableProcessWindowsGhosting();
#if !PLATFORM_SDL
// Register window class // Register window class
WNDCLASS windowsClass; WNDCLASS windowsClass;
Platform::MemoryClear(&windowsClass, sizeof(WNDCLASS)); Platform::MemoryClear(&windowsClass, sizeof(WNDCLASS));
@@ -528,12 +535,13 @@ void WindowsPlatform::PreInit(void* hInstance)
windowsClass.hInstance = (HINSTANCE)Instance; windowsClass.hInstance = (HINSTANCE)Instance;
windowsClass.hIcon = LoadIconW(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDR_MAINFRAME)); windowsClass.hIcon = LoadIconW(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDR_MAINFRAME));
windowsClass.hCursor = LoadCursor(nullptr, IDC_ARROW); windowsClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
windowsClass.lpszClassName = ApplicationWindowClass; windowsClass.lpszClassName = ApplicationClassName;
if (!RegisterClassW(&windowsClass)) if (!RegisterClassW(&windowsClass))
{ {
Error(TEXT("Window class registration failed!")); Error(TEXT("Window class registration failed!"));
exit(-1); exit(-1);
} }
#endif
// Init OLE // Init OLE
if (OleInitialize(nullptr) != S_OK) if (OleInitialize(nullptr) != S_OK)
@@ -682,11 +690,13 @@ bool WindowsPlatform::Init()
DWORD tmp; DWORD tmp;
Char buffer[256]; Char buffer[256];
#if !PLATFORM_SDL
// Get user locale string // Get user locale string
if (GetUserDefaultLocaleName(buffer, LOCALE_NAME_MAX_LENGTH)) if (GetUserDefaultLocaleName(buffer, LOCALE_NAME_MAX_LENGTH))
{ {
UserLocale = String(buffer); UserLocale = String(buffer);
} }
#endif
// Get computer name string // Get computer name string
if (GetComputerNameW(buffer, &tmp)) if (GetComputerNameW(buffer, &tmp))
@@ -702,7 +712,9 @@ bool WindowsPlatform::Init()
} }
OnPlatformUserAdd(New<User>(userName)); OnPlatformUserAdd(New<User>(userName));
#if !PLATFORM_SDL
WindowsInput::Init(); WindowsInput::Init();
#endif
return false; return false;
} }
@@ -748,7 +760,9 @@ void WindowsPlatform::LogInfo()
void WindowsPlatform::Tick() void WindowsPlatform::Tick()
{ {
#if !PLATFORM_SDL
WindowsInput::Update(); WindowsInput::Update();
#endif
// Check to see if any messages are waiting in the queue // Check to see if any messages are waiting in the queue
MSG msg; MSG msg;
@@ -779,8 +793,10 @@ void WindowsPlatform::Exit()
DbgHelpUnlock(); DbgHelpUnlock();
#endif #endif
#if !PLATFORM_SDL
// Unregister app class // Unregister app class
UnregisterClassW(ApplicationWindowClass, nullptr); UnregisterClassW(ApplicationClassName, nullptr);
#endif
Win32Platform::Exit(); Win32Platform::Exit();
} }
@@ -857,6 +873,7 @@ BatteryInfo WindowsPlatform::GetBatteryInfo()
return info; return info;
} }
#if !PLATFORM_SDL
int32 WindowsPlatform::GetDpi() int32 WindowsPlatform::GetDpi()
{ {
return SystemDpi; return SystemDpi;
@@ -866,6 +883,7 @@ String WindowsPlatform::GetUserLocaleName()
{ {
return UserLocale; return UserLocale;
} }
#endif
String WindowsPlatform::GetComputerName() String WindowsPlatform::GetComputerName()
{ {
@@ -1235,10 +1253,12 @@ int32 WindowsPlatform::CreateProcess(CreateProcessSettings& settings)
return result; return result;
} }
#if !PLATFORM_SDL
Window* WindowsPlatform::CreateWindow(const CreateWindowSettings& settings) Window* WindowsPlatform::CreateWindow(const CreateWindowSettings& settings)
{ {
return New<WindowsWindow>(settings); return New<WindowsWindow>(settings);
} }
#endif
void* WindowsPlatform::LoadLibrary(const Char* filename) void* WindowsPlatform::LoadLibrary(const Char* filename)
{ {

View File

@@ -13,11 +13,6 @@ class FLAXENGINE_API WindowsPlatform : public Win32Platform
{ {
public: public:
/// <summary>
/// Win32 application windows class name.
/// </summary>
static const Char* ApplicationWindowClass;
/// <summary> /// <summary>
/// Handle to Win32 application instance. /// Handle to Win32 application instance.
/// </summary> /// </summary>
@@ -70,8 +65,10 @@ public:
static String GetSystemName(); static String GetSystemName();
static Version GetSystemVersion(); static Version GetSystemVersion();
static BatteryInfo GetBatteryInfo(); static BatteryInfo GetBatteryInfo();
#if !PLATFORM_SDL
static int32 GetDpi(); static int32 GetDpi();
static String GetUserLocaleName(); static String GetUserLocaleName();
#endif
static String GetComputerName(); static String GetComputerName();
static bool GetHasFocus(); static bool GetHasFocus();
static bool CanOpenUrl(const StringView& url); static bool CanOpenUrl(const StringView& url);
@@ -85,7 +82,9 @@ public:
static bool GetEnvironmentVariable(const String& name, String& value); static bool GetEnvironmentVariable(const String& name, String& value);
static bool SetEnvironmentVariable(const String& name, const String& value); static bool SetEnvironmentVariable(const String& name, const String& value);
static int32 CreateProcess(CreateProcessSettings& settings); static int32 CreateProcess(CreateProcessSettings& settings);
#if !PLATFORM_SDL
static Window* CreateWindow(const CreateWindowSettings& settings); static Window* CreateWindow(const CreateWindowSettings& settings);
#endif
static void* LoadLibrary(const Char* filename); static void* LoadLibrary(const Char* filename);
#if CRASH_LOG_ENABLE #if CRASH_LOG_ENABLE
static Array<StackFrame, HeapAllocation> GetStackFrames(int32 skipCount = 0, int32 maxDepth = 60, void* context = nullptr); static Array<StackFrame, HeapAllocation> GetStackFrames(int32 skipCount = 0, int32 maxDepth = 60, void* context = nullptr);

View File

@@ -0,0 +1,54 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#if PLATFORM_WINDOWS
#include "Engine/Platform/Types.h"
#include "Engine/Platform/ScreenUtilities.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Core/Delegate.h"
#include "Engine/Core/Log.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include <Windows.h>
#pragma comment(lib, "Gdi32.lib")
Delegate<Color32> ScreenUtilitiesBase::PickColorDone;
static HHOOK MouseCallbackHook;
LRESULT CALLBACK OnScreenUtilsMouseCallback(_In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
if (nCode >= 0 && wParam == WM_LBUTTONDOWN)
{
UnhookWindowsHookEx(MouseCallbackHook);
// Push event with the picked color
const Float2 cursorPos = Platform::GetMousePosition();
const Color32 colorPicked = ScreenUtilities::GetColorAt(cursorPos);
ScreenUtilities::PickColorDone(colorPicked);
return 1;
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
Color32 WindowsScreenUtilities::GetColorAt(const Float2& pos)
{
PROFILE_CPU();
HDC deviceContext = GetDC(NULL);
COLORREF color = GetPixel(deviceContext, (int)pos.X, (int)pos.Y);
ReleaseDC(NULL, deviceContext);
return Color32(GetRValue(color), GetGValue(color), GetBValue(color), 255);
}
void WindowsScreenUtilities::PickColor()
{
MouseCallbackHook = SetWindowsHookEx(WH_MOUSE_LL, OnScreenUtilsMouseCallback, NULL, NULL);
if (MouseCallbackHook == NULL)
{
LOG(Warning, "Failed to set mouse hook.");
LOG(Warning, "Error: {0}", GetLastError());
}
}
#endif

View File

@@ -0,0 +1,25 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#if PLATFORM_WINDOWS
#include "Engine/Platform/Base/ScreenUtilitiesBase.h"
#include "Engine/Core/Types/BaseTypes.h"
#include "Engine/Core/Math/Color32.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Core/Delegate.h"
/// <summary>
/// Platform-dependent screen utilities.
/// </summary>
class FLAXENGINE_API WindowsScreenUtilities : public ScreenUtilitiesBase
{
public:
// [ScreenUtilitiesBase]
static Color32 GetColorAt(const Float2& pos);
static void PickColor();
};
#endif

Some files were not shown because too many files have changed in this diff Show More