diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml
index 760041b6a..93b33ebb2 100644
--- a/.github/workflows/build_linux.yml
+++ b/.github/workflows/build_linux.yml
@@ -17,7 +17,7 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update
- sudo apt-get install -y --fix-missing libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev
+ sudo apt-get install -y --fix-missing 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
uses: ./.github/actions/vulkan
- name: Setup .NET
@@ -45,7 +45,7 @@ jobs:
uses: actions/checkout@v3
- name: Install dependencies
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
uses: ./.github/actions/vulkan
- name: Setup .NET
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index fdc8e80c9..5f8c55502 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -29,7 +29,7 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update
- sudo apt-get install -y --fix-missing libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev
+ sudo apt-get install -y --fix-missing libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev libwayland-dev
- name: Build
run: |
./GenerateProjectFiles.sh -vs2022 -log -verbose -printSDKs -dotnet=8
diff --git a/Flax.flaxproj b/Flax.flaxproj
index cb082f268..3ee8f392f 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -4,7 +4,7 @@
"Major": 1,
"Minor": 12,
"Revision": 0,
- "Build": 6904
+ "Build": 6905
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.",
@@ -13,6 +13,7 @@
"Configuration": {
"UseCSharp": true,
"UseLargeWorlds": false,
- "UseDotNet": true
+ "UseDotNet": true,
+ "UseSDL": true
}
}
\ No newline at end of file
diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings
index f54190003..5a027a4d2 100644
--- a/Flax.sln.DotSettings
+++ b/Flax.sln.DotSettings
@@ -188,7 +188,7 @@
False
NEVER
False
- ALWAYS
+
NEVER
ON_SINGLE_LINE
False
diff --git a/Source/Editor/Content/Items/ContentFolder.cs b/Source/Editor/Content/Items/ContentFolder.cs
index 2bc4c03b3..462d0c1c1 100644
--- a/Source/Editor/Content/Items/ContentFolder.cs
+++ b/Source/Editor/Content/Items/ContentFolder.cs
@@ -282,7 +282,7 @@ namespace FlaxEditor.Content
if (data is DragDataFiles)
return DragDropEffect.Copy;
- return _dragOverItems.Effect;
+ return _dragOverItems?.Effect ?? DragDropEffect.None;
}
///
diff --git a/Source/Editor/Content/Items/CppScriptItem.cs b/Source/Editor/Content/Items/CppScriptItem.cs
index b10710e3a..9f1f55dd0 100644
--- a/Source/Editor/Content/Items/CppScriptItem.cs
+++ b/Source/Editor/Content/Items/CppScriptItem.cs
@@ -20,7 +20,7 @@ namespace FlaxEditor.Content
}
///
- 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";
///
public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.CPPScript128;
diff --git a/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs b/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs
index db7434125..960d5ad27 100644
--- a/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs
@@ -190,12 +190,12 @@ namespace FlaxEditor.CustomEditors.Dedicated
foreach (var file in files)
FindNewKeysCSharp(file, newKeys, allKeys);
- // C++
- files = Directory.GetFiles(Globals.ProjectSourceFolder, "*.cpp", SearchOption.AllDirectories);
+ // C/C++
+ files = Directory.GetFiles(Globals.ProjectSourceFolder, "*.cpp", SearchOption.AllDirectories).Concat(Directory.GetFiles(Globals.ProjectSourceFolder, "*.c", SearchOption.AllDirectories)).ToArray();
filesCount += files.Length;
foreach (var file in files)
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;
foreach (var file in files)
FindNewKeysCpp(file, newKeys, allKeys);
diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
index 2f517d903..d3af7b5cd 100644
--- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
+++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
@@ -1,7 +1,10 @@
-#if PLATFORM_WINDOWS
+#if PLATFORM_WINDOWS || PLATFORM_SDL
#define USE_IS_FOREGROUND
#else
#endif
+#if PLATFORM_SDL
+#define USE_SDL_WORKAROUNDS
+#endif
// Copyright (c) Wojciech Figat. All rights reserved.
using System.Collections.Generic;
@@ -130,7 +133,7 @@ namespace FlaxEditor.GUI.ContextMenu
}
///
- /// Shows the empty menu popup o na screen.
+ /// Shows the empty menu popup on a screen.
///
/// The target control.
/// The target control area to cover.
@@ -265,7 +268,9 @@ namespace FlaxEditor.GUI.ContextMenu
desc.AllowMaximize = false;
desc.AllowDragAndDrop = false;
desc.IsTopmost = true;
- desc.IsRegularWindow = false;
+ desc.Type = WindowType.Popup;
+ desc.Parent = parentWin.Window;
+ desc.Title = "ContextMenu";
desc.HasSizingFrame = false;
OnWindowCreating(ref desc);
_window = Platform.CreateWindow(ref desc);
@@ -275,6 +280,12 @@ namespace FlaxEditor.GUI.ContextMenu
_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
_parentCM = parent as ContextMenuBase;
Parent = _window.GUI;
@@ -449,6 +460,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()
{
var child = _childCM;
@@ -462,6 +484,7 @@ namespace FlaxEditor.GUI.ContextMenu
});
}
}
+#endif
private void OnWindowLostFocus()
{
@@ -560,7 +583,12 @@ namespace FlaxEditor.GUI.ContextMenu
// Let root context menu to check if none of the popup windows
if (_parentCM == null && UseVisibilityControl && !IsForeground)
{
+#if USE_SDL_WORKAROUNDS
+ if (!IsMouseOver)
+ Hide();
+#else
Hide();
+#endif
}
}
#endif
diff --git a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
index d727c15fc..d028b7c7c 100644
--- a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
+++ b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
@@ -303,19 +303,22 @@ namespace FlaxEditor.GUI.Dialogs
if (_activeEyedropper)
{
_activeEyedropper = false;
- Color color = colorPicked;
- if (_linear)
- color = color.ToLinear();
- SelectedColor = color;
- ScreenUtilities.PickColorDone -= OnColorPicked;
+ if (colorPicked != Color.Transparent)
+ {
+ Color color = colorPicked;
+ if (_linear)
+ color = color.ToLinear();
+ SelectedColor = color;
+ }
+ Platform.PickScreenColorDone -= OnColorPicked;
}
}
private void OnEyedropStart()
{
_activeEyedropper = true;
- ScreenUtilities.PickColor();
- ScreenUtilities.PickColorDone += OnColorPicked;
+ Platform.PickScreenColor();
+ Platform.PickScreenColorDone += OnColorPicked;
}
private void OnRGBAChanged()
@@ -351,11 +354,15 @@ namespace FlaxEditor.GUI.Dialogs
// Update eye dropper tool
if (_activeEyedropper)
{
+ // Try reading the color under the cursor in realtime if supported by the platform
Float2 mousePosition = Platform.MousePosition;
- Color color = ScreenUtilities.GetColorAt(mousePosition);
- if (_linear)
- color = color.ToLinear();
- SelectedColor = color;
+ Color color = Platform.GetScreenColorAt(mousePosition);
+ if (color != Color.Transparent)
+ {
+ if (_linear)
+ color = color.ToLinear();
+ SelectedColor = color;
+ }
}
}
@@ -437,7 +444,7 @@ namespace FlaxEditor.GUI.Dialogs
{
// Cancel eye dropping
_activeEyedropper = false;
- ScreenUtilities.PickColorDone -= OnColorPicked;
+ Platform.PickScreenColorDone -= OnColorPicked;
return true;
}
diff --git a/Source/Editor/GUI/Docking/DockHintWindow.cs b/Source/Editor/GUI/Docking/DockHintWindow.cs
deleted file mode 100644
index 30ffd6287..000000000
--- a/Source/Editor/GUI/Docking/DockHintWindow.cs
+++ /dev/null
@@ -1,545 +0,0 @@
-// Copyright (c) Wojciech Figat. All rights reserved.
-
-using System;
-using FlaxEngine;
-using FlaxEngine.GUI;
-
-namespace FlaxEditor.GUI.Docking
-{
- ///
- /// Helper class used to handle docking windows dragging and docking.
- ///
- 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();
- }
-
- ///
- /// Releases unmanaged and - optionally - managed resources.
- ///
- 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;
- }
-
- ///
- /// Creates the new dragging hit window.
- ///
- /// Floating dock panel to move.
- /// The dock hint window object.
- public static DockHintWindow Create(FloatWindowDockPanel toMove)
- {
- if (toMove == null)
- throw new ArgumentNullException();
-
- return new DockHintWindow(toMove);
- }
-
- ///
- /// Creates the new dragging hit window.
- ///
- /// Dock window to move.
- /// The dock hint window object.
- 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);
- }
-
- ///
- /// Calculates window rectangle in the dock window.
- ///
- /// Window dock state.
- /// Dock panel rectangle.
- /// Calculated window rectangle.
- public static Rectangle CalculateDockRect(DockState state, ref Rectangle rect)
- {
- Rectangle result = rect;
- switch (state)
- {
- case DockState.DockFill:
- result.Location.Y += Editor.Instance.Options.Options.Interface.TabHeight;
- result.Size.Y -= Editor.Instance.Options.Options.Interface.TabHeight;
- 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();
- }
-
- ///
- /// Contains helper proxy windows shared across docking panels. They are used to visualize docking window locations.
- ///
- public static class Proxy
- {
- ///
- /// The drag proxy window.
- ///
- public static Window Window;
-
- ///
- /// The left hint proxy window.
- ///
- public static Window Left;
-
- ///
- /// The right hint proxy window.
- ///
- public static Window Right;
-
- ///
- /// The up hint proxy window.
- ///
- public static Window Up;
-
- ///
- /// The down hint proxy window.
- ///
- public static Window Down;
-
- ///
- /// The center hint proxy window.
- ///
- public static Window Center;
-
- ///
- /// The hint windows size.
- ///
- public const float HintWindowsSize = 32.0f;
-
- ///
- /// Initializes the hit proxy windows. Those windows are used to indicate drag target areas (left, right, top, bottom, etc.).
- ///
- 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");
- }
-
- ///
- /// Initializes the hint window.
- ///
- /// Initial size of the proxy window.
- 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();
- }
- 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();
- }
-
- ///
- /// Hides proxy windows.
- ///
- 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();
- }
- }
-
- ///
- /// Releases proxy data and windows.
- ///
- 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;
- }
- }
- }
- }
-}
diff --git a/Source/Editor/GUI/Docking/DockPanelProxy.cs b/Source/Editor/GUI/Docking/DockPanelProxy.cs
index 888b78232..c3fc0fa13 100644
--- a/Source/Editor/GUI/Docking/DockPanelProxy.cs
+++ b/Source/Editor/GUI/Docking/DockPanelProxy.cs
@@ -19,11 +19,7 @@ namespace FlaxEditor.GUI.Docking
private float _tabHeight = Editor.Instance.Options.Options.Interface.TabHeight;
private bool _useMinimumTabWidth = Editor.Instance.Options.Options.Interface.UseMinimumTabWidth;
private float _minimumTabWidth = Editor.Instance.Options.Options.Interface.MinimumTabWidth;
-#if PLATFORM_WINDOWS
- private readonly bool _hideTabForSingleTab = Editor.Instance.Options.Options.Interface.HideSingleTabWindowTabBars;
-#else
- private readonly bool _hideTabForSingleTab = false;
-#endif
+ private readonly bool _hideTabForSingleTab = Utilities.Utils.HideSingleTabWindowTabBars();
///
/// The is mouse down flag (left button).
@@ -55,6 +51,11 @@ namespace FlaxEditor.GUI.Docking
///
public Float2 MousePosition = Float2.Minimum;
+ ///
+ /// The mouse position.
+ ///
+ public Float2 MouseStartPosition = Float2.Minimum;
+
///
/// The start drag asynchronous window.
///
@@ -196,7 +197,7 @@ namespace FlaxEditor.GUI.Docking
if (_panel.ChildPanelsCount == 0 && _panel.TabsCount == 1 && _panel.IsFloating)
{
// Create docking hint window but in an async manner
- DockHintWindow.Create(_panel as FloatWindowDockPanel);
+ WindowDragHelper.StartDragging(_panel as FloatWindowDockPanel);
}
else
{
@@ -207,7 +208,7 @@ namespace FlaxEditor.GUI.Docking
_panel.SelectTab(index - 1);
// Create docking hint window
- DockHintWindow.Create(win);
+ WindowDragHelper.StartDragging(win, _panel.RootWindow.Window);
}
}
}
@@ -393,6 +394,7 @@ namespace FlaxEditor.GUI.Docking
if (IsSingleFloatingWindow)
return base.OnMouseDown(location, button);
MouseDownWindow = GetTabAtPos(location, out IsMouseDownOverCross);
+ MouseStartPosition = location;
// Check buttons
if (button == MouseButton.Left)
@@ -479,6 +481,20 @@ namespace FlaxEditor.GUI.Docking
StartDrag(MouseDownWindow);
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
else if (MouseDownWindow != null && _panel.TabsCount > 1)
{
diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs
index 33ab459e4..c793dacfa 100644
--- a/Source/Editor/GUI/Docking/DockWindow.cs
+++ b/Source/Editor/GUI/Docking/DockWindow.cs
@@ -182,6 +182,26 @@ namespace FlaxEditor.GUI.Docking
/// Window size, set to use default.
/// Window location.
public void ShowFloating(Float2 location, Float2 size, WindowStartPosition position = WindowStartPosition.CenterParent)
+ {
+ CreateFloating(location, size, position, true);
+ }
+
+ ///
+ /// Creates the window in a floating state.
+ ///
+ public void CreateFloating()
+ {
+ CreateFloating(Float2.Zero, Float2.Zero);
+ }
+
+ ///
+ /// Creates the window in a floating state.
+ ///
+ /// Window location.
+ /// Window size, set to use default.
+ /// Window location.
+ /// Window visibility.
+ public void CreateFloating(Float2 location, Float2 size, WindowStartPosition position = WindowStartPosition.CenterParent, bool showWindow = false)
{
Undock();
@@ -199,14 +219,17 @@ namespace FlaxEditor.GUI.Docking
windowGUI.UnlockChildrenRecursive();
windowGUI.PerformLayout();
- // Show
- window.Show();
- window.BringToFront();
- window.Focus();
- OnShow();
+ if (showWindow)
+ {
+ // Show
+ window.Show();
+ window.BringToFront();
+ window.Focus();
+ OnShow();
- // Perform layout again
- windowGUI.PerformLayout();
+ // Perform layout again
+ windowGUI.PerformLayout();
+ }
}
///
diff --git a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
index 3104b013e..e53b9000b 100644
--- a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
+++ b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
@@ -11,6 +11,42 @@ namespace FlaxEditor.GUI.Docking
///
public class FloatWindowDockPanel : DockPanel
{
+ private class FloatWindowDecorations : WindowDecorations
+ {
+ private FloatWindowDockPanel _panel;
+
+ public FloatWindowDecorations(FloatWindowDockPanel panel)
+ : base(panel.RootWindow)
+ {
+ _panel = panel;
+ }
+
+ ///
+ public override bool OnMouseDown(Float2 location, MouseButton button)
+ {
+ if (Title.Bounds.Contains(location) && button == MouseButton.Left)
+ {
+ _panel.BeginDrag();
+ return true;
+ }
+ return base.OnMouseDown(location, button);
+ }
+
+#if !PLATFORM_WINDOWS
+ ///
+ protected override WindowHitCodes OnHitTest(ref Float2 mouse)
+ {
+ var hit = base.OnHitTest(ref mouse);
+ if (hit == WindowHitCodes.Caption)
+ {
+ // Override the system behaviour when interacting with the caption area
+ hit = WindowHitCodes.Client;
+ }
+ return hit;
+ }
+#endif
+ }
+
private MasterDockPanel _masterPanel;
private WindowRootControl _window;
@@ -40,6 +76,26 @@ namespace FlaxEditor.GUI.Docking
Parent = window;
_window.Window.Closing += OnClosing;
_window.Window.LeftButtonHit += OnLeftButtonHit;
+
+ if (Utilities.Utils.UseCustomWindowDecorations())
+ {
+ var decorations = Parent.AddChild(new FloatWindowDecorations(this));
+ decorations.SetAnchorPreset(AnchorPresets.HorizontalStretchTop, false);
+ }
+ }
+
+ ///
+ protected override void PerformLayoutBeforeChildren()
+ {
+ base.PerformLayoutBeforeChildren();
+
+ var decorations = Parent.GetChild();
+ if (decorations != null)
+ {
+ // Apply offset for the title bar
+ foreach (var child in Children)
+ child.Bounds = child.Bounds with { Y = decorations.Height, Height = Parent.Height - decorations.Height };
+ }
}
///
@@ -52,7 +108,11 @@ namespace FlaxEditor.GUI.Docking
return;
// Create docking hint window
- DockHintWindow.Create(this);
+ Window dragSourceWindow = null;
+#if !PLATFORM_SDL
+ dragSourceWindow = _window?.Window;
+#endif
+ WindowDragHelper.StartDragging(this, dragSourceWindow);
}
///
@@ -71,23 +131,33 @@ namespace FlaxEditor.GUI.Docking
settings.Title = title;
settings.Size = size;
settings.Position = location;
- settings.MinimumSize = new Float2(1);
+ settings.MinimumSize = new Float2(100, 100);
settings.MaximumSize = Float2.Zero; // Unlimited size
settings.Fullscreen = false;
settings.HasBorder = true;
+#if PLATFORM_SDL
+ settings.SupportsTransparency = true;
+#else
settings.SupportsTransparency = false;
+#endif
settings.ActivateWhenFirstShown = true;
settings.AllowInput = true;
settings.AllowMinimize = true;
settings.AllowMaximize = true;
settings.AllowDragAndDrop = true;
settings.IsTopmost = false;
- settings.IsRegularWindow = true;
+ settings.Type = WindowType.Regular;
settings.HasSizingFrame = true;
settings.ShowAfterFirstPaint = false;
settings.ShowInTaskbar = true;
settings.StartPosition = startPosition;
+ if (Utilities.Utils.UseCustomWindowDecorations())
+ {
+ settings.HasBorder = false;
+ //settings.HasSizingFrame = false;
+ }
+
// Create window
return Platform.CreateWindow(ref settings);
}
@@ -151,7 +221,12 @@ namespace FlaxEditor.GUI.Docking
base.OnSelectedTabChanged();
if (_window != null && SelectedTab != null)
+ {
_window.Title = SelectedTab.Title;
+ var decorations = Parent.GetChild();
+ if (decorations != null)
+ decorations.PerformLayout();
+ }
}
///
diff --git a/Source/Editor/GUI/Docking/MasterDockPanel.cs b/Source/Editor/GUI/Docking/MasterDockPanel.cs
index 8d5eede98..7649dc9f8 100644
--- a/Source/Editor/GUI/Docking/MasterDockPanel.cs
+++ b/Source/Editor/GUI/Docking/MasterDockPanel.cs
@@ -81,7 +81,6 @@ namespace FlaxEditor.GUI.Docking
public DockPanel HitTest(ref Float2 position, FloatWindowDockPanel excluded)
{
// Check all floating windows
- // TODO: gather windows order and take it into account when performing test
for (int i = 0; i < FloatingPanels.Count; i++)
{
var win = FloatingPanels[i];
@@ -94,9 +93,44 @@ namespace FlaxEditor.GUI.Docking
}
// Base
+ //if (!Root?.RootWindow.Window.IsFocused ?? false)
+ // return null;
return base.HitTest(ref position);
}
+ ///
+ /// Performs hit test over dock panel.
+ ///
+ /// Window space position to test.
+ /// Floating window to omit during searching (and all docked to that one).
+ /// Results of the hit test
+ /// True if any dock panels were hit, otherwise false.
+ public bool HitTest(ref Float2 position, FloatWindowDockPanel excluded, out DockPanel[] hitResults)
+ {
+ // Check all floating windows
+ List 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)
{
// Add to the windows list
diff --git a/Source/Editor/GUI/Docking/WindowDragHelper.cs b/Source/Editor/GUI/Docking/WindowDragHelper.cs
new file mode 100644
index 000000000..854d5628e
--- /dev/null
+++ b/Source/Editor/GUI/Docking/WindowDragHelper.cs
@@ -0,0 +1,514 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+using System;
+using FlaxEngine;
+using FlaxEngine.GUI;
+
+namespace FlaxEditor.GUI.Docking
+{
+ ///
+ /// Helper class used to handle docking windows dragging and docking.
+ ///
+ 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;
+
+ ///
+ /// The hint control size.
+ ///
+ public const float HintControlSize = 48.0f;
+
+ ///
+ /// The opacity of the dragged window when hint controls are shown.
+ ///
+ public const float DragWindowOpacity = 0.4f;
+
+ ///
+ /// Returns true if any windows are being dragged.
+ ///
+ 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;
+ var mousePos = Platform.MousePosition;
+
+ // Check if window is maximized and restore window for correct dragging
+ if (window.IsMaximized)
+ {
+ var windowMousePos = mousePos - window.Position;
+ var previousSize = window.Size;
+ window.Restore();
+ window.Position = mousePos - windowMousePos * window.Size / previousSize;
+ }
+
+ // When drag starts from a tabs the window might not be shown yet
+ if (!window.IsVisible)
+ {
+ window.Show();
+ window.Position = mousePos - new Float2(40, 10);
+ }
+
+ // Bind events
+ FlaxEngine.Scripting.Update += OnUpdate;
+ window.MouseUp += OnMouseUp;
+#if !PLATFORM_SDL
+ window.StartTrackingMouse(false);
+#endif
+
+ // Update rectangles
+ UpdateRects(mousePos);
+
+ // 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
+ {
+#if PLATFORM_SDL
+ _dragOffset = new Float2(window.Size.X / 2, 10.0f);
+#else
+ _dragOffset = mousePos - window.Position;
+#endif
+
+ // The mouse up event is sent to the source window on Windows
+ _dragSourceWindow.MouseUp += OnMouseUp;
+
+ // 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);
+#if !PLATFORM_SDL
+ _dragSourceWindow.BringToFront();
+#endif
+ }
+ else
+ {
+ _dragOffset = window.MousePosition;
+ window.DoDragDrop(window.Title, _dragOffset, window);
+ }
+ }
+
+ ///
+ /// Releases unmanaged and - optionally - managed resources.
+ ///
+ public void Dispose()
+ {
+ IsDragActive = false;
+ var window = _toMove?.Window?.Window;
+
+ // Unbind events
+ FlaxEngine.Scripting.Update -= OnUpdate;
+ if (window != null)
+ {
+ window.MouseUp -= OnMouseUp;
+#if !PLATFORM_SDL
+ window.EndTrackingMouse();
+#endif
+ }
+ 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;
+ }
+
+ ///
+ /// Start dragging a floating dock panel.
+ ///
+ /// Floating dock panel to move.
+ /// The window where dragging started from.
+ /// The window drag helper object.
+ public static WindowDragHelper StartDragging(FloatWindowDockPanel toMove, Window dragSourceWindow = null)
+ {
+ if (toMove == null)
+ throw new ArgumentNullException();
+
+ return new WindowDragHelper(toMove, dragSourceWindow);
+ }
+
+ ///
+ /// Start dragging a docked panel into a floating window.
+ ///
+ /// Dock window to move.
+ /// The window where dragging started from.
+ /// The window drag helper object.
+ 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();
+ 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
+ // TODO: this doesn't allow docking window into another floating window over the main window
+ /*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
+ if (_toDock != Editor.Instance.UI.MasterPanel)
+ _toDock?.RootWindow.Window.BringToFront();
+ //_toDock?.RootWindow.Window.Focus();
+
+#if PLATFORM_SDL
+ // Make the dragged window transparent when dock hints are visible
+ _toMove.Window.Window.Opacity = _toDock == null ? 1.0f : DragWindowOpacity;
+#else
+ // Bring the drop source always to the top
+ if (_dragSourceWindow != null)
+ _dragSourceWindow.BringToFront();
+#endif
+ }
+
+ // Check dock state to use
+ bool showProxyHints = _toDock != null;
+ bool showBorderHints = showProxyHints;
+ bool showCenterHint = showProxyHints;
+ Control hoveredHintControl = null;
+ Float2 hoveredLocationOffset = Float2.Zero;
+ Float2 hoveredSizeOverride = Float2.Zero;
+ DockState prevToSet = _toSet;
+ float hoveredMargin = 1.0f;
+ 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 = 10.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
+ var toSet = DockState.Float;
+ var hintTestPoint = _toDock.PointFromScreen(_mouse);
+ if (showBorderHints)
+ {
+ if (_rUpper.Contains(ref hintTestPoint))
+ {
+ toSet = DockState.DockTop;
+ hoveredHintControl = _dockHintUp;
+ hoveredSizeOverride = new Float2(size.X, size.Y * DockPanel.DefaultSplitterValue);
+ hoveredLocationOffset.Y -= borderMargin - hoveredMargin;
+ }
+ else if (_rBottom.Contains(ref hintTestPoint))
+ {
+ toSet = DockState.DockBottom;
+ hoveredHintControl = _dockHintDown;
+ hoveredSizeOverride = new Float2(size.X, size.Y * DockPanel.DefaultSplitterValue);
+ hoveredLocationOffset.Y += borderMargin - hoveredMargin;
+ }
+ else if (_rLeft.Contains(ref hintTestPoint))
+ {
+ toSet = DockState.DockLeft;
+ hoveredHintControl = _dockHintLeft;
+ hoveredSizeOverride = new Float2(size.X * DockPanel.DefaultSplitterValue, size.Y);
+ hoveredLocationOffset.X -= borderMargin - hoveredMargin;
+ }
+ else if (_rRight.Contains(ref hintTestPoint))
+ {
+ toSet = DockState.DockRight;
+ hoveredHintControl = _dockHintRight;
+ hoveredSizeOverride = new Float2(size.X * DockPanel.DefaultSplitterValue, size.Y);
+ hoveredLocationOffset.X += borderMargin - hoveredMargin;
+ }
+ }
+ if (showCenterHint && _rCenter.Contains(ref hintTestPoint))
+ {
+ 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)
+ {
+ var mainColor = Style.Current.Selection;
+ if (hoveredHintControl != _dockHintDown)
+ {
+ _dockHintDown.Size = new Float2(HintControlSize);
+ _dockHintDown.BackgroundColor = mainColor.AlphaMultiplied(0.6f);
+ }
+ if (hoveredHintControl != _dockHintLeft)
+ {
+ _dockHintLeft.Size = new Float2(HintControlSize);
+ _dockHintLeft.BackgroundColor = mainColor.AlphaMultiplied(0.6f);
+ }
+ if (hoveredHintControl != _dockHintRight)
+ {
+ _dockHintRight.Size = new Float2(HintControlSize);
+ _dockHintRight.BackgroundColor = mainColor.AlphaMultiplied(0.6f);
+ }
+ if (hoveredHintControl != _dockHintUp)
+ {
+ _dockHintUp.Size = new Float2(HintControlSize);
+ _dockHintUp.BackgroundColor = mainColor.AlphaMultiplied(0.6f);
+ }
+ if (hoveredHintControl != _dockHintCenter)
+ {
+ _dockHintCenter.Size = new Float2(HintControlSize);
+ _dockHintCenter.BackgroundColor = mainColor.AlphaMultiplied(0.6f);
+ }
+
+ if (_toSet != DockState.Float)
+ {
+ if (hoveredHintControl != null)
+ {
+ hoveredHintControl.BackgroundColor = mainColor;
+ if (_toSet != prevToSet)
+ hoveredHintControl.Location += hoveredLocationOffset;
+ hoveredHintControl.Size = hoveredSizeOverride - hoveredMargin;
+ }
+ }
+ }
+
+ // 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()
+ {
+ // If the engine lost focus during dragging, end the action
+ if (!Engine.HasFocus)
+ {
+ Dispose();
+ return;
+ }
+
+ var mousePos = Platform.MousePosition;
+ if (_mouse != mousePos)
+ {
+ if (_dragSourceWindow != null)
+ _toMove.Window.Window.Position = mousePos - _dragOffset;
+
+ UpdateRects(mousePos);
+ }
+ }
+ }
+}
diff --git a/Source/Editor/GUI/Input/ValueBox.cs b/Source/Editor/GUI/Input/ValueBox.cs
index 88ec9a4ee..8e3e4f4e7 100644
--- a/Source/Editor/GUI/Input/ValueBox.cs
+++ b/Source/Editor/GUI/Input/ValueBox.cs
@@ -292,6 +292,7 @@ namespace FlaxEditor.GUI.Input
return base.OnMouseDown(location, button);
}
+#if !PLATFORM_SDL
///
public override void OnMouseMove(Float2 location)
{
@@ -318,13 +319,45 @@ namespace FlaxEditor.GUI.Input
base.OnMouseMove(location);
}
+#else
+
+ ///
+ public override void OnMouseMoveRelative(Float2 motion)
+ {
+ 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(motion);
+ }
+
+#endif
+
///
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _isSliding)
{
+#if !PLATFORM_SDL
// End sliding and return mouse to original location
RootWindow.MousePosition = _mouseClickedPosition;
+#endif
EndSliding();
return true;
}
diff --git a/Source/Editor/GUI/MainMenu.cs b/Source/Editor/GUI/MainMenu.cs
index bce668451..6479938a1 100644
--- a/Source/Editor/GUI/MainMenu.cs
+++ b/Source/Editor/GUI/MainMenu.cs
@@ -12,16 +12,6 @@ namespace FlaxEditor.GUI
///
public sealed class MainMenu : ContainerControl
{
-#if PLATFORM_WINDOWS
- private bool _useCustomWindowSystem;
- private Image _icon;
- private Label _title;
- private Button _closeButton;
- private Button _minimizeButton;
- private Button _maximizeButton;
- private LocalizedString _charChromeRestore, _charChromeMaximize;
- private Window _window;
-#endif
private MainMenuButton _selected;
///
@@ -60,200 +50,12 @@ namespace FlaxEditor.GUI
///
/// Initializes a new instance of the class.
///
- /// The main window.
- public MainMenu(RootControl mainWindow)
+ public MainMenu()
: base(0, 0, 0, 20)
{
AutoFocus = false;
AnchorPreset = AnchorPresets.HorizontalStretchTop;
-
-#if PLATFORM_WINDOWS
- _useCustomWindowSystem = !Editor.Instance.Options.Options.Interface.UseNativeWindowSystem;
- if (_useCustomWindowSystem)
- {
- BackgroundColor = Style.Current.LightBackground;
- Height = 28;
-
- var windowIcon = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WindowIcon);
- FontAsset windowIconsFont = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WindowIconsFont);
- Font iconFont = windowIconsFont?.CreateFont(9);
-
- _window = mainWindow.RootWindow.Window;
- _window.HitTest += OnHitTest;
- _window.Closed += OnWindowClosed;
-
- ScriptsBuilder.GetBinariesConfiguration(out _, out _, out _, out var configuration);
-
- _icon = new Image
- {
- Margin = new Margin(6, 6, 6, 6),
- Brush = new TextureBrush(windowIcon),
- Color = Style.Current.Foreground,
- KeepAspectRatio = false,
- TooltipText = string.Format("{0}\nVersion {1}\nConfiguration {3}\nGraphics {2}", _window.Title, Globals.EngineVersion, GPUDevice.Instance.RendererType, configuration),
- Parent = this,
- };
-
- _title = new Label(0, 0, Width, Height)
- {
- Text = _window.Title,
- HorizontalAlignment = TextAlignment.Center,
- VerticalAlignment = TextAlignment.Center,
- ClipText = true,
- TextColor = Style.Current.ForegroundGrey,
- TextColorHighlighted = Style.Current.ForegroundGrey,
- Parent = this,
- };
-
- _closeButton = new Button
- {
- Text = ((char)EditorAssets.SegMDL2Icons.ChromeClose).ToString(),
- Font = new FontReference(iconFont),
- BackgroundColor = Color.Transparent,
- BorderColor = Color.Transparent,
- BorderColorHighlighted = Color.Transparent,
- BorderColorSelected = Color.Transparent,
- TextColor = Style.Current.Foreground,
- Width = 46,
- BackgroundColorHighlighted = Color.Red,
- BackgroundColorSelected = Color.Red.RGBMultiplied(1.3f),
- Parent = this,
- };
- _closeButton.Clicked += () => _window.Close(ClosingReason.User);
-
- _minimizeButton = new Button
- {
- Text = ((char)EditorAssets.SegMDL2Icons.ChromeMinimize).ToString(),
- Font = new FontReference(iconFont),
- BackgroundColor = Color.Transparent,
- BorderColor = Color.Transparent,
- BorderColorHighlighted = Color.Transparent,
- BorderColorSelected = Color.Transparent,
- TextColor = Style.Current.Foreground,
- Width = 46,
- BackgroundColorHighlighted = Style.Current.LightBackground.RGBMultiplied(1.3f),
- Parent = this,
- };
- _minimizeButton.Clicked += () => _window.Minimize();
-
- _maximizeButton = new Button
- {
- Text = ((char)(_window.IsMaximized ? EditorAssets.SegMDL2Icons.ChromeRestore : EditorAssets.SegMDL2Icons.ChromeMaximize)).ToString(),
- Font = new FontReference(iconFont),
- BackgroundColor = Color.Transparent,
- BorderColor = Color.Transparent,
- BorderColorHighlighted = Color.Transparent,
- BorderColorSelected = Color.Transparent,
- TextColor = Style.Current.Foreground,
- Width = 46,
- BackgroundColorHighlighted = Style.Current.LightBackground.RGBMultiplied(1.3f),
- Parent = this,
- };
- _maximizeButton.Clicked += () =>
- {
- if (_window.IsMaximized)
- _window.Restore();
- else
- _window.Maximize();
- };
- _charChromeRestore = ((char)EditorAssets.SegMDL2Icons.ChromeRestore).ToString();
- _charChromeMaximize = ((char)EditorAssets.SegMDL2Icons.ChromeMaximize).ToString();
- }
- else
-#endif
- {
- BackgroundColor = Style.Current.LightBackground;
- }
- }
-
-#if PLATFORM_WINDOWS
- ///
- public override void Update(float deltaTime)
- {
- base.Update(deltaTime);
-
- if (_maximizeButton != null)
- {
- _maximizeButton.Text = _window.IsMaximized ? _charChromeRestore : _charChromeMaximize;
- }
- }
-
- private void OnWindowClosed()
- {
- if (_window != null)
- {
- _window.HitTest = null;
- _window = null;
- }
- }
-
- private WindowHitCodes OnHitTest(ref Float2 mouse)
- {
- var dpiScale = _window.DpiScale;
-
- if (_window.IsMinimized)
- return WindowHitCodes.NoWhere;
-
- if (!_window.IsMaximized)
- {
- var pos = _window.ScreenToClient(mouse * dpiScale); // pos is not DPI adjusted
- var winSize = _window.Size;
-
- // Distance from which the mouse is considered to be on the border/corner
- float distance = 5.0f * dpiScale;
-
- if (pos.Y > winSize.Y - distance && pos.X < distance)
- return WindowHitCodes.BottomLeft;
-
- if (pos.X > winSize.X - distance && pos.Y > winSize.Y - distance)
- return WindowHitCodes.BottomRight;
-
- if (pos.Y < distance && pos.X < distance)
- return WindowHitCodes.TopLeft;
-
- if (pos.Y < distance && pos.X > winSize.X - distance)
- return WindowHitCodes.TopRight;
-
- if (pos.X > winSize.X - distance)
- return WindowHitCodes.Right;
-
- if (pos.X < distance)
- return WindowHitCodes.Left;
-
- if (pos.Y < distance)
- return WindowHitCodes.Top;
-
- if (pos.Y > winSize.Y - distance)
- return WindowHitCodes.Bottom;
- }
-
- var mousePos = PointFromScreen(mouse * dpiScale);
- var controlUnderMouse = GetChildAt(mousePos);
- var isMouseOverSth = controlUnderMouse != null && controlUnderMouse != _title;
- var rb = GetRightButton();
- if (rb != null && _minimizeButton != null && new Rectangle(rb.UpperRight, _minimizeButton.BottomLeft - rb.UpperRight).Contains(ref mousePos) && !isMouseOverSth)
- return WindowHitCodes.Caption;
-
- return WindowHitCodes.Client;
- }
-#endif
-
- ///
- /// Return the rightmost button.
- ///
- /// Rightmost button, null if there is no
- private MainMenuButton GetRightButton()
- {
- MainMenuButton b = null;
- foreach (var control in Children)
- {
- if (b == null && control is MainMenuButton)
- b = (MainMenuButton)control;
-
- if (control is MainMenuButton && control.Right > b.Right)
- b = (MainMenuButton)control;
- }
- return b;
+ BackgroundColor = Style.Current.LightBackground;
}
///
@@ -298,26 +100,6 @@ namespace FlaxEditor.GUI
return result;
}
- ///
- public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
- {
- if (base.OnMouseDoubleClick(location, button))
- return true;
-
-#if PLATFORM_WINDOWS
- var child = GetChildAtRecursive(location);
- if (_useCustomWindowSystem && child is not Button && child is not MainMenuButton)
- {
- if (_window.IsMaximized)
- _window.Restore();
- else
- _window.Maximize();
- }
-#endif
-
- return true;
- }
-
///
public override bool OnKeyDown(KeyboardKeys key)
{
@@ -333,16 +115,8 @@ namespace FlaxEditor.GUI
protected override void PerformLayoutAfterChildren()
{
float x = 0;
-
-#if PLATFORM_WINDOWS
- if (_useCustomWindowSystem)
- {
- // Icon
- _icon.X = x;
- _icon.Size = new Float2(Height);
- x += _icon.Width;
- }
-#endif
+ WindowDecorations decorations = Parent.GetChild();
+ x += decorations?.Icon?.Width ?? 0;
// Arrange controls
MainMenuButton rightMostButton = null;
@@ -361,37 +135,21 @@ namespace FlaxEditor.GUI
x += b.Width;
}
}
-
-#if PLATFORM_WINDOWS
- if (_useCustomWindowSystem)
- {
- // Buttons
- _closeButton.Height = Height;
- _closeButton.X = Width - _closeButton.Width;
- _maximizeButton.Height = Height;
- _maximizeButton.X = _closeButton.X - _maximizeButton.Width;
- _minimizeButton.Height = Height;
- _minimizeButton.X = _maximizeButton.X - _minimizeButton.Width;
-
- // Title
- _title.Bounds = new Rectangle(x + 2, 0, _minimizeButton.Left - x - 4, Height);
- //_title.Text = _title.Width < 300.0f ? Editor.Instance.ProjectInfo.Name : _window.Title;
- }
-#endif
+
+ // Fill the right side if title and buttons are not present
+ if (decorations?.Title == null)
+ Width = Parent.Width;
+ else
+ Width = x;
}
-#if PLATFORM_WINDOWS
///
public override void OnDestroy()
{
base.OnDestroy();
-
- if (_window != null)
- {
- _window.Closed -= OnWindowClosed;
- OnWindowClosed();
- }
+
+ if (_selected != null)
+ Selected = null;
}
-#endif
}
}
diff --git a/Source/Editor/GUI/MainMenuButton.cs b/Source/Editor/GUI/MainMenuButton.cs
index 291249d89..5007af92e 100644
--- a/Source/Editor/GUI/MainMenuButton.cs
+++ b/Source/Editor/GUI/MainMenuButton.cs
@@ -42,14 +42,12 @@ namespace FlaxEditor.GUI
Text = text;
var style = Style.Current;
-#if PLATFORM_WINDOWS
- if (Editor.Instance.Options.Options.Interface.UseNativeWindowSystem)
+ if (!Utilities.Utils.UseCustomWindowDecorations())
{
BackgroundColorMouseOver = style.BackgroundHighlighted;
BackgroundColorMouseOverOpened = style.Background;
}
else
-#endif
{
BackgroundColorMouseOver = BackgroundColorMouseOverOpened = style.LightBackground * 1.3f;
}
diff --git a/Source/Editor/GUI/WindowDecorations.cs b/Source/Editor/GUI/WindowDecorations.cs
new file mode 100644
index 000000000..74dc38273
--- /dev/null
+++ b/Source/Editor/GUI/WindowDecorations.cs
@@ -0,0 +1,339 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+using FlaxEngine;
+using FlaxEngine.GUI;
+
+namespace FlaxEditor.GUI;
+
+///
+/// Represents the title bar of the window with buttons.
+///
+///
+public class WindowDecorations : ContainerControl
+{
+ private Image _icon;
+ private Label _title;
+ private Button _closeButton;
+ private Button _minimizeButton;
+ private Button _maximizeButton;
+ private LocalizedString _charChromeRestore, _charChromeMaximize;
+ private Window _window;
+
+ ///
+ /// The title label in the title bar.
+ ///
+ public Label Title => _title;
+
+ ///
+ /// The icon used in the title bar.
+ ///
+ public Image Icon => _icon;
+
+ ///
+ /// The tooltip shown when hovering over the icon.
+ ///
+ public string IconTooltipText
+ {
+ get => _icon?.TooltipText ?? null;
+ set
+ {
+ if (_icon != null)
+ _icon.TooltipText = value;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The window.
+ /// When set, omit drawing title and buttons.
+ public WindowDecorations(RootControl window, bool iconOnly = false)
+ : base(0, 0, 0, 20)
+ {
+ _window = window.RootWindow.Window;
+
+ AutoFocus = false;
+ AnchorPreset = AnchorPresets.HorizontalStretchTop;
+ BackgroundColor = Color.Transparent;
+
+ var windowIcon = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WindowIcon);
+
+ _icon = new Image
+ {
+ Margin = new Margin(4, 4, 4, 4),
+ Brush = new TextureBrush(windowIcon),
+ Color = Style.Current.Foreground,
+ BackgroundColor = Style.Current.LightBackground,
+ KeepAspectRatio = false,
+ Parent = this,
+ };
+
+ if (!iconOnly)
+ {
+ _icon.Margin = new Margin(6, 6, 6, 6);
+ Height = 28;
+
+ _window.HitTest += OnHitTest;
+ _window.Closed += OnWindowClosed;
+
+ FontAsset windowIconsFont = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WindowIconsFont);
+ Font iconFont = windowIconsFont?.CreateFont(9);
+
+ _title = new Label(0, 0, Width, Height)
+ {
+ Text = _window.Title,
+ HorizontalAlignment = TextAlignment.Center,
+ VerticalAlignment = TextAlignment.Center,
+ ClipText = true,
+ TextColor = Style.Current.ForegroundGrey,
+ TextColorHighlighted = Style.Current.ForegroundGrey,
+ BackgroundColor = Style.Current.LightBackground,
+ Parent = this,
+ };
+
+ _closeButton = new Button
+ {
+ Text = ((char)EditorAssets.SegMDL2Icons.ChromeClose).ToString(),
+ Font = new FontReference(iconFont),
+ BackgroundColor = Style.Current.LightBackground,
+ BorderColor = Color.Transparent,
+ BorderColorHighlighted = Color.Transparent,
+ BorderColorSelected = Color.Transparent,
+ TextColor = Style.Current.Foreground,
+ Width = 46,
+ BackgroundColorHighlighted = Color.Red,
+ BackgroundColorSelected = Color.Red.RGBMultiplied(1.3f),
+ Parent = this,
+ };
+ _closeButton.Clicked += () => _window.Close(ClosingReason.User);
+
+ _minimizeButton = new Button
+ {
+ Text = ((char)EditorAssets.SegMDL2Icons.ChromeMinimize).ToString(),
+ Font = new FontReference(iconFont),
+ BackgroundColor = Style.Current.LightBackground,
+ BorderColor = Color.Transparent,
+ BorderColorHighlighted = Color.Transparent,
+ BorderColorSelected = Color.Transparent,
+ TextColor = Style.Current.Foreground,
+ Width = 46,
+ BackgroundColorHighlighted = Style.Current.LightBackground.RGBMultiplied(1.3f),
+ Parent = this,
+ };
+ _minimizeButton.Clicked += () => _window.Minimize();
+
+ _maximizeButton = new Button
+ {
+ Text = ((char)(_window.IsMaximized ? EditorAssets.SegMDL2Icons.ChromeRestore : EditorAssets.SegMDL2Icons.ChromeMaximize)).ToString(),
+ Font = new FontReference(iconFont),
+ BackgroundColor = Style.Current.LightBackground,
+ BorderColor = Color.Transparent,
+ BorderColorHighlighted = Color.Transparent,
+ BorderColorSelected = Color.Transparent,
+ TextColor = Style.Current.Foreground,
+ Width = 46,
+ BackgroundColorHighlighted = Style.Current.LightBackground.RGBMultiplied(1.3f),
+ Parent = this,
+ };
+ _maximizeButton.Clicked += () =>
+ {
+ if (_window.IsMaximized)
+ _window.Restore();
+ else
+ _window.Maximize();
+ };
+
+ _charChromeRestore = ((char)EditorAssets.SegMDL2Icons.ChromeRestore).ToString();
+ _charChromeMaximize = ((char)EditorAssets.SegMDL2Icons.ChromeMaximize).ToString();
+ }
+ }
+
+ ///
+ public override void Update(float deltaTime)
+ {
+ base.Update(deltaTime);
+
+ if (_maximizeButton != null)
+ {
+ var maximizeText = _window.IsMaximized ? _charChromeRestore : _charChromeMaximize;
+ if (_maximizeButton.Text != maximizeText)
+ _maximizeButton.Text = maximizeText;
+ }
+ }
+
+ private void OnWindowClosed()
+ {
+ if (_window != null)
+ {
+ _window.HitTest -= OnHitTest;
+ _window = null;
+ }
+ }
+
+ ///
+ /// Perform hit test on the window.
+ ///
+ /// The mouse position
+ /// The hit code for given position.
+ protected virtual WindowHitCodes OnHitTest(ref Float2 mouse)
+ {
+ if (_window.IsMinimized)
+ return WindowHitCodes.NoWhere;
+
+ var dpiScale = _window.DpiScale;
+ var pos = _window.ScreenToClient(mouse * dpiScale); // pos is not DPI adjusted
+ if (!_window.IsMaximized)
+ {
+ var winSize = _window.Size;
+
+ // Distance from which the mouse is considered to be on the border/corner
+ float distance = 5.0f * dpiScale;
+
+ if (pos.Y > winSize.Y - distance && pos.X < distance)
+ return WindowHitCodes.BottomLeft;
+
+ if (pos.X > winSize.X - distance && pos.Y > winSize.Y - distance)
+ return WindowHitCodes.BottomRight;
+
+ if (pos.Y < distance && pos.X < distance)
+ return WindowHitCodes.TopLeft;
+
+ if (pos.Y < distance && pos.X > winSize.X - distance)
+ return WindowHitCodes.TopRight;
+
+ if (pos.X > winSize.X - distance)
+ return WindowHitCodes.Right;
+
+ if (pos.X < distance)
+ return WindowHitCodes.Left;
+
+ if (pos.Y < distance)
+ return WindowHitCodes.Top;
+
+ if (pos.Y > winSize.Y - distance)
+ return WindowHitCodes.Bottom;
+ }
+
+ var controlUnderMouse = GetChildAt(pos, control => control != _title);
+ if (_title.Bounds.Contains(pos) && controlUnderMouse == null)
+ return WindowHitCodes.Caption;
+
+ return WindowHitCodes.Client;
+ }
+
+ ///
+ public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
+ {
+ // These may not work with main window due to SDL not passing mouse events
+ // when interacting with hit tests on caption area...
+
+ if (Title.Bounds.Contains(location) && button == MouseButton.Left)
+ {
+ if (_window.IsMaximized)
+ _window.Restore();
+ else
+ _window.Maximize();
+ return true;
+ }
+ else if (Icon.Bounds.Contains(location) && button == MouseButton.Left)
+ {
+ _window.Close(ClosingReason.User);
+ return true;
+ }
+ return base.OnMouseDoubleClick(location, button);
+ }
+
+ ///
+ protected override void PerformLayoutAfterChildren()
+ {
+ // Calculate extents for title bounds area excluding the icon and main menu area
+ float x = 0;
+
+ // Icon
+ if (_icon != null)
+ {
+ _icon.X = x;
+ _icon.Size = new Float2(Height);
+ x += _icon.Width;
+ }
+
+ // Main menu if present
+ if (Parent.GetChild() is MainMenu mainMenu)
+ {
+ for (int i = 0; i < mainMenu.Children.Count; i++)
+ {
+ var c = mainMenu.Children[i];
+ if (c is MainMenuButton b && c.Visible)
+ {
+ b.Bounds = new Rectangle(x, 0, b.Width, Height);
+ x += b.Width;
+ }
+ }
+ }
+
+ // Buttons
+ float rightMostButtonX = Width;
+ if (_closeButton != null)
+ {
+ _closeButton.Height = Height;
+ _closeButton.X = rightMostButtonX - _closeButton.Width;
+ rightMostButtonX = _closeButton.X;
+ }
+ if (_maximizeButton != null)
+ {
+ _maximizeButton.Height = Height;
+ _maximizeButton.X = rightMostButtonX - _maximizeButton.Width;
+ rightMostButtonX = _maximizeButton.X;
+ }
+ if (_minimizeButton != null)
+ {
+ _minimizeButton.Height = Height;
+ _minimizeButton.X = rightMostButtonX - _minimizeButton.Width;
+ rightMostButtonX = _minimizeButton.X;
+ }
+
+ // Title
+ if (_title != null)
+ {
+ _title.Text = _window.Title;
+ _title.Bounds = new Rectangle(x, 0, rightMostButtonX - x, Height);
+ }
+ }
+
+ ///
+ public override void Draw()
+ {
+ base.Draw();
+ DrawBorders();
+ }
+
+ ///
+ /// Draw borders around the window.
+ ///
+ public virtual void DrawBorders()
+ {
+ var win = RootWindow.Window;
+ if (win.IsMaximized)
+ return;
+
+ if (Editor.Instance.UI.StatusBar == null)
+ return;
+
+ const float thickness = 1.0f;
+ Color color = Editor.Instance.UI.StatusBar.StatusColor;
+ Rectangle rect = new Rectangle(thickness * 0.5f, thickness * 0.5f, Parent.Width - thickness, Parent.Height - thickness);
+ Render2D.DrawRectangle(rect, color);
+ }
+
+ ///
+ public override void OnDestroy()
+ {
+ base.OnDestroy();
+
+ if (_window != null)
+ {
+ _window.Closed -= OnWindowClosed;
+ OnWindowClosed();
+ }
+ }
+}
diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp
index de7532921..2220fb0f3 100644
--- a/Source/Editor/Managed/ManagedEditor.Internal.cpp
+++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp
@@ -402,10 +402,11 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa
break;
}
}
+ WindowsManager::WindowsLocker.Unlock();
for (const auto& e : inputEvents)
{
auto window = e.Target ? e.Target : defaultWindow;
- if (!window)
+ if (!window || window->IsClosed())
continue;
switch (e.Type)
{
@@ -435,12 +436,14 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa
case InputDevice::EventType::MouseMove:
window->OnMouseMove(window->ScreenToClient(e.MouseData.Position));
break;
+ case InputDevice::EventType::MouseMoveRelative:
+ window->OnMouseMoveRelative(e.MouseMovementData.PositionRelative);
+ break;
case InputDevice::EventType::MouseLeave:
window->OnMouseLeave();
break;
}
}
- WindowsManager::WindowsLocker.Unlock();
}
WindowsManager::WindowsLocker.Lock();
Array> windows;
diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs
index 53e45ff25..fc139f312 100644
--- a/Source/Editor/Modules/ContentDatabaseModule.cs
+++ b/Source/Editor/Modules/ContentDatabaseModule.cs
@@ -1013,7 +1013,7 @@ namespace FlaxEditor.Modules
ContentItem item;
if (path.EndsWith(".cs"))
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);
else if (path.EndsWith(".shader") || path.EndsWith(".hlsl"))
item = new ShaderSourceItem(path);
diff --git a/Source/Editor/Modules/ContentImportingModule.cs b/Source/Editor/Modules/ContentImportingModule.cs
index 2a6358758..8029c7418 100644
--- a/Source/Editor/Modules/ContentImportingModule.cs
+++ b/Source/Editor/Modules/ContentImportingModule.cs
@@ -222,7 +222,7 @@ namespace FlaxEditor.Modules
outputExtension = extension;
// 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
Editor.LogWarning(string.Format("Cannot import \'{0}\' to \'{1}\'. The target directory cannot have scripts.", inputPath, targetLocation.Node.Path));
diff --git a/Source/Editor/Modules/SimulationModule.cs b/Source/Editor/Modules/SimulationModule.cs
index fb1fb61d1..663c2ab32 100644
--- a/Source/Editor/Modules/SimulationModule.cs
+++ b/Source/Editor/Modules/SimulationModule.cs
@@ -267,7 +267,7 @@ namespace FlaxEditor.Modules
}
///
- public override void OnPlayBegin()
+ public override void OnPlayBeginning()
{
Editor.Windows.FlashMainWindow();
diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs
index 7cd7e7fef..f8abe6ee1 100644
--- a/Source/Editor/Modules/UIModule.cs
+++ b/Source/Editor/Modules/UIModule.cs
@@ -16,7 +16,6 @@ using FlaxEditor.Windows;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
-using DockHintWindow = FlaxEditor.GUI.Docking.DockHintWindow;
using MasterDockPanel = FlaxEditor.GUI.Docking.MasterDockPanel;
using FlaxEditor.Content.Settings;
using FlaxEditor.Options;
@@ -29,6 +28,40 @@ namespace FlaxEditor.Modules
///
public sealed class UIModule : EditorModule
{
+ private class MainWindowDecorations : WindowDecorations
+ {
+ public MainWindowDecorations(RootControl window, bool iconOnly)
+ : base(window, iconOnly)
+ {
+ }
+
+ ///
+ public override bool OnKeyDown(KeyboardKeys key)
+ {
+ if (base.OnKeyDown(key))
+ return true;
+
+ // Fallback to the edit window for shortcuts
+ var editor = Editor.Instance;
+ return editor.Windows.EditWin.InputActions.Process(editor, this, key);
+ }
+
+ ///
+ public override void DrawBorders()
+ {
+ // Draw main window borders if using a custom style
+ var win = RootWindow.Window;
+ if (win.IsMaximized)
+ return;
+
+ var color = Editor.Instance.UI.StatusBar.StatusColor;
+ var rect = new Rectangle(0.5f, 0.5f, Parent.Width - 1.0f, Parent.Height - 1.0f - StatusBar.DefaultHeight);
+ Render2D.DrawLine(rect.UpperLeft, rect.UpperRight, color);
+ Render2D.DrawLine(rect.UpperLeft, rect.BottomLeft, color);
+ Render2D.DrawLine(rect.UpperRight, rect.BottomRight, color);
+ }
+ }
+
private struct Status
{
public int ID;
@@ -45,7 +78,7 @@ namespace FlaxEditor.Modules
private bool _progressFailed;
ContextMenuSingleSelectGroup _numberOfClientsGroup = new ContextMenuSingleSelectGroup();
-
+
///
/// Defines a viewport scaling option.
///
@@ -66,7 +99,7 @@ namespace FlaxEditor.Modules
///
Aspect = 1,
}
-
+
///
/// The name.
///
@@ -82,7 +115,7 @@ namespace FlaxEditor.Modules
///
public Int2 Size;
}
-
+
///
/// The default viewport scaling options.
///
@@ -153,6 +186,11 @@ namespace FlaxEditor.Modules
///
public MainMenu MainMenu;
+ ///
+ /// The window decorations (title bar with buttons)
+ ///
+ public WindowDecorations WindowDecorations;
+
///
/// The tool strip control.
///
@@ -448,7 +486,7 @@ namespace FlaxEditor.Modules
// Update window background
mainWindow.BackgroundColor = Style.Current.Background;
-
+
InitViewportScaleOptions();
InitSharedMenus();
@@ -456,19 +494,11 @@ namespace FlaxEditor.Modules
InitToolstrip(mainWindow);
InitStatusBar(mainWindow);
InitDockPanel(mainWindow);
+ InitWindowDecorations(mainWindow);
Editor.Options.OptionsChanged += OnOptionsChanged;
- // Add dummy control for drawing the main window borders if using a custom style
-#if PLATFORM_WINDOWS
- if (!Editor.Options.Options.Interface.UseNativeWindowSystem)
-#endif
- {
- mainWindow.AddChild(new CustomWindowBorderControl
- {
- Size = Float2.Zero,
- });
- }
+ mainWindow.PerformLayout(true);
}
private void InitViewportScaleOptions()
@@ -506,7 +536,7 @@ namespace FlaxEditor.Modules
Size = new Int2(2560, 1440),
});
}
-
+
if (Editor.Instance.ProjectCache.TryGetCustomData("CustomViewportScalingOptions", out string data))
{
CustomViewportScaleOptions = JsonSerializer.Deserialize>(data);
@@ -519,7 +549,7 @@ namespace FlaxEditor.Modules
public void SaveCustomViewportScalingOptions()
{
var customOptions = JsonSerializer.Serialize(CustomViewportScaleOptions);
- Editor.Instance.ProjectCache.SetCustomData("CustomViewportScalingOptions", customOptions);
+ Editor.Instance.ProjectCache.SetCustomData("CustomViewportScalingOptions", customOptions);
}
///
@@ -540,23 +570,6 @@ namespace FlaxEditor.Modules
}
}
- private class CustomWindowBorderControl : Control
- {
- ///
- public override void Draw()
- {
- var win = RootWindow.Window;
- if (win.IsMaximized)
- return;
-
- var color = Editor.Instance.UI.StatusBar.StatusColor;
- var rect = new Rectangle(0.5f, 0.5f, Parent.Width - 1.0f, Parent.Height - 1.0f - StatusBar.DefaultHeight);
- Render2D.DrawLine(rect.UpperLeft, rect.UpperRight, color);
- Render2D.DrawLine(rect.UpperLeft, rect.BottomLeft, color);
- Render2D.DrawLine(rect.UpperRight, rect.BottomRight, color);
- }
- }
-
///
public override void OnEndInit()
{
@@ -588,13 +601,6 @@ namespace FlaxEditor.Modules
UpdateToolstrip();
}
- ///
- 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)
{
var dialog = new ColorPickerDialog(initialValue, colorChanged, pickerClosed, useDynamicEditing);
@@ -648,10 +654,12 @@ namespace FlaxEditor.Modules
private void InitMainMenu(RootControl mainWindow)
{
- MainMenu = new MainMenu(mainWindow)
+ MainMenu = new MainMenu()
{
Parent = mainWindow
};
+ if (Utilities.Utils.UseCustomWindowDecorations(isMainWindow: true))
+ MainMenu.Height = 28;
var inputOptions = Editor.Options.Options.Input;
@@ -770,7 +778,7 @@ namespace FlaxEditor.Modules
MenuWindow = MainMenu.AddButton("Window");
cm = MenuWindow.ContextMenu;
cm.VisibleChanged += OnMenuWindowVisibleChanged;
- cm.AddButton("Content", inputOptions.ContentWindow,Editor.Windows.ContentWin.FocusOrShow);
+ cm.AddButton("Content", inputOptions.ContentWindow, Editor.Windows.ContentWin.FocusOrShow);
cm.AddButton("Scene", inputOptions.SceneWindow, Editor.Windows.SceneWin.FocusOrShow);
cm.AddButton("Toolbox", inputOptions.ToolboxWindow, Editor.Windows.ToolboxWin.FocusOrShow);
cm.AddButton("Properties", inputOptions.PropertiesWindow, Editor.Windows.PropertiesWin.FocusOrShow);
@@ -803,6 +811,23 @@ namespace FlaxEditor.Modules
cm.AddButton("Information about Flax", () => new AboutDialog().Show());
}
+ private void InitWindowDecorations(RootControl mainWindow)
+ {
+ ScriptsBuilder.GetBinariesConfiguration(out _, out _, out _, out var configuration);
+ string driver = string.Empty;
+#if PLATFORM_LINUX
+ driver = LinuxPlatform.DisplayServer;
+ if (!string.IsNullOrEmpty(driver))
+ driver = $" ({driver})";
+#endif
+
+ WindowDecorations = new MainWindowDecorations(mainWindow, !Utilities.Utils.UseCustomWindowDecorations(isMainWindow: true))
+ {
+ Parent = mainWindow,
+ IconTooltipText = $"{mainWindow.RootWindow.Title}\nVersion {Globals.EngineVersion}\nConfiguration {configuration}\nGraphics {GPUDevice.Instance.RendererType}{driver}",
+ };
+ }
+
private void OnOptionsChanged(EditorOptions options)
{
var inputOptions = options.Input;
@@ -1227,6 +1252,7 @@ namespace FlaxEditor.Modules
{
// Clear UI references (GUI cannot be used after window closing)
MainMenu = null;
+ WindowDecorations = null;
ToolStrip = null;
MasterPanel = null;
StatusBar = null;
diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs
index 162fa3a40..0d1f0e2d7 100644
--- a/Source/Editor/Modules/WindowsModule.cs
+++ b/Source/Editor/Modules/WindowsModule.cs
@@ -10,7 +10,6 @@ using System.Text;
using System.Xml;
using FlaxEditor.Content;
using FlaxEditor.GUI.Dialogs;
-using FlaxEditor.GUI.Docking;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEditor.Windows.Profiler;
@@ -763,17 +762,19 @@ namespace FlaxEditor.Modules
var settings = CreateWindowSettings.Default;
settings.Title = "Flax Editor";
settings.Size = Platform.DesktopSize * 0.75f;
+ settings.MinimumSize = new Float2(200, 150);
settings.StartPosition = WindowStartPosition.CenterScreen;
settings.ShowAfterFirstPaint = true;
-#if PLATFORM_WINDOWS
- if (!Editor.Instance.Options.Options.Interface.UseNativeWindowSystem)
+
+ if (Utilities.Utils.UseCustomWindowDecorations(isMainWindow: true))
{
settings.HasBorder = false;
-
+#if PLATFORM_WINDOWS && !PLATFORM_SDL
// Skip OS sizing frame and implement it using LeftButtonHit
settings.HasSizingFrame = false;
+#endif
}
-#elif PLATFORM_LINUX
+#if PLATFORM_LINUX && !PLATFORM_SDL
settings.HasBorder = false;
#endif
MainWindow = Platform.CreateWindow(ref settings);
diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs
index f26506c74..dd38ea4ee 100644
--- a/Source/Editor/Options/InterfaceOptions.cs
+++ b/Source/Editor/Options/InterfaceOptions.cs
@@ -85,10 +85,12 @@ namespace FlaxEditor.Options
/// Never show the close button.
///
Never,
+
///
/// Show the close button on tabs that are currently selected.
///
SelectedTab,
+
///
/// Show the close button on all tabs that can be closed.
///
@@ -179,6 +181,34 @@ namespace FlaxEditor.Options
GameWindowThenRestore,
}
+ ///
+ /// Options for type of window decorations to use.
+ ///
+ public enum WindowDecorationsType
+ {
+ ///
+ /// Determined automatically based on the system and any known compatibility issues with native decorations.
+ ///
+ Auto,
+
+ ///
+ /// Automatically choose most compatible window decorations for child windows, prefer custom decorations on main window.
+ ///
+ [EditorDisplay(Name = "Auto (Child Only)")]
+ AutoChildOnly,
+
+ ///
+ /// Use native system window decorations on all windows.
+ ///
+ Native,
+
+ ///
+ /// Use custom client-side window decorations on all windows.
+ ///
+ [EditorDisplay(Name = "Client-side")]
+ ClientSide,
+ }
+
///
/// Gets or sets the 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.
///
@@ -268,7 +298,14 @@ namespace FlaxEditor.Options
[EditorDisplay("Interface"), EditorOrder(322)]
public bool ScrollToScriptOnAdd { get; set; } = true;
-#if PLATFORM_WINDOWS
+#if PLATFORM_SDL
+ ///
+ /// Gets or sets a value indicating whether use native window title bar decorations in child windows. Editor restart required.
+ ///
+ [DefaultValue(WindowDecorationsType.AutoChildOnly)]
+ [EditorDisplay("Interface"), EditorOrder(70), Tooltip("Determines whether use native window title bar decorations. Editor restart required.")]
+ public WindowDecorationsType WindowDecorations { get; set; } = WindowDecorationsType.AutoChildOnly;
+#elif PLATFORM_WINDOWS
///
/// Gets or sets a value indicating whether use native window title bar. Editor restart required.
///
@@ -277,7 +314,7 @@ namespace FlaxEditor.Options
public bool UseNativeWindowSystem { get; set; } = false;
#endif
-#if PLATFORM_WINDOWS
+#if PLATFORM_SDL || PLATFORM_WINDOWS
///
/// Gets or sets a value indicating whether a window containing a single tabs hides the tab bar. Editor restart recommended.
///
@@ -287,7 +324,7 @@ namespace FlaxEditor.Options
#endif
///
- /// Gets or sets a value indicating wether the minum tab width should be used. Editor restart required.
+ /// Gets or sets a value indicating whether the minimum tab width should be used. Editor restart required.
///
[DefaultValue(false)]
[EditorDisplay("Tabs & Windows"), EditorOrder(99)]
@@ -483,7 +520,7 @@ namespace FlaxEditor.Options
[DefaultValue(1), Range(1, 4)]
[EditorDisplay("Cook & Run"), EditorOrder(600)]
public int NumberOfGameClientsToLaunch = 1;
-
+
///
/// Gets or sets the build configuration to use when using Cook and Run option in the editor.
///
@@ -498,7 +535,7 @@ namespace FlaxEditor.Options
public float ConnectionCurvature { get; set; } = 1.0f;
///
- /// Gets or sets a value that indicates wether the context menu description panel is shown or not.
+ /// Gets or sets a value that indicates whether the context menu description panel is shown or not.
///
[DefaultValue(true)]
[EditorDisplay("Visject"), EditorOrder(550), Tooltip("Shows/hides the description panel in visual scripting context menu.")]
diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp
index 8f33d04d7..0d77c4cdd 100644
--- a/Source/Editor/Scripting/ScriptsBuilder.cpp
+++ b/Source/Editor/Scripting/ScriptsBuilder.cpp
@@ -121,9 +121,13 @@ void ScriptsBuilderImpl::sourceDirEvent(const String& path, FileSystemAction act
// Discard non-source files or generated files
if ((!path.EndsWith(TEXT(".cs")) &&
!path.EndsWith(TEXT(".cpp")) &&
+ !path.EndsWith(TEXT(".c")) &&
+ !path.EndsWith(TEXT(".hpp")) &&
!path.EndsWith(TEXT(".h"))) ||
path.EndsWith(TEXT(".Gen.cs")))
+ {
return;
+ }
ScopeLock scopeLock(_locker);
_lastSourceCodeEdited = DateTime::Now();
diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs
index 7a19567fa..6851e5d8b 100644
--- a/Source/Editor/Surface/SurfaceUtils.cs
+++ b/Source/Editor/Surface/SurfaceUtils.cs
@@ -163,6 +163,7 @@ namespace FlaxEditor.Surface
}
}
}
+ surface.Dispose();
}
}
diff --git a/Source/Editor/Utilities/ScreenUtilities.cpp b/Source/Editor/Utilities/ScreenUtilities.cpp
deleted file mode 100644
index b35e2817c..000000000
--- a/Source/Editor/Utilities/ScreenUtilities.cpp
+++ /dev/null
@@ -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 ScreenUtilities::PickColorDone;
-
-#if PLATFORM_WINDOWS
-
-#include
-
-#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
-#include
-
-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
diff --git a/Source/Editor/Utilities/ScreenUtilities.h b/Source/Editor/Utilities/ScreenUtilities.h
deleted file mode 100644
index 421ecf22f..000000000
--- a/Source/Editor/Utilities/ScreenUtilities.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 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"
-
-///
-/// Platform-dependent screen utilities.
-///
-API_CLASS(Static) class FLAXENGINE_API ScreenUtilities
-{
- DECLARE_SCRIPTING_TYPE_MINIMAL(ScreenUtilities);
-
- ///
- /// Gets the pixel color at the specified coordinates.
- ///
- /// Screen-space coordinate to read.
- /// Pixel color at the specified coordinates.
- API_FUNCTION() static Color32 GetColorAt(const Float2& pos);
-
- ///
- /// 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.
- ///
- API_FUNCTION() static void PickColor();
-
- ///
- /// Called when PickColor action is finished.
- ///
- API_EVENT() static Delegate PickColorDone;
-};
diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs
index 688cec470..d6d908f3f 100644
--- a/Source/Editor/Utilities/Utils.cs
+++ b/Source/Editor/Utilities/Utils.cs
@@ -1583,5 +1583,37 @@ namespace FlaxEditor.Utilities
c = c.Parent;
return c as ISceneEditingContext;
}
+
+ internal static bool UseCustomWindowDecorations(bool isMainWindow = false)
+ {
+#if PLATFORM_SDL
+ return Editor.Instance.Options.Options.Interface.WindowDecorations switch
+ {
+ Options.InterfaceOptions.WindowDecorationsType.Auto => !SDLPlatform.SupportsNativeDecorations,
+ Options.InterfaceOptions.WindowDecorationsType.AutoChildOnly => !isMainWindow ? !SDLPlatform.SupportsNativeDecorations : true,
+ Options.InterfaceOptions.WindowDecorationsType.Native => false,
+ Options.InterfaceOptions.WindowDecorationsType.ClientSide => true,
+ _ => throw new ArgumentOutOfRangeException()
+ };
+#elif PLATFORM_WINDOWS
+ return !Editor.Instance.Options.Options.Interface.UseNativeWindowSystem;
+#else
+ return false;
+#endif
+ }
+
+ internal static bool HideSingleTabWindowTabBars()
+ {
+#if PLATFORM_SDL
+ // We should not hide the tab bars if tab handle is the only way to dock the window
+ bool clientSideDecorations = UseCustomWindowDecorations(false);
+ bool draggableDecorations = clientSideDecorations || SDLPlatform.SupportsNativeDecorationDragging;
+ return draggableDecorations && Editor.Instance.Options.Options.Interface.HideSingleTabWindowTabBars;
+#elif PLATFORM_WINDOWS
+ return Editor.Instance.Options.Options.Interface.HideSingleTabWindowTabBars;
+#else
+ return false;
+#endif
+ }
}
}
diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs
index a28c9f04a..1eb31c0e6 100644
--- a/Source/Editor/Viewport/EditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/EditorGizmoViewport.cs
@@ -76,8 +76,13 @@ namespace FlaxEditor.Viewport
///
public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
+#if PLATFORM_SDL
+ ///
+ public Float2 MouseDelta => FlaxEngine.Input.MousePositionDelta;
+#else
///
public Float2 MouseDelta => _mouseDelta;
+#endif
///
public bool UseSnapping => Root?.GetKey(KeyboardKeys.Control) ?? false;
diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs
index 1e7d2295a..a996fa628 100644
--- a/Source/Editor/Viewport/EditorViewport.cs
+++ b/Source/Editor/Viewport/EditorViewport.cs
@@ -4,6 +4,7 @@ using System;
using System.Linq;
using FlaxEditor.Content.Settings;
using FlaxEditor.GUI.ContextMenu;
+using FlaxEditor.GUI.Docking;
using FlaxEditor.GUI.Input;
using FlaxEditor.Options;
using FlaxEditor.Viewport.Cameras;
@@ -158,18 +159,22 @@ namespace FlaxEditor.Viewport
private float _movementSpeed;
private float _minMovementSpeed;
private float _maxMovementSpeed;
+#if !PLATFORM_SDL
private float _mouseAccelerationScale;
private bool _useMouseFiltering;
private bool _useMouseAcceleration;
+#endif
// Input
internal bool _disableInputUpdate;
private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown;
- private int _deltaFilteringStep;
private Float2 _startPos;
+#if !PLATFORM_SDL
private Float2 _mouseDeltaLast;
+ private int _deltaFilteringStep;
private Float2[] _deltaFilteringBuffer = new Float2[FpsCameraFilteringFrames];
+#endif
///
/// The previous input (from the previous update).
@@ -522,10 +527,11 @@ namespace FlaxEditor.Viewport
: base(task)
{
_editor = Editor.Instance;
-
+#if !PLATFORM_SDL
_mouseAccelerationScale = 0.1f;
_useMouseFiltering = false;
_useMouseAcceleration = false;
+#endif
_camera = camera;
if (_camera != null)
_camera.Viewport = this;
@@ -1530,7 +1536,9 @@ namespace FlaxEditor.Viewport
// Hide cursor and start tracking mouse movement
win.StartTrackingMouse(false);
win.Cursor = CursorType.Hidden;
+ win.MouseMoveRelative += OnMouseMoveRelative;
+#if !PLATFORM_SDL
// Center mouse position if it's too close to the edge
var size = Size;
var center = Float2.Round(size * 0.5f);
@@ -1539,6 +1547,7 @@ namespace FlaxEditor.Viewport
_viewMousePos = center;
win.MousePosition = PointToWindow(_viewMousePos);
}
+#endif
}
///
@@ -1550,6 +1559,7 @@ namespace FlaxEditor.Viewport
// Restore cursor and stop tracking mouse movement
win.Cursor = CursorType.Default;
win.EndTrackingMouse();
+ win.MouseMoveRelative -= OnMouseMoveRelative;
}
///
@@ -1631,18 +1641,15 @@ namespace FlaxEditor.Viewport
// Get parent window
var win = (WindowRootControl)Root;
-
- // Get current mouse position in the view
+ if (win.IsFocused)
{
- // When the window is not focused, the position in window does not return sane values
- Float2 pos = PointFromWindow(win.MousePosition);
- if (!float.IsInfinity(pos.LengthSquared))
- _viewMousePos = pos;
+ // Get current mouse position in the view
+ _viewMousePos = PointFromWindow(win.MousePosition);
}
// Update input
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)
var isViewportControllingMouse = canUseInput && IsControllingMouse;
@@ -1654,9 +1661,17 @@ namespace FlaxEditor.Viewport
else
EndMouseCapture();
}
- bool useMouse = IsControllingMouse || (Mathf.IsInRange(_viewMousePos.X, 0, Width) && Mathf.IsInRange(_viewMousePos.Y, 0, Height));
+
_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));
+ 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)
_input.Gather(win.Window, useMouse, ref _prevInput);
else
@@ -1769,6 +1784,10 @@ namespace FlaxEditor.Viewport
if (_input.IsControlDown)
moveDelta *= 0.3f;
+#if PLATFORM_SDL
+ var mouseDelta = _mouseDelta;
+ _mouseDelta = Float2.Zero;
+#else
// Calculate smooth mouse delta not dependant on viewport size
var offset = _viewMousePos - _startPos;
if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown && !_isOrtho && !rmbWheel && !_isVirtualMouseRightDown)
@@ -1810,6 +1829,7 @@ namespace FlaxEditor.Viewport
mouseDelta += _mouseDeltaLast * _mouseAccelerationScale;
_mouseDeltaLast = currentDelta;
}
+#endif
// Update
moveDelta *= dt * (60.0f * 4.0f);
@@ -1818,12 +1838,14 @@ namespace FlaxEditor.Viewport
mouseDelta *= new Float2(1, -1);
UpdateView(dt, ref moveDelta, ref mouseDelta, out var centerMouse);
+#if !PLATFORM_SDL
// Move mouse back to the root position
if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown || _isVirtualMouseRightDown))
{
var center = PointToWindow(_startPos);
win.MousePosition = center;
}
+#endif
// Change Ortho size on mouse scroll
if (_isOrtho && !rmbWheel)
@@ -1835,6 +1857,8 @@ namespace FlaxEditor.Viewport
}
else
{
+#if PLATFORM_SDL
+#else
if (_input.IsMouseLeftDown || _input.IsMouseRightDown || _isVirtualMouseRightDown)
{
// Calculate smooth mouse delta not dependant on viewport size
@@ -1849,6 +1873,7 @@ namespace FlaxEditor.Viewport
_mouseDelta = Float2.Zero;
}
_mouseDeltaLast = Float2.Zero;
+#endif
if (ContainsFocus)
{
@@ -1898,6 +1923,11 @@ namespace FlaxEditor.Viewport
_input.MouseWheelDelta = 0;
}
+ private void OnMouseMoveRelative(ref Float2 motion)
+ {
+ _mouseDelta += motion;
+ }
+
///
public override bool OnMouseDown(Float2 location, MouseButton button)
{
@@ -1963,7 +1993,8 @@ namespace FlaxEditor.Viewport
if (_isControllingMouse)
{
- OnControlMouseEnd(RootWindow.Window);
+ if (RootWindow?.Window != null)
+ OnControlMouseEnd(RootWindow.Window);
_isControllingMouse = false;
_isVirtualMouseRightDown = false;
}
diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
index 3d366809c..1b3078051 100644
--- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
@@ -662,16 +662,25 @@ namespace FlaxEditor.Viewport
// Don't allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled
bool canStart = !(IsControllingMouse || IsRightMouseButtonDown || IsAltKeyDown) &&
- Gizmos?.Active is TransformGizmo && !Gizmos.Active.IsControllingMouse;
+ Gizmos?.Active is TransformGizmo;
_rubberBandSelector.TryCreateRubberBand(canStart, _viewMousePos);
}
+ ///
+ protected override void OnControlMouseBegin(Window win)
+ {
+ _rubberBandSelector.ReleaseRubberBandSelection();
+
+ base.OnControlMouseBegin(win);
+ }
+
///
protected override void OnLeftMouseButtonDown()
{
base.OnLeftMouseButtonDown();
- _rubberBandSelector.TryStartingRubberBandSelection(_viewMousePos);
+ if (!IsAltKeyDown)
+ _rubberBandSelector.TryStartingRubberBandSelection(_viewMousePos);
}
///
diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs
index 78b06f0bc..ec5db8301 100644
--- a/Source/Editor/Windows/AboutDialog.cs
+++ b/Source/Editor/Windows/AboutDialog.cs
@@ -130,6 +130,9 @@ namespace FlaxEditor.Windows
"Mono Project - www.mono-project.com",
#if USE_NETCORE
".NET - www.dotnet.microsoft.com",
+#endif
+#if PLATFORM_SDL
+ "Simple DirectMedia Layer - www.libsdl.org",
#endif
"FreeType Project - www.freetype.org",
"Assimp - www.assimp.sourceforge.net",
diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs
index 872e78509..5f9ad71fd 100644
--- a/Source/Editor/Windows/GameWindow.cs
+++ b/Source/Editor/Windows/GameWindow.cs
@@ -874,7 +874,7 @@ namespace FlaxEditor.Windows
_cursorVisible = Screen.CursorVisible;
_cursorLockMode = Screen.CursorLock;
Screen.CursorVisible = true;
- if (Screen.CursorLock == CursorLockMode.Clipped)
+ if (Screen.CursorLock == CursorLockMode.Clipped || Screen.CursorLock == CursorLockMode.Locked)
Screen.CursorLock = CursorLockMode.None;
// Defocus
@@ -963,8 +963,11 @@ namespace FlaxEditor.Windows
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
- if (CenterMouseOnFocus)
+ if (CenterMouseOnFocus || forceCenter)
{
var center = PointToWindow(Size * 0.5f);
Root.MousePosition = center;
@@ -989,9 +992,10 @@ namespace FlaxEditor.Windows
_cursorVisible = Screen.CursorVisible;
_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)
Screen.CursorVisible = true;
+ Screen.CursorLock = CursorLockMode.None;
if (Editor.IsPlayMode && IsDocked && IsSelected && RootWindow.FocusedControl == null)
{
diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs
index d9ba67aa3..b88a57fdc 100644
--- a/Source/Editor/Windows/SceneTreeWindow.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.cs
@@ -26,7 +26,6 @@ namespace FlaxEditor.Windows
private Tree _tree;
private Panel _sceneTreePanel;
private bool _isUpdatingSelection;
- private bool _isMouseDown;
private bool _blockSceneTreeScroll = false;
private DragAssets _dragAssets;
@@ -374,10 +373,7 @@ namespace FlaxEditor.Windows
return true;
if (buttons == MouseButton.Right)
- {
- _isMouseDown = true;
return true;
- }
return false;
}
@@ -388,10 +384,8 @@ namespace FlaxEditor.Windows
if (base.OnMouseUp(location, buttons))
return true;
- if (_isMouseDown && buttons == MouseButton.Right)
+ if (buttons == MouseButton.Right)
{
- _isMouseDown = false;
-
if (Editor.StateMachine.CurrentState.CanEditScene)
{
// Show context menu
@@ -416,14 +410,6 @@ namespace FlaxEditor.Windows
return false;
}
- ///
- public override void OnLostFocus()
- {
- _isMouseDown = false;
-
- base.OnLostFocus();
- }
-
///
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp
index 54a1a7418..64202e2d6 100644
--- a/Source/Editor/Windows/SplashScreen.cpp
+++ b/Source/Editor/Windows/SplashScreen.cpp
@@ -29,7 +29,7 @@ const Char* SplashScreenQuotes[] =
#elif PLATFORM_LINUX
TEXT("Try it on a Raspberry"),
TEXT("Trying to exit vim"),
- TEXT("Sudo flax --loadproject"),
+ TEXT("sudo flax --project HelloWorld.flaxproj"),
#elif PLATFORM_MAC
TEXT("don't compare Macbooks to oranges."),
TEXT("Why does macbook heat up?\nBecause it doesn't have windows"),
@@ -105,6 +105,7 @@ const Char* SplashScreenQuotes[] =
TEXT("You have my bow.\nAnd my axe!"),
TEXT("To the bridge of Khazad-dum."),
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("We could be compiling shaders here"),
TEXT("Hello There"),
@@ -166,7 +167,7 @@ void SplashScreen::Show()
settings.AllowMaximize = false;
settings.AllowDragAndDrop = false;
settings.IsTopmost = false;
- settings.IsRegularWindow = false;
+ settings.Type = WindowType::Utility;
settings.HasSizingFrame = false;
settings.ShowAfterFirstPaint = true;
settings.StartPosition = WindowStartPosition::CenterScreen;
diff --git a/Source/Engine/Engine/CommandLine.cpp b/Source/Engine/Engine/CommandLine.cpp
index e0f9441eb..f8823d961 100644
--- a/Source/Engine/Engine/CommandLine.cpp
+++ b/Source/Engine/Engine/CommandLine.cpp
@@ -3,6 +3,7 @@
#include "CommandLine.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Utilities.h"
+#include "Engine/Core/Types/StringView.h"
#include
CommandLine::OptionsData CommandLine::Options;
@@ -76,6 +77,8 @@ bool CommandLine::Parse(const Char* cmdLine)
Char* argStart;
Char* argEnd;
int32 len;
+ (void)argStart;
+ (void)argEnd;
#define PARSE_BOOL_SWITCH(text, field) \
pos = (Char*)StringUtils::FindIgnoreCase(buffer.Get(), TEXT(text)); \
@@ -145,6 +148,12 @@ bool CommandLine::Parse(const Char* cmdLine)
PARSE_BOOL_SWITCH("-monolog ", MonoLog);
PARSE_BOOL_SWITCH("-mute ", Mute);
PARSE_BOOL_SWITCH("-lowdpi ", LowDPI);
+
+#if PLATFORM_LINUX && PLATFORM_SDL
+ PARSE_BOOL_SWITCH("-wayland ", Wayland);
+ PARSE_BOOL_SWITCH("-x11 ", X11);
+#endif
+
#if USE_EDITOR
PARSE_BOOL_SWITCH("-clearcache ", ClearCache);
PARSE_BOOL_SWITCH("-clearcooker ", ClearCookerCache);
@@ -163,3 +172,63 @@ bool CommandLine::Parse(const Char* cmdLine)
return false;
}
+
+bool CommandLine::ParseArguments(const StringView& cmdLine, Array& arguments)
+{
+ int32 start = 0;
+ int32 quotesStart = -1;
+ int32 length = cmdLine.Length();
+ for (int32 i = 0; i < length; i++)
+ {
+ if (cmdLine[i] == ' ' && quotesStart == -1)
+ {
+ int32 count = i - start;
+ if (count > 0)
+ arguments.Add(StringAnsi(cmdLine.Substring(start, count)));
+ start = i + 1;
+ }
+ else if (cmdLine[i] == '\"')
+ {
+ if (quotesStart >= 0)
+ {
+ if (i + 1 < length && cmdLine[i + 1] != ' ')
+ {
+ // End quotes are in the middle of the current word,
+ // continue until the end of the current word.
+ }
+ else
+ {
+ int32 offset = 1;
+ if (quotesStart == start && cmdLine[start] == '\"')
+ {
+ // Word starts and ends with quotes, only include the quoted content.
+ quotesStart++;
+ offset--;
+ }
+ else if (quotesStart != start)
+ {
+ // Start quotes in the middle of the word, include the whole word.
+ quotesStart = start;
+ }
+
+ int32 count = i - quotesStart + offset;
+ if (count > 0)
+ arguments.Add(StringAnsi(cmdLine.Substring(quotesStart, count)));
+ start = i + 1;
+ }
+ quotesStart = -1;
+ }
+ else
+ {
+ quotesStart = i;
+ }
+ }
+ }
+ const int32 count = length - start;
+ if (count > 0)
+ arguments.Add(StringAnsi(cmdLine.Substring(start, count)));
+ if (quotesStart >= 0)
+ return true; // Missing last closing quote
+
+ return false;
+}
diff --git a/Source/Engine/Engine/CommandLine.h b/Source/Engine/Engine/CommandLine.h
index ad49bee04..1733afc1a 100644
--- a/Source/Engine/Engine/CommandLine.h
+++ b/Source/Engine/Engine/CommandLine.h
@@ -4,6 +4,7 @@
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/Nullable.h"
+#include "Engine/Core/Collections/Array.h"
///
/// Command line options helper.
@@ -127,6 +128,20 @@ public:
///
Nullable LowDPI;
+#if PLATFORM_LINUX && PLATFORM_SDL
+
+ ///
+ /// -wayland (prefer Wayland over X11 as display server)
+ ///
+ Nullable Wayland;
+
+ ///
+ /// -x11 (prefer X11 over Wayland as display server)
+ ///
+ Nullable X11;
+
+#endif
+
#if USE_EDITOR
///
/// -project !path! (Startup project path)
@@ -200,4 +215,12 @@ public:
/// The command line.
/// True if failed, otherwise false.
static bool Parse(const Char* cmdLine);
+
+ ///
+ /// Parses the command line arguments string into string list of arguments.
+ ///
+ /// The command line.
+ /// The parsed arguments
+ /// True if failed, otherwise false.
+ static bool ParseArguments(const StringView& cmdLine, Array& arguments);
};
diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp
index c0f1e06aa..3e1080d4f 100644
--- a/Source/Engine/Engine/Engine.cpp
+++ b/Source/Engine/Engine/Engine.cpp
@@ -101,6 +101,8 @@ int32 Engine::Main(const Char* cmdLine)
CommandLine::Options.Std = true;
#endif
+ Platform::SetHighDpiAwarenessEnabled(!CommandLine::Options.LowDPI.IsTrue());
+
if (Platform::Init())
{
Platform::Fatal(TEXT("Cannot init platform."));
@@ -110,7 +112,6 @@ int32 Engine::Main(const Char* cmdLine)
InitProfilerMemory(cmdLine, 1);
#endif
- Platform::SetHighDpiAwarenessEnabled(!CommandLine::Options.LowDPI.IsTrue());
Time::StartupTime = DateTime::Now();
Globals::StartupFolder = Globals::BinariesFolder = Platform::GetMainDirectory();
#if USE_EDITOR
diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp
index e0c1273f1..d2fb7db3d 100644
--- a/Source/Engine/Engine/Screen.cpp
+++ b/Source/Engine/Engine/Screen.cpp
@@ -6,6 +6,8 @@
#include "Engine/Core/Types/Nullable.h"
#include "Engine/Platform/Window.h"
#include "Engine/Engine/EngineService.h"
+#include "Engine/Input/Input.h"
+#include "Engine/Input/Mouse.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#include "Editor/Managed/ManagedEditor.h"
@@ -13,10 +15,14 @@
#include "Engine/Engine/Engine.h"
#endif
-Nullable Fullscreen;
-Nullable Size;
-bool CursorVisible = true;
-CursorLockMode CursorLock = CursorLockMode::None;
+namespace
+{
+ Nullable Fullscreen;
+ Nullable Size;
+ bool CursorVisible = true;
+ CursorLockMode CursorLock = CursorLockMode::None;
+ bool LastGameViewportFocus = false;
+}
class ScreenService : public EngineService
{
@@ -101,9 +107,9 @@ void Screen::SetCursorVisible(const bool value)
const auto win = Engine::MainWindow;
#endif
if (win && Engine::HasGameViewportFocus())
- {
win->SetCursor(value ? CursorType::Default : CursorType::Hidden);
- }
+ else if (win)
+ win->SetCursor(CursorType::Default);
CursorVisible = value;
}
@@ -116,21 +122,31 @@ void Screen::SetCursorLock(CursorLockMode mode)
{
#if USE_EDITOR
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
const auto win = Engine::MainWindow;
+ Rectangle bounds = win != nullptr ? win->GetClientBounds() : Rectangle();
#endif
- if (win && mode == CursorLockMode::Clipped)
+ if (win)
{
-#if USE_EDITOR
- Rectangle bounds(Editor::Managed->GameViewportToScreen(Float2::Zero), Editor::Managed->GetGameWindowSize());
-#else
- Rectangle bounds = win->GetClientBounds();
-#endif
- win->StartClippingCursor(bounds);
- }
- else if (win && CursorLock == CursorLockMode::Clipped)
- {
- win->EndClippingCursor();
+ bool inRelativeMode = Input::Mouse->IsRelative();
+ if (mode == CursorLockMode::Clipped)
+ win->StartClippingCursor(bounds);
+ else if (mode == CursorLockMode::Locked)
+ {
+ // 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();
+
+ // 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;
}
@@ -190,7 +206,11 @@ void ScreenService::Update()
{
#if USE_EDITOR
// Sync current cursor state in Editor (eg. when viewport focus can change)
- Screen::SetCursorVisible(CursorVisible);
+ const auto win = Editor::Managed->GetGameWindow(true);
+ bool gameViewportFocus = win && Engine::HasGameViewportFocus();
+ if (gameViewportFocus != LastGameViewportFocus)
+ Screen::SetCursorVisible(CursorVisible);
+ LastGameViewportFocus = gameViewportFocus;
#endif
}
diff --git a/Source/Engine/Engine/Time.h b/Source/Engine/Engine/Time.h
index 724f19c58..bda7a7537 100644
--- a/Source/Engine/Engine/Time.h
+++ b/Source/Engine/Engine/Time.h
@@ -16,6 +16,7 @@ API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API Time
friend class Engine;
friend class TimeService;
friend class PhysicsSettings;
+ friend Window;
public:
///
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.cpp b/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.cpp
index 81e638ba4..3d4183be0 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.cpp
@@ -4,6 +4,7 @@
#include "AndroidVulkanPlatform.h"
#include "../RenderToolsVulkan.h"
+#include "Engine/Platform/Window.h"
void AndroidVulkanPlatform::GetInstanceExtensions(Array& extensions, Array& layers)
{
@@ -17,8 +18,10 @@ void AndroidVulkanPlatform::GetDeviceExtensions(Array& extensions,
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);
VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR);
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.h
index d2da83892..30f7f5206 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.h
@@ -17,7 +17,7 @@ class AndroidVulkanPlatform : public VulkanPlatformBase
public:
static void GetInstanceExtensions(Array& extensions, Array& layers);
static void GetDeviceExtensions(Array& extensions, Array& layers);
- static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface);
+ static void CreateSurface(Window* window, GPUDeviceVulkan* device, VkInstance instance, VkSurfaceKHR* surface);
};
typedef AndroidVulkanPlatform VulkanPlatform;
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp
index a62e9023e..1d8625c10 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp
@@ -217,7 +217,7 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height)
ASSERT_LOW_LAYER(_backBuffers.Count() == 0);
// Create platform-dependent surface
- VulkanPlatform::CreateSurface(windowHandle, GPUDeviceVulkan::Instance, &_surface);
+ VulkanPlatform::CreateSurface(_window, _device, GPUDeviceVulkan::Instance, &_surface);
if (_surface == VK_NULL_HANDLE)
{
LOG(Warning, "Failed to create Vulkan surface.");
@@ -356,13 +356,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);
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(backbuffersCount, 3);
+#endif
+
ASSERT(surfProperties.minImageCount <= VULKAN_BACK_BUFFERS_COUNT_MAX);
VkSwapchainCreateInfoKHR swapChainInfo;
RenderToolsVulkan::ZeroStruct(swapChainInfo, VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR);
swapChainInfo.surface = _surface;
swapChainInfo.minImageCount = surfProperties.maxImageCount > 0 // A value of 0 means that there is no limit on the number of image
- ? Math::Min(VULKAN_BACK_BUFFERS_COUNT, surfProperties.maxImageCount)
- : VULKAN_BACK_BUFFERS_COUNT;
+ ? Math::Min(backbuffersCount, surfProperties.maxImageCount)
+ : backbuffersCount;
swapChainInfo.minImageCount = Math::Max(swapChainInfo.minImageCount, surfProperties.minImageCount);
swapChainInfo.minImageCount = Math::Min(swapChainInfo.minImageCount, VULKAN_BACK_BUFFERS_COUNT_MAX);
swapChainInfo.imageFormat = result.format;
@@ -381,7 +390,9 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height)
swapChainInfo.presentMode = presentMode;
swapChainInfo.clipped = VK_TRUE;
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;
// Create swap chain
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.cpp b/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.cpp
index 15ea6ca4e..659c46f32 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.cpp
@@ -4,62 +4,59 @@
#include "LinuxVulkanPlatform.h"
#include "../RenderToolsVulkan.h"
-
-// Contents of vulkan\vulkan_xlib.h inlined here to prevent typename collisions with engine types due to X11 types
+#include "Engine/Platform/Window.h"
#include "Engine/Platform/Linux/IncludeX11.h"
-#ifdef __cplusplus
-extern "C" {
-#endif
-#define VK_KHR_xlib_surface 1
-#define VK_KHR_XLIB_SURFACE_SPEC_VERSION 6
-#define VK_KHR_XLIB_SURFACE_EXTENSION_NAME "VK_KHR_xlib_surface"
-typedef VkFlags VkXlibSurfaceCreateFlagsKHR;
+#define Display X11::Display
+#define Window X11::Window
+#define VisualID X11::VisualID
+#include "vulkan/vulkan_xlib.h"
+#undef Display
+#undef Window
+#undef VisualID
+#include "vulkan/vulkan_wayland.h"
-typedef struct VkXlibSurfaceCreateInfoKHR
-{
- 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);
-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
+// Export extension from volk
extern PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR;
extern PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR;
+extern PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR;
+extern PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR;
void LinuxVulkanPlatform::GetInstanceExtensions(Array& extensions, Array& layers)
{
extensions.Add(VK_KHR_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;
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;
VALIDATE_VULKAN_RESULT(vkCreateXlibSurfaceKHR(instance, &surfaceCreateInfo, nullptr, surface));
+#else
+ SDLWindow* sdlWindow = static_cast(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
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h
index 69b8f040f..1f4c17a07 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h
@@ -19,7 +19,7 @@ class LinuxVulkanPlatform : public VulkanPlatformBase
{
public:
static void GetInstanceExtensions(Array& extensions, Array& layers);
- static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* outSurface);
+ static void CreateSurface(Window* window, GPUDeviceVulkan* device, VkInstance instance, VkSurfaceKHR* outSurface);
};
typedef LinuxVulkanPlatform VulkanPlatform;
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.cpp b/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.cpp
index eb68f9d71..a2cfc07e4 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.cpp
@@ -4,21 +4,40 @@
#include "MacVulkanPlatform.h"
#include "../RenderToolsVulkan.h"
+#include "Engine/Platform/Window.h"
+#include "Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h"
#include
+#include
void MacVulkanPlatform::GetInstanceExtensions(Array& extensions, Array& layers)
{
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;
- VkMacOSSurfaceCreateInfoMVK surfaceCreateInfo;
- RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK);
- surfaceCreateInfo.pView = (void*)window.contentView;
- VALIDATE_VULKAN_RESULT(vkCreateMacOSSurfaceMVK(instance, &surfaceCreateInfo, nullptr, surface));
+ 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;
+ RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK);
+ surfaceCreateInfo.pView = (void*)nswindow.contentView;
+ VALIDATE_VULKAN_RESULT(vkCreateMacOSSurfaceMVK(instance, &surfaceCreateInfo, nullptr, surface));
+ }
}
#endif
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.h
index 05ed07792..a4f7b0a3f 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.h
@@ -18,7 +18,7 @@ class MacVulkanPlatform : public VulkanPlatformBase
{
public:
static void GetInstanceExtensions(Array& extensions, Array& layers);
- static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* outSurface);
+ static void CreateSurface(Window* window, GPUDeviceVulkan* device, VkInstance instance, VkSurfaceKHR* outSurface);
};
typedef MacVulkanPlatform VulkanPlatform;
diff --git a/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatformBase.h b/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatformBase.h
index 2122b3709..3095472ee 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatformBase.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatformBase.h
@@ -17,6 +17,8 @@ enum class VulkanValidationLevel
All = 5,
};
+class GPUDeviceVulkan;
+
///
/// The base implementation for the Vulkan API platform support.
///
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.cpp b/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.cpp
index 2dc1c8455..8b23b4024 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.cpp
@@ -6,6 +6,7 @@
#include "../RenderToolsVulkan.h"
#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Platform/Window.h"
void Win32VulkanPlatform::GetInstanceExtensions(Array& extensions, Array& layers)
{
@@ -13,12 +14,12 @@ void Win32VulkanPlatform::GetInstanceExtensions(Array& extensions,
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)
{
VkWin32SurfaceCreateInfoKHR surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR);
surfaceCreateInfo.hinstance = GetModuleHandle(nullptr);
- surfaceCreateInfo.hwnd = static_cast(windowHandle);
+ surfaceCreateInfo.hwnd = static_cast(window->GetNativePtr());
VALIDATE_VULKAN_RESULT(vkCreateWin32SurfaceKHR(instance, &surfaceCreateInfo, nullptr, surface));
}
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.h
index 08ad578a4..96dbf2e18 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.h
@@ -17,7 +17,7 @@ class Win32VulkanPlatform : public VulkanPlatformBase
{
public:
static void GetInstanceExtensions(Array& extensions, Array& layers);
- static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface);
+ static void CreateSurface(Window* window, GPUDeviceVulkan* device, VkInstance instance, VkSurfaceKHR* surface);
};
typedef Win32VulkanPlatform VulkanPlatform;
diff --git a/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.cpp b/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.cpp
index f5b732f44..54e147214 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.cpp
@@ -5,6 +5,7 @@
#include "iOSVulkanPlatform.h"
#include "../RenderToolsVulkan.h"
#include "Engine/Core/Delegate.h"
+#include "Engine/Platform/Window.h"
#include
void iOSVulkanPlatform::GetInstanceExtensions(Array& extensions, Array& layers)
@@ -13,8 +14,10 @@ void iOSVulkanPlatform::GetInstanceExtensions(Array& extensions, Ar
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
Function func = [&windowHandle, &instance, &surface]()
{
diff --git a/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.h
index 8db71ec6e..d1c5f64ad 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.h
@@ -18,7 +18,7 @@ class iOSVulkanPlatform : public VulkanPlatformBase
{
public:
static void GetInstanceExtensions(Array& extensions, Array& layers);
- static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* outSurface);
+ static void CreateSurface(Window* window, GPUDeviceVulkan* device, VkInstance instance, VkSurfaceKHR* outSurface);
};
typedef iOSVulkanPlatform VulkanPlatform;
diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp
index 7048140ef..6860c3463 100644
--- a/Source/Engine/Input/Input.cpp
+++ b/Source/Engine/Input/Input.cpp
@@ -79,6 +79,7 @@ Delegate Input::MouseUp;
Delegate Input::MouseDoubleClick;
Delegate Input::MouseWheel;
Delegate Input::MouseMove;
+Delegate Input::MouseMoveRelative;
Action Input::MouseLeave;
Delegate Input::GamepadButtonDown;
Delegate Input::GamepadButtonUp;
@@ -217,6 +218,14 @@ void Mouse::OnMouseMove(const Float2& position, Window* target)
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)
{
PROFILE_MEM(Input);
@@ -284,6 +293,11 @@ bool Mouse::Update(EventQueue& queue)
_state.MousePosition = e.MouseData.Position;
break;
}
+ case EventType::MouseMoveRelative:
+ {
+ _state.MousePosition += e.MouseMovementData.PositionRelative;
+ break;
+ }
case EventType::MouseLeave:
{
break;
@@ -924,11 +938,10 @@ void InputService::Update()
WindowsManager::WindowsLocker.Unlock();
// Send input events for the focused window
- WindowsManager::WindowsLocker.Lock();
for (const auto& e : InputEvents)
{
auto window = e.Target ? e.Target : defaultWindow;
- if (!window || !WindowsManager::Windows.Contains(window))
+ if (!window || window->IsClosed())
continue;
switch (e.Type)
{
@@ -958,6 +971,9 @@ void InputService::Update()
case InputDevice::EventType::MouseMove:
window->OnMouseMove(window->ScreenToClient(e.MouseData.Position));
break;
+ case InputDevice::EventType::MouseMoveRelative:
+ window->OnMouseMoveRelative(e.MouseMovementData.PositionRelative);
+ break;
case InputDevice::EventType::MouseLeave:
window->OnMouseLeave();
break;
@@ -973,7 +989,6 @@ void InputService::Update()
break;
}
}
- WindowsManager::WindowsLocker.Unlock();
// Skip if game has no focus to handle the input
if (!Engine::HasGameViewportFocus())
@@ -1014,6 +1029,9 @@ void InputService::Update()
case InputDevice::EventType::MouseMove:
Input::MouseMove(e.MouseData.Position);
break;
+ case InputDevice::EventType::MouseMoveRelative:
+ Input::MouseMoveRelative(e.MouseMovementData.PositionRelative);
+ break;
case InputDevice::EventType::MouseLeave:
Input::MouseLeave();
break;
@@ -1240,6 +1258,7 @@ void InputService::Update()
}
}
+#if !PLATFORM_SDL
// Lock mouse if need to
const auto lockMode = Screen::GetCursorLock();
if (lockMode == CursorLockMode::Locked)
@@ -1248,6 +1267,7 @@ void InputService::Update()
Screen::ScreenToGameViewport(Float2::Zero);
Input::SetMousePosition(pos);
}
+#endif
// Send events for the active actions and axes (send events only in play mode)
if (!Time::GetGamePaused())
diff --git a/Source/Engine/Input/Input.h b/Source/Engine/Input/Input.h
index 73e87f5f0..964a247d9 100644
--- a/Source/Engine/Input/Input.h
+++ b/Source/Engine/Input/Input.h
@@ -108,6 +108,11 @@ public:
///
API_EVENT() static Delegate MouseMove;
+ ///
+ /// Event fired when mouse moves while in relative mode.
+ ///
+ API_EVENT() static Delegate MouseMoveRelative;
+
///
/// Event fired when mouse leaves window.
///
diff --git a/Source/Engine/Input/InputDevice.h b/Source/Engine/Input/InputDevice.h
index 5d2a383be..80f98fbdd 100644
--- a/Source/Engine/Input/InputDevice.h
+++ b/Source/Engine/Input/InputDevice.h
@@ -25,6 +25,7 @@ public:
MouseDoubleClick,
MouseWheel,
MouseMove,
+ MouseMoveRelative,
MouseLeave,
TouchDown,
TouchMove,
@@ -54,6 +55,11 @@ public:
Float2 Position;
} MouseData;
+ struct
+ {
+ Float2 PositionRelative;
+ } MouseMovementData;
+
struct
{
float WheelDelta;
diff --git a/Source/Engine/Input/Mouse.h b/Source/Engine/Input/Mouse.h
index e5e1b3639..92dad9595 100644
--- a/Source/Engine/Input/Mouse.h
+++ b/Source/Engine/Input/Mouse.h
@@ -46,12 +46,14 @@ public:
protected:
State _state;
State _prevState;
+ bool _relativeMode;
explicit Mouse()
: InputDevice(SpawnParams(Guid::New(), TypeInitializer), TEXT("Mouse"))
{
_state.Clear();
_prevState.Clear();
+ _relativeMode = false;
}
public:
@@ -114,6 +116,16 @@ public:
return !_state.MouseButtons[static_cast(button)] && _prevState.MouseButtons[static_cast(button)];
}
+ ///
+ /// Gets the current state of mouse relative mode.
+ ///
+ /// The window to check against, or null to check for any window.
+ /// True if mouse is in relative mode, otherwise false.
+ API_FUNCTION() virtual bool IsRelative(Window* window = nullptr) const
+ {
+ return _relativeMode;
+ }
+
public:
///
/// Sets the mouse position.
@@ -121,6 +133,17 @@ public:
/// The new position.
virtual void SetMousePosition(const Float2& newPosition) = 0;
+ ///
+ /// Sets the mouse relative mode state. While enabled, the mouse movement tracking becomes more accurate.
+ /// The cursor will be hidden while in relative mode.
+ ///
+ /// The new relative mode state.
+ /// The window.
+ virtual void SetRelativeMode(bool relativeMode, Window* window)
+ {
+ _relativeMode = relativeMode;
+ }
+
///
/// Called when mouse cursor gets moved by the application. Invalidates the previous cached mouse position to prevent mouse jitter when locking the cursor programmatically.
///
@@ -158,6 +181,13 @@ public:
/// The target window to receive this event, otherwise input system will pick the window automatically.
void OnMouseMove(const Float2& position, Window* target = nullptr);
+ ///
+ /// Called when mouse moves in relative mode.
+ ///
+ /// The mouse position change.
+ /// The target window to receive this event, otherwise input system will pick the window automatically.
+ void OnMouseMoveRelative(const Float2& positionRelative, Window* target = nullptr);
+
///
/// Called when mouse leaves the input source area.
///
diff --git a/Source/Engine/Main/Android/main.cpp b/Source/Engine/Main/Android/main.cpp
index 146cd4004..495833a3e 100644
--- a/Source/Engine/Main/Android/main.cpp
+++ b/Source/Engine/Main/Android/main.cpp
@@ -5,7 +5,6 @@
#include "Engine/Engine/Engine.h"
#include "Engine/Platform/Platform.h"
#include "android_native_app_glue.h"
-#include "android_native_app_glue.c"
void android_main(android_app* app)
{
diff --git a/Source/Engine/Platform/Android/AndroidPlatform.h b/Source/Engine/Platform/Android/AndroidPlatform.h
index 6153fa951..a1491fb06 100644
--- a/Source/Engine/Platform/Android/AndroidPlatform.h
+++ b/Source/Engine/Platform/Android/AndroidPlatform.h
@@ -12,15 +12,20 @@ struct android_app;
///
/// The Android platform implementation and application management utilities.
///
+API_CLASS(Static, Tag="NoTypeInitializer")
class FLAXENGINE_API AndroidPlatform : public UnixPlatform
{
public:
static android_app* GetApp();
- static String GetAppPackageName();
- static String GetDeviceManufacturer();
- static String GetDeviceModel();
- static String GetDeviceBuildNumber();
+ // Gets 'getPackageName()' value.
+ API_PROPERTY() static String GetAppPackageName();
+ // Gets 'android.os.Build.MANUFACTURER' value.
+ API_PROPERTY() static String GetDeviceManufacturer();
+ // Gets 'android.os.Build.MODEL' value.
+ API_PROPERTY() static String GetDeviceModel();
+ // Gets 'android.os.Build.DISPLAY' value.
+ API_PROPERTY() static String GetDeviceBuildNumber();
static void PreInit(android_app* app);
public:
diff --git a/Source/Engine/Platform/Base/Enums.h b/Source/Engine/Platform/Base/Enums.h
new file mode 100644
index 000000000..4a903ad19
--- /dev/null
+++ b/Source/Engine/Platform/Base/Enums.h
@@ -0,0 +1,251 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Core/Config.h"
+
+///
+/// Window closing reasons.
+///
+API_ENUM() enum class ClosingReason
+{
+ ///
+ /// The unknown.
+ ///
+ Unknown = 0,
+
+ ///
+ /// The user.
+ ///
+ User,
+
+ ///
+ /// The engine exit.
+ ///
+ EngineExit,
+
+ ///
+ /// The close event.
+ ///
+ CloseEvent,
+};
+
+///
+/// Types of default cursors.
+///
+API_ENUM() enum class CursorType
+{
+ ///
+ /// The default.
+ ///
+ Default = 0,
+
+ ///
+ /// The cross.
+ ///
+ Cross,
+
+ ///
+ /// The hand.
+ ///
+ Hand,
+
+ ///
+ /// The help icon
+ ///
+ Help,
+
+ ///
+ /// The I beam.
+ ///
+ IBeam,
+
+ ///
+ /// The blocking image.
+ ///
+ No,
+
+ ///
+ /// The wait.
+ ///
+ Wait,
+
+ ///
+ /// The size all sides.
+ ///
+ SizeAll,
+
+ ///
+ /// The size NE-SW.
+ ///
+ SizeNESW,
+
+ ///
+ /// The size NS.
+ ///
+ SizeNS,
+
+ ///
+ /// The size NW-SE.
+ ///
+ SizeNWSE,
+
+ ///
+ /// The size WE.
+ ///
+ SizeWE,
+
+ ///
+ /// The cursor is hidden.
+ ///
+ Hidden,
+
+ MAX
+};
+
+///
+/// Data drag and drop effects.
+///
+API_ENUM() enum class DragDropEffect
+{
+ ///
+ /// The none.
+ ///
+ None = 0,
+
+ ///
+ /// The copy.
+ ///
+ Copy,
+
+ ///
+ /// The move.
+ ///
+ Move,
+
+ ///
+ /// The link.
+ ///
+ Link,
+};
+
+///
+/// Window hit test codes. Note: they are 1:1 mapping for Win32 values.
+///
+API_ENUM() enum class WindowHitCodes
+{
+ ///
+ /// The transparent area.
+ ///
+ Transparent = -1,
+
+ ///
+ /// The no hit.
+ ///
+ NoWhere = 0,
+
+ ///
+ /// The client area.
+ ///
+ Client = 1,
+
+ ///
+ /// The caption area.
+ ///
+ Caption = 2,
+
+ ///
+ /// The system menu.
+ ///
+ SystemMenu = 3,
+
+ ///
+ /// The grow box
+ ///
+ GrowBox = 4,
+
+ ///
+ /// The menu.
+ ///
+ Menu = 5,
+
+ ///
+ /// The horizontal scroll.
+ ///
+ HScroll = 6,
+
+ ///
+ /// The vertical scroll.
+ ///
+ VScroll = 7,
+
+ ///
+ /// The minimize button.
+ ///
+ MinButton = 8,
+
+ ///
+ /// The maximize button.
+ ///
+ MaxButton = 9,
+
+ ///
+ /// The left side;
+ ///
+ Left = 10,
+
+ ///
+ /// The right side.
+ ///
+ Right = 11,
+
+ ///
+ /// The top side.
+ ///
+ Top = 12,
+
+ ///
+ /// The top left corner.
+ ///
+ TopLeft = 13,
+
+ ///
+ /// The top right corner.
+ ///
+ TopRight = 14,
+
+ ///
+ /// The bottom side.
+ ///
+ Bottom = 15,
+
+ ///
+ /// The bottom left corner.
+ ///
+ BottomLeft = 16,
+
+ ///
+ /// The bottom right corner.
+ ///
+ BottomRight = 17,
+
+ ///
+ /// The border.
+ ///
+ Border = 18,
+
+ ///
+ /// The object.
+ ///
+ Object = 19,
+
+ ///
+ /// The close button.
+ ///
+ Close = 20,
+
+ ///
+ /// The help button.
+ ///
+ Help = 21,
+};
diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp
index 4512a59b3..29d13a2b0 100644
--- a/Source/Engine/Platform/Base/PlatformBase.cpp
+++ b/Source/Engine/Platform/Base/PlatformBase.cpp
@@ -46,6 +46,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 % 4 == 0, "Threads limit must be multiple of 4.");
+const Char* PlatformBase::ApplicationClassName = TEXT("FlaxWindow");
float PlatformBase::CustomDpiScale = 1.0f;
Array> PlatformBase::Users;
Delegate PlatformBase::UserAdded;
@@ -773,6 +774,26 @@ void PlatformBase::CollectCrashData(const String& crashDataFolder, void* context
{
}
+#if USE_EDITOR
+
+#include "Engine/Core/Math/Color32.h"
+
+Delegate PlatformBase::PickScreenColorDone;
+
+Color32 PlatformBase::GetScreenColorAt(const Float2& pos)
+{
+ // No supported
+ return Color32::Transparent;
+}
+
+void PlatformBase::PickScreenColor()
+{
+ // Just return transparent color when not implemented/supported
+ PickScreenColorDone(Color32::Transparent);
+}
+
+#endif
+
const Char* ToString(PlatformType type)
{
switch (type)
diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h
index 44a0c8415..535c825a1 100644
--- a/Source/Engine/Platform/Base/PlatformBase.h
+++ b/Source/Engine/Platform/Base/PlatformBase.h
@@ -190,6 +190,13 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(PlatformBase);
///
static void Exit();
+public:
+
+ ///
+ /// Application windows class name.
+ ///
+ static const Char* ApplicationClassName;
+
public:
///
/// Copy memory region
@@ -866,6 +873,26 @@ public:
// Crash dump data handling
static void CollectCrashData(const String& crashDataFolder, void* context = nullptr);
+
+public:
+#if USE_EDITOR
+ ///
+ /// Gets the pixel color at the specified coordinates.
+ ///
+ /// Screen-space coordinate to read.
+ /// Pixel color at the specified coordinates, or transparent color when color couldn't be picked up.
+ API_FUNCTION() static Color32 GetScreenColorAt(const Float2& pos);
+
+ ///
+ /// Starts async color picking. Color will be returned through PickColorDone event when the action ends (user selected the final color with a mouse). When action is active, GetColorAt can be used to read the current value.
+ ///
+ API_FUNCTION() static void PickScreenColor();
+
+ ///
+ /// Called when PickColor action is finished.
+ ///
+ API_EVENT() static Delegate PickScreenColorDone;
+#endif
};
extern FLAXENGINE_API const Char* ToString(PlatformType type);
diff --git a/Source/Engine/Platform/Base/WindowBase.cpp b/Source/Engine/Platform/Base/WindowBase.cpp
index 3bd34a1a0..e76aa4066 100644
--- a/Source/Engine/Platform/Base/WindowBase.cpp
+++ b/Source/Engine/Platform/Base/WindowBase.cpp
@@ -1,13 +1,14 @@
// Copyright (c) Wojciech Figat. All rights reserved.
+#include "Engine/Core/Math/Color32.h"
#include "Engine/Platform/Window.h"
+#include "Engine/Platform/WindowsManager.h"
+#include "Engine/Platform/IGuiData.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Graphics/RenderTask.h"
-#include "Engine/Platform/WindowsManager.h"
#include "Engine/Graphics/GPUSwapChain.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Input/Input.h"
-#include "Engine/Platform/IGuiData.h"
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
@@ -107,7 +108,7 @@ WindowBase::WindowBase(const CreateWindowSettings& settings)
if (settings.StartPosition == WindowStartPosition::CenterParent
|| 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)
parentBounds = settings.Parent->GetClientBounds();
@@ -165,6 +166,15 @@ void WindowBase::SetIsVisible(bool isVisible)
}
}
+bool WindowBase::IsAlwaysOnTop() const
+{
+ return false;
+}
+
+void WindowBase::SetIsAlwaysOnTop(bool isAlwaysOnTop)
+{
+}
+
String WindowBase::ToString() const
{
return GetTitle();
@@ -266,6 +276,13 @@ void WindowBase::OnMouseMove(const Float2& 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()
{
PROFILE_CPU_NAMED("GUI.OnMouseLeave");
@@ -583,6 +600,11 @@ void WindowBase::Close(ClosingReason reason)
OnClosed();
}
+bool WindowBase::IsClosed() const
+{
+ return _isClosing || EnumHasAnyFlags(Flags, ObjectFlags::WasMarkedToDelete);
+}
+
bool WindowBase::IsForegroundWindow() const
{
return _focused;
diff --git a/Source/Engine/Platform/Base/WindowBase.h b/Source/Engine/Platform/Base/WindowBase.h
index b1e11f620..f11abb398 100644
--- a/Source/Engine/Platform/Base/WindowBase.h
+++ b/Source/Engine/Platform/Base/WindowBase.h
@@ -8,6 +8,7 @@
#include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Input/KeyboardKeys.h"
#include "Engine/Input/Enums.h"
+#include "Enums.h"
class Input;
class Engine;
@@ -17,252 +18,6 @@ class GPUSwapChain;
class TextureData;
class IGuiData;
-///
-/// Window closing reasons.
-///
-API_ENUM() enum class ClosingReason
-{
- ///
- /// The unknown.
- ///
- Unknown = 0,
-
- ///
- /// The user.
- ///
- User,
-
- ///
- /// The engine exit.
- ///
- EngineExit,
-
- ///
- /// The close event.
- ///
- CloseEvent,
-};
-
-///
-/// Types of default cursors.
-///
-API_ENUM() enum class CursorType
-{
- ///
- /// The default.
- ///
- Default = 0,
-
- ///
- /// The cross.
- ///
- Cross,
-
- ///
- /// The hand.
- ///
- Hand,
-
- ///
- /// The help icon
- ///
- Help,
-
- ///
- /// The I beam.
- ///
- IBeam,
-
- ///
- /// The blocking image.
- ///
- No,
-
- ///
- /// The wait.
- ///
- Wait,
-
- ///
- /// The size all sides.
- ///
- SizeAll,
-
- ///
- /// The size NE-SW.
- ///
- SizeNESW,
-
- ///
- /// The size NS.
- ///
- SizeNS,
-
- ///
- /// The size NW-SE.
- ///
- SizeNWSE,
-
- ///
- /// The size WE.
- ///
- SizeWE,
-
- ///
- /// The cursor is hidden.
- ///
- Hidden,
-
- MAX
-};
-
-///
-/// Data drag and drop effects.
-///
-API_ENUM() enum class DragDropEffect
-{
- ///
- /// The none.
- ///
- None = 0,
-
- ///
- /// The copy.
- ///
- Copy,
-
- ///
- /// The move.
- ///
- Move,
-
- ///
- /// The link.
- ///
- Link,
-};
-
-///
-/// Window hit test codes. Note: they are 1:1 mapping for Win32 values.
-///
-API_ENUM() enum class WindowHitCodes
-{
- ///
- /// The transparent area.
- ///
- Transparent = -1,
-
- ///
- /// The no hit.
- ///
- NoWhere = 0,
-
- ///
- /// The client area.
- ///
- Client = 1,
-
- ///
- /// The caption area.
- ///
- Caption = 2,
-
- ///
- /// The system menu.
- ///
- SystemMenu = 3,
-
- ///
- /// The grow box
- ///
- GrowBox = 4,
-
- ///
- /// The menu.
- ///
- Menu = 5,
-
- ///
- /// The horizontal scroll.
- ///
- HScroll = 6,
-
- ///
- /// The vertical scroll.
- ///
- VScroll = 7,
-
- ///
- /// The minimize button.
- ///
- MinButton = 8,
-
- ///
- /// The maximize button.
- ///
- MaxButton = 9,
-
- ///
- /// The left side;
- ///
- Left = 10,
-
- ///
- /// The right side.
- ///
- Right = 11,
-
- ///
- /// The top side.
- ///
- Top = 12,
-
- ///
- /// The top left corner.
- ///
- TopLeft = 13,
-
- ///
- /// The top right corner.
- ///
- TopRight = 14,
-
- ///
- /// The bottom side.
- ///
- Bottom = 15,
-
- ///
- /// The bottom left corner.
- ///
- BottomLeft = 16,
-
- ///
- /// The bottom right corner.
- ///
- BottomRight = 17,
-
- ///
- /// The border.
- ///
- Border = 18,
-
- ///
- /// The object.
- ///
- Object = 19,
-
- ///
- /// The close button.
- ///
- Close = 20,
-
- ///
- /// The help button.
- ///
- Help = 21,
-};
-
API_INJECT_CODE(cpp, "#include \"Engine/Platform/Window.h\"");
///
@@ -290,6 +45,7 @@ protected:
bool _isHorizontalFlippingMouse = false;
bool _isVerticalFlippingMouse = false;
bool _isClippingCursor = false;
+ bool _restoreRelativeMode = false;
explicit WindowBase(const CreateWindowSettings& settings);
virtual ~WindowBase();
@@ -402,6 +158,17 @@ public:
return _maximized;
}
+ ///
+ /// Gets a value that indicates whether a window is always on top of other windows.
+ ///
+ API_PROPERTY() virtual bool IsAlwaysOnTop() const;
+
+ ///
+ /// Sets a value that indicates whether a window is always on top of other windows.
+ ///
+ /// True if always on top.
+ API_PROPERTY() virtual void SetIsAlwaysOnTop(bool isAlwaysOnTop);
+
///
/// Gets the native window handle.
///
@@ -475,10 +242,7 @@ public:
///
/// Checks if window is closed.
///
- API_PROPERTY() virtual bool IsClosed() const
- {
- return _isClosing;
- }
+ API_PROPERTY() virtual bool IsClosed() const;
///
/// Checks if window is foreground (the window with which the user is currently working).
@@ -663,7 +427,7 @@ public:
public:
///
- /// Starts drag and drop operation
+ /// Starts a drag and drop operation.
///
/// The data.
/// The result.
@@ -672,6 +436,18 @@ public:
return DragDropEffect::None;
}
+ ///
+ /// Starts a window drag and drop operation.
+ ///
+ /// The data.
+ /// The offset for positioning the window from cursor.
+ /// The window where dragging started.
+ /// The result.
+ API_FUNCTION() virtual DragDropEffect DoDragDrop(const StringView& data, const Float2& offset, Window* dragSourceWindow)
+ {
+ return DragDropEffect::None;
+ }
+
///
/// Starts the mouse tracking.
///
@@ -742,6 +518,17 @@ public:
{
}
+ ///
+ /// Gets the mouse position in window coordinates.
+ ///
+ API_PROPERTY() virtual Float2 GetMousePosition() const;
+
+ ///
+ /// Sets the mouse position in window coordinates.
+ ///
+ /// Mouse position to set on
+ API_PROPERTY() virtual void SetMousePosition(const Float2& position) const;
+
///
/// Gets the mouse cursor.
///
@@ -837,6 +624,12 @@ public:
MouseDelegate MouseMove;
void OnMouseMove(const Float2& mousePosition);
+ ///
+ /// Event fired when mouse moves in relative mode.
+ ///
+ MouseDelegate MouseMoveRelative;
+ void OnMouseMoveRelative(const Float2& mousePositionRelative);
+
///
/// Event fired when mouse leaves window.
///
@@ -932,16 +725,6 @@ public:
API_FUNCTION() bool GetKeyUp(KeyboardKeys key) const;
public:
- ///
- /// Gets the mouse position in window coordinates.
- ///
- API_PROPERTY() Float2 GetMousePosition() const;
-
- ///
- /// Sets the mouse position in window coordinates.
- ///
- /// Mouse position to set on
- API_PROPERTY() void SetMousePosition(const Float2& position) const;
///
/// Gets the mouse position change during the last frame.
diff --git a/Source/Engine/Platform/Clipboard.h b/Source/Engine/Platform/Clipboard.h
index 0eed3cb5e..c2fb23df0 100644
--- a/Source/Engine/Platform/Clipboard.h
+++ b/Source/Engine/Platform/Clipboard.h
@@ -2,7 +2,9 @@
#pragma once
-#if PLATFORM_WINDOWS
+#if PLATFORM_SDL && PLATFORM_LINUX
+#include "SDL/SDLClipboard.h"
+#elif PLATFORM_WINDOWS
#include "Windows/WindowsClipboard.h"
#elif PLATFORM_LINUX
#include "Linux/LinuxClipboard.h"
diff --git a/Source/Engine/Platform/CreateWindowSettings.cs b/Source/Engine/Platform/CreateWindowSettings.cs
index a866480ff..197771298 100644
--- a/Source/Engine/Platform/CreateWindowSettings.cs
+++ b/Source/Engine/Platform/CreateWindowSettings.cs
@@ -11,7 +11,7 @@ namespace FlaxEngine
{
Position = new Float2(100, 100),
Size = new Float2(640, 480),
- MinimumSize = Float2.One,
+ MinimumSize = new Float2(8, 8),
MaximumSize = Float2.Zero, // Unlimited size
StartPosition = WindowStartPosition.CenterParent,
HasBorder = true,
@@ -21,7 +21,7 @@ namespace FlaxEngine
AllowMinimize = true,
AllowMaximize = true,
AllowDragAndDrop = true,
- IsRegularWindow = true,
+ Type = WindowType.Regular,
HasSizingFrame = true,
ShowAfterFirstPaint = true,
};
diff --git a/Source/Engine/Platform/CreateWindowSettings.h b/Source/Engine/Platform/CreateWindowSettings.h
index ecbf721ae..4285bee1c 100644
--- a/Source/Engine/Platform/CreateWindowSettings.h
+++ b/Source/Engine/Platform/CreateWindowSettings.h
@@ -26,6 +26,32 @@ API_ENUM() enum class WindowStartPosition
Manual,
};
+///
+/// Specifies the type of the window.
+///
+API_ENUM() enum class WindowType
+{
+ ///
+ /// Regular window.
+ ///
+ Regular,
+
+ ///
+ /// Utility window.
+ ///
+ Utility,
+
+ ///
+ /// Tooltip window.
+ ///
+ Tooltip,
+
+ ///
+ /// Popup window.
+ ///
+ Popup,
+};
+
///
/// Settings for new window.
///
@@ -119,9 +145,16 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(CreateWindowSettings);
API_FIELD() bool IsTopmost = false;
///
- /// 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.
+ /// [Deprecated in v1.12]
///
- API_FIELD() bool IsRegularWindow = true;
+ API_FIELD() DEPRECATED("Use Type instead") bool IsRegularWindow = true;
+
+ ///
+ /// 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.
+ ///
+ API_FIELD() WindowType Type = WindowType::Regular;
///
/// Enable/disable window sizing frame.
diff --git a/Source/Engine/Platform/Defines.h b/Source/Engine/Platform/Defines.h
index 9958545fd..d6e817bc6 100644
--- a/Source/Engine/Platform/Defines.h
+++ b/Source/Engine/Platform/Defines.h
@@ -140,6 +140,9 @@ API_ENUM() enum class ArchitectureType
#if !defined(PLATFORM_SWITCH)
#define PLATFORM_SWITCH 0
#endif
+#if !defined(PLATFORM_SDL)
+#define PLATFORM_SDL 0
+#endif
#if PLATFORM_WINDOWS
#include "Windows/WindowsDefines.h"
diff --git a/Source/Engine/Platform/GDK/GDKPlatform.cpp b/Source/Engine/Platform/GDK/GDKPlatform.cpp
index ad9bf07c0..969f24934 100644
--- a/Source/Engine/Platform/GDK/GDKPlatform.cpp
+++ b/Source/Engine/Platform/GDK/GDKPlatform.cpp
@@ -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;
}
-const Char* GDKPlatform::ApplicationWindowClass = TEXT("FlaxWindow");
void* GDKPlatform::Instance = nullptr;
Delegate<> GDKPlatform::Suspended;
Delegate<> GDKPlatform::Resumed;
@@ -317,7 +316,7 @@ void GDKPlatform::PreInit(void* hInstance)
windowsClass.style = CS_HREDRAW | CS_VREDRAW;
windowsClass.lpfnWndProc = WndProc;
windowsClass.hInstance = (HINSTANCE)Instance;
- windowsClass.lpszClassName = ApplicationWindowClass;
+ windowsClass.lpszClassName = ApplicationClassName;
if (!RegisterClassW(&windowsClass))
{
Error(TEXT("Window class registration failed!"));
@@ -477,7 +476,7 @@ void GDKPlatform::Exit()
if (PlmSignalResume)
CloseHandle(PlmSignalResume);
- UnregisterClassW(ApplicationWindowClass, nullptr);
+ UnregisterClassW(ApplicationClassName, nullptr);
XGameRuntimeUninitialize();
}
diff --git a/Source/Engine/Platform/GDK/GDKPlatform.h b/Source/Engine/Platform/GDK/GDKPlatform.h
index 24bdd8dc9..e2c23dbf3 100644
--- a/Source/Engine/Platform/GDK/GDKPlatform.h
+++ b/Source/Engine/Platform/GDK/GDKPlatform.h
@@ -9,15 +9,11 @@
///
/// The GDK platform implementation and application management utilities.
///
+API_CLASS(Static, Tag="NoTypeInitializer")
class FLAXENGINE_API GDKPlatform : public Win32Platform
{
public:
- ///
- /// Win32 application windows class name.
- ///
- static const Char* ApplicationWindowClass;
-
///
/// Handle to Win32 application instance.
///
@@ -42,10 +38,13 @@ public:
/// The Win32 application instance.
static void PreInit(void* hInstance);
- static bool IsRunningOnDevKit();
-
- static void SignInSilently();
- static void SignInWithUI();
+ // True, if game is running Xbox Devkit.
+ API_PROPERTY() static bool IsRunningOnDevKit();
+ // Signs in user without showing UI. If user is not signed in, it will fail and return false. Use SignInWithUI to show UI and let user sign in.
+ API_FUNCTION() static void SignInSilently();
+ // Signs in user with showing UI. If user is already signed in, it will succeed and return true. If user is not signed in, it will show UI and let user sign in.
+ API_FUNCTION() static void SignInWithUI();
+ // Searches for a user with a specific local ID.
static User* FindUser(const struct XUserLocalId& id);
public:
diff --git a/Source/Engine/Platform/GDK/GDKWindow.cpp b/Source/Engine/Platform/GDK/GDKWindow.cpp
index a82fb4aba..c8be2df3f 100644
--- a/Source/Engine/Platform/GDK/GDKWindow.cpp
+++ b/Source/Engine/Platform/GDK/GDKWindow.cpp
@@ -71,7 +71,7 @@ GDKWindow::GDKWindow(const CreateWindowSettings& settings)
// Creating the window
_handle = CreateWindowExW(
exStyle,
- Platform::ApplicationWindowClass,
+ Platform::ApplicationClassName,
settings.Title.GetText(),
style,
x,
diff --git a/Source/Engine/Platform/Linux/IncludeX11.h b/Source/Engine/Platform/Linux/IncludeX11.h
index a2b0f6e7c..f84c8039b 100644
--- a/Source/Engine/Platform/Linux/IncludeX11.h
+++ b/Source/Engine/Platform/Linux/IncludeX11.h
@@ -21,6 +21,7 @@ namespace X11
#include
#include
#include
+#include
}
// Helper macros
diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp
index 98e0cbbfc..d3358a2e7 100644
--- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp
+++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp
@@ -97,7 +97,9 @@ X11::Cursor Cursors[(int32)CursorType::MAX];
X11::XcursorImage* CursorsImg[(int32)CursorType::MAX];
Dictionary KeyNameMap;
Array KeyCodeMap;
-Delegate LinuxPlatform::xEventRecieved;
+#if !PLATFORM_SDL
+Delegate LinuxPlatform::xEventReceived;
+#endif
Window* MouseTrackingWindow = nullptr;
// Message boxes configuration
@@ -655,7 +657,11 @@ static int X11_MessageBoxLoop(MessageBoxData* data)
return 0;
}
+#if !PLATFORM_SDL
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())
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;
}
+#if !PLATFORM_SDL
+
int X11ErrorHandler(X11::Display* display, X11::XErrorEvent* event)
{
if (event->error_code == 5)
@@ -852,6 +860,8 @@ int X11ErrorHandler(X11::Display* display, X11::XErrorEvent* event)
return 0;
}
+#endif
+
int32 CalculateDpi()
{
int dpi = 96;
@@ -1207,17 +1217,20 @@ public:
}
};
+#if !PLATFORM_SDL
struct Property
{
- unsigned char* data;
- int format, nitems;
- X11::Atom type;
+ unsigned char* data;
+ int format, nitems;
+ X11::Atom type;
};
+#endif
namespace Impl
{
LinuxKeyboard* Keyboard;
LinuxMouse* Mouse;
+#if !PLATFORM_SDL
StringAnsi ClipboardText;
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);
return FindAppWindow(display, child);
}
+#endif
Dictionary LoadConfigFile(StringView path)
{
@@ -1361,6 +1375,7 @@ namespace Impl
}
}
+#if !PLATFORM_SDL
class LinuxDropFilesData : public IGuiData
{
public:
@@ -1398,7 +1413,7 @@ public:
}
};
-DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
+DragDropEffect Window::DoDragDrop(const StringView& data)
{
if (CommandLine::Options.Headless.IsTrue())
return DragDropEffect::None;
@@ -1412,13 +1427,14 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
StringAnsi dataAnsi(data);
LinuxDropTextData dropData;
dropData.Text = data;
+ unsigned long mainWindow = _window;
// Begin dragging
auto screen = X11::XDefaultScreen(xDisplay);
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;
- X11::XSetSelectionOwner(xDisplay, xAtomXdndSelection, _window, CurrentTime);
+ X11::XSetSelectionOwner(xDisplay, xAtomXdndSelection, mainWindow, CurrentTime);
// Process events
X11::XEvent event;
@@ -1526,7 +1542,7 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
m.window = previousWindow;
m.message_type = xAtomXdndLeave;
m.format = 32;
- m.data.l[0] = _window;
+ m.data.l[0] = mainWindow;
m.data.l[1] = 0;
m.data.l[2] = 0;
m.data.l[3] = 0;
@@ -1555,7 +1571,7 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
m.window = window;
m.message_type = xAtomXdndEnter;
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[2] = formats.Count() > 0 ? formats[0] : 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.message_type = xAtomXdndPosition;
m.format = 32;
- m.data.l[0] = _window;
+ m.data.l[0] = mainWindow;
m.data.l[1] = 0;
m.data.l[2] = (x << 16) | y;
m.data.l[3] = CurrentTime;
@@ -1633,7 +1649,7 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
m.window = previousWindow;
m.message_type = xAtomXdndDrop;
m.format = 32;
- m.data.l[0] = _window;
+ m.data.l[0] = mainWindow;
m.data.l[1] = 0;
m.data.l[2] = CurrentTime;
m.data.l[3] = 0;
@@ -1680,7 +1696,7 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
m.window = previousWindow;
m.message_type = xAtomXdndLeave;
m.format = 32;
- m.data.l[0] = _window;
+ m.data.l[0] = mainWindow;
m.data.l[1] = 0;
m.data.l[2] = 0;
m.data.l[3] = 0;
@@ -1706,7 +1722,7 @@ void LinuxClipboard::SetText(const StringView& text)
{
if (CommandLine::Options.Headless.IsTrue())
return;
- auto mainWindow = (LinuxWindow*)Engine::MainWindow;
+ auto mainWindow = Engine::MainWindow;
if (!mainWindow)
return;
X11::Window window = (X11::Window)mainWindow->GetNativePtr();
@@ -1729,7 +1745,7 @@ String LinuxClipboard::GetText()
if (CommandLine::Options.Headless.IsTrue())
return String::Empty;
String result;
- auto mainWindow = (LinuxWindow*)Engine::MainWindow;
+ auto mainWindow = Engine::MainWindow;
if (!mainWindow)
return result;
X11::Window window = (X11::Window)mainWindow->GetNativePtr();
@@ -1758,9 +1774,11 @@ Array LinuxClipboard::GetFiles()
return Array();
}
+#endif
+
void* LinuxPlatform::GetXDisplay()
{
- return xDisplay;
+ return xDisplay;
}
bool LinuxPlatform::CreateMutex(const Char* name)
@@ -1783,6 +1801,11 @@ const String& LinuxPlatform::GetHomeDirectory()
return HomeDir;
}
+String LinuxPlatform::GetDisplayServer()
+{
+ return xDisplay ? TEXT("X11") : TEXT("");
+}
+
bool LinuxPlatform::Is64BitPlatform()
{
#ifdef PLATFORM_64BITS
@@ -2095,6 +2118,7 @@ bool LinuxPlatform::Init()
UnixGetMacAddress(MacAddress);
+#if !PLATFORM_SDL
// Get user locale string
setlocale(LC_ALL, "");
const char* locale = setlocale(LC_CTYPE, NULL);
@@ -2104,6 +2128,7 @@ bool LinuxPlatform::Init()
UserLocale.Replace('_', '-');
if (UserLocale == TEXT("C"))
UserLocale = TEXT("en");
+#endif
// Get computer name string
gethostname(buffer, UNIX_APP_BUFF_SIZE);
@@ -2147,7 +2172,11 @@ bool LinuxPlatform::Init()
// Skip setup if running in headless mode (X11 might not be available on servers)
if (CommandLine::Options.Headless.IsTrue())
return false;
-
+#if PLATFORM_SDL
+ xDisplay = X11::XOpenDisplay(nullptr);
+#endif
+
+#if !PLATFORM_SDL
X11::XInitThreads();
xDisplay = X11::XOpenDisplay(nullptr);
@@ -2286,7 +2315,7 @@ bool LinuxPlatform::Init()
Input::Mouse = Impl::Mouse = New();
Input::Keyboard = Impl::Keyboard = New();
LinuxInput::Init();
-
+#endif
return false;
}
@@ -2296,6 +2325,7 @@ void LinuxPlatform::BeforeRun()
void LinuxPlatform::Tick()
{
+#if !PLATFORM_SDL
UnixPlatform::Tick();
LinuxInput::UpdateState();
@@ -2312,9 +2342,9 @@ void LinuxPlatform::Tick()
continue;
// External event handling
- xEventRecieved(&event);
+ xEventReceived(&event);
- LinuxWindow* window;
+ Window* window;
switch (event.type)
{
case ClientMessage:
@@ -2646,6 +2676,7 @@ void LinuxPlatform::Tick()
}
//X11::XFlush(xDisplay);
+#endif
}
void LinuxPlatform::BeforeExit()
@@ -2654,6 +2685,7 @@ void LinuxPlatform::BeforeExit()
void LinuxPlatform::Exit()
{
+#if !PLATFORM_SDL
for (int32 i = 0; i < (int32)CursorType::MAX; i++)
{
if (Cursors[i])
@@ -2679,6 +2711,7 @@ void LinuxPlatform::Exit()
X11::XCloseDisplay(xDisplay);
xDisplay = nullptr;
}
+#endif
}
String LinuxPlatform::GetSystemName()
@@ -2700,6 +2733,7 @@ Version LinuxPlatform::GetSystemVersion()
return Version(0, 0);
}
+#if !PLATFORM_SDL
int32 LinuxPlatform::GetDpi()
{
return SystemDpi;
@@ -2709,6 +2743,7 @@ String LinuxPlatform::GetUserLocaleName()
{
return UserLocale;
}
+#endif
String LinuxPlatform::GetComputerName()
{
@@ -2916,10 +2951,12 @@ bool LinuxPlatform::SetWorkingDirectory(const String& path)
return chdir(StringAsANSI<>(*path).Get()) != 0;
}
+#if !PLATFORM_SDL
Window* LinuxPlatform::CreateWindow(const CreateWindowSettings& settings)
{
return New(settings);
}
+#endif
extern char **environ;
@@ -2961,6 +2998,7 @@ bool LinuxPlatform::SetEnvironmentVariable(const String& name, const String& val
return setenv(StringAsANSI<>(*name).Get(), StringAsANSI<>(*value).Get(), true) != 0;
}
+#if !PLATFORM_SDL
int32 LinuxPlatform::CreateProcess(CreateProcessSettings& settings)
{
LOG(Info, "Command: {0} {1}", settings.FileName, settings.Arguments);
@@ -3074,6 +3112,7 @@ int32 LinuxPlatform::CreateProcess(CreateProcessSettings& settings)
return returnCode;
}
+#endif
void* LinuxPlatform::LoadLibrary(const Char* filename)
{
@@ -3130,4 +3169,160 @@ Array LinuxPlatform::GetStackFrames(int32 skipCount,
return result;
}
+#if USE_EDITOR
+
+#include "Engine/Core/Math/Vector2.h"
+#include "Engine/Core/Math/Vector4.h"
+#include "Engine/Profiler/ProfilerCPU.h"
+#include "Engine/Platform/Linux/LinuxPlatform.h"
+#include "Engine/Platform/Linux/IncludeX11.h"
+
+#if PLATFORM_SDL
+#include
+#include
+
+namespace PortalImpl
+{
+ XdpPortal* Portal = nullptr;
+ int64 MainLoopReady = 0;
+
+ gpointer GLibMainLoop(gpointer data);
+ void PickColorCallback(GObject* source, GAsyncResult* result, gpointer data);
+}
+#endif
+
+Color32 LinuxPlatform::GetScreenColorAt(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
+ }
+ }
+
+ return Color32::Transparent;
+}
+
+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 = LinuxPlatform::GetScreenColorAt(cursorPos);
+ X11::XUngrabPointer(display, CurrentTime);
+ PlatformBase::PickScreenColorDone(colorPicked);
+ LinuxPlatform::xEventReceived.Unbind(OnScreenUtilsXEventCallback);
+ }
+}
+
+void LinuxPlatform::PickScreenColor()
+{
+ 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);
+ return;
+ }
+#if PLATFORM_SDL
+ if (PortalImpl::MainLoopReady == 0)
+ {
+ // Initialize portal
+ GError* error = nullptr;
+ PortalImpl::Portal = xdp_portal_initable_new(&error);
+ if (error != nullptr)
+ {
+ PortalImpl::MainLoopReady = 2;
+ LOG(Error, "Failed to initialize XDP Portal");
+ return;
+ }
+
+ // Run the GLib main loop in other thread in order to process asynchronous callbacks
+ g_thread_new(nullptr, PortalImpl::GLibMainLoop, nullptr);
+ while (Platform::AtomicRead(&PortalImpl::MainLoopReady) != 1)
+ Platform::Sleep(1);
+ }
+
+ if (PortalImpl::Portal != nullptr)
+ {
+ // Enter color picking mode, the callback receives the final color
+ xdp_portal_pick_color(PortalImpl::Portal, nullptr, nullptr, PortalImpl::PickColorCallback, nullptr);
+ }
+#endif
+}
+
+#if PLATFORM_SDL
+
+gpointer PortalImpl::GLibMainLoop(gpointer data)
+{
+ GMainContext* mainContext = g_main_context_get_thread_default();
+ GMainLoop* mainLoop = g_main_loop_new(mainContext, false);
+
+ Platform::AtomicStore(&PortalImpl::MainLoopReady, 1);
+
+ g_main_loop_run(mainLoop);
+ g_main_loop_unref(mainLoop);
+ return nullptr;
+}
+
+void PortalImpl::PickColorCallback(GObject* source, GAsyncResult* result, gpointer data)
+{
+ GError* error = nullptr;
+ GVariant* variant = xdp_portal_pick_color_finish(PortalImpl::Portal, result, &error);
+ if (error)
+ {
+ if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ LOG(Info, "XDP Portal pick color cancelled");
+ else
+ LOG(Error, "XDP Portal pick color failed: {}", String(error->message));
+ return;
+ }
+
+ // The color is stored in a triple double variant, extract the values
+ Double4 colorDouble;
+ g_variant_get(variant, "(ddd)", &colorDouble.X, &colorDouble.Y, &colorDouble.Z);
+ g_variant_unref(variant);
+ colorDouble.W = 1.0f;
+ Vector4 colorVector = colorDouble;
+ Color32 color = Color32(colorVector);
+
+ PlatformBase::PickScreenColorDone(color);
+}
+#endif
+
+#endif
+
#endif
diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h
index 071566f41..6c857ccf9 100644
--- a/Source/Engine/Platform/Linux/LinuxPlatform.h
+++ b/Source/Engine/Platform/Linux/LinuxPlatform.h
@@ -10,6 +10,7 @@
///
/// The Linux platform implementation and application management utilities.
///
+API_CLASS(Static, Tag="NoTypeInitializer")
class FLAXENGINE_API LinuxPlatform : public UnixPlatform
{
public:
@@ -30,13 +31,17 @@ public:
///
/// Gets the current user home directory.
///
- /// The user home directory.
static const String& GetHomeDirectory();
+ ///
+ /// Returns the display server name on Linux (eg. X11, Wayland).
+ ///
+ API_PROPERTY() static String GetDisplayServer();
+
///
/// An event that is fired when an XEvent is received during platform tick.
///
- static Delegate xEventRecieved;
+ static Delegate xEventReceived;
public:
@@ -124,8 +129,10 @@ public:
static void Tick();
static void BeforeExit();
static void Exit();
+#if !PLATFORM_SDL
static int32 GetDpi();
static String GetUserLocaleName();
+#endif
static String GetComputerName();
static bool GetHasFocus();
static bool CanOpenUrl(const StringView& url);
@@ -140,15 +147,23 @@ public:
static Guid GetUniqueDeviceId();
static String GetWorkingDirectory();
static bool SetWorkingDirectory(const String& path);
+#if !PLATFORM_SDL
static Window* CreateWindow(const CreateWindowSettings& settings);
+#endif
static void GetEnvironmentVariables(Dictionary& result);
static bool GetEnvironmentVariable(const String& name, String& value);
static bool SetEnvironmentVariable(const String& name, const String& value);
+#if !PLATFORM_SDL
static int32 CreateProcess(CreateProcessSettings& settings);
+#endif
static void* LoadLibrary(const Char* filename);
static void FreeLibrary(void* handle);
static void* GetProcAddress(void* handle, const char* symbol);
static Array GetStackFrames(int32 skipCount = 0, int32 maxDepth = 60, void* context = nullptr);
+#if USE_EDITOR
+ static Color32 GetScreenColorAt(const Float2& pos);
+ static void PickScreenColor();
+#endif
};
#endif
diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp
index e7996229b..9e43d778b 100644
--- a/Source/Engine/Platform/Linux/LinuxWindow.cpp
+++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp
@@ -1,6 +1,6 @@
// Copyright (c) Wojciech Figat. All rights reserved.
-#if PLATFORM_LINUX
+#if PLATFORM_LINUX && !PLATFORM_SDL
#include "../Window.h"
#include "Engine/Input/Input.h"
@@ -112,7 +112,7 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
windowAttributes.border_pixel = XBlackPixel(display, screen);
windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask;
- if (!settings.IsRegularWindow)
+ if (settings.Type != WindowType::Regular)
{
windowAttributes.save_under = true;
windowAttributes.override_redirect = true;
@@ -126,7 +126,7 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
*/
unsigned long valueMask = CWBackPixel | CWBorderPixel | CWEventMask | CWColormap;
- if (!settings.IsRegularWindow)
+ if (settings.Type != WindowType::Regular)
{
valueMask |= CWOverrideRedirect | CWSaveUnder;
}
@@ -183,7 +183,7 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
X11::XClassHint* classHint = X11::XAllocClassHint();
if (classHint)
{
- const char* className = settings.IsRegularWindow ? "FlexEditor" : "FlaxPopup";
+ const char* className = settings.Type == WindowType::Regular ? "FlaxEditor" : "FlaxPopup";
classHint->res_name = const_cast(className);
classHint->res_class = const_cast(className);
@@ -234,7 +234,7 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
}
// Adjust type for utility windows
- if (!settings.IsRegularWindow)
+ if (settings.Type != WindowType::Regular)
{
X11::Atom value = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DOCK", 0);
X11::Atom wmType = XInternAtom(display, "_NET_WM_WINDOW_TYPE", 0);
@@ -374,11 +374,6 @@ void LinuxWindow::BringToFront(bool force)
X11::XFlush(display);
}
-bool LinuxWindow::IsClosed() const
-{
- return _isClosing;
-}
-
bool LinuxWindow::IsForegroundWindow() const
{
return _focused || _focusOnMapped;
@@ -631,7 +626,7 @@ void LinuxWindow::OnButtonPress(void* event)
}
// Handle double-click
- if (buttonEvent->button == Button1)
+ if (buttonEvent->button == Button1 && !Input::Mouse->IsRelative())
{
if (
buttonEvent->time < (MouseLastButtonPressTime + MouseDoubleClickTime) &&
diff --git a/Source/Engine/Platform/Linux/LinuxWindow.h b/Source/Engine/Platform/Linux/LinuxWindow.h
index 680b38e42..468e062ad 100644
--- a/Source/Engine/Platform/Linux/LinuxWindow.h
+++ b/Source/Engine/Platform/Linux/LinuxWindow.h
@@ -72,7 +72,6 @@ public:
void Minimize() override;
void Maximize() override;
void Restore() override;
- bool IsClosed() const override;
bool IsForegroundWindow() const override;
void BringToFront(bool force = false) override;
void SetClientBounds(const Rectangle& clientArea) override;
@@ -89,6 +88,7 @@ public:
void Focus() override;
void SetTitle(const StringView& title) override;
DragDropEffect DoDragDrop(const StringView& data) override;
+ using WindowBase::DoDragDrop;
void StartTrackingMouse(bool useMouseScreenOffset) override;
void EndTrackingMouse() override;
void SetCursor(CursorType type) override;
diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp
index 6ba001f20..f8a36d24d 100644
--- a/Source/Engine/Platform/Mac/MacPlatform.cpp
+++ b/Source/Engine/Platform/Mac/MacPlatform.cpp
@@ -276,6 +276,7 @@ bool MacPlatform::Init()
CFRelease(computerName);
}
+#if !PLATFORM_SDL
// Find the maximum scale of the display to handle high-dpi displays scaling factor
{
NSArray* screenArray = [NSScreen screens];
@@ -300,6 +301,7 @@ bool MacPlatform::Init()
Input::Mouse = New();
Input::Keyboard = New();
+#endif
return false;
}
@@ -426,11 +428,16 @@ String MacPlatform::GetMainDirectory()
return path;
}
+#if !PLATFORM_SDL
+
Window* MacPlatform::CreateWindow(const CreateWindowSettings& settings)
{
return New(settings);
}
+#endif
+
+#if !PLATFORM_SDL
int32 MacPlatform::CreateProcess(CreateProcessSettings& settings)
{
LOG(Info, "Command: {0} {1}", settings.FileName, settings.Arguments);
@@ -570,5 +577,6 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings)
return returnCode;
}
+#endif
#endif
diff --git a/Source/Engine/Platform/Mac/MacPlatform.h b/Source/Engine/Platform/Mac/MacPlatform.h
index e80635126..e732e48f3 100644
--- a/Source/Engine/Platform/Mac/MacPlatform.h
+++ b/Source/Engine/Platform/Mac/MacPlatform.h
@@ -27,7 +27,9 @@ public:
static Rectangle GetVirtualDesktopBounds();
static String GetMainDirectory();
static Window* CreateWindow(const CreateWindowSettings& settings);
+#if !PLATFORM_SDL
static int32 CreateProcess(CreateProcessSettings& settings);
+#endif
};
#endif
diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp
index b1dc0768f..0930ccb1a 100644
--- a/Source/Engine/Platform/Mac/MacWindow.cpp
+++ b/Source/Engine/Platform/Mac/MacWindow.cpp
@@ -1,6 +1,6 @@
// Copyright (c) Wojciech Figat. All rights reserved.
-#if PLATFORM_MAC
+#if PLATFORM_MAC && !PLATFORM_SDL
#include "../Window.h"
#include "Engine/Platform/Apple/AppleUtils.h"
@@ -507,7 +507,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
Float2 mousePos = GetMousePosition(Window, event);
mousePos = Window->ClientToScreen(mousePos);
MouseButton mouseButton = MouseButton::Left;
- if ([event clickCount] == 2)
+ if ([event clickCount] == 2 && !Input::Mouse->IsRelative())
Input::Mouse->OnMouseDoubleClick(mousePos, mouseButton, Window);
else
Input::Mouse->OnMouseDown(mousePos, mouseButton, Window);
@@ -544,7 +544,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton = MouseButton::Right;
- if ([event clickCount] == 2)
+ if ([event clickCount] == 2 && !Input::Mouse->IsRelative())
Input::Mouse->OnMouseDoubleClick(Window->ClientToScreen(mousePos), mouseButton, Window);
else
Input::Mouse->OnMouseDown(Window->ClientToScreen(mousePos), mouseButton, Window);
@@ -582,7 +582,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
default:
return;
}
- if ([event clickCount] == 2)
+ if ([event clickCount] == 2 && !Input::Mouse->IsRelative())
Input::Mouse->OnMouseDoubleClick(Window->ClientToScreen(mousePos), mouseButton, Window);
else
Input::Mouse->OnMouseDown(Window->ClientToScreen(mousePos), mouseButton, Window);
@@ -696,7 +696,7 @@ MacWindow::MacWindow(const CreateWindowSettings& settings)
Float2 pos = AppleUtils::PosToCoca(settings.Position);
NSRect frame = NSMakeRect(pos.X, pos.Y - settings.Size.Y, settings.Size.X, settings.Size.Y);
NSUInteger styleMask = NSWindowStyleMaskClosable;
- if (settings.IsRegularWindow)
+ if (settings.Type == WindowType::Regular)
{
styleMask |= NSWindowStyleMaskTitled;
if (settings.AllowMinimize)
diff --git a/Source/Engine/Platform/Mac/MacWindow.h b/Source/Engine/Platform/Mac/MacWindow.h
index 8d9c5e4fd..f4a8cb706 100644
--- a/Source/Engine/Platform/Mac/MacWindow.h
+++ b/Source/Engine/Platform/Mac/MacWindow.h
@@ -55,6 +55,7 @@ public:
void Focus() override;
void SetTitle(const StringView& title) override;
DragDropEffect DoDragDrop(const StringView& data) override;
+ using WindowBase::DoDragDrop;
void StartTrackingMouse(bool useMouseScreenOffset) override;
void EndTrackingMouse() override;
void SetCursor(CursorType type) override;
diff --git a/Source/Engine/Platform/MessageBox.h b/Source/Engine/Platform/MessageBox.h
index 16ae161cb..80c8e7938 100644
--- a/Source/Engine/Platform/MessageBox.h
+++ b/Source/Engine/Platform/MessageBox.h
@@ -237,4 +237,9 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(MessageBox);
/// One of the MessageBoxIcon values that specifies which icon to display in the message box.
/// The message box dialog result.
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
};
diff --git a/Source/Engine/Platform/Platform.Build.cs b/Source/Engine/Platform/Platform.Build.cs
index bdc55ef55..7272270c0 100644
--- a/Source/Engine/Platform/Platform.Build.cs
+++ b/Source/Engine/Platform/Platform.Build.cs
@@ -89,6 +89,21 @@ public class Platform : EngineModule
break;
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)
{
// Include platform settings headers
diff --git a/Source/Engine/Platform/Platform.h b/Source/Engine/Platform/Platform.h
index 8082cd6a2..279e4141f 100644
--- a/Source/Engine/Platform/Platform.h
+++ b/Source/Engine/Platform/Platform.h
@@ -8,7 +8,9 @@
#include "Types.h"
#include "Defines.h"
-#if PLATFORM_WINDOWS
+#if PLATFORM_SDL
+#include "SDL/SDLPlatform.h"
+#elif PLATFORM_WINDOWS
#include "Windows/WindowsPlatform.h"
#elif PLATFORM_UWP
#include "UWP/UWPPlatform.h"
diff --git a/Source/Engine/Platform/SDL/SDLClipboard.h b/Source/Engine/Platform/SDL/SDLClipboard.h
new file mode 100644
index 000000000..f9862b621
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLClipboard.h
@@ -0,0 +1,26 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_SDL && PLATFORM_LINUX
+
+#include "Engine/Platform/Base/ClipboardBase.h"
+
+///
+/// SDL implementation of the clipboard service for Linux platform.
+///
+class FLAXENGINE_API SDLClipboard : public ClipboardBase
+{
+public:
+
+ // [ClipboardBase]
+ static void Clear();
+ static void SetText(const StringView& text);
+ static void SetRawData(const Span& data);
+ static void SetFiles(const Array& files);
+ static String GetText();
+ static Array GetRawData();
+ static Array GetFiles();
+};
+
+#endif
diff --git a/Source/Engine/Platform/SDL/SDLInput.cpp b/Source/Engine/Platform/SDL/SDLInput.cpp
new file mode 100644
index 000000000..76476f162
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLInput.cpp
@@ -0,0 +1,836 @@
+// Copyright (c) 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
+
+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 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
+};
+
+///
+/// Implementation of the keyboard device for Windows platform.
+///
+///
+class SDLKeyboard : public Keyboard
+{
+public:
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ explicit SDLKeyboard()
+ : Keyboard()
+ {
+ }
+
+public:
+
+};
+
+///
+/// Implementation of the mouse device for SDL platform.
+///
+///
+class SDLMouse : public Mouse
+{
+private:
+ Float2 _oldPosition = Float2::Zero;
+ Window* _relativeModeWindow = nullptr;
+ const SDL_Rect* _oldScreenRect = nullptr;
+public:
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ explicit SDLMouse()
+ : Mouse()
+ {
+ }
+
+public:
+
+ ///
+ /// Returns the previous known position of the mouse before entering relative mode.
+ ///
+ 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(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(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;
+ }
+};
+
+///
+/// Implementation of the gamepad device for SDL platform.
+///
+///
+class SDLGamepad : public Gamepad
+{
+private:
+
+ SDL_Gamepad* _gamepad = nullptr;
+ SDL_JoystickID _instanceId;
+
+public:
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The joystick.
+ explicit SDLGamepad(SDL_JoystickID instanceId);
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~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();
+ Input::Keyboard = SDLInputImpl::Keyboard = New();
+}
+
+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(event.gdevice.which));
+ Input::OnGamepadsChanged();
+ break;
+ }
+ case SDL_EVENT_GAMEPAD_REMOVED:
+ {
+ for (int i = 0; i < Input::Gamepads.Count(); i++)
+ {
+ SDLGamepad* gamepad = static_cast(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
diff --git a/Source/Engine/Platform/SDL/SDLInput.h b/Source/Engine/Platform/SDL/SDLInput.h
new file mode 100644
index 000000000..cb1e652a0
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLInput.h
@@ -0,0 +1,22 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_SDL
+
+class SDLWindow;
+union SDL_Event;
+
+///
+/// SDL specific implementation of the input system parts.
+///
+class SDLInput
+{
+public:
+
+ static void Init();
+ static void Update();
+ static bool HandleEvent(SDLWindow* window, SDL_Event& event);
+};
+
+#endif
diff --git a/Source/Engine/Platform/SDL/SDLPlatform.Linux.cpp b/Source/Engine/Platform/SDL/SDLPlatform.Linux.cpp
new file mode 100644
index 000000000..a9fabefba
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLPlatform.Linux.cpp
@@ -0,0 +1,1931 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if PLATFORM_SDL && PLATFORM_LINUX
+
+#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/Linux/IncludeX11.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+namespace SDLImpl
+{
+ extern String WaylandDisplayEnv;
+ extern String XDGCurrentDesktop;
+}
+
+class LinuxDropFilesData : public IGuiData
+{
+public:
+ Array Files;
+ SDLWindow* Window;
+
+ Type GetType() const override
+ {
+ return Type::Files;
+ }
+ String GetAsText() const override
+ {
+ return String::Empty;
+ }
+ void GetAsFiles(Array* files) const override
+ {
+ files->Add(Files);
+ }
+};
+
+class LinuxDropTextData : public IGuiData
+{
+public:
+ StringView Text;
+
+ Type GetType() const override
+ {
+ return Type::Text;
+ }
+ String GetAsText() const override
+ {
+ return String(Text);
+ }
+ void GetAsFiles(Array* files) const override
+ {
+ }
+};
+
+namespace WaylandImpl
+{
+ wl_display* WaylandDisplay = nullptr;
+ int64 DragOverFlag = 0;
+ int64 Serial = 0;
+ wl_pointer_listener PointerListener =
+ {
+ [](void* data, wl_pointer* wl_pointer, uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { }, // Enter event
+ [](void* data, wl_pointer* wl_pointer, uint32_t serial, wl_surface* surface) { }, // Leave event
+ [](void* data, wl_pointer* wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { }, // Motion event
+ [](void* data, wl_pointer* wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) // Button event
+ {
+ // Store the serial for upcoming drag-and-drop action
+ if (state == 1)
+ Platform::AtomicStore(&Serial, serial);
+ else
+ Platform::AtomicStore(&Serial, 0);
+ },
+ [](void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { }, // Axis event
+ [](void* data, wl_pointer* wl_pointer) { }, // Frame event
+ [](void* data, wl_pointer* wl_pointer, uint32_t axis_source) { }, // Axis source event
+ [](void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis) { }, // Axis stop event
+ [](void* data, wl_pointer* wl_pointer, uint32_t axis, int32_t discrete) { }, // axis discrete
+ [](void* data, wl_pointer* wl_pointer, uint32_t axis, int32_t value120) { }, // Scroll wheel event
+ [](void* data, wl_pointer* wl_pointer, uint32_t axis, uint32_t direction) { }, // Relative direction event
+ };
+
+ wl_pointer* WaylandPointer = nullptr;
+ wl_seat_listener SeatListener =
+ {
+ [](void* data, wl_seat* seat, uint32 capabilities) // Seat capabilities changed event
+ {
+ if ((capabilities & wl_seat_capability::WL_SEAT_CAPABILITY_POINTER) != 0)
+ {
+ WaylandPointer = wl_seat_get_pointer(seat);
+ wl_pointer_add_listener(WaylandPointer, &PointerListener, nullptr);
+ }
+ },
+ [](void* data, wl_seat* seat, const char* name) { } // Seat name event
+ };
+
+ xdg_toplevel_drag_manager_v1* DragManager = nullptr;
+ wl_seat* Seat = nullptr;
+ wl_data_device_manager* DataDeviceManager = nullptr;
+ wl_registry_listener RegistryListener =
+ {
+ [](void* data, wl_registry* registry, uint32 id, const char* interface, uint32 version) // Announce global object event
+ {
+ StringAnsi interfaceStr(interface);
+ if (interfaceStr == "xdg_toplevel_drag_manager_v1")
+ DragManager = static_cast(wl_registry_bind(registry, id, &xdg_toplevel_drag_manager_v1_interface, Math::Min(1U, version)));
+ else if (interfaceStr == "wl_seat")
+ {
+ Seat = static_cast(wl_registry_bind(registry, id, &wl_seat_interface, Math::Min(9U, version)));
+ wl_seat_add_listener(Seat, &SeatListener, nullptr);
+ }
+ else if (interfaceStr == "wl_data_device_manager")
+ DataDeviceManager = static_cast(wl_registry_bind(registry, id, &wl_data_device_manager_interface, Math::Min(3U, version)));
+ },
+ [] (void* data, wl_registry* registry, uint32 id) { }, // Announce global remove event
+ };
+
+ wl_data_offer* DataOffer = nullptr; // The last accepted offer
+ wl_data_offer* SelectionOffer = nullptr;
+ wl_data_device_listener DataDeviceListener =
+ {
+ [](void* data, wl_data_device* data_device, wl_data_offer* id) { }, // Data offer event
+ [](void* data, wl_data_device* data_device, uint32 serial, wl_surface* surface, wl_fixed_t x, wl_fixed_t y, wl_data_offer* id) // Enter event
+ {
+ DataOffer = id;
+
+ SDLWindow* sourceWindow = (SDLWindow*)data;
+ if (sourceWindow != nullptr)
+ {
+ // Let them know that we support the following action at this given point
+ wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE, WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE);
+ }
+ else
+ {
+ wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE, WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE);
+ }
+ },
+ [](void* data, wl_data_device* data_device) // Leave event
+ {
+ // The cursor left the surface area
+ if (DataOffer != nullptr)
+ wl_data_offer_destroy(DataOffer);
+ DataOffer = nullptr;
+ },
+ [](void* data, wl_data_device* data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y) { }, // Motion event
+ [](void* data, wl_data_device* data_device) // Drop event
+ {
+ // The drop is accepted
+ if (DataOffer != nullptr)
+ {
+ wl_data_offer_finish(DataOffer);
+ wl_data_offer_destroy(DataOffer);
+ DataOffer = nullptr;
+ }
+ },
+ [](void* data, wl_data_device* data_device, wl_data_offer* id) // Selection event
+ {
+ // Clipboard: We can read the clipboard content
+ if (SelectionOffer != nullptr)
+ wl_data_offer_destroy(SelectionOffer);
+ SelectionOffer = id;
+ },
+ };
+
+ wl_data_source_listener DataSourceListener =
+ {
+ [](void* data, wl_data_source* source, const char* mime_type) { }, // Target event
+ [](void* data, wl_data_source* source, const char* mime_type, int32_t fd) // Send event
+ {
+ // Clipboard: The other end has accepted and is requesting the data
+ IGuiData* inputData = static_cast(data);
+ if (inputData->GetType() == IGuiData::Type::Text)
+ {
+ UnixFile file(fd);
+ StringAnsi text = StringAnsi(inputData->GetAsText());
+ file.Write(text.Get(), text.Length() * sizeof(StringAnsi::CharType));
+ file.Close();
+ }
+ },
+ [](void* data, wl_data_source* source) // Cancelled event
+ {
+ // Clipboard: other application has replaced the content in clipboad
+ wl_data_source_destroy(source);
+
+ IGuiData* inputData = static_cast(data);
+ Platform::AtomicStore(&WaylandImpl::DragOverFlag, 1);
+ },
+ [](void* data, wl_data_source* source) { }, // DnD drop performed event
+ [](void* data, wl_data_source* source) // DnD Finished event
+ {
+ // The destination has finally accepted the last given dnd_action
+ wl_data_source_destroy(source);
+
+ IGuiData* inputData = static_cast(data);
+ Platform::AtomicStore(&WaylandImpl::DragOverFlag, 1);
+ },
+ [](void* data, wl_data_source* source, uint32_t dnd_action) { }, // Action event
+ };
+
+ wl_data_device* DataDevice = nullptr;
+ wl_event_queue* EventQueue = nullptr;
+ wl_data_device_manager* WrappedDataDeviceManager = nullptr;
+ wl_data_device* WrappedDataDevice = nullptr;
+ bool DraggingActive = false;
+ bool DraggingWindow = false;
+ StringView DraggingData = nullptr;
+ class DragDropJob : public ThreadPoolTask
+ {
+ public:
+ int64 StartFlag = 0;
+ int64 WaitFlag = 0;
+ int64 ExitFlag = 0;
+ SDLWindow* Window = nullptr;
+ SDLWindow* DragSourceWindow = nullptr;
+ Float2 DragOffset = Float2::Zero;
+ uint32 DragSerial = 0;
+
+ // [ThreadPoolTask]
+ bool Run() override
+ {
+ bool dragWindow = DraggingWindow;
+
+ if (EventQueue == nullptr)
+ {
+ if (WrappedDataDevice != nullptr)
+ wl_proxy_wrapper_destroy(WrappedDataDevice);
+ if (WrappedDataDeviceManager != nullptr)
+ wl_proxy_wrapper_destroy(WrappedDataDeviceManager);
+ if (DataDevice != nullptr)
+ wl_data_device_destroy(DataDevice);
+
+ // This seems to throw bogus warnings about wl_data_source still being attached to the queue
+ if (EventQueue != nullptr)
+ wl_event_queue_destroy(EventQueue);
+ EventQueue = wl_display_create_queue(WaylandDisplay);
+
+ WrappedDataDeviceManager = static_cast(wl_proxy_create_wrapper(DataDeviceManager));
+ wl_proxy_set_queue(reinterpret_cast(WrappedDataDeviceManager), EventQueue);
+
+ DataDevice = wl_data_device_manager_get_data_device(WrappedDataDeviceManager, Seat);
+ wl_data_device_add_listener(DataDevice, &DataDeviceListener, nullptr);
+ wl_display_roundtrip(WaylandDisplay);
+ wl_data_device_set_user_data(DataDevice, dragWindow ? DragSourceWindow : Window);
+
+ WrappedDataDevice = static_cast(wl_proxy_create_wrapper(DataDevice));
+ wl_proxy_set_queue(reinterpret_cast(WrappedDataDevice), EventQueue);
+ }
+
+ // Offer data for consumption, the data source is destroyed elsewhere
+ wl_data_source* dataSource = wl_data_device_manager_create_data_source(WrappedDataDeviceManager);
+ wl_data_source* wrappedDataSource = (wl_data_source*)wl_proxy_create_wrapper(dataSource);
+ wl_proxy_set_queue(reinterpret_cast(wrappedDataSource), EventQueue);
+ if (dragWindow)
+ {
+ wl_data_source_offer(dataSource, "flaxengine/window");
+ wl_data_source_offer(dataSource, "text/plain;charset=utf-8"); // TODO: needs support for custom mime-types in SDL
+ wl_data_source_set_actions(dataSource, WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE);
+ }
+ else
+ {
+ wl_data_source_offer(dataSource, "text/plain");
+ wl_data_source_offer(dataSource, "text/plain;charset=utf-8");
+ wl_data_source_set_actions(dataSource, WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
+ }
+ LinuxDropTextData textData;
+ textData.Text = *DraggingData;
+ wl_data_source_add_listener(dataSource, &DataSourceListener, &textData);
+
+ // Begin dragging operation
+ auto draggedWindow = Window->GetSDLWindow();
+ auto dragStartWindow = DragSourceWindow != nullptr ? DragSourceWindow->GetSDLWindow() : draggedWindow;
+ wl_surface* originSurface = static_cast(SDL_GetPointerProperty(SDL_GetWindowProperties(dragStartWindow), SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr));
+ wl_surface* iconSurface = nullptr;
+ wl_data_device_start_drag(WrappedDataDevice, dataSource, originSurface, iconSurface, DragSerial);
+
+ Platform::AtomicStore(&StartFlag, 1);
+
+ xdg_toplevel_drag_v1* toplevelDrag = nullptr;
+ xdg_toplevel* wrappedToplevel = nullptr;
+
+ // Start dispatching events to keep data offers alive
+ while (Platform::AtomicRead(&ExitFlag) == 0 && Platform::AtomicRead(&Serial) == DragSerial && Platform::AtomicRead(&DragOverFlag) == 0)
+ {
+ if (DragManager != nullptr && wrappedToplevel == nullptr && dragWindow && Platform::AtomicRead(&WaitFlag) != 0)
+ {
+ // Wait until the dragged window has showed up
+ auto toplevel = static_cast(SDL_GetPointerProperty(SDL_GetWindowProperties(draggedWindow), SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, nullptr));
+ if (toplevel != nullptr)
+ {
+ if (Platform::AtomicRead(&DragOverFlag) == 1 || Platform::AtomicRead(&Serial) != DragSerial)
+ break;
+
+ // Attach the window to the ongoing drag operation
+ wrappedToplevel = static_cast(wl_proxy_create_wrapper(toplevel));
+ wl_proxy_set_queue(reinterpret_cast(wrappedToplevel), EventQueue);
+ toplevelDrag = xdg_toplevel_drag_manager_v1_get_xdg_toplevel_drag(DragManager, dataSource);
+
+ Float2 scaledOffset = DragOffset / Window->GetDpiScale();
+ xdg_toplevel_drag_v1_attach(toplevelDrag, wrappedToplevel, static_cast(scaledOffset.X), static_cast(scaledOffset.Y));
+ }
+ }
+
+ if (wl_display_roundtrip_queue(WaylandDisplay, EventQueue) == -1)
+ LOG(Warning, "wl_display_roundtrip_queue failed, errno: {}", errno);
+ }
+
+ if (toplevelDrag != nullptr)
+ {
+ // Wait for pending operations to finish
+ while (Platform::AtomicRead(&DragOverFlag) == 0 && Platform::AtomicRead(&Serial) == DragSerial)
+ {
+ if (wl_display_dispatch_queue_pending(WaylandDisplay, EventQueue) == -1)
+ LOG(Warning, "wl_display_dispatch_queue_pending failed, errno: {}", errno);
+ Platform::Sleep(1);
+ }
+
+ wl_proxy_wrapper_destroy(wrappedToplevel);
+ xdg_toplevel_drag_v1_destroy(toplevelDrag);
+ toplevelDrag = nullptr;
+ }
+
+ Platform::AtomicStore(&DragOverFlag, 1);
+
+ if (wrappedDataSource != nullptr)
+ wl_proxy_wrapper_destroy(wrappedDataSource);
+
+ if (SelectionOffer != nullptr)
+ {
+ wl_data_offer_destroy(SelectionOffer);
+ SelectionOffer = nullptr;
+ }
+
+ // We can't release the queue immediately due to some resources being still used for a while
+ /*if (WaylandQueue != nullptr)
+ {
+ wl_event_queue_destroy(WaylandQueue);
+ WaylandQueue = nullptr;
+ }*/
+
+ return false;
+ }
+ };
+}
+
+// X11
+Delegate LinuxPlatform::xEventReceived;
+
+namespace X11Impl
+{
+ Window* DraggedWindow = nullptr;
+
+ struct Property
+ {
+ unsigned char* data;
+ int format, nitems;
+ X11::Atom type;
+ };
+
+ X11::Display* xDisplay = nullptr;
+ X11::XIM IM = nullptr;
+ X11::XIC IC = nullptr;
+ X11::Atom xAtomDeleteWindow;
+ X11::Atom xAtomXdndEnter;
+ X11::Atom xAtomXdndPosition;
+ X11::Atom xAtomXdndLeave;
+ X11::Atom xAtomXdndDrop;
+ X11::Atom xAtomXdndActionCopy;
+ X11::Atom xAtomXdndStatus;
+ X11::Atom xAtomXdndSelection;
+ X11::Atom xAtomXdndFinished;
+ X11::Atom xAtomXdndAware;
+ X11::Atom xAtomWmState;
+ X11::Atom xAtomWmStateHidden;
+ X11::Atom xAtomWmStateMaxVert;
+ X11::Atom xAtomWmStateMaxHorz;
+ X11::Atom xAtomWmWindowOpacity;
+ X11::Atom xAtomWmName;
+ X11::Atom xAtomAtom;
+ X11::Atom xAtomClipboard;
+ X11::Atom xAtomPrimary;
+ X11::Atom xAtomTargets;
+ X11::Atom xAtomText;
+ X11::Atom xAtomString;
+ X11::Atom xAtomUTF8String;
+ X11::Atom xAtomFlaxRaw;
+ X11::Atom xAtomUriList;
+ X11::Atom xAtomXselData;
+
+ X11::Atom xDnDRequested = 0;
+ X11::Window xDndSourceWindow = 0;
+ DragDropEffect xDndResult;
+ Float2 xDndPos;
+ int32 xDnDVersion = 0;
+ int32 XFixesSelectionNotifyEvent = 0;
+
+ StringAnsi ClipboardText;
+ Array ClipboardData;
+ Array ClipboardFiles;
+
+ void ClipboardGetText(String& result, X11::Atom source, X11::Atom atom, X11::Window window)
+ {
+ X11::Window selectionOwner = X11::XGetSelectionOwner(xDisplay, source);
+ if (selectionOwner == 0)
+ {
+ // No copy owner
+ return;
+ }
+ if (selectionOwner == window)
+ {
+ // Copy/paste from self
+ result.Set(ClipboardText.Get(), ClipboardText.Length());
+ return;
+ }
+
+ // Send event to get data from the owner
+ int format;
+ unsigned long N, size;
+ char* data;
+ X11::Atom target;
+ X11::XEvent event;
+ X11::XConvertSelection(xDisplay, xAtomClipboard, atom, xAtomXselData, window, CurrentTime);
+ X11::XSync(xDisplay, 0);
+ if (X11::XCheckTypedEvent(xDisplay, SelectionNotify, &event))
+ {
+ if (event.xselection.selection != xAtomClipboard)
+ return;
+ if (event.xselection.property)
+ {
+ X11::XGetWindowProperty(event.xselection.display, event.xselection.requestor, event.xselection.property, 0L, (~0L), 0, AnyPropertyType, &target, &format, &size, &N, (unsigned char**)&data);
+ if (target == xAtomUTF8String || target == xAtomString)
+ {
+ result.Set(data, size);
+ X11::XFree(data);
+ }
+ X11::XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property);
+ }
+ }
+ }
+
+ void ClipboardGetData(Array& result, X11::Atom source, X11::Atom atom, X11::Window window)
+ {
+ X11::Window selectionOwner = X11::XGetSelectionOwner(xDisplay, source);
+ if (selectionOwner == 0)
+ {
+ // No copy owner
+ return;
+ }
+ if (selectionOwner == window)
+ {
+ // Copy/paste from self
+ result.Set(ClipboardData.Get(), ClipboardData.Count());
+ return;
+ }
+
+ // Send event to get data from the owner
+ int format;
+ unsigned long N, size;
+ byte* data;
+ X11::Atom target;
+ X11::XEvent event;
+ X11::XConvertSelection(xDisplay, xAtomClipboard, atom, xAtomXselData, window, CurrentTime);
+ X11::XSync(xDisplay, 0);
+ if (X11::XCheckTypedEvent(xDisplay, SelectionNotify, &event))
+ {
+ if (event.xselection.selection != xAtomClipboard)
+ return;
+ if (event.xselection.property)
+ {
+ X11::XGetWindowProperty(event.xselection.display, event.xselection.requestor, event.xselection.property, 0L,(~0L), 0, AnyPropertyType, &target, &format, &size, &N, (byte**)&data);
+ if (target == atom)
+ {
+ result.Set(data, size);
+ X11::XFree(data);
+ }
+ X11::XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property);
+ }
+ }
+ }
+
+ void ClipboardGetFiles(Array& result, X11::Atom source, X11::Atom atom, X11::Window window)
+ {
+ X11::Window selectionOwner = X11::XGetSelectionOwner(xDisplay, source);
+ if (selectionOwner == 0)
+ {
+ // No copy owner
+ return;
+ }
+ if (selectionOwner == window)
+ {
+ // Copy/paste from self
+ result.Clear();
+ for (auto file : ClipboardFiles)
+ result.Add(String(file));
+ return;
+ }
+
+ // Send event to get data from the owner
+ int format;
+ unsigned long N, size;
+ char* data;
+ X11::Atom target;
+ X11::XEvent event;
+ X11::XConvertSelection(xDisplay, xAtomClipboard, atom, xAtomXselData, window, CurrentTime);
+ X11::XSync(xDisplay, 0);
+ if (X11::XCheckTypedEvent(xDisplay, SelectionNotify, &event))
+ {
+ if (event.xselection.selection != xAtomClipboard)
+ return;
+ if (event.xselection.property)
+ {
+ X11::XGetWindowProperty(event.xselection.display, event.xselection.requestor, event.xselection.property, 0L, (~0L), 0, AnyPropertyType, &target, &format, &size, &N, (unsigned char**)&data);
+ if (target == atom)
+ {
+ String filesString(StringAnsi(data, size));
+ filesString.Split('\n', result);
+ for (auto& file : result)
+ {
+ if (file.StartsWith(TEXT("file://")))
+ file = file.Substring(7);
+ file = file.TrimTrailing(); // Trim '\r'
+ }
+ X11::XFree(data);
+ }
+ X11::XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property);
+ }
+ }
+ }
+
+ Property ReadProperty(X11::Display* display, X11::Window window, X11::Atom property)
+ {
+ X11::Atom readType = 0;
+ int readFormat = 0;
+ unsigned long nitems = 0;
+ unsigned long readBytes = 0;
+ unsigned char* result = nullptr;
+ int bytesCount = 1024;
+ if (property != 0)
+ {
+ do
+ {
+ if (result != nullptr)
+ X11::XFree(result);
+ XGetWindowProperty(display, window, property, 0, bytesCount, 0, AnyPropertyType, &readType, &readFormat, &nitems, &readBytes, &result);
+ bytesCount *= 2;
+ } while (readBytes != 0);
+ }
+ Property p = { result, readFormat, (int)nitems, readType };
+ return p;
+ }
+
+ static StringAnsi GetAtomName(X11::Display* display, X11::Atom atom)
+ {
+ char* atomNamePtr = X11::XGetAtomName(display, atom);
+ StringAnsi atomName = StringAnsi(atomNamePtr);
+ X11::XFree(atomNamePtr);
+ return atomName;
+ }
+
+ static X11::Atom SelectTargetFromList(X11::Display* display, const char* targetType, X11::Atom* list, int count)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ X11::Atom atom = list[i];
+ if (atom != 0 && GetAtomName(display, atom) == targetType)
+ return atom;
+ }
+ return 0;
+ }
+
+ static X11::Atom SelectTargetFromAtoms(X11::Display* display, const char* targetType, X11::Atom t1, X11::Atom t2, X11::Atom t3)
+ {
+ if (t1 != 0 && GetAtomName(display, t1) == targetType)
+ return t1;
+ if (t2 != 0 && GetAtomName(display, t2) == targetType)
+ return t2;
+ if (t3 != 0 && GetAtomName(display, t3) == targetType)
+ return t3;
+ return 0;
+ }
+
+ static X11::Window FindAppWindow(X11::Display* display, X11::Window w)
+ {
+ int nprops, i = 0;
+ X11::Atom* a;
+ if (w == 0)
+ return 0;
+ a = X11::XListProperties(display, w, &nprops);
+ for (i = 0; i < nprops; i++)
+ {
+ if (a[i] == xAtomXdndAware)
+ break;
+ }
+ if (nprops)
+ X11::XFree(a);
+ if (i != nprops)
+ return w;
+ X11::Window child, wtmp;
+ int tmp;
+ unsigned int utmp;
+ X11::XQueryPointer(display, w, &wtmp, &child, &tmp, &tmp, &tmp, &tmp, &utmp);
+ return FindAppWindow(display, child);
+ }
+
+ static Float2 GetX11MousePosition()
+ {
+ if (!xDisplay)
+ return Float2::Zero;
+ int32 x = 0, y = 0;
+ uint32 screenCount = (uint32)X11::XScreenCount(xDisplay);
+ for (uint32 i = 0; i < screenCount; i++)
+ {
+ X11::Window outRoot, outChild;
+ int32 childX, childY;
+ uint32 mask;
+ if (X11::XQueryPointer(xDisplay, X11::XRootWindow(xDisplay, i), &outRoot, &outChild, &x, &y, &childX, &childY, &mask))
+ break;
+ }
+ return Float2((float)x, (float)y);
+ }
+}
+
+DragDropEffect Window::DoDragDrop(const StringView& data)
+{
+ if (CommandLine::Options.Headless.IsTrue())
+ return DragDropEffect::None;
+
+ if (SDLPlatform::UsesWayland())
+ return DoDragDropWayland(data);
+ else
+ return DoDragDropX11(data);
+}
+
+DragDropEffect Window::DoDragDropWayland(const StringView& data, Window* dragSourceWindow, Float2 dragOffset)
+{
+ // HACK: For drag-and-drop, we need to run another event queue in a separate thread to avoid racing issues
+ // while SDL is dispatching the main Wayland event queue when receiving the data offer from us.
+
+ Engine::OnDraw();
+
+ if (WaylandImpl::DraggingActive)
+ LOG(Fatal, "Previous drag and drop operation was not finished");
+
+ // Read the latest serial code from mouse event, and check if we are still holding the mouse before committing
+ auto dragSerial = Platform::AtomicRead(&WaylandImpl::Serial);
+ SDLPlatform::Tick(); // Handle events to check for serial changes
+
+ if (!Input::Mouse->GetButton(MouseButton::Left) ||
+ dragSerial == 0 ||
+ dragSerial != Platform::AtomicRead(&WaylandImpl::Serial))
+ {
+ // The mouse up event was ignored earlier, release the button now
+ Input::Mouse->OnMouseUp(Platform::GetMousePosition(), MouseButton::Left, this);
+
+ return DragDropEffect::None;
+ }
+
+ WaylandImpl::DraggingActive = true;
+ WaylandImpl::DraggingData = StringView(data.Get(), data.Length());
+ WaylandImpl::DragOverFlag = 0;
+
+ auto task = New();
+ task->Window = this;
+ task->DragSourceWindow = dragSourceWindow; // Needs to be the parent window when dragging a tab to window
+ task->DragOffset = dragOffset;
+ task->DragSerial = dragSerial;
+ Task::StartNew(task);
+
+ while (Platform::AtomicRead(&task->StartFlag) == 0)
+ {
+ SDLPlatform::Tick();
+ Platform::Sleep(1);
+ }
+
+ while (Platform::AtomicRead(&WaylandImpl::DragOverFlag) == 0)
+ {
+ SDLPlatform::Tick();
+ Engine::OnUpdate(); // For docking updates
+ Engine::OnDraw();
+
+ // The window needs to be finished showing up before we can start dragging it
+ if (IsVisible() && Platform::AtomicRead(&task->WaitFlag) == 0)
+ Platform::AtomicStore(&task->WaitFlag, 1);
+
+ if (!WaylandImpl::DraggingWindow && !Input::Mouse->GetButton(MouseButton::Left))
+ {
+ // Abort in case the dragging was interrupted before receiving any data offers
+ Platform::AtomicStore(&task->ExitFlag, 1);
+ break;
+ }
+
+ Platform::Sleep(1);
+ }
+
+ // The mouse up event was ignored earlier, release the button now
+ Input::Mouse->OnMouseUp(Platform::GetMousePosition(), MouseButton::Left, this);
+
+ Platform::AtomicStore(&task->ExitFlag, 1);
+ while (task->GetState() != TaskState::Finished)
+ {
+ SDLPlatform::Tick();
+ Platform::Sleep(1);
+ }
+
+ WaylandImpl::DraggingActive = false;
+ WaylandImpl::DraggingData = nullptr;
+
+ return DragDropEffect::None;
+}
+
+DragDropEffect Window::DoDragDropX11(const StringView& data)
+{
+ using namespace X11Impl;
+ auto cursorWrong = X11::XCreateFontCursor(xDisplay, 54);
+ auto cursorTransient = X11::XCreateFontCursor(xDisplay, 24);
+ auto cursorGood = X11::XCreateFontCursor(xDisplay, 4);
+ Array> formats;
+ formats.Add(X11::XInternAtom(xDisplay, "text/plain", 0));
+ formats.Add(xAtomText);
+ formats.Add(xAtomString);
+ StringAnsi dataAnsi(data);
+ LinuxDropTextData dropData;
+ dropData.Text = data;
+#if !PLATFORM_SDL
+ X11::Window mainWindow = _window;
+#else
+ X11::Window mainWindow = reinterpret_cast(GetNativePtr());
+#endif
+
+ // Make sure SDL hasn't grabbed the pointer, and force ungrab it
+ XUngrabPointer(xDisplay, CurrentTime);
+ auto hintAutoCapture = SDL_GetHint(SDL_HINT_MOUSE_AUTO_CAPTURE);
+ SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
+
+ // Begin dragging
+ auto screen = X11::XDefaultScreen(xDisplay);
+ auto rootWindow = X11::XRootWindow(xDisplay, screen);
+
+ if (X11::XGrabPointer(xDisplay, mainWindow, 1, Button1MotionMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, rootWindow, cursorWrong, CurrentTime) != GrabSuccess)
+ {
+ SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, hintAutoCapture);
+ return DragDropEffect::None;
+ }
+ X11::XSetSelectionOwner(xDisplay, xAtomXdndSelection, mainWindow, CurrentTime);
+
+ // Process events
+ X11::XEvent event;
+ enum Status
+ {
+ Unaware,
+ Unreceptive,
+ CanDrop,
+ };
+ int status = Unaware, previousVersion = -1;
+ X11::Window previousWindow = 0;
+ DragDropEffect result = DragDropEffect::None;
+ float lastDraw = Platform::GetTimeSeconds();
+ float startTime = lastDraw;
+ while (true)
+ {
+ X11::XNextEvent(xDisplay, &event);
+
+ if (event.type == SelectionClear)
+ break;
+ if (event.type == SelectionRequest)
+ {
+ // Extract the relavent data
+ X11::Window owner = event.xselectionrequest.owner;
+ X11::Atom selection = event.xselectionrequest.selection;
+ X11::Atom target = event.xselectionrequest.target;
+ X11::Atom property = event.xselectionrequest.property;
+ X11::Window requestor = event.xselectionrequest.requestor;
+ X11::Time timestamp = event.xselectionrequest.time;
+ X11::Display* disp = event.xselection.display;
+ X11::XEvent s;
+ s.xselection.type = SelectionNotify;
+ s.xselection.requestor = requestor;
+ s.xselection.selection = selection;
+ s.xselection.target = target;
+ s.xselection.property = 0;
+ s.xselection.time = timestamp;
+ if (target == xAtomTargets)
+ {
+ Array targets;
+ targets.Add(target);
+ targets.Add(X11::XInternAtom(disp, "MULTIPLE", 0));
+ targets.Add(formats.Get(), formats.Count());
+ X11::XChangeProperty(disp, requestor, property, xAtomAtom, 32, PropModeReplace, (unsigned char*)targets.Get(), targets.Count());
+ s.xselection.property = property;
+ }
+ else if (formats.Contains(target))
+ {
+ s.xselection.property = property;
+ X11::XChangeProperty(disp, requestor, property, target, 8, PropModeReplace, reinterpret_cast(dataAnsi.Get()), dataAnsi.Length());
+ }
+ X11::XSendEvent(event.xselection.display, event.xselectionrequest.requestor, 1, 0, &s);
+ }
+ else if (event.type == MotionNotify)
+ {
+ // Find window under mouse
+ auto window = X11Impl::FindAppWindow(xDisplay, rootWindow);
+ int fmt, version = -1;
+ X11::Atom atmp;
+ unsigned long nitems, bytesLeft;
+ unsigned char* data = nullptr;
+ if (window == previousWindow)
+ version = previousVersion;
+ else if(window == 0)
+ ;
+ else if (X11::XGetWindowProperty(xDisplay, window, xAtomXdndAware, 0, 2, 0, AnyPropertyType, &atmp, &fmt, &nitems, &bytesLeft, &data) != Success)
+ continue;
+ else if (data == 0)
+ continue;
+ else if (fmt != 32)
+ continue;
+ else if (nitems != 1)
+ continue;
+ else
+ version = data[0];
+ if (status == Unaware && version != -1)
+ status = Unreceptive;
+ else if(version == -1)
+ status = Unaware;
+ xDndPos = Float2((float)event.xmotion.x_root, (float)event.xmotion.y_root);
+
+ // Update mouse grab
+ if (status == Unaware)
+ X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, cursorWrong, CurrentTime);
+ else if(status == Unreceptive)
+ X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, cursorTransient, CurrentTime);
+ else
+ X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, cursorGood, CurrentTime);
+
+ if (window != previousWindow && previousVersion != -1)
+ {
+ // Send drag left event
+ auto ww = WindowsManager::GetByNativePtr((void*)previousWindow);
+ if (ww)
+ {
+ ww->_dragOver = false;
+ ww->OnDragLeave();
+ }
+ else
+ {
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = previousWindow;
+ m.message_type = xAtomXdndLeave;
+ m.format = 32;
+ m.data.l[0] = mainWindow;
+ m.data.l[1] = 0;
+ m.data.l[2] = 0;
+ m.data.l[3] = 0;
+ m.data.l[4] = 0;
+ X11::XSendEvent(xDisplay, previousWindow, 0, NoEventMask, (X11::XEvent*)&m);
+ X11::XFlush(xDisplay);
+ }
+ }
+
+ if (window != previousWindow && version != -1)
+ {
+ // Send drag enter event
+ auto ww = WindowsManager::GetByNativePtr((void*)window);
+ if (ww)
+ {
+ xDndPos = ww->ScreenToClient(X11Impl::GetX11MousePosition());
+ xDndResult = DragDropEffect::None;
+ ww->OnDragEnter(&dropData, xDndPos, xDndResult);
+ }
+ else
+ {
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = window;
+ m.message_type = xAtomXdndEnter;
+ m.format = 32;
+ m.data.l[0] = mainWindow;
+ 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[3] = formats.Count() > 1 ? formats[1] : 0;
+ m.data.l[4] = formats.Count() > 2 ? formats[2] : 0;
+ X11::XSendEvent(xDisplay, window, 0, NoEventMask, (X11::XEvent*)&m);
+ X11::XFlush(xDisplay);
+ }
+ }
+
+ if (version != -1)
+ {
+ // Send position event
+ auto ww = WindowsManager::GetByNativePtr((void*)window);
+ if (ww)
+ {
+ xDndPos = ww->ScreenToClient(X11Impl::GetX11MousePosition());
+ ww->_dragOver = true;
+ xDndResult = DragDropEffect::None;
+ ww->OnDragOver(&dropData, xDndPos, xDndResult);
+ status = CanDrop;
+ }
+ else
+ {
+ int x, y, tmp;
+ unsigned int utmp;
+ X11::Window wtmp;
+ X11::XQueryPointer(xDisplay, window, &wtmp, &wtmp, &tmp, &tmp, &x, &y, &utmp);
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = window;
+ m.message_type = xAtomXdndPosition;
+ m.format = 32;
+ m.data.l[0] = mainWindow;
+ m.data.l[1] = 0;
+ m.data.l[2] = (x << 16) | y;
+ m.data.l[3] = CurrentTime;
+ m.data.l[4] = xAtomXdndActionCopy;
+ X11::XSendEvent(xDisplay, window, 0, NoEventMask, (X11::XEvent*)&m);
+ X11::XFlush(xDisplay);
+ }
+ }
+
+ previousWindow = window;
+ previousVersion = version;
+ }
+ else if (event.type == ClientMessage && event.xclient.message_type == xAtomXdndStatus)
+ {
+ if ((event.xclient.data.l[1]&1) && status != Unaware)
+ status = CanDrop;
+ if (!(event.xclient.data.l[1]&1) && status != Unaware)
+ status = Unreceptive;
+ }
+ else if (event.type == ButtonRelease && event.xbutton.button == Button1)
+ {
+ if (status == CanDrop)
+ {
+ // Send drop event
+ auto ww = WindowsManager::GetByNativePtr((void*)previousWindow);
+ if (ww)
+ {
+ xDndPos = ww->ScreenToClient(X11Impl::GetX11MousePosition());
+ xDndResult = DragDropEffect::None;
+ ww->OnDragDrop(&dropData, xDndPos, xDndResult);
+ ww->Focus();
+ result = xDndResult;
+ }
+ else
+ {
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = previousWindow;
+ m.message_type = xAtomXdndDrop;
+ m.format = 32;
+ m.data.l[0] = mainWindow;
+ m.data.l[1] = 0;
+ m.data.l[2] = CurrentTime;
+ m.data.l[3] = 0;
+ m.data.l[4] = 0;
+ X11::XSendEvent(xDisplay, previousWindow, 0, NoEventMask, (X11::XEvent*)&m);
+ X11::XFlush(xDisplay);
+ result = DragDropEffect::Copy;
+ }
+ }
+ break;
+ }
+
+ // Redraw
+ const float time = Platform::GetTimeSeconds();
+ if (time - lastDraw >= 1.0f / 20.0f)
+ {
+ lastDraw = time;
+
+ Engine::OnDraw();
+ }
+
+ // Prevent dead-loop
+ if (time - startTime >= 10.0f)
+ {
+ LOG(Warning, "DoDragDrop timed out after 10 seconds.");
+ break;
+ }
+ }
+
+ // Drag end
+ if (previousWindow != 0 && previousVersion != -1)
+ {
+ // Send drag left event
+ auto ww = WindowsManager::GetByNativePtr((void*)previousWindow);
+ if (ww)
+ {
+ ww->_dragOver = false;
+ ww->OnDragLeave();
+ }
+ else
+ {
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = previousWindow;
+ m.message_type = xAtomXdndLeave;
+ m.format = 32;
+ m.data.l[0] = mainWindow;
+ m.data.l[1] = 0;
+ m.data.l[2] = 0;
+ m.data.l[3] = 0;
+ m.data.l[4] = 0;
+ X11::XSendEvent(xDisplay, previousWindow, 0, NoEventMask, (X11::XEvent*)&m);
+ X11::XFlush(xDisplay);
+ }
+ }
+
+ // End grabbing
+ X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, 0, CurrentTime);
+ XUngrabPointer(xDisplay, CurrentTime);
+ X11::XFlush(xDisplay);
+
+ SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, hintAutoCapture);
+
+ return result;
+}
+
+void SDLPlatform::PreHandleEvents()
+{
+}
+
+void SDLPlatform::PostHandleEvents()
+{
+ // Handle window dragging release here
+ if (X11Impl::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(X11Impl::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;
+ X11Impl::DraggedWindow->HandleEvent(buttonUpEvent);
+ X11Impl::DraggedWindow = nullptr;
+ }
+ }
+}
+
+bool SDLWindow::HandleEventInternal(SDL_Event& event)
+{
+ switch (event.type)
+ {
+ case SDL_EVENT_WINDOW_MOVED:
+ {
+ if (SDLPlatform::UsesX11())
+ {
+ // X11 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 && X11Impl::DraggedWindow == nullptr)
+ {
+ // TODO: verify mouse position, window focus
+ bool result = false;
+ OnLeftButtonHit(WindowHitCodes::Caption, result);
+ if (result)
+ X11Impl::DraggedWindow = this;
+ }
+ }
+ break;
+ }
+ case SDL_EVENT_MOUSE_BUTTON_UP:
+ case SDL_EVENT_MOUSE_MOTION:
+ {
+ if (SDLPlatform::UsesWayland() && WaylandImpl::DraggingActive)
+ {
+ // Ignore mouse events in dragged window
+ return true;
+ }
+ break;
+ }
+ case SDL_EVENT_WINDOW_MOUSE_LEAVE:
+ {
+ OnDragLeave(); // Check for release of mouse button too?
+ break;
+ }
+ 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:
+ {
+ if (SDLPlatform::UsesWayland())
+ {
+ // 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();
+ const Float2 mousePos = Float2(event.drop.x * dpiScale, event.drop.y * dpiScale);
+ DragDropEffect effect = DragDropEffect::None;
+ String text(event.drop.data);
+ LinuxDropTextData textData;
+ LinuxDropFilesData filesData;
+
+ if (WaylandImpl::DraggingActive && (event.type == SDL_EVENT_DROP_BEGIN || event.type == SDL_EVENT_DROP_POSITION))
+ {
+ // We don't have the window dragging data during these events...
+ text = WaylandImpl::DraggingData;
+ }
+ textData.Text = 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 (!WaylandImpl::DraggingActive)
+ OnDragEnter(&filesData, mousePos, effect);
+ if (effect == DragDropEffect::None)
+ OnDragEnter(&textData, 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 (!WaylandImpl::DraggingActive)
+ OnDragOver(&filesData, mousePos, effect);
+ if (effect == DragDropEffect::None)
+ OnDragOver(&textData, mousePos, effect);
+ }
+ else if (event.type == SDL_EVENT_DROP_FILE)
+ {
+ text.Split('\n', filesData.Files);
+ OnDragDrop(&filesData, mousePos, effect);
+ }
+ else if (event.type == SDL_EVENT_DROP_TEXT)
+ OnDragDrop(&textData, mousePos, effect);
+ else if (event.type == SDL_EVENT_DROP_COMPLETE)
+ OnDragLeave();
+
+ // TODO: Implement handling for feedback effect result (https://github.com/libsdl-org/SDL/issues/10448)
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
+
+void SDLClipboard::Clear()
+{
+ SetText(StringView::Empty);
+}
+
+void SDLClipboard::SetText(const StringView& text)
+{
+ if (CommandLine::Options.Headless.IsTrue())
+ return;
+ auto mainWindow = Engine::MainWindow;
+ if (!mainWindow)
+ return;
+
+ if (X11Impl::xDisplay)
+ {
+ X11Impl::ClipboardText.Set(text.Get(), text.Length());
+ X11Impl::ClipboardData.Clear();
+ X11Impl::ClipboardFiles.Clear();
+
+ X11::Window window = (X11::Window)(mainWindow->GetNativePtr());
+ X11::XSetSelectionOwner(X11Impl::xDisplay, X11Impl::xAtomClipboard, window, CurrentTime); // CLIPBOARD
+ //X11::XSetSelectionOwner(xDisplay, xAtomPrimary, window, CurrentTime); // XA_PRIMARY
+ X11::XFlush(X11Impl::xDisplay);
+ }
+ else
+ {
+ SDL_SetClipboardText(StringAnsi(text).GetText());
+ }
+}
+
+void SDLClipboard::SetRawData(const Span& data)
+{
+ if (CommandLine::Options.Headless.IsTrue())
+ return;
+ auto mainWindow = Engine::MainWindow;
+ if (!mainWindow)
+ return;
+
+ if (X11Impl::xDisplay)
+ {
+ X11Impl::ClipboardData.Set(data.Get(), data.Length());
+ X11Impl::ClipboardText.Clear();
+ X11Impl::ClipboardFiles.Clear();
+
+ X11::Window window = (X11::Window)(mainWindow->GetNativePtr());
+ X11::XSetSelectionOwner(X11Impl::xDisplay, X11Impl::xAtomClipboard, window, CurrentTime); // CLIPBOARD
+ //X11::XSetSelectionOwner(xDisplay, xAtomPrimary, window, CurrentTime); // XA_PRIMARY
+ X11::XFlush(X11Impl::xDisplay);
+ }
+ else
+ {
+ if (data.Length() == 0)
+ return;
+
+ const char* uriList = "flaxengine/raw";
+ Array* buffer = New>(data.Get(), data.Length());
+
+ SDL_SetClipboardData([](void* userdata, const char* mime_type, size_t* size) -> const void*
+ {
+ // Clipboard data is requested
+ Array* buffer = static_cast*>(userdata);
+ if (StringUtils::Compare(mime_type, "flaxengine/raw") == 0)
+ {
+ *size = buffer->Count();
+ return buffer->Get();
+ }
+ *size = 0;
+ return nullptr;
+ },
+ [](void* userdata)
+ {
+ // Cleanup previous clipboard data
+ Array* buffer = static_cast*>(userdata);
+ Delete(buffer);
+ }, buffer, &uriList, 1);
+ }
+}
+
+void SDLClipboard::SetFiles(const Array& files)
+{
+ if (CommandLine::Options.Headless.IsTrue())
+ return;
+ auto mainWindow = Engine::MainWindow;
+ if (!mainWindow)
+ return;
+
+ if (X11Impl::xDisplay)
+ {
+ X11Impl::ClipboardFiles.Clear();
+ for (auto file : files)
+ X11Impl::ClipboardFiles.Add(StringAnsi(file));
+ X11Impl::ClipboardText.Clear();
+ X11Impl::ClipboardData.Clear();
+
+ X11::Window window = (X11::Window)(mainWindow->GetNativePtr());
+ X11::XSetSelectionOwner(X11Impl::xDisplay, X11Impl::xAtomClipboard, window, CurrentTime); // CLIPBOARD
+ //X11::XSetSelectionOwner(xDisplay, xAtomPrimary, window, CurrentTime); // XA_PRIMARY
+ X11::XFlush(X11Impl::xDisplay);
+ }
+ else
+ {
+ if (files.Count() == 0)
+ return;
+
+ const char* uriList = "text/uri-list";
+ StringAnsi* buffer = New();
+ for (auto file : files)
+ {
+ buffer->Append("file://");
+ buffer->Append(StringAnsi(file));
+ buffer->Append("\n");
+ }
+
+ SDL_SetClipboardData([](void* userdata, const char* mime_type, size_t* size) -> const void*
+ {
+ // Clipboard data is requested
+ StringAnsi* buffer = (StringAnsi*)userdata;
+ if (StringUtils::Compare(mime_type, "text/uri-list") == 0)
+ {
+ *size = buffer->Length();
+ return buffer->Get();
+ }
+ *size = 0;
+ return nullptr;
+ },
+ [](void* userdata)
+ {
+ // Cleanup previous clipboard data
+ StringAnsi* buffer = (StringAnsi*)userdata;
+ Delete(buffer);
+ }, buffer, &uriList, 1);
+ }
+}
+
+String SDLClipboard::GetText()
+{
+ if (CommandLine::Options.Headless.IsTrue())
+ return String::Empty;
+ String result;
+ auto mainWindow = Engine::MainWindow;
+ if (!mainWindow)
+ return result;
+ if (X11Impl::xDisplay)
+ {
+ X11::Window window = reinterpret_cast(mainWindow->GetNativePtr());
+ X11Impl::ClipboardGetText(result, X11Impl::xAtomClipboard, X11Impl::xAtomUTF8String, window);
+ if (result.HasChars())
+ return result;
+ X11Impl::ClipboardGetText(result, X11Impl::xAtomClipboard, X11Impl::xAtomString, window);
+ if (result.HasChars())
+ return result;
+ X11Impl::ClipboardGetText(result, X11Impl::xAtomPrimary, X11Impl::xAtomUTF8String, window);
+ if (result.HasChars())
+ return result;
+ X11Impl::ClipboardGetText(result, X11Impl::xAtomPrimary, X11Impl::xAtomString, window);
+ return result;
+ }
+ else
+ {
+ return String(SDL_GetClipboardText());
+ }
+}
+
+Array SDLClipboard::GetRawData()
+{
+ auto mainWindow = Engine::MainWindow;
+ if (!mainWindow)
+ return Array();
+
+ if (X11Impl::xDisplay)
+ {
+ X11::Window window = reinterpret_cast(mainWindow->GetNativePtr());
+ Array array;
+ X11Impl::ClipboardGetData(array, X11Impl::xAtomClipboard, X11Impl::xAtomFlaxRaw, window);
+ return array;
+ }
+ else
+ {
+ size_t length = 0;
+ byte* data = static_cast(SDL_GetClipboardData("flaxengine/raw", &length));
+ Array rawData(data, length);
+ return rawData;
+ }
+}
+
+Array SDLClipboard::GetFiles()
+{
+ auto mainWindow = Engine::MainWindow;
+ if (!mainWindow)
+ return Array();
+
+ if (X11Impl::xDisplay)
+ {
+ X11::Window window = reinterpret_cast(mainWindow->GetNativePtr());
+ Array array;
+ X11Impl::ClipboardGetFiles(array, X11Impl::xAtomClipboard, X11Impl::xAtomUriList, window);
+ if (array.Count() > 0)
+ return array;
+ return Array();
+ }
+ else
+ {
+ size_t length = 0;
+ const char* data = static_cast(SDL_GetClipboardData("text/uri-list", &length));
+ String stringBuffer = String(StringAnsi(data, length));
+ Array files;
+ stringBuffer.Split('\n', files);
+ for (auto& file : files)
+ {
+ if (file.StartsWith(TEXT("file://")))
+ file = file.Substring(7);
+ file = file.TrimTrailing(); // Trim '\r'
+ }
+ return files;
+ }
+}
+
+bool SDLCALL SDLPlatform::X11EventHook(void* userdata, _XEvent* xevent)
+{
+ using namespace X11Impl;
+ const X11::XEvent& event = *(X11::XEvent*)xevent;
+ Window* window;
+
+ // External event handling
+ xEventReceived(xevent);
+
+ if (event.type == ClientMessage)
+ {
+ if ((uint32)event.xclient.message_type == (uint32)xAtomXdndEnter)
+ {
+ // Drag&drop enter
+ X11::Window source = event.xclient.data.l[0];
+ xDnDVersion = (int32)(event.xclient.data.l[1] >> 24);
+ const char* targetTypeFiles = "text/uri-list";
+ if (event.xclient.data.l[1] & 1)
+ {
+ Property p = X11Impl::ReadProperty(xDisplay, source, XInternAtom(xDisplay, "XdndTypeList", 0));
+ xDnDRequested = X11Impl::SelectTargetFromList(xDisplay, targetTypeFiles, (X11::Atom*)p.data, p.nitems);
+ X11::XFree(p.data);
+ }
+ else
+ {
+ xDnDRequested = X11Impl::SelectTargetFromAtoms(xDisplay, targetTypeFiles, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]);
+ }
+ return false;
+ }
+ else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndPosition)
+ {
+ // Drag&drop move
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = event.xclient.data.l[0];
+ m.message_type = xAtomXdndStatus;
+ m.format = 32;
+ m.data.l[0] = event.xany.window;
+ m.data.l[1] = (xDnDRequested != 0);
+ m.data.l[2] = 0;
+ m.data.l[3] = 0;
+ m.data.l[4] = xAtomXdndActionCopy;
+ X11::XSendEvent(xDisplay, event.xclient.data.l[0], 0, NoEventMask, (X11::XEvent*)&m);
+ X11::XFlush(xDisplay);
+ xDndPos = Float2((float)(event.xclient.data.l[2] >> 16), (float)(event.xclient.data.l[2] & 0xffff));
+ window = WindowsManager::GetByNativePtr((void*)event.xany.window);
+ if (window)
+ {
+ xDndPos = window->ScreenToClient(xDndPos);
+ LinuxDropFilesData dropData;
+ xDndResult = DragDropEffect::None;
+ if (window->_dragOver)
+ {
+ window->OnDragOver(&dropData, xDndPos, xDndResult);
+ }
+ else
+ {
+ window->_dragOver = true;
+ window->OnDragEnter(&dropData, xDndPos, xDndResult);
+ }
+ }
+ return false;
+ }
+ else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndLeave)
+ {
+ window = WindowsManager::GetByNativePtr((void*)event.xany.window);
+ if (window && window->_dragOver)
+ {
+ window->_dragOver = false;
+ window->OnDragLeave();
+ }
+ return false;
+ }
+ else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndDrop)
+ {
+ if (xDnDRequested != 0)
+ {
+ xDndSourceWindow = event.xclient.data.l[0];
+ XConvertSelection(xDisplay, xAtomXdndSelection, xDnDRequested, xAtomPrimary,
+ event.xany.window, xDnDVersion >= 1 ? event.xclient.data.l[2] : CurrentTime);
+ X11::XFlush(xDisplay);
+ }
+ else
+ {
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = event.xclient.data.l[0];
+ m.message_type = xAtomXdndFinished;
+ m.format = 32;
+ m.data.l[0] = event.xany.window;
+ m.data.l[1] = 0;
+ m.data.l[2] = 0;
+ X11::XSendEvent(xDisplay, event.xclient.data.l[0], 0, NoEventMask, (X11::XEvent*)&m);
+ }
+ return false;
+ }
+ }
+ else if (event.type == SelectionNotify)
+ {
+ if (event.xselection.target == xDnDRequested)
+ {
+ // Drag&drop
+ window = WindowsManager::GetByNativePtr((void*)event.xany.window);
+ if (window)
+ {
+ Property p = X11Impl::ReadProperty(xDisplay, event.xany.window, xAtomPrimary);
+ if (xDndResult != DragDropEffect::None)
+ {
+ LinuxDropFilesData dropData;
+ const String filesList((const char*)p.data);
+ filesList.Split('\n', dropData.Files);
+ for (auto& e : dropData.Files)
+ {
+ e.Replace(TEXT("file://"), TEXT(""));
+ e.Replace(TEXT("%20"), TEXT(" "));
+ e = e.TrimTrailing();
+ }
+ xDndResult = DragDropEffect::None;
+ window->OnDragDrop(&dropData, xDndPos, xDndResult);
+ }
+ }
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = xDisplay;
+ m.window = xDndSourceWindow;
+ m.message_type = xAtomXdndFinished;
+ m.format = 32;
+ m.data.l[0] = event.xany.window;
+ m.data.l[1] = 1;
+ m.data.l[2] = xAtomXdndActionCopy;
+ XSendEvent(xDisplay, xDndSourceWindow, 0, NoEventMask, (X11::XEvent*)&m);
+ return false;
+ }
+ return false;
+ }
+ else if (event.type == SelectionRequest)
+ {
+ // Clipboard request
+ if (event.xselectionrequest.selection != xAtomClipboard)
+ return false;
+
+ const X11::XSelectionRequestEvent* xsr = &event.xselectionrequest;
+ X11::XSelectionEvent ev = { 0 };
+ ev.type = SelectionNotify;
+ ev.display = xsr->display;
+ ev.requestor = xsr->requestor;
+ ev.selection = xsr->selection;
+ ev.time = xsr->time;
+ ev.target = xsr->target;
+ ev.property = xsr->property;
+
+ int result = 0;
+ if (ev.target == xAtomTargets)
+ {
+ // Request supported targets for clipboard content
+ Array> types(2);
+ types.Add(xAtomTargets);
+ if (X11Impl::ClipboardData.Count() > 0)
+ types.Add(xAtomFlaxRaw);
+ else if (X11Impl::ClipboardFiles.Count() > 0)
+ types.Add(xAtomUriList);
+ else
+ types.Add(xAtomUTF8String);
+ result = X11::XChangeProperty(xDisplay, ev.requestor, ev.property, xAtomAtom, 32, PropModeReplace, (unsigned char*)types.Get(), types.Count());
+ }
+ // Request target type
+ else if (ev.target == xAtomString || ev.target == xAtomText)
+ result = X11::XChangeProperty(xDisplay, ev.requestor, ev.property, xAtomString, 8, PropModeReplace, (unsigned char*)X11Impl::ClipboardText.Get(), X11Impl::ClipboardText.Length());
+ else if (ev.target == xAtomUTF8String)
+ result = X11::XChangeProperty(xDisplay, ev.requestor, ev.property, xAtomUTF8String, 8, PropModeReplace, (unsigned char*)X11Impl::ClipboardText.Get(), X11Impl::ClipboardText.Length());
+ else if (ev.target == xAtomFlaxRaw)
+ result = X11::XChangeProperty(xDisplay, ev.requestor, ev.property, xAtomFlaxRaw, 8, PropModeReplace, X11Impl::ClipboardData.Get(), X11Impl::ClipboardData.Count());
+ else if (ev.target == xAtomUriList)
+ {
+ StringAnsi fileString;
+ for (auto file : X11Impl::ClipboardFiles)
+ {
+ fileString.Append("file://");
+ fileString.Append(file);
+ fileString.Append("\n");
+ }
+ result = X11::XChangeProperty(xDisplay, ev.requestor, ev.property, xAtomUriList, 8, PropModeReplace, (unsigned char*)fileString.Get(), fileString.Length());
+ }
+ else
+ ev.property = 0;
+ if ((result & 2) == 0)
+ X11::XSendEvent(xDisplay, ev.requestor, 0, 0, (X11::XEvent*)&ev);
+ return false;
+ }
+ else if (event.type == SelectionClear)
+ return false;
+ else if (event.type == XFixesSelectionNotifyEvent)
+ return false;
+ return true;
+}
+
+int X11ErrorHandler(X11::Display* display, X11::XErrorEvent* event)
+{
+ if (event->error_code == 5)
+ return 0; // BadAtom (invalid Atom parameter)
+ char buffer[256];
+ XGetErrorText(display, event->error_code, buffer, sizeof(buffer));
+ LOG(Error, "X11 Error: {0}", String(buffer));
+ return 0;
+}
+
+bool SDLPlatform::InitInternal()
+{
+ bool waylandRequested = (!CommandLine::Options.X11.GetValueOr(false) || CommandLine::Options.Wayland.IsTrue()) && StringAnsi(SDL_GetHint(SDL_HINT_VIDEO_DRIVER)) == "wayland";
+ if (!CommandLine::Options.Headless.IsTrue() && waylandRequested)
+ {
+ // Ignore in X11 session
+ if (!SDLImpl::WaylandDisplayEnv.IsEmpty())
+ {
+ WaylandImpl::WaylandDisplay = (wl_display*)SDL_GetPointerProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, nullptr);
+ if (WaylandImpl::WaylandDisplay != nullptr)
+ {
+ // Tap into Wayland registry so we can start listening for events
+ wl_registry* registry = wl_display_get_registry(WaylandImpl::WaylandDisplay);
+ wl_registry_add_listener(registry, &WaylandImpl::RegistryListener, nullptr);
+ wl_display_roundtrip(WaylandImpl::WaylandDisplay);
+ }
+ }
+ }
+
+ return false;
+}
+
+bool SDLPlatform::InitX11(void* display)
+{
+ using namespace X11Impl;
+ if (xDisplay || WaylandImpl::WaylandDisplay)
+ return false;
+
+ // The Display instance must be the same one SDL uses internally
+ xDisplay = (X11::Display*)display;
+ SDL_SetX11EventHook((SDL_X11EventHook)&X11EventHook, nullptr);
+ X11::XSetErrorHandler(X11ErrorHandler);
+
+ xAtomDeleteWindow = X11::XInternAtom(xDisplay, "WM_DELETE_WINDOW", 0);
+ xAtomXdndEnter = X11::XInternAtom(xDisplay, "XdndEnter", 0);
+ xAtomXdndPosition = X11::XInternAtom(xDisplay, "XdndPosition", 0);
+ xAtomXdndLeave = X11::XInternAtom(xDisplay, "XdndLeave", 0);
+ xAtomXdndDrop = X11::XInternAtom(xDisplay, "XdndDrop", 0);
+ xAtomXdndActionCopy = X11::XInternAtom(xDisplay, "XdndActionCopy", 0);
+ xAtomXdndStatus = X11::XInternAtom(xDisplay, "XdndStatus", 0);
+ xAtomXdndSelection = X11::XInternAtom(xDisplay, "XdndSelection", 0);
+ xAtomXdndFinished = X11::XInternAtom(xDisplay, "XdndFinished", 0);
+ xAtomXdndAware = X11::XInternAtom(xDisplay, "XdndAware", 0);
+ xAtomWmStateHidden = X11::XInternAtom(xDisplay, "_NET_WM_STATE_HIDDEN", 0);
+ xAtomWmStateMaxHorz = X11::XInternAtom(xDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", 0);
+ xAtomWmStateMaxVert = X11::XInternAtom(xDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", 0);
+ xAtomWmWindowOpacity = X11::XInternAtom(xDisplay, "_NET_WM_WINDOW_OPACITY", 0);
+ xAtomWmName = X11::XInternAtom(xDisplay, "_NET_WM_NAME", 0);
+ xAtomAtom = static_cast(4); // XA_ATOM
+ xAtomClipboard = X11::XInternAtom(xDisplay, "CLIPBOARD", 0);
+ xAtomPrimary = static_cast(1); // XA_PRIMARY
+ xAtomTargets = X11::XInternAtom(xDisplay, "TARGETS", 0);
+ xAtomText = X11::XInternAtom(xDisplay, "TEXT", 0);
+ xAtomString = static_cast(31); // XA_STRING
+ xAtomUTF8String = X11::XInternAtom(xDisplay, "UTF8_STRING", 1);
+ if (xAtomUTF8String == 0)
+ xAtomUTF8String = xAtomString;
+ xAtomFlaxRaw = X11::XInternAtom(xDisplay, "flaxengine/raw", 0);
+ xAtomUriList = X11::XInternAtom(xDisplay, "text/uri-list", 0);
+ xAtomXselData = X11::XInternAtom(xDisplay, "XSEL_DATA", 0);
+
+ // We need to override handling of the XFixes selection tracking events from SDL
+ auto screen = X11::XDefaultScreen(xDisplay);
+ auto rootWindow = X11::XRootWindow(xDisplay, screen);
+ int eventBase = 0, errorBase = 0;
+ if (X11::XFixesQueryExtension(xDisplay, &eventBase, &errorBase))
+ {
+ XFixesSelectionNotifyEvent = eventBase + XFixesSelectionNotify;
+ X11::XFixesSelectSelectionInput(xDisplay, rootWindow, xAtomClipboard, XFixesSetSelectionOwnerNotifyMask);
+ X11::XFixesSelectSelectionInput(xDisplay, rootWindow, xAtomPrimary, XFixesSetSelectionOwnerNotifyMask);
+ }
+
+ return false;
+}
+
+void* SDLPlatform::GetXDisplay()
+{
+ return X11Impl::xDisplay;
+}
+
+String SDLPlatform::GetDisplayServer()
+{
+ String driver(SDL_GetCurrentVideoDriver());
+ if (driver.Length() > 0)
+ driver[0] = StringUtils::ToUpper(driver[0]);
+ return driver;
+}
+
+void SDLPlatform::SetHighDpiAwarenessEnabled(bool enable)
+{
+ SDLPlatformBase::SetHighDpiAwarenessEnabled(enable);
+}
+
+bool SDLPlatform::UsesWindows()
+{
+ return false;
+}
+
+bool SDLPlatform::UsesWayland()
+{
+ if (X11Impl::xDisplay == nullptr && WaylandImpl::WaylandDisplay == nullptr)
+ {
+ // In case the X11 display pointer has not been updated yet
+ return strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0;
+ }
+ return WaylandImpl::WaylandDisplay != nullptr;
+}
+
+bool SDLPlatform::UsesX11()
+{
+ if (X11Impl::xDisplay == nullptr && WaylandImpl::WaylandDisplay == nullptr)
+ {
+ // In case the X11 display pointer has not been updated yet
+ return strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0;
+ }
+ return X11Impl::xDisplay != nullptr;
+}
+
+DragDropEffect SDLWindow::DoDragDrop(const StringView& data, const Float2& offset, Window* dragSourceWindow)
+{
+ if (SDLPlatform::UsesWayland())
+ {
+ Float2 dragOffset = offset;
+ if (SDLPlatform::SupportsNativeDecorations() && _settings.HasBorder && dragSourceWindow == this)
+ {
+ // Wayland includes the decorations in the client-space coordinates, adjust the offset for it.
+ // Assume the title decoration based on the current desktop environment...
+ float topOffset = 25.0f;
+ if (SDLImpl::XDGCurrentDesktop.Compare(String("KDE"), StringSearchCase::IgnoreCase) == 0)
+ topOffset = 25.0f;
+ else if (SDLImpl::XDGCurrentDesktop.Compare(String("GNOME"), StringSearchCase::IgnoreCase) == 0)
+ topOffset = 48.0f;
+ dragOffset += Float2(0.0f, topOffset);
+ }
+
+ // Show the window without changing focus
+ if (!_visible)
+ {
+ if (_showAfterFirstPaint)
+ {
+ if (RenderTask)
+ RenderTask->Enabled = true;
+ }
+ else
+ SDL_ShowWindow(_window);
+ }
+ // Only show the window if toplevel dragging is supported
+ if (WaylandImpl::DragManager != nullptr)
+ WindowBase::Show();
+ else
+ Hide();
+
+ WaylandImpl::DraggingWindow = true;
+ DoDragDropWayland(String(""), dragSourceWindow, dragOffset);
+ WaylandImpl::DraggingWindow = false;
+ }
+ else
+ Show();
+ return DragDropEffect::None;
+}
+
+DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon)
+{
+ StringAnsi textAnsi(text);
+ StringAnsi captionAnsi(caption);
+
+ SDL_MessageBoxData data;
+ SDL_MessageBoxButtonData dataButtons[3];
+ data.window = parent ? static_cast(parent)->_window : nullptr;
+ data.title = captionAnsi.GetText();
+ data.message = textAnsi.GetText();
+ data.colorScheme = nullptr;
+
+ switch (icon)
+ {
+ case MessageBoxIcon::Error:
+ case MessageBoxIcon::Hand:
+ case MessageBoxIcon::Stop:
+ data.flags |= SDL_MESSAGEBOX_ERROR;
+ break;
+ case MessageBoxIcon::Asterisk:
+ case MessageBoxIcon::Information:
+ case MessageBoxIcon::Question:
+ data.flags |= SDL_MESSAGEBOX_INFORMATION;
+ break;
+ case MessageBoxIcon::Exclamation:
+ case MessageBoxIcon::Warning:
+ data.flags |= SDL_MESSAGEBOX_WARNING;
+ break;
+ default:
+ break;
+ }
+
+ switch (buttons)
+ {
+ case MessageBoxButtons::AbortRetryIgnore:
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::Abort,
+ "Abort"
+ };
+ dataButtons[1] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+ (int)DialogResult::Retry,
+ "Retry"
+ };
+ dataButtons[2] =
+ {
+ 0,
+ (int)DialogResult::Ignore,
+ "Ignore"
+ };
+ data.numbuttons = 3;
+ break;
+ case MessageBoxButtons::OK:
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT | SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::OK,
+ "OK"
+ };
+ data.numbuttons = 1;
+ break;
+ case MessageBoxButtons::OKCancel:
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+ (int)DialogResult::OK,
+ "OK"
+ };
+ dataButtons[1] =
+ {
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::Cancel,
+ "Cancel"
+ };
+ data.numbuttons = 2;
+ break;
+ case MessageBoxButtons::RetryCancel:
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+ (int)DialogResult::Retry,
+ "Retry"
+ };
+ dataButtons[1] =
+ {
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::Cancel,
+ "Cancel"
+ };
+ data.numbuttons = 2;
+ break;
+ case MessageBoxButtons::YesNo:
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+ (int)DialogResult::Yes,
+ "Yes"
+ };
+ dataButtons[1] =
+ {
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::No,
+ "No"
+ };
+ data.numbuttons = 2;
+ break;
+ case MessageBoxButtons::YesNoCancel:
+ {
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+ (int)DialogResult::Yes,
+ "Yes"
+ };
+ dataButtons[1] =
+ {
+ 0,
+ (int)DialogResult::No,
+ "No"
+ };
+ dataButtons[2] =
+ {
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::Cancel,
+ "Cancel"
+ };
+ data.numbuttons = 3;
+ break;
+ }
+ default:
+ break;
+ }
+ data.buttons = dataButtons;
+
+ int result = -1;
+ if (!SDL_ShowMessageBox(&data, &result))
+ {
+#if PLATFORM_LINUX
+ // Fallback to native messagebox implementation in case some system fonts are missing
+ if (SDLPlatform::UsesX11())
+ {
+ LOG(Warning, "Failed to show SDL message box: {0}", String(SDL_GetError()));
+ return ShowFallback(parent, text, caption, buttons, icon);
+ }
+#endif
+ LOG(Error, "Failed to show SDL message box: {0}", String(SDL_GetError()));
+ return DialogResult::Abort;
+ }
+ if (result < 0)
+ return DialogResult::None;
+ return (DialogResult)result;
+}
+
+#endif
diff --git a/Source/Engine/Platform/SDL/SDLPlatform.Mac.cpp b/Source/Engine/Platform/SDL/SDLPlatform.Mac.cpp
new file mode 100644
index 000000000..5770b56d5
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLPlatform.Mac.cpp
@@ -0,0 +1,82 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#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/Linux/IncludeX11.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+bool SDLPlatform::InitInternal()
+{
+ return false;
+}
+
+bool SDLPlatform::UsesWindows()
+{
+ return false;
+}
+
+bool SDLPlatform::UsesWayland()
+{
+ return false;
+}
+
+bool SDLPlatform::UsesX11()
+{
+ return false;
+}
+
+void SDLPlatform::PreHandleEvents()
+{
+}
+
+void SDLPlatform::PostHandleEvents()
+{
+}
+
+bool SDLWindow::HandleEventInternal(SDL_Event& event)
+{
+ 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)
+}
+
+DragDropEffect SDLWindow::DoDragDrop(const StringView& data)
+{
+ return DragDropEffect::None;
+}
+
+DragDropEffect SDLWindow::DoDragDrop(const StringView& data, const Float2& offset, Window* dragSourceWindow)
+{
+ Show();
+ return DragDropEffect::None;
+}
+
+#endif
diff --git a/Source/Engine/Platform/SDL/SDLPlatform.Windows.cpp b/Source/Engine/Platform/SDL/SDLPlatform.Windows.cpp
new file mode 100644
index 000000000..ae841b1b4
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLPlatform.Windows.cpp
@@ -0,0 +1,259 @@
+// Copyright (c) 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
+#include
+#include
+#include
+
+#if USE_EDITOR
+#include
+#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(static_cast(WINDOWS_GET_X_LPARAM(msg->lParam))), static_cast(static_cast(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(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);
+
+ Float2 windowSize = window->GetClientSize();
+ if (WinImpl::DraggedWindowSize != windowSize)
+ {
+ // 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 = windowSize;
+ }
+ Float2 windowPosition = Float2(static_cast(event->window.data1), static_cast(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;
+ 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
diff --git a/Source/Engine/Platform/SDL/SDLPlatform.cpp b/Source/Engine/Platform/SDL/SDLPlatform.cpp
new file mode 100644
index 000000000..8f9d4e967
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLPlatform.cpp
@@ -0,0 +1,487 @@
+// Copyright (c) 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/CreateProcessSettings.h"
+#include "Engine/Platform/WindowsManager.h"
+#include "Engine/Platform/SDL/SDLInput.h"
+#include "Engine/Engine/CommandLine.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if PLATFORM_LINUX
+#include "Engine/Engine/CommandLine.h"
+#endif
+
+#define DefaultDPI 96
+
+namespace SDLImpl
+{
+ int32 SystemDpi = 96;
+#if PLATFORM_LINUX
+ String UserLocale("en");
+#endif
+ bool WindowDecorationsSupported = true;
+ bool SupportsDecorationDragging = true;
+ String WaylandDisplayEnv;
+ String XDGCurrentDesktop;
+}
+
+bool SDLPlatform::Init()
+{
+#if PLATFORM_LINUX
+ bool waylandSession = false;
+ if (!GetEnvironmentVariable(String("WAYLAND_DISPLAY"), SDLImpl::WaylandDisplayEnv))
+ waylandSession = true;
+ GetEnvironmentVariable(String("XDG_CURRENT_DESKTOP"), SDLImpl::XDGCurrentDesktop);
+
+ if (CommandLine::Options.X11.IsTrue())
+ {
+ SDL_SetHintWithPriority(SDL_HINT_VIDEO_DRIVER, "x11", SDL_HINT_OVERRIDE);
+ waylandSession = false;
+ }
+ else if (CommandLine::Options.Wayland.IsTrue())
+ SDL_SetHintWithPriority(SDL_HINT_VIDEO_DRIVER, "wayland", SDL_HINT_OVERRIDE);
+ else if (waylandSession)
+ {
+ // Override the X11 preference when running in Wayland session
+ SDL_SetHintWithPriority(SDL_HINT_VIDEO_DRIVER, "wayland", SDL_HINT_OVERRIDE);
+ }
+
+ // Workaround for libdecor in Gnome+Wayland causing freezes when interacting with the native decorations
+ if (waylandSession && SDLImpl::XDGCurrentDesktop.Compare(String("GNOME"), StringSearchCase::IgnoreCase) == 0)
+ {
+ SDL_SetHint(SDL_HINT_VIDEO_WAYLAND_ALLOW_LIBDECOR, "0");
+ SDLImpl::WindowDecorationsSupported = false;
+ }
+ if (waylandSession)
+ SDLImpl::SupportsDecorationDragging = false;
+#endif
+
+#if PLATFORM_LINUX
+ // The name follows the .desktop entry specification, this is used to get a fallback icon on Wayland:
+ // https://specifications.freedesktop.org/desktop-entry-spec/latest/file-naming.html
+#if USE_EDITOR
+ SDL_SetHint(SDL_HINT_APP_ID, StringAnsi("com.FlaxEngine.FlaxEditor").Get());
+#else
+ // TODO: This should be read from the platform configuration (needed for desktop icon handling)
+ 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())));
+
+#if PLATFORM_LINUX
+ 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)
+ SDLImpl::UserLocale = String::Format(TEXT("{0}-{1}"), String(language), String(locales[i]->country));
+ else
+ SDLImpl::UserLocale = String(language);
+ break;
+ }
+ }
+ SDL_free(locales);
+#endif
+
+ 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();
+
+ SDLImpl::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 SDLPlatformBase::Init();
+}
+
+void SDLPlatform::LogInfo()
+{
+ SDLPlatformBase::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;
+}
+
+bool SDLPlatform::SupportsNativeDecorations()
+{
+ return SDLImpl::WindowDecorationsSupported;
+}
+
+bool SDLPlatform::SupportsNativeDecorationDragging()
+{
+ return SDLImpl::SupportsDecorationDragging;
+}
+
+#if !PLATFORM_WINDOWS
+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;
+}
+#endif
+
+int32 SDLPlatform::GetDpi()
+{
+ return SDLImpl::SystemDpi;
+}
+
+#if PLATFORM_LINUX
+String SDLPlatform::GetUserLocaleName()
+{
+ return SDLImpl::UserLocale;
+}
+#endif
+
+bool SDLPlatform::CanOpenUrl(const StringView& url)
+{
+ return true;
+}
+
+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();
+ }
+#endif
+ Float2 pos;
+ SDL_GetGlobalMouseState(&pos.X, &pos.Y);
+ return pos;
+}
+
+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(rect.w), static_cast(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(rect.x), static_cast(rect.y), static_cast(rect.w), static_cast(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(rect.x), static_cast(rect.y), static_cast(rect.w), static_cast(rect.h)));
+ }
+ SDL_free((void*)displays);
+ return bounds;
+}
+
+Window* SDLPlatform::CreateWindow(const CreateWindowSettings& settings)
+{
+ return New(settings);
+}
+
+#if !PLATFORM_WINDOWS
+
+bool ReadStream(SDL_IOStream*& stream, char* buffer, int64 bufferLength, int64& bufferPosition, LogType logType, CreateProcessSettings& settings)
+{
+ bool flushBuffer = false;
+ bool success = true;
+ auto read = SDL_ReadIO(stream, buffer + bufferPosition, bufferLength - bufferPosition - 1);
+ if (read == 0)
+ {
+ SDL_IOStatus status = SDL_GetIOStatus(stream);
+ if (status != SDL_IO_STATUS_NOT_READY && status != SDL_IO_STATUS_EOF)
+ success = false;
+ if (status != SDL_IO_STATUS_NOT_READY)
+ {
+ stream = nullptr;
+ flushBuffer = true;
+ }
+ }
+ else
+ {
+ int64 startPosition = bufferPosition;
+ bufferPosition += (int64)read;
+ if (bufferPosition == bufferLength - 1)
+ {
+ flushBuffer = true;
+ buffer[bufferPosition++] = '\n'; // Make sure to flush fully filled buffer
+ }
+ else
+ {
+ for (int64 i = startPosition; i < bufferPosition; ++i)
+ {
+ if (buffer[i] == '\n')
+ {
+ flushBuffer = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (flushBuffer)
+ {
+ int64 start = 0;
+ for (int64 i = 0; i < bufferPosition; ++i)
+ {
+ if (buffer[i] != '\n')
+ continue;
+
+ String str(&buffer[start], (int32)(i - start + 1));
+#if LOG_ENABLE
+ if (settings.LogOutput)
+ Log::Logger::Write(logType, StringView(str.Get(), str.Length() - 1));
+#endif
+ if (settings.SaveOutput)
+ settings.Output.Add(str.Get(), str.Length());
+ start = i + 1;
+ }
+ int64 length = bufferPosition - start;
+ if (length > 0)
+ {
+ // TODO: Use memmove here? Overlapped memory regions with memcpy is undefined behaviour
+ char temp[2048];
+ Platform::MemoryCopy(temp, buffer + start, length);
+ Platform::MemoryCopy(buffer, temp, length);
+ bufferPosition = length;
+ }
+ else
+ bufferPosition = 0;
+ }
+ return success;
+}
+
+int32 SDLPlatform::CreateProcess(CreateProcessSettings& settings)
+{
+ LOG(Info, "Command: {0} {1}", settings.FileName, settings.Arguments);
+ if (settings.WorkingDirectory.HasChars())
+ LOG(Info, "Working directory: {0}", settings.WorkingDirectory);
+
+ int32 result = 0;
+ const bool captureStdOut = settings.LogOutput || settings.SaveOutput;
+ const StringAnsi cmdLine = StringAnsi::Format("\"{0}\" {1}", StringAnsi(settings.FileName), StringAnsi(settings.Arguments));
+ StringAnsi workingDirectory(settings.WorkingDirectory);
+
+ // Populate environment with current values from parent environment.
+ // SDL does not populate the environment with the latest values but with a snapshot captured during initialization.
+ Dictionary envDictionary;
+ GetEnvironmentVariables(envDictionary);
+ SDL_Environment* env = SDL_CreateEnvironment(false);
+ for (auto iter = envDictionary.Begin(); iter != envDictionary.End(); ++iter)
+ SDL_SetEnvironmentVariable(env, StringAnsi(iter->Key).Get(), StringAnsi(iter->Value).Get(), true);
+ for (auto iter = settings.Environment.Begin(); iter != settings.Environment.End(); ++iter)
+ SDL_SetEnvironmentVariable(env, StringAnsi(iter->Key).Get(), StringAnsi(iter->Value).Get(), true);
+
+ // Parse argument list with possible quotes included
+ Array arguments;
+ arguments.Add(StringAnsi(settings.FileName));
+ if (CommandLine::ParseArguments(settings.Arguments, arguments))
+ {
+ LOG(Error, "Failed to parse arguments for process {}: '{}'", settings.FileName.Get(), settings.Arguments.Get());
+ return -1;
+ }
+ Array cmd;
+ for (const StringAnsi& str : arguments)
+ cmd.Add(str.Get());
+ cmd.Add((const char*)0);
+
+#if PLATFORM_WINDOWS
+ bool background = !settings.WaitForEnd || settings.HiddenWindow; // This also hides the window on Windows
+#else
+ bool background = !settings.WaitForEnd;
+#endif
+
+ SDL_PropertiesID props = SDL_CreateProperties();
+ SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, cmd.Get());
+ SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, env);
+ SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN, background);
+ if (workingDirectory.HasChars())
+ SDL_SetStringProperty(props, SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING, workingDirectory.Get());
+ if (captureStdOut)
+ {
+ SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP);
+ SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_APP);
+ }
+ SDL_Process* process = SDL_CreateProcessWithProperties(props);
+ SDL_DestroyProperties(props);
+ SDL_DestroyEnvironment(env);
+ if (process == nullptr)
+ {
+ LOG(Error, "Failed to run process {}: {}", settings.FileName.Get(), String(SDL_GetError()));
+ return -1;
+ }
+
+ props = SDL_GetProcessProperties(process);
+ int64 pid = SDL_GetNumberProperty(props, SDL_PROP_PROCESS_PID_NUMBER, 0);
+ SDL_IOStream* stdoutStream = nullptr;
+ SDL_IOStream* stderrStream = nullptr;
+ if (captureStdOut)
+ {
+ stdoutStream = static_cast(SDL_GetPointerProperty(props, SDL_PROP_PROCESS_STDOUT_POINTER, nullptr));
+ stderrStream = static_cast(SDL_GetPointerProperty(props, SDL_PROP_PROCESS_STDERR_POINTER, nullptr));
+ }
+
+ // Handle process output in realtime
+ char stdoutBuffer[2049];
+ int64 stdoutPosition = 0;
+ char stderrBuffer[2049];
+ int64 stderrPosition = 0;
+ while (stdoutStream != nullptr && stderrStream != nullptr)
+ {
+ if (stdoutStream != nullptr && !ReadStream(stdoutStream, stdoutBuffer, sizeof(stdoutBuffer), stdoutPosition, LogType::Info, settings))
+ LOG(Warning, "Failed to read process {} stdout: {}", pid, String(SDL_GetError()));
+ if (stderrStream != nullptr && !ReadStream(stderrStream, stderrBuffer, sizeof(stderrBuffer), stderrPosition, LogType::Error, settings))
+ LOG(Warning, "Failed to read process {} stderr: {}", pid, String(SDL_GetError()));
+ Sleep(1);
+ }
+
+ if (settings.WaitForEnd)
+ SDL_WaitProcess(process, true, &result);
+
+ SDL_DestroyProcess(process);
+ return result;
+}
+
+#endif
+
+#endif
diff --git a/Source/Engine/Platform/SDL/SDLPlatform.h b/Source/Engine/Platform/SDL/SDLPlatform.h
new file mode 100644
index 000000000..bb8211485
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLPlatform.h
@@ -0,0 +1,103 @@
+// Copyright (c) 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;
+
+#if PLATFORM_WINDOWS
+typedef WindowsPlatform SDLPlatformBase;
+#elif PLATFORM_LINUX
+typedef LinuxPlatform SDLPlatformBase;
+#elif PLATFORM_MAC
+typedef MacPlatform SDLPlatformBase;
+#else
+static_assert(false, "Unsupported SDL platform.");
+#endif
+
+///
+/// The SDL platform implementation and application management utilities.
+///
+API_CLASS(Static, Tag="NoTypeInitializer")
+class FLAXENGINE_API SDLPlatform : public SDLPlatformBase
+{
+ 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();
+ static String GetDisplayServer();
+#endif
+ static bool UsesWindows();
+ static bool UsesWayland();
+ static bool UsesX11();
+
+ ///
+ /// Returns true if system provides decorations for windows.
+ ///
+ API_PROPERTY() static bool SupportsNativeDecorations();
+
+ ///
+ /// Returns true if system provides support for native window dragging events.
+ ///
+ API_PROPERTY() static bool SupportsNativeDecorationDragging();
+
+public:
+ // [PlatformBase]
+ static bool Init();
+ static void LogInfo();
+ static void Tick();
+ static void SetHighDpiAwarenessEnabled(bool enable);
+#if !PLATFORM_WINDOWS
+ static BatteryInfo GetBatteryInfo();
+#endif
+ static int32 GetDpi();
+#if PLATFORM_LINUX
+ static String GetUserLocaleName();
+#endif
+ static bool CanOpenUrl(const StringView& url);
+ 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);
+#if !PLATFORM_WINDOWS
+ static int32 CreateProcess(CreateProcessSettings& settings);
+#endif
+};
+
+#endif
diff --git a/Source/Engine/Platform/SDL/SDLWindow.cpp b/Source/Engine/Platform/SDL/SDLWindow.cpp
new file mode 100644
index 000000000..45e686148
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLWindow.cpp
@@ -0,0 +1,1023 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if PLATFORM_SDL
+
+#include "SDLWindow.h"
+#include "SDLInput.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Core/Math/Color32.h"
+#include "Engine/Core/Math/Math.h"
+#include "Engine/Core/Math/Rectangle.h"
+#include "Engine/Engine/Globals.h"
+#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Graphics/GPUSwapChain.h"
+#include "Engine/Graphics/RenderTask.h"
+#include "Engine/Graphics/Textures/TextureData.h"
+#include "Engine/Input/Input.h"
+#include "Engine/Input/Keyboard.h"
+#include "Engine/Input/Mouse.h"
+#include "Engine/Platform/FileSystem.h"
+#include "Engine/Platform/WindowsManager.h"
+#if PLATFORM_LINUX
+#define COMPILE_WITH_TEXTURE_TOOL 1 // FIXME
+#include "Engine/Tools/TextureTool/TextureTool.h"
+#endif
+
+#define NOGDI
+#include
+#include
+#include
+#undef CreateWindow
+
+#if PLATFORM_WINDOWS
+#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
+#if USE_EDITOR
+#include
+#endif
+#elif PLATFORM_LINUX
+#include "Engine/Platform/Linux/IncludeX11.h"
+#elif PLATFORM_MAC
+#include
+#else
+static_assert(false, "Unsupported Platform");
+#endif
+
+#define DefaultDPI 96
+
+namespace SDLImpl
+{
+ SDLWindow* LastEventWindow = nullptr;
+ SDL_Cursor* Cursors[SDL_SYSTEM_CURSOR_COUNT] = { nullptr };
+ extern String XDGCurrentDesktop;
+}
+
+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::Empty)
+#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;
+
+#if !PLATFORM_WINDOWS // Fixed on Windows by adding WS_EX_APPWINDOW flag down below
+ // 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;
+#endif
+
+ // 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);
+
+#if PLATFORM_WINDOWS
+ // Windows that have parent are hidden in taskbar on Windows in SDL so hack it
+ if (_settings.ShowInTaskbar && _settings.Parent != nullptr && (_settings.Type != WindowType::Tooltip && _settings.Type != WindowType::Popup))
+ {
+ LONG lStyle = GetWindowLong((HWND)_handle, GWL_EXSTYLE);
+ lStyle |= WS_EX_APPWINDOW;
+ SetWindowLong((HWND)_handle, GWL_EXSTYLE, lStyle);
+ }
+#endif
+
+ 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
+
+ SDLImpl::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
+
+#if PLATFORM_LINUX && COMPILE_WITH_TEXTURE_TOOL
+ // Ensure windows other than the main window have some kind of icon
+ static SDL_Surface* surface = nullptr;
+ static Array colorData;
+ if (surface == nullptr)
+ {
+ const String iconPath = Globals::BinariesFolder / TEXT("Logo.png");
+ if (FileSystem::FileExists(iconPath))
+ {
+ TextureData icon;
+ if (!TextureTool::ImportTexture(iconPath, icon))
+ {
+ icon.GetPixels(colorData);
+ surface = SDL_CreateSurfaceFrom(icon.Width, icon.Height, SDL_PIXELFORMAT_ABGR8888, colorData.Get(), sizeof(Color32) * icon.Width);
+ }
+ }
+ }
+ if (surface != nullptr)
+ SDL_SetWindowIcon(_window, surface);
+#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 (SDLImpl::LastEventWindow == this)
+ SDLImpl::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;
+}
+
+WindowHitCodes SDLWindow::OnWindowHit(const Float2 point)
+{
+ WindowHitCodes hit = WindowHitCodes::Client;
+ if (!IsFullscreen())
+ {
+ Float2 screenPosition = ClientToScreen(point);
+ bool handled = false;
+ OnHitTest(screenPosition, hit, handled);
+ if (!handled)
+ {
+ int margin = _settings.HasBorder ? 0 : 0;
+ auto size = GetClientSize();
+ //if (point.Y < 0)
+ // hit = WindowHitCodes::Caption;
+ if (point.Y < margin && point.X < margin)
+ hit = WindowHitCodes::TopLeft;
+ else if (point.Y < margin && point.X > size.X - margin)
+ hit = WindowHitCodes::TopRight;
+ else if (point.Y < margin)
+ hit = WindowHitCodes::Top;
+ else if (point.X < margin && point.Y > size.Y - margin)
+ hit = WindowHitCodes::BottomLeft;
+ else if (point.X < margin)
+ hit = WindowHitCodes::Left;
+ else if (point.X > size.X - margin && point.Y > size.Y - margin)
+ hit = WindowHitCodes::BottomRight;
+ else if (point.X > size.X - margin)
+ hit = WindowHitCodes::Right;
+ else if (point.Y > size.Y - margin)
+ hit = WindowHitCodes::Bottom;
+ else
+ hit = WindowHitCodes::Client;
+ }
+ }
+ return hit;
+}
+
+SDL_HitTestResult OnWindowHitTest(SDL_Window* win, const SDL_Point* area, void* data)
+{
+ SDLWindow* window = static_cast(data);
+ const Float2 point(static_cast(area->x), static_cast(area->y));
+ WindowHitCodes hit = window->OnWindowHit(point);
+ 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 (SDLImpl::LastEventWindow == nullptr || window != SDLImpl::LastEventWindow->_window)
+ SDLImpl::LastEventWindow = GetWindowWithSDLWindow(window);
+ return SDLImpl::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(event.window.data1), static_cast(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(width), static_cast(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(_dpiScale * DefaultDPI);
+ int w = static_cast(_cachedClientRectangle.GetWidth() * (scale / oldScale));
+ int h = static_cast(_cachedClientRectangle.GetHeight() * (scale / oldScale));
+ _cachedClientRectangle.Size = Float2(static_cast(w), static_cast(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(clientArea.GetWidth());
+ int newH = static_cast(clientArea.GetHeight());
+
+ SDL_SetWindowSize(_window, newW, newH);
+ SetSDLWindowScreenPosition(this, newPos);
+}
+
+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(position.X), static_cast(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(position.X), static_cast(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(w + left + right), static_cast(h + top + bottom));
+}
+
+Float2 SDLWindow::GetClientSize() const
+{
+ int w, h;
+ SDL_GetWindowSizeInPixels(_window, &w, &h);
+
+ return Float2(static_cast(w), static_cast(h));;
+}
+
+Float2 SDLWindow::ScreenToClient(const Float2& screenPos) const
+{
+ Int2 position = GetSDLWindowScreenPosition(this);
+ return screenPos - Float2(static_cast(position.X), static_cast(position.Y));
+}
+
+Float2 SDLWindow::ClientToScreen(const Float2& clientPos) const
+{
+ Int2 position = GetSDLWindowScreenPosition(this);
+ return clientPos + Float2(static_cast(position.X), static_cast(position.Y));
+}
+
+void SDLWindow::FlashWindow()
+{
+#if PLATFORM_LINUX
+ // KDE bug: flashing brings the window on top of other windows, disable it for now...
+ if (SDLPlatform::UsesWayland() && SDLImpl::XDGCurrentDesktop.Compare(String("KDE"), StringSearchCase::IgnoreCase) == 0)
+ return;
+#endif
+ 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);
+ _restoreRelativeMode = false;
+}
+
+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 (SDLImpl::Cursors[index] == nullptr)
+ SDLImpl::Cursors[index] = SDL_CreateSystemCursor(static_cast(index));
+ SDL_SetCursor(SDLImpl::Cursors[index]);
+}
+
+void SDLWindow::SetIcon(TextureData& icon)
+{
+ Array colorData;
+ icon.GetPixels(colorData);
+ SDL_Surface* surface = SDL_CreateSurfaceFrom(icon.Width, icon.Height, SDL_PIXELFORMAT_ABGR8888, colorData.Get(), sizeof(Color32) * icon.Width);
+
+ SDL_SetWindowIcon(_window, surface);
+ SDL_free(surface);
+}
+
+#endif
+
diff --git a/Source/Engine/Platform/SDL/SDLWindow.h b/Source/Engine/Platform/SDL/SDLWindow.h
new file mode 100644
index 000000000..a84ec24c9
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLWindow.h
@@ -0,0 +1,130 @@
+// Copyright (c) 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
+
+///
+/// Implementation of the window class for SDL platform
+///
+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:
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The initial window settings.
+ SDLWindow(const CreateWindowSettings& settings);
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~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
+ WindowHitCodes OnWindowHit(const Float2 point);
+
+ // [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;
+ void SetIcon(TextureData& icon) 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
diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h
index 79fe02efc..7a008abc8 100644
--- a/Source/Engine/Platform/Types.h
+++ b/Source/Engine/Platform/Types.h
@@ -4,8 +4,6 @@
#if PLATFORM_WINDOWS
-class WindowsClipboard;
-typedef WindowsClipboard Clipboard;
class Win32CriticalSection;
typedef Win32CriticalSection CriticalSection;
class Win32ReadWriteLock;
@@ -18,12 +16,16 @@ class WindowsFileSystemWatcher;
typedef WindowsFileSystemWatcher FileSystemWatcher;
class Win32File;
typedef Win32File File;
-class WindowsPlatform;
-typedef WindowsPlatform Platform;
class Win32Thread;
typedef Win32Thread Thread;
+class WindowsClipboard;
+typedef WindowsClipboard Clipboard;
+#if !PLATFORM_SDL
+class WindowsPlatform;
+typedef WindowsPlatform Platform;
class WindowsWindow;
typedef WindowsWindow Window;
+#endif
class Win32Network;
typedef Win32Network Network;
class UserBase;
@@ -31,8 +33,6 @@ typedef UserBase User;
#elif PLATFORM_UWP
-class ClipboardBase;
-typedef ClipboardBase Clipboard;
class Win32CriticalSection;
typedef Win32CriticalSection CriticalSection;
class Win32ReadWriteLock;
@@ -45,10 +45,12 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher;
class Win32File;
typedef Win32File File;
-class UWPPlatform;
-typedef UWPPlatform Platform;
class Win32Thread;
typedef Win32Thread Thread;
+class ClipboardBase;
+typedef ClipboardBase Clipboard;
+class UWPPlatform;
+typedef UWPPlatform Platform;
class UWPWindow;
typedef UWPWindow Window;
class Win32Network;
@@ -58,8 +60,6 @@ typedef UserBase User;
#elif PLATFORM_LINUX
-class LinuxClipboard;
-typedef LinuxClipboard Clipboard;
class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection;
class UnixReadWriteLock;
@@ -72,12 +72,16 @@ class LinuxFileSystemWatcher;
typedef LinuxFileSystemWatcher FileSystemWatcher;
class UnixFile;
typedef UnixFile File;
-class LinuxPlatform;
-typedef LinuxPlatform Platform;
class LinuxThread;
typedef LinuxThread Thread;
+#if !PLATFORM_SDL
+class LinuxClipboard;
+typedef LinuxClipboard Clipboard;
+class LinuxPlatform;
+typedef LinuxPlatform Platform;
class LinuxWindow;
typedef LinuxWindow Window;
+#endif
class UnixNetwork;
typedef UnixNetwork Network;
class UserBase;
@@ -85,8 +89,6 @@ typedef UserBase User;
#elif PLATFORM_PS4
-class ClipboardBase;
-typedef ClipboardBase Clipboard;
class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection;
class UnixReadWriteLock;
@@ -99,10 +101,12 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher;
class UnixFile;
typedef UnixFile File;
-class PS4Platform;
-typedef PS4Platform Platform;
class PS4Thread;
typedef PS4Thread Thread;
+class ClipboardBase;
+typedef ClipboardBase Clipboard;
+class PS4Platform;
+typedef PS4Platform Platform;
class PS4Window;
typedef PS4Window Window;
class PS4Network;
@@ -112,8 +116,6 @@ typedef PS4User User;
#elif PLATFORM_PS5
-class ClipboardBase;
-typedef ClipboardBase Clipboard;
class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection;
class UnixReadWriteLock;
@@ -126,10 +128,12 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher;
class UnixFile;
typedef UnixFile File;
-class PS5Platform;
-typedef PS5Platform Platform;
+class ClipboardBase;
+typedef ClipboardBase Clipboard;
class PS5Thread;
typedef PS5Thread Thread;
+class PS5Platform;
+typedef PS5Platform Platform;
class PS5Window;
typedef PS5Window Window;
class PS5Network;
@@ -139,8 +143,6 @@ typedef PS5User User;
#elif PLATFORM_XBOX_ONE
-class ClipboardBase;
-typedef ClipboardBase Clipboard;
class Win32CriticalSection;
typedef Win32CriticalSection CriticalSection;
class Win32ReadWriteLock;
@@ -153,10 +155,12 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher;
class Win32File;
typedef Win32File File;
-class XboxOnePlatform;
-typedef XboxOnePlatform Platform;
class Win32Thread;
typedef Win32Thread Thread;
+class ClipboardBase;
+typedef ClipboardBase Clipboard;
+class XboxOnePlatform;
+typedef XboxOnePlatform Platform;
class GDKWindow;
typedef GDKWindow Window;
class Win32Network;
@@ -166,8 +170,6 @@ typedef GDKUser User;
#elif PLATFORM_XBOX_SCARLETT
-class ClipboardBase;
-typedef ClipboardBase Clipboard;
class Win32CriticalSection;
typedef Win32CriticalSection CriticalSection;
class Win32ReadWriteLock;
@@ -180,10 +182,12 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher;
class Win32File;
typedef Win32File File;
-class XboxScarlettPlatform;
-typedef XboxScarlettPlatform Platform;
class Win32Thread;
typedef Win32Thread Thread;
+class ClipboardBase;
+typedef ClipboardBase Clipboard;
+class XboxScarlettPlatform;
+typedef XboxScarlettPlatform Platform;
class GDKWindow;
typedef GDKWindow Window;
class Win32Network;
@@ -193,8 +197,6 @@ typedef GDKUser User;
#elif PLATFORM_ANDROID
-class ClipboardBase;
-typedef ClipboardBase Clipboard;
class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection;
class UnixReadWriteLock;
@@ -207,10 +209,12 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher;
class AndroidFile;
typedef AndroidFile File;
-class AndroidPlatform;
-typedef AndroidPlatform Platform;
class AndroidThread;
typedef AndroidThread Thread;
+class ClipboardBase;
+typedef ClipboardBase Clipboard;
+class AndroidPlatform;
+typedef AndroidPlatform Platform;
class AndroidWindow;
typedef AndroidWindow Window;
class UnixNetwork;
@@ -220,8 +224,6 @@ typedef UserBase User;
#elif PLATFORM_SWITCH
-class ClipboardBase;
-typedef ClipboardBase Clipboard;
class SwitchCriticalSection;
typedef SwitchCriticalSection CriticalSection;
class SwitchReadWriteLock;
@@ -234,10 +236,12 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher;
class SwitchFile;
typedef SwitchFile File;
-class SwitchPlatform;
-typedef SwitchPlatform Platform;
class SwitchThread;
typedef SwitchThread Thread;
+class ClipboardBase;
+typedef ClipboardBase Clipboard;
+class SwitchPlatform;
+typedef SwitchPlatform Platform;
class SwitchWindow;
typedef SwitchWindow Window;
class SwitchNetwork;
@@ -247,8 +251,6 @@ typedef SwitchUser User;
#elif PLATFORM_MAC
-class MacClipboard;
-typedef MacClipboard Clipboard;
class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection;
class UnixReadWriteLock;
@@ -261,12 +263,16 @@ class MacFileSystemWatcher;
typedef MacFileSystemWatcher FileSystemWatcher;
class UnixFile;
typedef UnixFile File;
-class MacPlatform;
-typedef MacPlatform Platform;
class AppleThread;
typedef AppleThread Thread;
+class MacClipboard;
+typedef MacClipboard Clipboard;
+#if !PLATFORM_SDL
+class MacPlatform;
+typedef MacPlatform Platform;
class MacWindow;
typedef MacWindow Window;
+#endif
class UnixNetwork;
typedef UnixNetwork Network;
class UserBase;
@@ -274,8 +280,6 @@ typedef UserBase User;
#elif PLATFORM_IOS
-class ClipboardBase;
-typedef ClipboardBase Clipboard;
class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection;
class UnixReadWriteLock;
@@ -288,10 +292,12 @@ class FileSystemWatcherBase;
typedef FileSystemWatcherBase FileSystemWatcher;
class iOSFile;
typedef iOSFile File;
-class iOSPlatform;
-typedef iOSPlatform Platform;
class AppleThread;
typedef AppleThread Thread;
+class ClipboardBase;
+typedef ClipboardBase Clipboard;
+class iOSPlatform;
+typedef iOSPlatform Platform;
class iOSWindow;
typedef iOSWindow Window;
class UnixNetwork;
@@ -304,3 +310,14 @@ typedef UserBase User;
#error Missing Types implementation!
#endif
+
+#if PLATFORM_SDL
+#if PLATFORM_LINUX
+class SDLClipboard;
+typedef SDLClipboard Clipboard;
+#endif
+class SDLPlatform;
+typedef SDLPlatform Platform;
+class SDLWindow;
+typedef SDLWindow Window;
+#endif
diff --git a/Source/Engine/Platform/UWP/UWPWindow.cpp b/Source/Engine/Platform/UWP/UWPWindow.cpp
index 0a061e427..05a88306a 100644
--- a/Source/Engine/Platform/UWP/UWPWindow.cpp
+++ b/Source/Engine/Platform/UWP/UWPWindow.cpp
@@ -364,11 +364,6 @@ void UWPWindow::Restore()
// Not supported
}
-bool UWPWindow::IsClosed() const
-{
- return _isClosing;
-}
-
void UWPWindow::BringToFront(bool force)
{
Focus();
diff --git a/Source/Engine/Platform/UWP/UWPWindow.h b/Source/Engine/Platform/UWP/UWPWindow.h
index 3da2a906e..6f4f0400b 100644
--- a/Source/Engine/Platform/UWP/UWPWindow.h
+++ b/Source/Engine/Platform/UWP/UWPWindow.h
@@ -160,7 +160,6 @@ public:
void Minimize() override;
void Maximize() override;
void Restore() override;
- bool IsClosed() const override;
void BringToFront(bool force = false) override;
void SetClientBounds(const Rectangle& clientArea) override;
void SetPosition(const Float2& position) override;
@@ -183,6 +182,7 @@ public:
void Focus() override;
void SetTitle(const StringView& title) override;
DragDropEffect DoDragDrop(const StringView& data) override;
+ using WindowBase::DoDragDrop;
void StartTrackingMouse(bool useMouseScreenOffset) override;
void EndTrackingMouse() override;
void SetCursor(CursorType type) override;
diff --git a/Source/Engine/Platform/Window.cs b/Source/Engine/Platform/Window.cs
index 86b872513..1a593616d 100644
--- a/Source/Engine/Platform/Window.cs
+++ b/Source/Engine/Platform/Window.cs
@@ -17,31 +17,37 @@ namespace FlaxEngine
///
/// Perform window hit test delegate.
///
- /// The mouse position. The coordinate is relative to the upper-left corner of the screen. Use to convert position into client space coordinates.
+ /// The mouse position. The coordinate is relative to the upper-left corner of the screen. Use to convert position into client space coordinates.
/// Hit result.
- public delegate WindowHitCodes HitTestDelegate(ref Float2 mouse);
+ public delegate WindowHitCodes HitTestDelegate(ref Float2 mousePosition);
///
/// Perform mouse buttons action.
///
- /// The mouse position.
+ /// The mouse position.
/// The mouse buttons state.
/// 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.
- public delegate void MouseButtonDelegate(ref Float2 mouse, MouseButton button, ref bool handled);
+ public delegate void MouseButtonDelegate(ref Float2 mousePosition, MouseButton button, ref bool handled);
///
/// Perform mouse move action.
///
- /// The mouse position.
- public delegate void MouseMoveDelegate(ref Float2 mouse);
+ /// The mouse position.
+ public delegate void MouseMoveDelegate(ref Float2 mousePosition);
+
+ ///
+ /// Perform mouse move action in relative mode.
+ ///
+ /// The relative mouse motion.
+ public delegate void MouseMoveRelativeDelegate(ref Float2 motion);
///
/// Perform mouse wheel action.
///
- /// The mouse position.
+ /// The mouse position.
/// The mouse wheel move delta (can be positive or negative; normalized to [-1;1] range).
/// 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.
- public delegate void MouseWheelDelegate(ref Float2 mouse, float delta, ref bool handled);
+ public delegate void MouseWheelDelegate(ref Float2 mousePosition, float delta, ref bool handled);
///
/// Perform touch action.
@@ -99,9 +105,14 @@ namespace FlaxEngine
public event MouseWheelDelegate MouseWheel;
///
- /// Event fired when mouse moves
+ /// Event fired when mouse moves.
///
public event MouseMoveDelegate MouseMove;
+
+ ///
+ /// Event fired when mouse moves in relative mode.
+ ///
+ public event MouseMoveRelativeDelegate MouseMoveRelative;
///
/// Event fired when mouse leaves window.
@@ -273,6 +284,12 @@ namespace FlaxEngine
MouseMove?.Invoke(ref pos);
GUI.OnMouseMove(pos);
}
+
+ internal void Internal_OnMouseMoveRelative(ref Float2 motion)
+ {
+ MouseMoveRelative?.Invoke(ref motion);
+ GUI.OnMouseMoveRelative(motion);
+ }
internal void Internal_OnMouseLeave()
{
diff --git a/Source/Engine/Platform/Window.h b/Source/Engine/Platform/Window.h
index 616b76bd2..92eca4d41 100644
--- a/Source/Engine/Platform/Window.h
+++ b/Source/Engine/Platform/Window.h
@@ -2,7 +2,9 @@
#pragma once
-#if PLATFORM_WINDOWS
+#if PLATFORM_SDL
+#include "SDL/SDLWindow.h"
+#elif PLATFORM_WINDOWS
#include "Windows/WindowsWindow.h"
#elif PLATFORM_UWP
#include "UWP/UWPWindow.h"
diff --git a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp
index efce1abdc..3019b49a7 100644
--- a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp
+++ b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp
@@ -2,6 +2,7 @@
#if PLATFORM_WINDOWS
+#include "WindowsWindow.h"
#include "WindowsFileSystem.h"
#include "Engine/Platform/File.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))))
fd->SetFolder(defaultFolder);
- HWND hwndOwner = parentWindow ? parentWindow->GetHWND() : NULL;
+ HWND hwndOwner = parentWindow ? (HWND)parentWindow->GetNativePtr() : NULL;
if (SUCCEEDED(fd->Show(hwndOwner)))
{
ComPtr si;
diff --git a/Source/Engine/Platform/Windows/WindowsInput.cpp b/Source/Engine/Platform/Windows/WindowsInput.cpp
index edcc01141..64299b6a3 100644
--- a/Source/Engine/Platform/Windows/WindowsInput.cpp
+++ b/Source/Engine/Platform/Windows/WindowsInput.cpp
@@ -1,6 +1,6 @@
// Copyright (c) Wojciech Figat. All rights reserved.
-#if PLATFORM_WINDOWS
+#if PLATFORM_WINDOWS && !PLATFORM_SDL
#include "WindowsInput.h"
#include "WindowsWindow.h"
@@ -265,19 +265,38 @@ bool WindowsMouse::WndProc(Window* window, const UINT msg, WPARAM wParam, LPARAM
}
case WM_LBUTTONDBLCLK:
{
- OnMouseDoubleClick(mousePos, MouseButton::Left, window);
+ if (!Input::Mouse->IsRelative())
+ OnMouseDoubleClick(mousePos, MouseButton::Left, window);
+ else
+ OnMouseDown(mousePos, MouseButton::Left, window);
result = true;
break;
}
case WM_RBUTTONDBLCLK:
{
- OnMouseDoubleClick(mousePos, MouseButton::Right, window);
+ if (!Input::Mouse->IsRelative())
+ OnMouseDoubleClick(mousePos, MouseButton::Right, window);
+ else
+ OnMouseDown(mousePos, MouseButton::Right, window);
result = true;
break;
}
case WM_MBUTTONDBLCLK:
{
- OnMouseDoubleClick(mousePos, MouseButton::Middle, window);
+ if (!Input::Mouse->IsRelative())
+ 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;
break;
}
diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp
index 42accf298..cc339c9e9 100644
--- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp
+++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp
@@ -4,6 +4,8 @@
#include "Engine/Platform/Platform.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/CreateWindowSettings.h"
#include "Engine/Platform/CreateProcessSettings.h"
@@ -34,7 +36,6 @@
#define CLR_EXCEPTION 0xE0434352
#define VCPP_EXCEPTION 0xE06D7363
-const Char* WindowsPlatform::ApplicationWindowClass = TEXT("FlaxWindow");
void* WindowsPlatform::Instance = nullptr;
#if CRASH_LOG_ENABLE || TRACY_ENABLE
@@ -288,6 +289,8 @@ struct CPUBrand
#endif
+#if !PLATFORM_SDL
+
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Find window to process that message
@@ -305,6 +308,8 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
return DefWindowProc(hwnd, msg, wParam, lParam);
}
+#endif
+
long __stdcall WindowsPlatform::SehExceptionHandler(EXCEPTION_POINTERS* ep)
{
if (ep->ExceptionRecord->ExceptionCode == CLR_EXCEPTION)
@@ -469,11 +474,12 @@ DialogResult MessageBox::Show(Window* parent, const StringView& text, const Stri
flags |= MB_ICONHAND;
break;
case MessageBoxIcon::Information:
+ case MessageBoxIcon::Question:
flags |= MB_ICONINFORMATION;
break;
- case MessageBoxIcon::Question:
- flags |= MB_ICONQUESTION;
- break;
+ //case MessageBoxIcon::Question:
+ // flags |= MB_ICONQUESTION;
+ // break;
case MessageBoxIcon::Stop:
flags |= MB_ICONSTOP;
break;
@@ -611,6 +617,7 @@ void WindowsPlatform::PreInit(void* hInstance)
// Disable the process from being showing "ghosted" while not responding messages during slow tasks
DisableProcessWindowsGhosting();
+#if !PLATFORM_SDL
// Register window class
WNDCLASS windowsClass;
Platform::MemoryClear(&windowsClass, sizeof(WNDCLASS));
@@ -619,12 +626,13 @@ void WindowsPlatform::PreInit(void* hInstance)
windowsClass.hInstance = (HINSTANCE)Instance;
windowsClass.hIcon = LoadIconW(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDR_MAINFRAME));
windowsClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
- windowsClass.lpszClassName = ApplicationWindowClass;
+ windowsClass.lpszClassName = ApplicationClassName;
if (!RegisterClassW(&windowsClass))
{
Error(TEXT("Window class registration failed!"));
exit(-1);
}
+#endif
// Init OLE
if (OleInitialize(nullptr) != S_OK)
@@ -787,7 +795,9 @@ bool WindowsPlatform::Init()
}
OnPlatformUserAdd(New(userName));
+#if !PLATFORM_SDL
WindowsInput::Init();
+#endif
return false;
}
@@ -821,7 +831,9 @@ void WindowsPlatform::LogInfo()
void WindowsPlatform::Tick()
{
+#if !PLATFORM_SDL
WindowsInput::Update();
+#endif
// Check to see if any messages are waiting in the queue
MSG msg;
@@ -852,8 +864,10 @@ void WindowsPlatform::Exit()
FlaxDbgHelpUnlock();
#endif
+#if !PLATFORM_SDL
// Unregister app class
- UnregisterClassW(ApplicationWindowClass, nullptr);
+ UnregisterClassW(ApplicationClassName, nullptr);
+#endif
Win32Platform::Exit();
}
@@ -930,10 +944,12 @@ BatteryInfo WindowsPlatform::GetBatteryInfo()
return info;
}
+#if !PLATFORM_SDL
int32 WindowsPlatform::GetDpi()
{
return SystemDpi;
}
+#endif
String WindowsPlatform::GetUserLocaleName()
{
@@ -1310,10 +1326,12 @@ int32 WindowsPlatform::CreateProcess(CreateProcessSettings& settings)
return result;
}
+#if !PLATFORM_SDL
Window* WindowsPlatform::CreateWindow(const CreateWindowSettings& settings)
{
return New(settings);
}
+#endif
void* WindowsPlatform::LoadLibrary(const Char* filename)
{
@@ -1542,4 +1560,49 @@ void WindowsPlatform::CollectCrashData(const String& crashDataFolder, void* cont
#endif
+#if USE_EDITOR
+
+#include "Engine/Core/Math/Color32.h"
+
+WIN_API COLORREF WIN_API_CALLCONV GetPixel(_In_ HDC hdc, _In_ int x, _In_ int y);
+#define GetRValue(rgb) (LOBYTE(rgb))
+#define GetGValue(rgb) (LOBYTE(((WORD)(rgb)) >> 8))
+#define GetBValue(rgb) (LOBYTE((rgb) >> 16))
+#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 = PlatformBase::GetScreenColorAt(cursorPos);
+ PlatformBase::PickScreenColorDone(colorPicked);
+ return 1;
+ }
+ return CallNextHookEx(NULL, nCode, wParam, lParam);
+}
+
+Color32 WindowsPlatform::GetScreenColorAt(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 WindowsPlatform::PickScreenColor()
+{
+ MouseCallbackHook = SetWindowsHookEx(WH_MOUSE_LL, OnScreenUtilsMouseCallback, NULL, NULL);
+ if (MouseCallbackHook == NULL)
+ LOG(Warning, "Failed to set mouse hook (GetLastError={})", GetLastError());
+}
+
+#endif
+
#endif
diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.h b/Source/Engine/Platform/Windows/WindowsPlatform.h
index a8f0e4996..a30e3e145 100644
--- a/Source/Engine/Platform/Windows/WindowsPlatform.h
+++ b/Source/Engine/Platform/Windows/WindowsPlatform.h
@@ -13,11 +13,6 @@ class FLAXENGINE_API WindowsPlatform : public Win32Platform
{
public:
- ///
- /// Win32 application windows class name.
- ///
- static const Char* ApplicationWindowClass;
-
///
/// Handle to Win32 application instance.
///
@@ -70,7 +65,9 @@ public:
static String GetSystemName();
static Version GetSystemVersion();
static BatteryInfo GetBatteryInfo();
+#if !PLATFORM_SDL
static int32 GetDpi();
+#endif
static String GetUserLocaleName();
static String GetComputerName();
static bool GetHasFocus();
@@ -85,12 +82,18 @@ public:
static bool GetEnvironmentVariable(const String& name, String& value);
static bool SetEnvironmentVariable(const String& name, const String& value);
static int32 CreateProcess(CreateProcessSettings& settings);
+#if !PLATFORM_SDL
static Window* CreateWindow(const CreateWindowSettings& settings);
+#endif
static void* LoadLibrary(const Char* filename);
#if CRASH_LOG_ENABLE
static Array GetStackFrames(int32 skipCount = 0, int32 maxDepth = 60, void* context = nullptr);
static void CollectCrashData(const String& crashDataFolder, void* context = nullptr);
#endif
+#if USE_EDITOR
+ static Color32 GetScreenColorAt(const Float2& pos);
+ static void PickScreenColor();
+#endif
};
#endif
diff --git a/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp b/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp
new file mode 100644
index 000000000..1aa5c903e
--- /dev/null
+++ b/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp
@@ -0,0 +1,665 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if PLATFORM_WINDOWS
+
+#if PLATFORM_SDL
+#include "Engine/Platform/SDL/SDLWindow.h"
+#endif
+
+#include "Engine/Platform/Windows/WindowsWindow.h"
+
+#if USE_EDITOR
+#include "Engine/Core/Collections/Array.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Platform/IGuiData.h"
+#include "Engine/Platform/Base/DragDropHelper.h"
+#include "Engine/Input/Input.h"
+#include "Engine/Input/Mouse.h"
+#endif
+
+#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
+#include
+
+#if USE_EDITOR
+#include
+#include
+#endif
+
+#if USE_EDITOR
+
+Windows::HRESULT Window::QueryInterface(const Windows::IID& id, void** ppvObject)
+{
+ // Check to see what interface has been requested
+ if ((const IID&)id == IID_IUnknown || (const IID&)id == IID_IDropTarget)
+ {
+ AddRef();
+ *ppvObject = this;
+ return S_OK;
+ }
+
+ // No interface
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+}
+
+Windows::ULONG Window::AddRef()
+{
+ _InterlockedIncrement(&_refCount);
+ return _refCount;
+}
+
+Windows::ULONG Window::Release()
+{
+ return _InterlockedDecrement(&_refCount);
+}
+
+HGLOBAL duplicateGlobalMem(HGLOBAL hMem)
+{
+ auto len = GlobalSize(hMem);
+ auto source = GlobalLock(hMem);
+ auto dest = GlobalAlloc(GMEM_FIXED, len);
+ Platform::MemoryCopy(dest, source, len);
+ GlobalUnlock(hMem);
+ return dest;
+}
+
+DWORD dropEffect2OleEnum(DragDropEffect effect)
+{
+ DWORD result;
+ switch (effect)
+ {
+ case DragDropEffect::None:
+ result = DROPEFFECT_NONE;
+ break;
+ case DragDropEffect::Copy:
+ result = DROPEFFECT_COPY;
+ break;
+ case DragDropEffect::Move:
+ result = DROPEFFECT_MOVE;
+ break;
+ case DragDropEffect::Link:
+ result = DROPEFFECT_LINK;
+ break;
+ default:
+ result = DROPEFFECT_NONE;
+ break;
+ }
+ return result;
+}
+
+DragDropEffect dropEffectFromOleEnum(DWORD effect)
+{
+ DragDropEffect result;
+ switch (effect)
+ {
+ case DROPEFFECT_NONE:
+ result = DragDropEffect::None;
+ break;
+ case DROPEFFECT_COPY:
+ result = DragDropEffect::Copy;
+ break;
+ case DROPEFFECT_MOVE:
+ result = DragDropEffect::Move;
+ break;
+ case DROPEFFECT_LINK:
+ result = DragDropEffect::Link;
+ break;
+ default:
+ result = DragDropEffect::None;
+ break;
+ }
+ return result;
+}
+
+HANDLE StringToHandle(const StringView& str)
+{
+ // Allocate and lock a global memory buffer.
+ // Make it fixed data so we don't have to use GlobalLock
+ const int32 length = str.Length();
+ char* ptr = static_cast(GlobalAlloc(GMEM_FIXED, length + 1));
+
+ // Copy the string into the buffer as ANSI text
+ StringUtils::ConvertUTF162ANSI(str.Get(), ptr, length);
+ ptr[length] = '\0';
+
+ return ptr;
+}
+
+void DeepCopyFormatEtc(FORMATETC* dest, FORMATETC* source)
+{
+ // Copy the source FORMATETC into dest
+ *dest = *source;
+
+ if (source->ptd)
+ {
+ // Allocate memory for the DVTARGETDEVICE if necessary
+ dest->ptd = static_cast(CoTaskMemAlloc(sizeof(DVTARGETDEVICE)));
+
+ // Copy the contents of the source DVTARGETDEVICE into dest->ptd
+ *(dest->ptd) = *(source->ptd);
+ }
+}
+
+HRESULT CreateEnumFormatEtc(UINT nNumFormats, FORMATETC* pFormatEtc, IEnumFORMATETC** ppEnumFormatEtc);
+
+///
+/// GUI data for Windows platform
+///
+class WindowsGuiData : public IGuiData
+{
+private:
+ Type _type;
+ Array _data;
+
+public:
+ ///
+ /// Init
+ ///
+ WindowsGuiData()
+ : _type(Type::Unknown)
+ , _data(1)
+ {
+ }
+
+public:
+ ///
+ /// Init from Ole IDataObject
+ ///
+ /// Object
+ void Init(IDataObject* pDataObj)
+ {
+ // Temporary data
+ FORMATETC fmtetc;
+ STGMEDIUM stgmed;
+
+ // Clear
+ _type = Type::Unknown;
+ _data.Clear();
+
+ // Check type
+ fmtetc = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+ if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK)
+ {
+ // Text
+ _type = Type::Text;
+
+ // Get data
+ char* text = static_cast(GlobalLock(stgmed.hGlobal));
+ _data.Add(String(text));
+ GlobalUnlock(stgmed.hGlobal);
+ ReleaseStgMedium(&stgmed);
+ }
+ else
+ {
+ fmtetc = { CF_UNICODETEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+ if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK)
+ {
+ // Unicode Text
+ _type = Type::Text;
+
+ // Get data
+ Char* text = static_cast(GlobalLock(stgmed.hGlobal));
+ _data.Add(String(text));
+ GlobalUnlock(stgmed.hGlobal);
+ ReleaseStgMedium(&stgmed);
+ }
+ else
+ {
+ fmtetc = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+ if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK)
+ {
+ // Files
+ _type = Type::Files;
+
+ // Get data
+ Char item[MAX_PATH];
+ HDROP hdrop = static_cast(GlobalLock(stgmed.hGlobal));
+ UINT filesCount = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0);
+ for (UINT i = 0; i < filesCount; i++)
+ {
+ if (DragQueryFileW(hdrop, i, item, MAX_PATH) != 0)
+ {
+ _data.Add(String(item));
+ }
+ }
+ GlobalUnlock(stgmed.hGlobal);
+ ReleaseStgMedium(&stgmed);
+ }
+ }
+ }
+ }
+
+public:
+ // [IGuiData]
+ Type GetType() const override
+ {
+ return _type;
+ }
+ String GetAsText() const override
+ {
+ String result;
+ if (_type == Type::Text)
+ {
+ result = _data[0];
+ }
+ return result;
+ }
+ void GetAsFiles(Array* files) const override
+ {
+ if (_type == Type::Files)
+ {
+ files->Add(_data);
+ }
+ }
+};
+
+///
+/// Tool class for Windows Ole support
+///
+class WindowsEnumFormatEtc : public IEnumFORMATETC
+{
+private:
+ ULONG _refCount;
+ ULONG _index;
+ ULONG _formatsCount;
+ FORMATETC* _formatEtc;
+
+public:
+ WindowsEnumFormatEtc(FORMATETC* pFormatEtc, int32 nNumFormats)
+ : _refCount(1)
+ , _index(0)
+ , _formatsCount(nNumFormats)
+ , _formatEtc(nullptr)
+ {
+ // Allocate memory
+ _formatEtc = new FORMATETC[nNumFormats];
+
+ // Copy the FORMATETC structures
+ for (int32 i = 0; i < nNumFormats; i++)
+ {
+ DeepCopyFormatEtc(&_formatEtc[i], &pFormatEtc[i]);
+ }
+ }
+
+ ~WindowsEnumFormatEtc()
+ {
+ if (_formatEtc)
+ {
+ for (uint32 i = 0; i < _formatsCount; i++)
+ {
+ if (_formatEtc[i].ptd)
+ {
+ CoTaskMemFree(_formatEtc[i].ptd);
+ }
+ }
+
+ delete[] _formatEtc;
+ }
+ }
+
+public:
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) override
+ {
+ // Check to see what interface has been requested
+ if (riid == IID_IEnumFORMATETC || riid == IID_IUnknown)
+ {
+ AddRef();
+ *ppvObject = this;
+ return S_OK;
+ }
+
+ // No interface
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+ }
+ ULONG STDMETHODCALLTYPE AddRef() override
+ {
+ _InterlockedIncrement(&_refCount);
+ return _refCount;
+ }
+ ULONG STDMETHODCALLTYPE Release() override
+ {
+ ULONG ulRefCount = _InterlockedDecrement(&_refCount);
+ if (_refCount == 0)
+ {
+ delete this;
+ }
+ return ulRefCount;
+ }
+
+ // [IEnumFormatEtc]
+ HRESULT STDMETHODCALLTYPE Next(ULONG celt, FORMATETC* pFormatEtc, ULONG* pceltFetched) override
+ {
+ ULONG copied = 0;
+
+ // validate arguments
+ if (celt == 0 || pFormatEtc == nullptr)
+ return E_INVALIDARG;
+
+ // copy FORMATETC structures into caller's buffer
+ while (_index < _formatsCount && copied < celt)
+ {
+ DeepCopyFormatEtc(&pFormatEtc[copied], &_formatEtc[_index]);
+ copied++;
+ _index++;
+ }
+
+ // store result
+ if (pceltFetched != nullptr)
+ *pceltFetched = copied;
+
+ // did we copy all that was requested?
+ return (copied == celt) ? S_OK : S_FALSE;
+ }
+ HRESULT STDMETHODCALLTYPE Skip(ULONG celt) override
+ {
+ _index += celt;
+ return (_index <= _formatsCount) ? S_OK : S_FALSE;
+ }
+ HRESULT STDMETHODCALLTYPE Reset() override
+ {
+ _index = 0;
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE Clone(IEnumFORMATETC** ppEnumFormatEtc) override
+ {
+ HRESULT result;
+
+ // Make a duplicate enumerator
+ result = CreateEnumFormatEtc(_formatsCount, _formatEtc, ppEnumFormatEtc);
+
+ if (result == S_OK)
+ {
+ // Manually set the index state
+ static_cast(*ppEnumFormatEtc)->_index = _index;
+ }
+
+ return result;
+ }
+};
+
+HRESULT CreateEnumFormatEtc(UINT nNumFormats, FORMATETC* pFormatEtc, IEnumFORMATETC** ppEnumFormatEtc)
+{
+ if (nNumFormats == 0 || pFormatEtc == nullptr || ppEnumFormatEtc == nullptr)
+ return E_INVALIDARG;
+ *ppEnumFormatEtc = new WindowsEnumFormatEtc(pFormatEtc, nNumFormats);
+ return *ppEnumFormatEtc ? S_OK : E_OUTOFMEMORY;
+}
+
+///
+/// Drag drop source and data container for Ole
+///
+class WindowsDragSource : public IDataObject, public IDropSource
+{
+private:
+ ULONG _refCount;
+ int32 _formatsCount;
+ FORMATETC* _formatEtc;
+ STGMEDIUM* _stgMedium;
+
+public:
+ WindowsDragSource(FORMATETC* fmtetc, STGMEDIUM* stgmed, int32 count)
+ : _refCount(1)
+ , _formatsCount(count)
+ , _formatEtc(nullptr)
+ , _stgMedium(nullptr)
+ {
+ // Allocate memory
+ _formatEtc = new FORMATETC[count];
+ _stgMedium = new STGMEDIUM[count];
+
+ // Copy descriptors
+ for (int32 i = 0; i < count; i++)
+ {
+ _formatEtc[i] = fmtetc[i];
+ _stgMedium[i] = stgmed[i];
+ }
+ }
+
+ virtual ~WindowsDragSource()
+ {
+ delete[] _formatEtc;
+ delete[] _stgMedium;
+ }
+
+public:
+ // [IUnknown]
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) override
+ {
+ // Check to see what interface has been requested
+ if (riid == IID_IDataObject || riid == IID_IUnknown || riid == IID_IDropSource)
+ {
+ AddRef();
+ *ppvObject = this;
+ return S_OK;
+ }
+
+ // No interface
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+ }
+
+ ULONG STDMETHODCALLTYPE AddRef() override
+ {
+ _InterlockedIncrement(&_refCount);
+ return _refCount;
+ }
+
+ ULONG STDMETHODCALLTYPE Release() override
+ {
+ ULONG ulRefCount = _InterlockedDecrement(&_refCount);
+ if (_refCount == 0)
+ {
+ delete this;
+ }
+ return ulRefCount;
+ }
+
+ // [IDropSource]
+ HRESULT STDMETHODCALLTYPE QueryContinueDrag(_In_ BOOL fEscapePressed, _In_ DWORD grfKeyState) override
+ {
+ // If the Escape key has been pressed since the last call, cancel the drop
+ if (fEscapePressed == TRUE || grfKeyState & MK_RBUTTON)
+ return DRAGDROP_S_CANCEL;
+
+ // If the LeftMouse button has been released, then do the drop!
+ if ((grfKeyState & MK_LBUTTON) == 0)
+ return DRAGDROP_S_DROP;
+
+ // Continue with the drag-drop
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE GiveFeedback(_In_ DWORD dwEffect) override
+ {
+ // TODO: allow to use custom mouse cursor during drop and drag operation
+ return DRAGDROP_S_USEDEFAULTCURSORS;
+ }
+
+ // [IDataObject]
+ HRESULT STDMETHODCALLTYPE GetData(_In_ FORMATETC* pformatetcIn, _Out_ STGMEDIUM* pmedium) override
+ {
+ if (pformatetcIn == nullptr || pmedium == nullptr)
+ return E_INVALIDARG;
+
+ // Try to match the specified FORMATETC with one of our supported formats
+ int32 index = lookupFormatEtc(pformatetcIn);
+ if (index == INVALID_INDEX)
+ return DV_E_FORMATETC;
+
+ // Found a match - transfer data into supplied storage medium
+ pmedium->tymed = _formatEtc[index].tymed;
+ pmedium->pUnkForRelease = nullptr;
+
+ // Copy the data into the caller's storage medium
+ switch (_formatEtc[index].tymed)
+ {
+ case TYMED_HGLOBAL:
+ pmedium->hGlobal = duplicateGlobalMem(_stgMedium[index].hGlobal);
+ break;
+
+ default:
+ return DV_E_FORMATETC;
+ }
+
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE GetDataHere(_In_ FORMATETC* pformatetc, _Inout_ STGMEDIUM* pmedium) override
+ {
+ return DATA_E_FORMATETC;
+ }
+ HRESULT STDMETHODCALLTYPE QueryGetData(__RPC__in_opt FORMATETC* pformatetc) override
+ {
+ return lookupFormatEtc(pformatetc) == INVALID_INDEX ? DV_E_FORMATETC : S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc(__RPC__in_opt FORMATETC* pformatectIn, __RPC__out FORMATETC* pformatetcOut) override
+ {
+ // Apparently we have to set this field to NULL even though we don't do anything else
+ pformatetcOut->ptd = nullptr;
+ return E_NOTIMPL;
+ }
+ HRESULT STDMETHODCALLTYPE SetData(_In_ FORMATETC* pformatetc, _In_ STGMEDIUM* pmedium, BOOL fRelease) override
+ {
+ return E_NOTIMPL;
+ }
+ HRESULT STDMETHODCALLTYPE EnumFormatEtc(DWORD dwDirection, __RPC__deref_out_opt IEnumFORMATETC** ppenumFormatEtc) override
+ {
+ // Only the get direction is supported for OLE
+ if (dwDirection == DATADIR_GET)
+ {
+ // TODO: use SHCreateStdEnumFmtEtc API call
+ return CreateEnumFormatEtc(_formatsCount, _formatEtc, ppenumFormatEtc);
+ }
+
+ // The direction specified is not supported for drag+drop
+ return E_NOTIMPL;
+ }
+ HRESULT STDMETHODCALLTYPE DAdvise(__RPC__in FORMATETC* pformatetc, DWORD advf, __RPC__in_opt IAdviseSink* pAdvSink, __RPC__out DWORD* pdwConnection) override
+ {
+ return OLE_E_ADVISENOTSUPPORTED;
+ }
+ HRESULT STDMETHODCALLTYPE DUnadvise(DWORD dwConnection) override
+ {
+ return OLE_E_ADVISENOTSUPPORTED;
+ }
+ HRESULT STDMETHODCALLTYPE EnumDAdvise(__RPC__deref_out_opt IEnumSTATDATA** ppenumAdvise) override
+ {
+ return OLE_E_ADVISENOTSUPPORTED;
+ }
+
+private:
+ int32 lookupFormatEtc(FORMATETC* pFormatEtc) const
+ {
+ // Check each of our formats in turn to see if one matches
+ for (int32 i = 0; i < _formatsCount; i++)
+ {
+ if ((_formatEtc[i].tymed & pFormatEtc->tymed) &&
+ _formatEtc[i].cfFormat == pFormatEtc->cfFormat &&
+ _formatEtc[i].dwAspect == pFormatEtc->dwAspect)
+ {
+ // Return index of stored format
+ return i;
+ }
+ }
+
+ // Format not found
+ return INVALID_INDEX;
+ }
+};
+
+WindowsGuiData GuiDragDropData;
+
+DragDropEffect Window::DoDragDrop(const StringView& data)
+{
+ // Create background worker that will keep updating GUI (perform rendering)
+ const auto task = New();
+ Task::StartNew(task);
+ while (task->GetState() == TaskState::Queued)
+ Platform::Sleep(1);
+
+ // Create descriptors
+ FORMATETC fmtetc = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+ STGMEDIUM stgmed = { TYMED_HGLOBAL, { nullptr }, nullptr };
+
+ // Create a HGLOBAL inside the storage medium
+ stgmed.hGlobal = StringToHandle(data);
+
+ // Create drop source
+ auto dropSource = new WindowsDragSource(&fmtetc, &stgmed, 1);
+
+ // Do the drag drop operation
+ DWORD dwEffect;
+ HRESULT result = ::DoDragDrop(dropSource, dropSource, DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK | DROPEFFECT_SCROLL, &dwEffect);
+
+ // Wait for job end
+ Platform::AtomicStore(&task->ExitFlag, 1);
+ task->Wait();
+
+ // Release allocated data
+ dropSource->Release();
+ ReleaseStgMedium(&stgmed);
+
+ // Fix hanging mouse state (Windows doesn't send WM_LBUTTONUP when we end the drag and drop)
+ if (Input::GetMouseButton(MouseButton::Left))
+ {
+ ::POINT point;
+ ::GetCursorPos(&point);
+#if PLATFORM_SDL
+ // Reset the internal button state in SDL
+ SendMessageA((HWND)_handle, WM_LBUTTONUP, 0, MAKELPARAM(point.x, point.y));
+#else
+ Input::Mouse->OnMouseUp(Float2((float)point.x, (float)point.y), MouseButton::Left, this);
+#endif
+ }
+
+ return SUCCEEDED(result) ? dropEffectFromOleEnum(dwEffect) : DragDropEffect::None;
+}
+
+HRESULT Window::DragEnter(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect)
+{
+ POINT p = { pt.x, pt.y };
+ ::ScreenToClient((HWND)_handle, &p);
+ GuiDragDropData.Init((IDataObject*)pDataObj);
+ DragDropEffect effect = DragDropEffect::None;
+ OnDragEnter(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect);
+ Focus();
+ *pdwEffect = dropEffect2OleEnum(effect);
+ return S_OK;
+}
+
+HRESULT Window::DragOver(Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect)
+{
+ POINT p = { pt.x, pt.y };
+ ::ScreenToClient((HWND)_handle, &p);
+ DragDropEffect effect = DragDropEffect::None;
+ OnDragOver(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect);
+ *pdwEffect = dropEffect2OleEnum(effect);
+ return S_OK;
+}
+
+HRESULT Window::DragLeave()
+{
+ OnDragLeave();
+ return S_OK;
+}
+
+HRESULT Window::Drop(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect)
+{
+ POINT p = { pt.x, pt.y };
+ ::ScreenToClient((HWND)_handle, &p);
+ GuiDragDropData.Init((IDataObject*)pDataObj);
+ DragDropEffect effect = DragDropEffect::None;
+ OnDragDrop(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect);
+ *pdwEffect = dropEffect2OleEnum(effect);
+ return S_OK;
+}
+
+#else
+
+DragDropEffect Window::DoDragDrop(const StringView& data)
+{
+ return DragDropEffect::None;
+}
+
+#endif
+
+#endif
diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp
index 9b9a8a670..bb8575496 100644
--- a/Source/Engine/Platform/Windows/WindowsWindow.cpp
+++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp
@@ -1,6 +1,6 @@
// Copyright (c) Wojciech Figat. All rights reserved.
-#if PLATFORM_WINDOWS
+#if PLATFORM_WINDOWS && !PLATFORM_SDL
#include "WindowsWindow.h"
#include "WindowsPlatform.h"
@@ -10,13 +10,6 @@
#include "Engine/Graphics/GPUSwapChain.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/GPUDevice.h"
-#if USE_EDITOR
-#include "Engine/Core/Collections/Array.h"
-#include "Engine/Platform/IGuiData.h"
-#include "Engine/Platform/Base/DragDropHelper.h"
-#include "Engine/Input/Input.h"
-#include "Engine/Input/Mouse.h"
-#endif
#include "../Win32/IncludeWindowsHeaders.h"
#include
#if USE_EDITOR
@@ -115,10 +108,10 @@ WindowsWindow::WindowsWindow(const CreateWindowSettings& settings)
// Create window style flags
style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
#if WINDOWS_USE_NEW_BORDER_LESS
- if (settings.IsRegularWindow)
+ if (settings.Type == WindowType::Regular)
style |= WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_SYSMENU | WS_THICKFRAME | WS_GROUP;
#elif WINDOWS_USE_NEWER_BORDER_LESS
- if (settings.IsRegularWindow)
+ if (settings.Type == WindowType::Regular)
style |= WS_THICKFRAME | WS_SYSMENU | WS_CAPTION;
#endif
exStyle |= WS_EX_WINDOWEDGE;
@@ -127,7 +120,7 @@ WindowsWindow::WindowsWindow(const CreateWindowSettings& settings)
// Creating the window
_handle = CreateWindowExW(
exStyle,
- Platform::ApplicationWindowClass,
+ Platform::ApplicationClassName,
settings.Title.GetText(),
style,
x,
@@ -162,7 +155,7 @@ WindowsWindow::WindowsWindow(const CreateWindowSettings& settings)
#if WINDOWS_USE_NEWER_BORDER_LESS
// Enable shadow
- if (_settings.IsRegularWindow && !_settings.HasBorder && IsCompositionEnabled())
+ if (settings.Type == WindowType::Regular && !_settings.HasBorder && IsCompositionEnabled())
{
const int margin[4] = { 1, 1, 1, 1 };
::DwmExtendFrameIntoClientArea(_handle, margin);
@@ -222,7 +215,7 @@ void WindowsWindow::Show()
// Show
ShowWindow(_handle, (_settings.AllowInput && _settings.ActivateWhenFirstShown) ? SW_SHOW : SW_SHOWNA);
#if WINDOWS_USE_NEW_BORDER_LESS
- if (!_settings.HasBorder && _settings.IsRegularWindow)
+ if (!_settings.HasBorder && settings.Type == WindowType::Regular)
{
SetWindowPos(_handle, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
}
@@ -292,10 +285,10 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool maximized)
lStyle |= WS_POPUP;
lStyle |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
#if WINDOWS_USE_NEW_BORDER_LESS
- if (_settings.IsRegularWindow)
+ if (_settings.Type == WindowType::Regular)
style |= WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_SYSMENU | WS_THICKFRAME | WS_GROUP;
#elif WINDOWS_USE_NEWER_BORDER_LESS
- if (_settings.IsRegularWindow)
+ if (_settings.Type == WindowType::Regular)
lStyle |= WS_THICKFRAME | WS_SYSMENU;
#endif
@@ -359,7 +352,7 @@ void WindowsWindow::Restore()
bool WindowsWindow::IsClosed() const
{
- return !HasHWND();
+ return !HasHWND() || EnumHasAnyFlags(Flags, ObjectFlags::WasMarkedToDelete);
}
bool WindowsWindow::IsForegroundWindow() const
@@ -371,7 +364,7 @@ void WindowsWindow::BringToFront(bool force)
{
ASSERT(HasHWND());
- if (_settings.IsRegularWindow)
+ if (_settings.Type == WindowType::Regular)
{
if (IsIconic(_handle))
{
@@ -698,36 +691,6 @@ void WindowsWindow::SetCursor(CursorType type)
UpdateCursor();
}
-#if USE_EDITOR
-
-Windows::HRESULT WindowsWindow::QueryInterface(const Windows::IID& id, void** ppvObject)
-{
- // Check to see what interface has been requested
- if ((const IID&)id == IID_IUnknown || (const IID&)id == IID_IDropTarget)
- {
- AddRef();
- *ppvObject = this;
- return S_OK;
- }
-
- // No interface
- *ppvObject = nullptr;
- return E_NOINTERFACE;
-}
-
-Windows::ULONG WindowsWindow::AddRef()
-{
- _InterlockedIncrement(&_refCount);
- return _refCount;
-}
-
-Windows::ULONG WindowsWindow::Release()
-{
- return _InterlockedDecrement(&_refCount);
-}
-
-#endif
-
void WindowsWindow::CheckForWindowResize()
{
// Skip for minimized window (GetClientRect for minimized window returns 0)
@@ -872,7 +835,7 @@ void WindowsWindow::UpdateRegion()
{
#if WINDOWS_USE_NEW_BORDER_LESS
// Use region to remove rounded corners of the window
- if (!_settings.HasBorder && _settings.IsRegularWindow)
+ if (!_settings.HasBorder && _settings.Type == WindowType::Regular)
{
if (!_maximized && !_isResizing)
{
@@ -998,7 +961,7 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam)
case WM_NCCALCSIZE:
{
#if WINDOWS_USE_NEW_BORDER_LESS
- if (wParam && !_settings.HasBorder && _settings.IsRegularWindow)
+ if (wParam && !_settings.HasBorder && _settings.Type == WindowType::Regular)
{
if (_maximized)
{
@@ -1032,7 +995,7 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam)
}
}
#elif WINDOWS_USE_NEWER_BORDER_LESS
- if (wParam == TRUE && !_settings.HasBorder) // && _settings.IsRegularWindow)
+ if (wParam == TRUE && !_settings.HasBorder) // && _settings.Type == WindowType::Regular)
{
// In maximized mode fill the whole work area of the monitor (excludes task bar)
if (IsWindowMaximized(_handle))
@@ -1345,617 +1308,11 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam)
if (_settings.AllowInput)
{
- if (WindowsInput::WndProc(this, msg, wParam, lParam))
+ if (WindowsInput::WndProc((Window*)this, msg, wParam, lParam))
return true;
}
return DefWindowProc(_handle, msg, wParam, lParam);
}
-#if USE_EDITOR
-
-HGLOBAL duplicateGlobalMem(HGLOBAL hMem)
-{
- auto len = GlobalSize(hMem);
- auto source = GlobalLock(hMem);
- auto dest = GlobalAlloc(GMEM_FIXED, len);
- Platform::MemoryCopy(dest, source, len);
- GlobalUnlock(hMem);
- return dest;
-}
-
-DWORD dropEffect2OleEnum(DragDropEffect effect)
-{
- DWORD result;
- switch (effect)
- {
- case DragDropEffect::None:
- result = DROPEFFECT_NONE;
- break;
- case DragDropEffect::Copy:
- result = DROPEFFECT_COPY;
- break;
- case DragDropEffect::Move:
- result = DROPEFFECT_MOVE;
- break;
- case DragDropEffect::Link:
- result = DROPEFFECT_LINK;
- break;
- default:
- result = DROPEFFECT_NONE;
- break;
- }
- return result;
-}
-
-DragDropEffect dropEffectFromOleEnum(DWORD effect)
-{
- DragDropEffect result;
- switch (effect)
- {
- case DROPEFFECT_NONE:
- result = DragDropEffect::None;
- break;
- case DROPEFFECT_COPY:
- result = DragDropEffect::Copy;
- break;
- case DROPEFFECT_MOVE:
- result = DragDropEffect::Move;
- break;
- case DROPEFFECT_LINK:
- result = DragDropEffect::Link;
- break;
- default:
- result = DragDropEffect::None;
- break;
- }
- return result;
-}
-
-HANDLE StringToHandle(const StringView& str)
-{
- // Allocate and lock a global memory buffer.
- // Make it fixed data so we don't have to use GlobalLock
- const int32 length = str.Length();
- char* ptr = static_cast(GlobalAlloc(GMEM_FIXED, length + 1));
-
- // Copy the string into the buffer as ANSI text
- StringUtils::ConvertUTF162ANSI(str.Get(), ptr, length);
- ptr[length] = '\0';
-
- return ptr;
-}
-
-void DeepCopyFormatEtc(FORMATETC* dest, FORMATETC* source)
-{
- // Copy the source FORMATETC into dest
- *dest = *source;
-
- if (source->ptd)
- {
- // Allocate memory for the DVTARGETDEVICE if necessary
- dest->ptd = static_cast(CoTaskMemAlloc(sizeof(DVTARGETDEVICE)));
-
- // Copy the contents of the source DVTARGETDEVICE into dest->ptd
- *(dest->ptd) = *(source->ptd);
- }
-}
-
-HRESULT CreateEnumFormatEtc(UINT nNumFormats, FORMATETC* pFormatEtc, IEnumFORMATETC** ppEnumFormatEtc);
-
-///
-/// GUI data for Windows platform
-///
-class WindowsGuiData : public IGuiData
-{
-private:
- Type _type;
- Array _data;
-
-public:
- ///
- /// Init
- ///
- WindowsGuiData()
- : _type(Type::Unknown)
- , _data(1)
- {
- }
-
-public:
- ///
- /// Init from Ole IDataObject
- ///
- /// Object
- void Init(IDataObject* pDataObj)
- {
- // Temporary data
- FORMATETC fmtetc;
- STGMEDIUM stgmed;
-
- // Clear
- _type = Type::Unknown;
- _data.Clear();
-
- // Check type
- fmtetc = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
- if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK)
- {
- // Text
- _type = Type::Text;
-
- // Get data
- char* text = static_cast(GlobalLock(stgmed.hGlobal));
- _data.Add(String(text));
- GlobalUnlock(stgmed.hGlobal);
- ReleaseStgMedium(&stgmed);
- }
- else
- {
- fmtetc = { CF_UNICODETEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
- if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK)
- {
- // Unicode Text
- _type = Type::Text;
-
- // Get data
- Char* text = static_cast(GlobalLock(stgmed.hGlobal));
- _data.Add(String(text));
- GlobalUnlock(stgmed.hGlobal);
- ReleaseStgMedium(&stgmed);
- }
- else
- {
- fmtetc = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
- if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK)
- {
- // Files
- _type = Type::Files;
-
- // Get data
- Char item[MAX_PATH];
- HDROP hdrop = static_cast(GlobalLock(stgmed.hGlobal));
- UINT filesCount = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0);
- for (UINT i = 0; i < filesCount; i++)
- {
- if (DragQueryFileW(hdrop, i, item, MAX_PATH) != 0)
- {
- _data.Add(String(item));
- }
- }
- GlobalUnlock(stgmed.hGlobal);
- ReleaseStgMedium(&stgmed);
- }
- }
- }
- }
-
-public:
- // [IGuiData]
- Type GetType() const override
- {
- return _type;
- }
- String GetAsText() const override
- {
- String result;
- if (_type == Type::Text)
- {
- result = _data[0];
- }
- return result;
- }
- void GetAsFiles(Array* files) const override
- {
- if (_type == Type::Files)
- {
- files->Add(_data);
- }
- }
-};
-
-///
-/// Tool class for Windows Ole support
-///
-class WindowsEnumFormatEtc : public IEnumFORMATETC
-{
-private:
- ULONG _refCount;
- ULONG _index;
- ULONG _formatsCount;
- FORMATETC* _formatEtc;
-
-public:
- WindowsEnumFormatEtc(FORMATETC* pFormatEtc, int32 nNumFormats)
- : _refCount(1)
- , _index(0)
- , _formatsCount(nNumFormats)
- , _formatEtc(nullptr)
- {
- // Allocate memory
- _formatEtc = new FORMATETC[nNumFormats];
-
- // Copy the FORMATETC structures
- for (int32 i = 0; i < nNumFormats; i++)
- {
- DeepCopyFormatEtc(&_formatEtc[i], &pFormatEtc[i]);
- }
- }
-
- ~WindowsEnumFormatEtc()
- {
- if (_formatEtc)
- {
- for (uint32 i = 0; i < _formatsCount; i++)
- {
- if (_formatEtc[i].ptd)
- {
- CoTaskMemFree(_formatEtc[i].ptd);
- }
- }
-
- delete[] _formatEtc;
- }
- }
-
-public:
- HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR * ppvObject) override
- {
- // Check to see what interface has been requested
- if (riid == IID_IEnumFORMATETC || riid == IID_IUnknown)
- {
- AddRef();
- *ppvObject = this;
- return S_OK;
- }
-
- // No interface
- *ppvObject = nullptr;
- return E_NOINTERFACE;
- }
- ULONG STDMETHODCALLTYPE AddRef() override
- {
- _InterlockedIncrement(&_refCount);
- return _refCount;
- }
- ULONG STDMETHODCALLTYPE Release() override
- {
- ULONG ulRefCount = _InterlockedDecrement(&_refCount);
- if (_refCount == 0)
- {
- delete this;
- }
- return ulRefCount;
- }
-
- // [IEnumFormatEtc]
- HRESULT STDMETHODCALLTYPE Next(ULONG celt, FORMATETC* pFormatEtc, ULONG* pceltFetched) override
- {
- ULONG copied = 0;
-
- // validate arguments
- if (celt == 0 || pFormatEtc == nullptr)
- return E_INVALIDARG;
-
- // copy FORMATETC structures into caller's buffer
- while (_index < _formatsCount && copied < celt)
- {
- DeepCopyFormatEtc(&pFormatEtc[copied], &_formatEtc[_index]);
- copied++;
- _index++;
- }
-
- // store result
- if (pceltFetched != nullptr)
- *pceltFetched = copied;
-
- // did we copy all that was requested?
- return (copied == celt) ? S_OK : S_FALSE;
- }
- HRESULT STDMETHODCALLTYPE Skip(ULONG celt) override
- {
- _index += celt;
- return (_index <= _formatsCount) ? S_OK : S_FALSE;
- }
- HRESULT STDMETHODCALLTYPE Reset() override
- {
- _index = 0;
- return S_OK;
- }
- HRESULT STDMETHODCALLTYPE Clone(IEnumFORMATETC** ppEnumFormatEtc) override
- {
- HRESULT result;
-
- // Make a duplicate enumerator
- result = CreateEnumFormatEtc(_formatsCount, _formatEtc, ppEnumFormatEtc);
-
- if (result == S_OK)
- {
- // Manually set the index state
- static_cast(*ppEnumFormatEtc)->_index = _index;
- }
-
- return result;
- }
-};
-
-HRESULT CreateEnumFormatEtc(UINT nNumFormats, FORMATETC* pFormatEtc, IEnumFORMATETC** ppEnumFormatEtc)
-{
- if (nNumFormats == 0 || pFormatEtc == nullptr || ppEnumFormatEtc == nullptr)
- return E_INVALIDARG;
- *ppEnumFormatEtc = new WindowsEnumFormatEtc(pFormatEtc, nNumFormats);
- return *ppEnumFormatEtc ? S_OK : E_OUTOFMEMORY;
-}
-
-///
-/// Drag drop source and data container for Ole
-///
-class WindowsDragSource : public IDataObject, public IDropSource
-{
-private:
- ULONG _refCount;
- int32 _formatsCount;
- FORMATETC* _formatEtc;
- STGMEDIUM* _stgMedium;
-
-public:
- WindowsDragSource(FORMATETC* fmtetc, STGMEDIUM* stgmed, int32 count)
- : _refCount(1)
- , _formatsCount(count)
- , _formatEtc(nullptr)
- , _stgMedium(nullptr)
- {
- // Allocate memory
- _formatEtc = new FORMATETC[count];
- _stgMedium = new STGMEDIUM[count];
-
- // Copy descriptors
- for (int32 i = 0; i < count; i++)
- {
- _formatEtc[i] = fmtetc[i];
- _stgMedium[i] = stgmed[i];
- }
- }
-
- virtual ~WindowsDragSource()
- {
- delete[] _formatEtc;
- delete[] _stgMedium;
- }
-
-public:
- // [IUnknown]
- HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR * ppvObject) override
- {
- // Check to see what interface has been requested
- if (riid == IID_IDataObject || riid == IID_IUnknown || riid == IID_IDropSource)
- {
- AddRef();
- *ppvObject = this;
- return S_OK;
- }
-
- // No interface
- *ppvObject = nullptr;
- return E_NOINTERFACE;
- }
-
- ULONG STDMETHODCALLTYPE AddRef() override
- {
- _InterlockedIncrement(&_refCount);
- return _refCount;
- }
-
- ULONG STDMETHODCALLTYPE Release() override
- {
- ULONG ulRefCount = _InterlockedDecrement(&_refCount);
- if (_refCount == 0)
- {
- delete this;
- }
- return ulRefCount;
- }
-
- // [IDropSource]
- HRESULT STDMETHODCALLTYPE QueryContinueDrag(_In_ BOOL fEscapePressed, _In_ DWORD grfKeyState) override
- {
- // If the Escape key has been pressed since the last call, cancel the drop
- if (fEscapePressed == TRUE || grfKeyState & MK_RBUTTON)
- return DRAGDROP_S_CANCEL;
-
- // If the LeftMouse button has been released, then do the drop!
- if ((grfKeyState & MK_LBUTTON) == 0)
- return DRAGDROP_S_DROP;
-
- // Continue with the drag-drop
- return S_OK;
- }
- HRESULT STDMETHODCALLTYPE GiveFeedback(_In_ DWORD dwEffect) override
- {
- // TODO: allow to use custom mouse cursor during drop and drag operation
- return DRAGDROP_S_USEDEFAULTCURSORS;
- }
-
- // [IDataObject]
- HRESULT STDMETHODCALLTYPE GetData(_In_ FORMATETC* pformatetcIn, _Out_ STGMEDIUM* pmedium) override
- {
- if (pformatetcIn == nullptr || pmedium == nullptr)
- return E_INVALIDARG;
-
- // Try to match the specified FORMATETC with one of our supported formats
- int32 index = lookupFormatEtc(pformatetcIn);
- if (index == INVALID_INDEX)
- return DV_E_FORMATETC;
-
- // Found a match - transfer data into supplied storage medium
- pmedium->tymed = _formatEtc[index].tymed;
- pmedium->pUnkForRelease = nullptr;
-
- // Copy the data into the caller's storage medium
- switch (_formatEtc[index].tymed)
- {
- case TYMED_HGLOBAL:
- pmedium->hGlobal = duplicateGlobalMem(_stgMedium[index].hGlobal);
- break;
-
- default:
- return DV_E_FORMATETC;
- }
-
- return S_OK;
- }
- HRESULT STDMETHODCALLTYPE GetDataHere(_In_ FORMATETC* pformatetc, _Inout_ STGMEDIUM* pmedium) override
- {
- return DATA_E_FORMATETC;
- }
- HRESULT STDMETHODCALLTYPE QueryGetData(__RPC__in_opt FORMATETC* pformatetc) override
- {
- return lookupFormatEtc(pformatetc) == INVALID_INDEX ? DV_E_FORMATETC : S_OK;
- }
- HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc(__RPC__in_opt FORMATETC* pformatectIn, __RPC__out FORMATETC* pformatetcOut) override
- {
- // Apparently we have to set this field to NULL even though we don't do anything else
- pformatetcOut->ptd = nullptr;
- return E_NOTIMPL;
- }
- HRESULT STDMETHODCALLTYPE SetData(_In_ FORMATETC* pformatetc, _In_ STGMEDIUM* pmedium, BOOL fRelease) override
- {
- return E_NOTIMPL;
- }
- HRESULT STDMETHODCALLTYPE EnumFormatEtc(DWORD dwDirection, __RPC__deref_out_opt IEnumFORMATETC** ppenumFormatEtc) override
- {
- // Only the get direction is supported for OLE
- if (dwDirection == DATADIR_GET)
- {
- // TODO: use SHCreateStdEnumFmtEtc API call
- return CreateEnumFormatEtc(_formatsCount, _formatEtc, ppenumFormatEtc);
- }
-
- // The direction specified is not supported for drag+drop
- return E_NOTIMPL;
- }
- HRESULT STDMETHODCALLTYPE DAdvise(__RPC__in FORMATETC* pformatetc, DWORD advf, __RPC__in_opt IAdviseSink* pAdvSink, __RPC__out DWORD* pdwConnection) override
- {
- return OLE_E_ADVISENOTSUPPORTED;
- }
- HRESULT STDMETHODCALLTYPE DUnadvise(DWORD dwConnection) override
- {
- return OLE_E_ADVISENOTSUPPORTED;
- }
- HRESULT STDMETHODCALLTYPE EnumDAdvise(__RPC__deref_out_opt IEnumSTATDATA** ppenumAdvise) override
- {
- return OLE_E_ADVISENOTSUPPORTED;
- }
-
-private:
- int32 lookupFormatEtc(FORMATETC* pFormatEtc) const
- {
- // Check each of our formats in turn to see if one matches
- for (int32 i = 0; i < _formatsCount; i++)
- {
- if ((_formatEtc[i].tymed & pFormatEtc->tymed) &&
- _formatEtc[i].cfFormat == pFormatEtc->cfFormat &&
- _formatEtc[i].dwAspect == pFormatEtc->dwAspect)
- {
- // Return index of stored format
- return i;
- }
- }
-
- // Format not found
- return INVALID_INDEX;
- }
-};
-
-WindowsGuiData GuiDragDropData;
-
-DragDropEffect WindowsWindow::DoDragDrop(const StringView& data)
-{
- // Create background worker that will keep updating GUI (perform rendering)
- const auto task = New();
- Task::StartNew(task);
- while (task->GetState() == TaskState::Queued)
- Platform::Sleep(1);
-
- // Create descriptors
- FORMATETC fmtetc = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
- STGMEDIUM stgmed = { TYMED_HGLOBAL, { nullptr }, nullptr };
-
- // Create a HGLOBAL inside the storage medium
- stgmed.hGlobal = StringToHandle(data);
-
- // Create drop source
- auto dropSource = new WindowsDragSource(&fmtetc, &stgmed, 1);
-
- // Do the drag drop operation
- DWORD dwEffect;
- HRESULT result = ::DoDragDrop(dropSource, dropSource, DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK | DROPEFFECT_SCROLL, &dwEffect);
-
- // Wait for job end
- Platform::AtomicStore(&task->ExitFlag, 1);
- task->Wait();
-
- // Release allocated data
- dropSource->Release();
- ReleaseStgMedium(&stgmed);
-
- // Fix hanging mouse state (Windows doesn't send WM_LBUTTONUP when we end the drag and drop)
- if (Input::GetMouseButton(MouseButton::Left))
- {
- ::POINT point;
- ::GetCursorPos(&point);
- Input::Mouse->OnMouseUp(Float2((float)point.x, (float)point.y), MouseButton::Left, this);
- }
-
- return SUCCEEDED(result) ? dropEffectFromOleEnum(dwEffect) : DragDropEffect::None;
-}
-
-HRESULT WindowsWindow::DragEnter(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect)
-{
- POINT p = { pt.x, pt.y };
- ::ScreenToClient(_handle, &p);
- GuiDragDropData.Init((IDataObject*)pDataObj);
- DragDropEffect effect = DragDropEffect::None;
- OnDragEnter(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect);
- Focus();
- *pdwEffect = dropEffect2OleEnum(effect);
- return S_OK;
-}
-
-HRESULT WindowsWindow::DragOver(Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect)
-{
- POINT p = { pt.x, pt.y };
- ::ScreenToClient(_handle, &p);
- DragDropEffect effect = DragDropEffect::None;
- OnDragOver(&GuiDragDropData, Float2(static_cast