60 Commits

Author SHA1 Message Date
f5974a7448 Merge remote-tracking branch 'origin/1.10' into sdl_platform_1.10 2025-03-13 18:43:06 +02:00
72ef6f92ae Update SDL3 to 3.2.6 2025-03-09 22:47:17 +02:00
45c388c92d Merge remote-tracking branch 'origin/1.10' into sdl_platform
# Conflicts:
#	Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
#	Source/Engine/Platform/Linux/LinuxPlatform.cpp
2025-03-09 22:47:12 +02:00
83d6f9fcf3 Avoid showing tooltips in inactive windows 2025-03-09 13:05:03 +02:00
4d3588d09e Update SDL3 to 3.2.4 2025-03-09 13:05:02 +02:00
d72f8a61cb Fix window dragging when not supported by Wayland compositor
(cherry picked from commit 3554747a67)
2025-03-09 13:05:02 +02:00
0e173de542 Show current display server in Editor window tooltip
(cherry picked from commit 62968dd437)
2025-03-09 13:05:01 +02:00
8aa0aa4292 Properly mark floating windows with transparency support
(cherry picked from commit c660fac524)
2025-03-09 13:05:01 +02:00
26f8aa9985 Enable transparency support in Vulkan swapchains
(cherry picked from commit 431a69e357)
2025-03-09 13:05:00 +02:00
24c62e2a8d Fix compilation for game builds
(cherry picked from commit f4fcc07288)
2025-03-09 13:05:00 +02:00
91c1d3ce0a Fix cloning SDL repository 2025-03-09 13:04:59 +02:00
93aba7b959 Fix text input not working on X11 2025-03-09 13:04:59 +02:00
fbe61eee9e Fix button latching on Windows after drag and drop operation 2025-03-09 13:04:58 +02:00
12bb3f3a22 Implement new window dragging system 2025-03-09 13:04:58 +02:00
713c4f5d1d Fix mouse resetting issues after ending relative mode 2025-03-09 13:04:57 +02:00
2fe286eed0 Fix frame stutter when window is focused 2025-03-09 13:04:56 +02:00
4539ec4dcf Fix error when docking to sides of tabbed panel 2025-03-09 13:04:56 +02:00
17714c52e0 Cleanup Linux SDL implementation 2025-03-09 13:04:55 +02:00
8810a98c28 Support compiling third party library C files as C code 2025-03-09 13:04:55 +02:00
1da6ffe920 Implement Wayland protocols module and file generation 2025-03-09 13:04:54 +02:00
f966f92583 Fix mouse warping after ending relative mode 2025-03-09 13:04:54 +02:00
ca17bc501f Add git fetch method for dependencies 2025-03-09 13:04:53 +02:00
2e866aa156 Fix window ShowInTaskbar setting 2025-03-09 13:04:53 +02:00
696be807bd Fix various issues with child window positioning 2025-03-09 13:04:52 +02:00
ed63f47620 Add Window.IsAlwaysOnTop property 2025-03-09 13:04:52 +02:00
0d546a82c7 Use SDL locale 2025-03-09 13:04:51 +02:00
9a6c0dd5f4 Allow window with single tab to be dragged from tab area 2025-03-09 13:04:50 +02:00
a00342b1c3 Fix ValueBox mouse position resetting after releasing the button 2025-03-09 13:04:50 +02:00
a798965d9a Fix SDL build process on Linux 2025-03-09 13:04:49 +02:00
d0925b82df Update SDL to 3.2.0 2025-03-09 13:04:49 +02:00
0c535d5a33 Force cursor to center of Game Window when tab handle is clicked 2025-03-09 13:04:48 +02:00
Chandler Cox
05cf5a4d7a Fix rotation using SDL 2025-03-09 13:04:48 +02:00
da59713152 Fix Linux compilation without SDL 2025-03-09 13:04:47 +02:00
8510fd237f Fix compilation 2025-03-09 13:04:46 +02:00
3dc2fe0558 Update SDL3 2025-03-09 13:04:46 +02:00
716f76dca4 Fix compilation issues 2025-03-09 13:04:45 +02:00
29e732c783 Fix windows not being hidden initially 2025-03-09 13:04:45 +02:00
53862298b6 Fix parent window position handling with popup/tooltip windows 2025-03-09 13:04:44 +02:00
4464555dcc Fix compilation errors in other platforms 2025-03-09 13:04:44 +02:00
ec383ffb1d Fix CI for Linux 2025-03-09 13:04:43 +02:00
909ba336b0 Prevent building with SDL in unsupported platforms 2025-03-09 13:04:43 +02:00
ecaa03365f Fallback to X11 message box implementation when SDL fails 2025-03-09 13:04:42 +02:00
fb88460903 Fix popup and context menus not working on Wayland 2025-03-09 13:04:42 +02:00
db840ebef9 Hide warnings for unsupported SDL operations on Wayland 2025-03-09 13:04:41 +02:00
3f303a7a77 Log a warning for not implemented Wayland functionality 2025-03-09 13:04:40 +02:00
848d8e35e5 Fix compilation in Linux 2025-03-09 13:04:40 +02:00
2f3b45ae82 Enable warning sound in question dialogs 2025-03-09 13:04:39 +02:00
48600c6ac5 Enable modern Windows dialog boxes 2025-03-09 13:04:39 +02:00
bb132305e3 Implement relative mouse mode (raw input) for SDL platform 2025-03-09 13:04:38 +02:00
769823ddf5 Add flag for Window types 2025-03-09 13:04:38 +02:00
8a0ce55fec Enable native windowing system settings with SDL platform 2025-03-09 13:04:37 +02:00
832d902699 Add command-line switches to force X11 and Wayland SDL drivers 2025-03-09 13:04:37 +02:00
6843205ec9 Implement SDL platform, windowing and input handling 2025-03-09 13:04:36 +02:00
74fb1a74a6 Refactor application window class name 2025-03-09 13:04:35 +02:00
29c3a9e4bf Move Window related enums to separate header file 2025-03-09 13:04:35 +02:00
6f55b5452f Refactor Windows drag and drop implementation 2025-03-09 13:04:34 +02:00
716ae95630 Refactor ScreenUtilities 2025-03-09 13:04:34 +02:00
02e9d60c5b Add more helper methods for managing Git repos 2025-03-09 13:04:33 +02:00
a1b7666c1c Fix centered window location on X11 2025-03-09 13:04:33 +02:00
1b9663a390 Fix initial position of Tooltips 2025-03-09 13:04:32 +02:00
207 changed files with 90366 additions and 1849 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -186,12 +186,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);

View File

@@ -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) 2012-2024 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
@@ -121,7 +124,7 @@ namespace FlaxEditor.GUI.ContextMenu
}
/// <summary>
/// Shows the empty menu popup o na screen.
/// Shows the empty menu popup on a screen.
/// </summary>
/// <param name="control">The target control.</param>
/// <param name="area">The target control area to cover.</param>
@@ -256,7 +259,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);
@@ -266,16 +271,22 @@ 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;
// Show
Visible = true;
if (_window == null)
return;
_window.Show();
}
// Show
Visible = true;
if (_window == null)
return;
_window.Show();
PerformLayout();
if (UseVisibilityControl)
{
@@ -426,6 +437,17 @@ namespace FlaxEditor.GUI.ContextMenu
}
}
#if USE_SDL_WORKAROUNDS
private void OnWindowGotFocus()
{
}
private void OnWindowMouseDown(ref Float2 mousePosition, MouseButton button, ref bool handled)
{
// The user clicked outside the popup window
Hide();
}
#else
private void OnWindowGotFocus()
{
var child = _childCM;
@@ -439,6 +461,7 @@ namespace FlaxEditor.GUI.ContextMenu
});
}
}
#endif
private void OnWindowLostFocus()
{
@@ -537,7 +560,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

View File

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

View File

@@ -49,6 +49,11 @@ namespace FlaxEditor.GUI.Docking
/// The mouse position.
/// </summary>
public Float2 MousePosition = Float2.Minimum;
/// <summary>
/// The mouse position.
/// </summary>
public Float2 MouseStartPosition = Float2.Minimum;
/// <summary>
/// The start drag asynchronous window.
@@ -165,7 +170,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
{
@@ -176,7 +181,7 @@ namespace FlaxEditor.GUI.Docking
_panel.SelectTab(index - 1);
// Create docking hint window
DockHintWindow.Create(win);
WindowDragHelper.StartDragging(win, _panel.RootWindow.Window);
}
}
}
@@ -355,6 +360,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)
@@ -441,6 +447,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)
{

View File

@@ -182,6 +182,25 @@ namespace FlaxEditor.GUI.Docking
/// <param name="size">Window size, set <see cref="Float2.Zero"/> to use default.</param>
/// <param name="position">Window location.</param>
public void ShowFloating(Float2 location, Float2 size, WindowStartPosition position = WindowStartPosition.CenterParent)
{
CreateFloating(location, size, position, true);
}
/// <summary>
/// Creates the window in a floating state.
/// </summary>
public void CreateFloating()
{
CreateFloating(Float2.Zero, Float2.Zero);
}
/// <summary>
/// Creates the window in a floating state.
/// </summary>
/// <param name="location">Window location.</param>
/// <param name="size">Window size, set <see cref="Float2.Zero"/> to use default.</param>
/// <param name="position">Window location.</param>
/// <param name="showWindow">Window visibility.</param>
public void CreateFloating(Float2 location, Float2 size, WindowStartPosition position = WindowStartPosition.CenterParent, bool showWindow = false)
{
Undock();
@@ -199,14 +218,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();
}
}
/// <summary>

View File

@@ -52,7 +52,7 @@ namespace FlaxEditor.GUI.Docking
return;
// Create docking hint window
DockHintWindow.Create(this);
WindowDragHelper.StartDragging(this);
}
/// <summary>
@@ -75,14 +75,14 @@ namespace FlaxEditor.GUI.Docking
settings.MaximumSize = Float2.Zero; // Unlimited size
settings.Fullscreen = false;
settings.HasBorder = true;
settings.SupportsTransparency = false;
settings.SupportsTransparency = true;
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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -435,6 +435,9 @@ 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;

View File

@@ -1008,7 +1008,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);

View File

@@ -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));

View File

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

View File

@@ -760,8 +760,10 @@ namespace FlaxEditor.Modules
{
settings.HasBorder = false;
#if !PLATFORM_SDL
// Skip OS sizing frame and implement it using LeftButtonHit
settings.HasSizingFrame = false;
#endif
}
#elif PLATFORM_LINUX
settings.HasBorder = false;

View File

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

View File

@@ -120,9 +120,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();

View File

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

View File

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

View File

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

View File

@@ -158,18 +158,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
/// <summary>
/// The previous input (from the previous update).
@@ -522,10 +526,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;
@@ -1460,7 +1465,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);
@@ -1469,6 +1476,7 @@ namespace FlaxEditor.Viewport
_viewMousePos = center;
win.MousePosition = PointToWindow(_viewMousePos);
}
#endif
}
/// <summary>
@@ -1480,6 +1488,7 @@ namespace FlaxEditor.Viewport
// Restore cursor and stop tracking mouse movement
win.Cursor = CursorType.Default;
win.EndTrackingMouse();
win.MouseMoveRelative -= OnMouseMoveRelative;
}
/// <summary>
@@ -1584,6 +1593,14 @@ namespace FlaxEditor.Viewport
else
EndMouseCapture();
}
#if PLATFORM_SDL
bool useMouse = IsControllingMouse || true;
_prevInput = _input;
if (canUseInput && ContainsFocus)
_input.Gather(win.Window, useMouse, ref _prevInput);
else
_input.Clear();
#else
bool useMouse = IsControllingMouse || (Mathf.IsInRange(_viewMousePos.X, 0, Width) && Mathf.IsInRange(_viewMousePos.Y, 0, Height));
_prevInput = _input;
var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl) && !(c is UIEditorRoot));
@@ -1591,6 +1608,7 @@ namespace FlaxEditor.Viewport
_input.Gather(win.Window, useMouse, ref _prevInput);
else
_input.Clear();
#endif
// Track controlling mouse state change
bool wasControllingMouse = _prevInput.IsControllingMouse;
@@ -1699,6 +1717,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)
@@ -1740,6 +1762,7 @@ namespace FlaxEditor.Viewport
mouseDelta += _mouseDeltaLast * _mouseAccelerationScale;
_mouseDeltaLast = currentDelta;
}
#endif
// Update
moveDelta *= dt * (60.0f * 4.0f);
@@ -1748,12 +1771,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)
@@ -1765,6 +1790,8 @@ namespace FlaxEditor.Viewport
}
else
{
#if PLATFORM_SDL
#else
if (_input.IsMouseLeftDown || _input.IsMouseRightDown || _isVirtualMouseRightDown)
{
// Calculate smooth mouse delta not dependant on viewport size
@@ -1779,6 +1806,7 @@ namespace FlaxEditor.Viewport
_mouseDelta = Float2.Zero;
}
_mouseDeltaLast = Float2.Zero;
#endif
if (ContainsFocus)
{
@@ -1828,6 +1856,12 @@ namespace FlaxEditor.Viewport
_input.MouseWheelDelta = 0;
}
/// <inheritdoc />
public void OnMouseMoveRelative(ref Float2 mouseMotion)
{
_mouseDelta += mouseMotion;
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{

View File

@@ -1150,8 +1150,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;

View File

@@ -28,7 +28,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"),
@@ -104,6 +104,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"),
@@ -164,7 +165,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;

View File

@@ -145,6 +145,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);

View File

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

View File

@@ -99,13 +99,14 @@ 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."));
return -1;
}
Platform::SetHighDpiAwarenessEnabled(!CommandLine::Options.LowDPI.IsTrue());
Time::StartupTime = DateTime::Now();
Globals::StartupFolder = Globals::BinariesFolder = Platform::GetMainDirectory();
#if USE_EDITOR

View File

@@ -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<bool> Fullscreen;
Nullable<Float2> Size;
bool CursorVisible = true;
CursorLockMode CursorLock = CursorLockMode::None;
namespace
{
Nullable<bool> Fullscreen;
Nullable<Float2> Size;
bool CursorVisible = true;
CursorLockMode CursorLock = CursorLockMode::None;
bool LastGameViewportFocus = false;
}
class ScreenService : public EngineService
{
@@ -100,11 +106,25 @@ void Screen::SetCursorVisible(const bool value)
#else
const auto win = Engine::MainWindow;
#endif
bool focused = false;
if (win && Engine::HasGameViewportFocus())
{
win->SetCursor(value ? CursorType::Default : CursorType::Hidden);
focused = true;
}
else if (win)
win->SetCursor(CursorType::Default);
CursorVisible = value;
// Just enable relative mode when cursor is constrained and not visible
if (CursorLock != CursorLockMode::None && !CursorVisible && focused)
{
Input::Mouse->SetRelativeMode(true, win);
}
else if (CursorLock == CursorLockMode::None || CursorVisible || !focused)
{
Input::Mouse->SetRelativeMode(false, win);
}
}
CursorLockMode Screen::GetCursorLock()
@@ -133,6 +153,17 @@ void Screen::SetCursorLock(CursorLockMode mode)
win->EndClippingCursor();
}
CursorLock = mode;
// Just enable relative mode when cursor is constrained and not visible
bool focused = win && Engine::HasGameViewportFocus();
if (CursorLock != CursorLockMode::None && !CursorVisible && focused)
{
Input::Mouse->SetRelativeMode(true, win);
}
else if (CursorLock == CursorLockMode::None || CursorVisible || !focused)
{
Input::Mouse->SetRelativeMode(false, win);
}
}
GameWindowMode Screen::GetGameWindowMode()
@@ -190,7 +221,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
}

View File

@@ -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:
/// <summary>

View File

@@ -4,6 +4,7 @@
#include "AndroidVulkanPlatform.h"
#include "../RenderToolsVulkan.h"
#include "Engine/Platform/Window.h"
void AndroidVulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers)
{
@@ -17,8 +18,10 @@ void AndroidVulkanPlatform::GetDeviceExtensions(Array<const char*>& extensions,
extensions.Add(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
}
void AndroidVulkanPlatform::CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface)
void AndroidVulkanPlatform::CreateSurface(Window* window, 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);

View File

@@ -17,7 +17,7 @@ class AndroidVulkanPlatform : public VulkanPlatformBase
public:
static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers);
static void GetDeviceExtensions(Array<const char*>& extensions, Array<const char*>& layers);
static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface);
static void CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* surface);
};
typedef AndroidVulkanPlatform VulkanPlatform;

View File

@@ -192,7 +192,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, GPUDeviceVulkan::Instance, &_surface);
if (_surface == VK_NULL_HANDLE)
{
LOG(Warning, "Failed to create Vulkan surface.");
@@ -374,7 +374,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

View File

@@ -4,62 +4,62 @@
#include "LinuxVulkanPlatform.h"
#include "../RenderToolsVulkan.h"
#include "Engine/Platform/Window.h"
// Contents of vulkan\vulkan_xlib.h inlined here to prevent typename collisions with engine types due to X11 types
#include "Engine/Platform/Linux/IncludeX11.h"
#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
typedef struct VkXlibSurfaceCreateInfoKHR
{
VkStructureType sType;
const void* pNext;
VkXlibSurfaceCreateFlagsKHR flags;
X11::Display* dpy;
X11::Window window;
} VkXlibSurfaceCreateInfoKHR;
#include "vulkan/vulkan_wayland.h"
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<const char*>& extensions, Array<const char*>& 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, 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*>(window);
X11::Window x11Window = (X11::Window)sdlWindow->GetX11WindowHandle();
wl_surface* waylandSurface = (wl_surface*)sdlWindow->GetWaylandSurfacePtr();
if (waylandSurface != nullptr)
{
VkWaylandSurfaceCreateInfoKHR surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR);
surfaceCreateInfo.display = (wl_display*)sdlWindow->GetWaylandDisplay();
surfaceCreateInfo.surface = waylandSurface;
VALIDATE_VULKAN_RESULT(vkCreateWaylandSurfaceKHR(instance, &surfaceCreateInfo, nullptr, surface));
}
else if (x11Window != 0)
{
VkXlibSurfaceCreateInfoKHR surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR);
surfaceCreateInfo.dpy = (X11::Display*)sdlWindow->GetX11Display();
surfaceCreateInfo.window = x11Window;
VALIDATE_VULKAN_RESULT(vkCreateXlibSurfaceKHR(instance, &surfaceCreateInfo, nullptr, surface));
}
#endif
}
#endif

View File

@@ -19,7 +19,7 @@ class LinuxVulkanPlatform : public VulkanPlatformBase
{
public:
static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers);
static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* outSurface);
static void CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* outSurface);
};
typedef LinuxVulkanPlatform VulkanPlatform;

View File

@@ -4,6 +4,7 @@
#include "MacVulkanPlatform.h"
#include "../RenderToolsVulkan.h"
#include "Engine/Platform/Window.h"
#include <Cocoa/Cocoa.h>
void MacVulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers)
@@ -12,12 +13,13 @@ void MacVulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Ar
extensions.Add(VK_MVK_MACOS_SURFACE_EXTENSION_NAME);
}
void MacVulkanPlatform::CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface)
void MacVulkanPlatform::CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* surface)
{
NSWindow* window = (NSWindow*)windowHandle;
void* windowHandle = window->GetNativePtr();
NSWindow* nswindow = (NSWindow*)windowHandle;
VkMacOSSurfaceCreateInfoMVK surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK);
surfaceCreateInfo.pView = (void*)window.contentView;
surfaceCreateInfo.pView = (void*)nswindow.contentView;
VALIDATE_VULKAN_RESULT(vkCreateMacOSSurfaceMVK(instance, &surfaceCreateInfo, nullptr, surface));
}

View File

@@ -18,7 +18,7 @@ class MacVulkanPlatform : public VulkanPlatformBase
{
public:
static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers);
static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* outSurface);
static void CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* outSurface);
};
typedef MacVulkanPlatform VulkanPlatform;

View File

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

View File

@@ -17,7 +17,7 @@ class Win32VulkanPlatform : public VulkanPlatformBase
{
public:
static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers);
static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface);
static void CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* surface);
};
typedef Win32VulkanPlatform VulkanPlatform;

View File

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

View File

@@ -18,7 +18,7 @@ class iOSVulkanPlatform : public VulkanPlatformBase
{
public:
static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers);
static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* outSurface);
static void CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* outSurface);
};
typedef iOSVulkanPlatform VulkanPlatform;

View File

@@ -78,6 +78,7 @@ Delegate<const Float2&, MouseButton> Input::MouseUp;
Delegate<const Float2&, MouseButton> Input::MouseDoubleClick;
Delegate<const Float2&, float> Input::MouseWheel;
Delegate<const Float2&> Input::MouseMove;
Delegate<const Float2&> Input::MouseMoveRelative;
Action Input::MouseLeave;
Delegate<const Float2&, int32> Input::TouchDown;
Delegate<const Float2&, int32> Input::TouchMove;
@@ -208,6 +209,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)
{
Event& e = _queue.AddOne();
@@ -273,6 +282,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;
@@ -933,6 +947,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;
@@ -989,6 +1006,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;
@@ -1202,12 +1222,14 @@ void InputService::Update()
}
}
#if !PLATFORM_SDL
// Lock mouse if need to
const auto lockMode = Screen::GetCursorLock();
if (lockMode == CursorLockMode::Locked)
{
Input::SetMousePosition(Screen::GetSize() * 0.5f);
}
#endif
// Send events for the active actions and axes (send events only in play mode)
if (!Time::GetGamePaused())

View File

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

View File

@@ -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;

View File

@@ -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,14 @@ public:
return !_state.MouseButtons[static_cast<int32>(button)] && _prevState.MouseButtons[static_cast<int32>(button)];
}
/// <summary>
/// Gets the current state of mouse relative mode.
/// </summary>
API_FUNCTION() FORCE_INLINE bool IsRelative() const
{
return _relativeMode;
}
public:
/// <summary>
/// Sets the mouse position.
@@ -121,6 +131,17 @@ public:
/// <param name="newPosition">The new position.</param>
virtual void SetMousePosition(const Float2& newPosition) = 0;
/// <summary>
/// Sets the mouse relative mode state. While enabled, the mouse movement tracking becomes more accurate.
/// The cursor will be hidden while in relative mode.
/// </summary>
/// <param name="relativeMode">The new relative mode state.</param>
/// <param name="window">The window.</param>
virtual void SetRelativeMode(bool relativeMode, Window* window)
{
_relativeMode = relativeMode;
}
/// <summary>
/// Called when mouse cursor gets moved by the application. Invalidates the previous cached mouse position to prevent mouse jitter when locking the cursor programmatically.
/// </summary>
@@ -158,6 +179,13 @@ public:
/// <param name="target">The target window to receive this event, otherwise input system will pick the window automatically.</param>
void OnMouseMove(const Float2& position, Window* target = nullptr);
/// <summary>
/// Called when mouse moves in relative mode.
/// </summary>
/// <param name="positionRelative">The mouse position change.</param>
/// <param name="target">The target window to receive this event, otherwise input system will pick the window automatically.</param>
void OnMouseMoveRelative(const Float2& positionRelative, Window* target = nullptr);
/// <summary>
/// Called when mouse leaves the input source area.
/// </summary>

View File

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

View File

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

View File

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

View File

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

View File

@@ -106,7 +106,7 @@ WindowBase::WindowBase(const CreateWindowSettings& settings)
if (settings.StartPosition == WindowStartPosition::CenterParent
|| 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();
@@ -164,6 +164,15 @@ void WindowBase::SetIsVisible(bool isVisible)
}
}
bool WindowBase::IsAlwaysOnTop() const
{
return false;
}
void WindowBase::SetIsAlwaysOnTop(bool isAlwaysOnTop)
{
}
String WindowBase::ToString() const
{
return GetTitle();
@@ -257,6 +266,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");

View File

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

View File

@@ -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"

View File

@@ -21,7 +21,7 @@ namespace FlaxEngine
AllowMinimize = true,
AllowMaximize = true,
AllowDragAndDrop = true,
IsRegularWindow = true,
Type = WindowType.Regular,
HasSizingFrame = true,
ShowAfterFirstPaint = true,
};

View File

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

View File

@@ -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"

View File

@@ -30,7 +30,6 @@ inline bool operator==(const APP_LOCAL_DEVICE_ID& l, const APP_LOCAL_DEVICE_ID&
return Platform::MemoryCompare(&l, &r, sizeof(APP_LOCAL_DEVICE_ID)) == 0;
}
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();
}

View File

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

View File

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

View File

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

View File

@@ -96,7 +96,9 @@ X11::Cursor Cursors[(int32)CursorType::MAX];
X11::XcursorImage* CursorsImg[(int32)CursorType::MAX];
Dictionary<StringAnsi, X11::KeyCode> KeyNameMap;
Array<KeyboardKeys> KeyCodeMap;
Delegate<void*> LinuxPlatform::xEventRecieved;
#if !PLATFORM_SDL
Delegate<void*> LinuxPlatform::xEventReceived;
#endif
Window* MouseTrackingWindow = nullptr;
// Message boxes configuration
@@ -654,7 +656,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;
@@ -841,6 +847,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)
@@ -851,6 +859,8 @@ int X11ErrorHandler(X11::Display* display, X11::XErrorEvent* event)
return 0;
}
#endif
int32 CalculateDpi()
{
int dpi = 96;
@@ -1206,17 +1216,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)
@@ -1360,6 +1373,7 @@ namespace Impl
}
}
#if !PLATFORM_SDL
class LinuxDropFilesData : public IGuiData
{
public:
@@ -1397,7 +1411,7 @@ public:
}
};
DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
DragDropEffect Window::DoDragDrop(const StringView& data)
{
if (CommandLine::Options.Headless.IsTrue())
return DragDropEffect::None;
@@ -1411,13 +1425,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;
@@ -1525,7 +1540,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;
@@ -1554,7 +1569,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;
@@ -1589,7 +1604,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;
@@ -1632,7 +1647,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;
@@ -1679,7 +1694,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;
@@ -1705,7 +1720,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();
@@ -1728,7 +1743,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();
@@ -1757,9 +1772,11 @@ Array<String> LinuxClipboard::GetFiles()
return Array<String>();
}
#endif
void* LinuxPlatform::GetXDisplay()
{
return xDisplay;
return xDisplay;
}
bool LinuxPlatform::CreateMutex(const Char* name)
@@ -2089,6 +2106,7 @@ bool LinuxPlatform::Init()
UnixGetMacAddress(MacAddress);
#if !PLATFORM_SDL
// Get user locale string
setlocale(LC_ALL, "");
const char* locale = setlocale(LC_CTYPE, NULL);
@@ -2098,6 +2116,7 @@ bool LinuxPlatform::Init()
UserLocale.Replace('_', '-');
if (UserLocale == TEXT("C"))
UserLocale = TEXT("en");
#endif
// Get computer name string
gethostname(buffer, UNIX_APP_BUFF_SIZE);
@@ -2141,7 +2160,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);
@@ -2280,7 +2303,7 @@ bool LinuxPlatform::Init()
Input::Mouse = Impl::Mouse = New<LinuxMouse>();
Input::Keyboard = Impl::Keyboard = New<LinuxKeyboard>();
LinuxInput::Init();
#endif
return false;
}
@@ -2290,6 +2313,7 @@ void LinuxPlatform::BeforeRun()
void LinuxPlatform::Tick()
{
#if !PLATFORM_SDL
UnixPlatform::Tick();
LinuxInput::UpdateState();
@@ -2306,9 +2330,9 @@ void LinuxPlatform::Tick()
continue;
// External event handling
xEventRecieved(&event);
xEventReceived(&event);
LinuxWindow* window;
Window* window;
switch (event.type)
{
case ClientMessage:
@@ -2640,6 +2664,7 @@ void LinuxPlatform::Tick()
}
//X11::XFlush(xDisplay);
#endif
}
void LinuxPlatform::BeforeExit()
@@ -2648,6 +2673,7 @@ void LinuxPlatform::BeforeExit()
void LinuxPlatform::Exit()
{
#if !PLATFORM_SDL
for (int32 i = 0; i < (int32)CursorType::MAX; i++)
{
if (Cursors[i])
@@ -2673,6 +2699,7 @@ void LinuxPlatform::Exit()
X11::XCloseDisplay(xDisplay);
xDisplay = nullptr;
}
#endif
}
String LinuxPlatform::GetSystemName()
@@ -2694,6 +2721,7 @@ Version LinuxPlatform::GetSystemVersion()
return Version(0, 0);
}
#if !PLATFORM_SDL
int32 LinuxPlatform::GetDpi()
{
return SystemDpi;
@@ -2703,6 +2731,7 @@ String LinuxPlatform::GetUserLocaleName()
{
return UserLocale;
}
#endif
String LinuxPlatform::GetComputerName()
{
@@ -2910,10 +2939,12 @@ bool LinuxPlatform::SetWorkingDirectory(const String& path)
return chdir(StringAsANSI<>(*path).Get()) != 0;
}
#if !PLATFORM_SDL
Window* LinuxPlatform::CreateWindow(const CreateWindowSettings& settings)
{
return New<LinuxWindow>(settings);
}
#endif
extern char **environ;

View File

@@ -36,7 +36,7 @@ public:
/// <summary>
/// An event that is fired when an XEvent is received during platform tick.
/// </summary>
static Delegate<void*> xEventRecieved;
static Delegate<void*> xEventReceived;
public:
@@ -123,8 +123,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);
@@ -139,7 +141,9 @@ 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<String, String, HeapAllocation>& result);
static bool GetEnvironmentVariable(const String& name, String& value);
static bool SetEnvironmentVariable(const String& name, const String& value);

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#if PLATFORM_LINUX
#if PLATFORM_LINUX && !PLATFORM_SDL
#include "../Window.h"
#include "Engine/Input/Input.h"
@@ -616,7 +616,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) &&

View File

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

View File

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

View File

@@ -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);

View File

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

View File

@@ -89,6 +89,21 @@ public class Platform : EngineModule
break;
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

View File

@@ -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"

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#if PLATFORM_SDL && PLATFORM_MAC
static_assert(false, "TODO");
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)
}
#endif

View File

@@ -0,0 +1,292 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#if PLATFORM_SDL && PLATFORM_WINDOWS
#define BORDERLESS_MAXIMIZE_WORKAROUND 2
#include "SDLPlatform.h"
#include "SDLInput.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Platform/WindowsManager.h"
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
#include "Engine/Input/Mouse.h"
#include "Engine/Core/Log.h"
#include "Engine/Engine/Engine.h"
#include <SDL3/SDL_hints.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_system.h>
#include <SDL3/SDL_timer.h>
#if USE_EDITOR
#include <oleidl.h>
#endif
#define STYLE_RESIZABLE (WS_THICKFRAME | WS_MAXIMIZEBOX)
namespace WinImpl
{
Window* DraggedWindow;
Float2 DraggedWindowStartPosition = Float2::Zero;
Float2 DraggedWindowMousePosition = 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)
{
#define GET_WINDOW_WITH_HWND(window, hwnd) \
do { \
(window) = nullptr; \
WindowsManager::WindowsLocker.Lock(); \
for (int32 i = 0; i < WindowsManager::Windows.Count(); i++) \
{ \
if (WindowsManager::Windows[i]->GetNativePtr() == (hwnd)) \
{ \
(window) = WindowsManager::Windows[i]; \
break; \
} \
} \
WindowsManager::WindowsLocker.Unlock(); \
ASSERT((window) != nullptr); \
} while (false)
if (msg->message == WM_NCLBUTTONDOWN)
{
Window* window;
GET_WINDOW_WITH_HWND(window, msg->hwnd);
WinImpl::DraggedWindow = window;
WinImpl::DraggedWindowStartPosition = WinImpl::DraggedWindow->GetClientPosition();
Float2 mousePos(static_cast<float>(static_cast<LONG>(WINDOWS_GET_X_LPARAM(msg->lParam))), static_cast<float>(static_cast<LONG>(WINDOWS_GET_Y_LPARAM(msg->lParam))));
WinImpl::DraggedWindowMousePosition = mousePos;
WinImpl::DraggedWindowMousePosition -= WinImpl::DraggedWindowStartPosition;
bool result = false;
WindowHitCodes hit = static_cast<WindowHitCodes>(msg->wParam);
window->OnHitTest(mousePos, 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);
}
}
/*else if (msg->message == WM_NCLBUTTONUP || msg->message == WM_CAPTURECHANGED)
{
windowDragging = false;
Window* window;
GET_WINDOW_WITH_HWND(window, msg->hwnd);
SDL_Event event{ 0 };
event.button.type = SDL_EVENT_MOUSE_BUTTON_UP;
event.button.down = false;
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 = static_cast<float>(static_cast<LONG>(WINDOWS_GET_X_LPARAM(msg->lParam)));
event.button.y = static_cast<float>(static_cast<LONG>(WINDOWS_GET_Y_LPARAM(msg->lParam)));
SDL_PushEvent(&event);
}*/
return true;
#undef GET_WINDOW_WITH_HWND
}
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();//Scripting::Update(); // For docking updates
Engine::OnDraw();
return false;
}
else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN)
{
SDLWindow* window = SDLWindow::GetWindowFromEvent(*event);
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)
{
Float2 start = WinImpl::DraggedWindowStartPosition;
Float2 newPos = Float2(static_cast<float>(event->window.data1), static_cast<float>(event->window.data2));
Float2 offset = newPos - start;
Float2 mousePos = WinImpl::DraggedWindowMousePosition;
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 = mousePos.X;
mouseMovedEvent.motion.y = mousePos.Y;
if (window)
window->HandleEvent(mouseMovedEvent);
if (window)
window->HandleEvent(*event);
return false;
}
if (window)
window->HandleEvent(*event);
return true;
}
void SDLPlatform::PreHandleEvents()
{
SDL_AddEventWatch(EventFilterCallback, &WinImpl::DraggedWindow);
}
void SDLPlatform::PostHandleEvents()
{
SDL_RemoveEventWatch(EventFilterCallback, &WinImpl::DraggedWindow);
// Handle window dragging release here
if (WinImpl::DraggedWindow != nullptr)
{
Float2 mousePosition;
auto buttons = SDL_GetGlobalMouseState(&mousePosition.X, &mousePosition.Y);
// Send simulated mouse up event
SDL_Event buttonUpEvent { 0 };
buttonUpEvent.motion.type = SDL_EVENT_MOUSE_BUTTON_UP;
buttonUpEvent.button.down = false;
buttonUpEvent.motion.windowID = SDL_GetWindowID(WinImpl::DraggedWindow->GetSDLWindow());
buttonUpEvent.motion.timestamp = SDL_GetTicksNS();
buttonUpEvent.motion.state = SDL_BUTTON_LEFT;
buttonUpEvent.button.clicks = 1;
buttonUpEvent.motion.x = mousePosition.X;
buttonUpEvent.motion.y = mousePosition.Y;
WinImpl::DraggedWindow->HandleEvent(buttonUpEvent);
WinImpl::DraggedWindow = nullptr;
}
}
bool SDLWindow::HandleEventInternal(SDL_Event& event)
{
switch (event.type)
{
case SDL_EVENT_WINDOW_DESTROYED:
{
#if USE_EDITOR
// Disable file dropping
if (_settings.AllowDragAndDrop)
{
const auto result = RevokeDragDrop((HWND)_handle);
if (result != S_OK)
LOG(Warning, "Window drag and drop service error: 0x{0:x}:{1}", result, 2);
}
#endif
break;
}
case SDL_EVENT_MOUSE_BUTTON_UP:
{
if (WinImpl::DraggedWindow != nullptr && WinImpl::DraggedWindow->_windowId != event.button.windowID)
{
// Send the button event to dragged window as well
Float2 mousePos = ClientToScreen({ event.button.x, event.button.y });
Float2 clientPos = WinImpl::DraggedWindow->ScreenToClient(mousePos);
SDL_Event event2 = event;
event2.button.windowID = WinImpl::DraggedWindow->_windowId;
event2.button.x = clientPos.X;
event2.button.y = clientPos.Y;
SDLInput::HandleEvent(WinImpl::DraggedWindow, event2);
}
break;
}
default:
break;
}
return false;
}
bool SDLPlatform::UsesWindows()
{
return true;
}
bool SDLPlatform::UsesWayland()
{
return false;
}
bool SDLPlatform::UsesX11()
{
return false;
}
void SDLWindow::Focus()
{
auto activateWhenRaised = SDL_GetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED);
SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, "1");
// Forcing the window to focus causes issues with opening context menus while window is maximized
//auto forceRaiseWindow = SDL_GetHint(SDL_HINT_FORCE_RAISEWINDOW);
//SDL_SetHint(SDL_HINT_FORCE_RAISEWINDOW, "1");
SDL_RaiseWindow(_window);
SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, activateWhenRaised);
//SDL_SetHint(SDL_HINT_FORCE_RAISEWINDOW, forceRaiseWindow);
}
void SDLPlatform::SetHighDpiAwarenessEnabled(bool enable)
{
// Other supported values: "permonitor", "permonitorv2"
SDL_SetHint("SDL_WINDOWS_DPI_AWARENESS", enable ? "system" : "unaware");
}
DragDropEffect SDLWindow::DoDragDrop(const StringView& data, const Float2& offset, Window* dragSourceWindow)
{
Show();
return DragDropEffect::None;
}
#endif

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,8 +4,6 @@
#if PLATFORM_WINDOWS
class WindowsClipboard;
typedef WindowsClipboard Clipboard;
class Win32CriticalSection;
typedef Win32CriticalSection CriticalSection;
class Win32ConditionVariable;
@@ -16,21 +14,25 @@ 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;
typedef UserBase User;
class WindowsScreenUtilities;
typedef WindowsScreenUtilities ScreenUtilities;
#elif PLATFORM_UWP
class ClipboardBase;
typedef ClipboardBase Clipboard;
class Win32CriticalSection;
typedef Win32CriticalSection CriticalSection;
class Win32ConditionVariable;
@@ -41,21 +43,23 @@ 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;
typedef Win32Network Network;
class UserBase;
typedef UserBase User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#elif PLATFORM_LINUX
class LinuxClipboard;
typedef LinuxClipboard Clipboard;
class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection;
class UnixConditionVariable;
@@ -66,21 +70,25 @@ 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;
typedef UserBase User;
class LinuxScreenUtilities;
typedef LinuxScreenUtilities ScreenUtilities;
#elif PLATFORM_PS4
class ClipboardBase;
typedef ClipboardBase Clipboard;
class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection;
class UnixConditionVariable;
@@ -91,21 +99,23 @@ 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;
typedef PS4Network Network;
class PS4User;
typedef PS4User User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#elif PLATFORM_PS5
class ClipboardBase;
typedef ClipboardBase Clipboard;
class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection;
class UnixConditionVariable;
@@ -116,21 +126,23 @@ 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;
typedef PS5Network Network;
class PS5User;
typedef PS5User User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#elif PLATFORM_XBOX_ONE
class ClipboardBase;
typedef ClipboardBase Clipboard;
class Win32CriticalSection;
typedef Win32CriticalSection CriticalSection;
class Win32ConditionVariable;
@@ -141,21 +153,23 @@ 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;
typedef Win32Network Network;
class GDKUser;
typedef GDKUser User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#elif PLATFORM_XBOX_SCARLETT
class ClipboardBase;
typedef ClipboardBase Clipboard;
class Win32CriticalSection;
typedef Win32CriticalSection CriticalSection;
class Win32ConditionVariable;
@@ -166,21 +180,23 @@ 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;
typedef Win32Network Network;
class GDKUser;
typedef GDKUser User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#elif PLATFORM_ANDROID
class ClipboardBase;
typedef ClipboardBase Clipboard;
class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection;
class UnixConditionVariable;
@@ -191,21 +207,23 @@ 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;
typedef UnixNetwork Network;
class UserBase;
typedef UserBase User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#elif PLATFORM_SWITCH
class ClipboardBase;
typedef ClipboardBase Clipboard;
class SwitchCriticalSection;
typedef SwitchCriticalSection CriticalSection;
class SwitchConditionVariable;
@@ -216,21 +234,23 @@ 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;
typedef SwitchNetwork Network;
class SwitchUser;
typedef SwitchUser User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#elif PLATFORM_MAC
class MacClipboard;
typedef MacClipboard Clipboard;
class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection;
class UnixConditionVariable;
@@ -241,21 +261,23 @@ 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;
class MacPlatform;
typedef MacPlatform Platform;
class MacWindow;
typedef MacWindow Window;
class UnixNetwork;
typedef UnixNetwork Network;
class UserBase;
typedef UserBase User;
class MacScreenUtilities;
typedef MacScreenUtilities ScreenUtilities;
#elif PLATFORM_IOS
class ClipboardBase;
typedef ClipboardBase Clipboard;
class UnixCriticalSection;
typedef UnixCriticalSection CriticalSection;
class UnixConditionVariable;
@@ -266,19 +288,34 @@ 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;
typedef UnixNetwork Network;
class UserBase;
typedef UserBase User;
class ScreenUtilitiesBase;
typedef ScreenUtilitiesBase ScreenUtilities;
#else
#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

View File

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

View File

@@ -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"

View File

@@ -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<IShellItem> si;

View File

@@ -1,6 +1,6 @@
// Copyright (c) 2012-2024 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;
}

View File

@@ -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"
@@ -33,7 +35,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
@@ -256,6 +257,8 @@ void GetWindowsVersion(String& windowsName, int32& versionMajor, int32& versionM
RegCloseKey(hKey);
}
#if !PLATFORM_SDL
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Find window to process that message
@@ -273,6 +276,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)
@@ -437,11 +442,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;
@@ -519,6 +525,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));
@@ -527,12 +534,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)
@@ -681,11 +689,13 @@ bool WindowsPlatform::Init()
DWORD tmp;
Char buffer[256];
#if !PLATFORM_SDL
// Get user locale string
if (GetUserDefaultLocaleName(buffer, LOCALE_NAME_MAX_LENGTH))
{
UserLocale = String(buffer);
}
#endif
// Get computer name string
if (GetComputerNameW(buffer, &tmp))
@@ -701,7 +711,9 @@ bool WindowsPlatform::Init()
}
OnPlatformUserAdd(New<User>(userName));
#if !PLATFORM_SDL
WindowsInput::Init();
#endif
return false;
}
@@ -747,7 +759,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;
@@ -778,8 +792,10 @@ void WindowsPlatform::Exit()
DbgHelpUnlock();
#endif
#if !PLATFORM_SDL
// Unregister app class
UnregisterClassW(ApplicationWindowClass, nullptr);
UnregisterClassW(ApplicationClassName, nullptr);
#endif
Win32Platform::Exit();
}
@@ -856,6 +872,7 @@ BatteryInfo WindowsPlatform::GetBatteryInfo()
return info;
}
#if !PLATFORM_SDL
int32 WindowsPlatform::GetDpi()
{
return SystemDpi;
@@ -865,6 +882,7 @@ String WindowsPlatform::GetUserLocaleName()
{
return UserLocale;
}
#endif
String WindowsPlatform::GetComputerName()
{
@@ -1234,10 +1252,12 @@ int32 WindowsPlatform::CreateProcess(CreateProcessSettings& settings)
return result;
}
#if !PLATFORM_SDL
Window* WindowsPlatform::CreateWindow(const CreateWindowSettings& settings)
{
return New<WindowsWindow>(settings);
}
#endif
void* WindowsPlatform::LoadLibrary(const Char* filename)
{

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,662 @@
// Copyright (c) 2012-2024 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/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 <propidl.h>
#if USE_EDITOR
#include <oleidl.h>
#include <shellapi.h>
#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<char*>(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<DVTARGETDEVICE*>(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);
/// <summary>
/// GUI data for Windows platform
/// </summary>
class WindowsGuiData : public IGuiData
{
private:
Type _type;
Array<String> _data;
public:
/// <summary>
/// Init
/// </summary>
WindowsGuiData()
: _type(Type::Unknown)
, _data(1)
{
}
public:
/// <summary>
/// Init from Ole IDataObject
/// </summary>
/// <param name="pDataObj">Object</param>
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<char*>(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<Char*>(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<HDROP>(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<String>* files) const override
{
if (_type == Type::Files)
{
files->Add(_data);
}
}
};
/// <summary>
/// Tool class for Windows Ole support
/// </summary>
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<WindowsEnumFormatEtc*>(*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;
}
/// <summary>
/// Drag drop source and data container for Ole
/// </summary>
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<DoDragDropJob>();
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<float>(p.x), static_cast<float>(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<float>(p.x), static_cast<float>(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<float>(p.x), static_cast<float>(p.y)), effect);
*pdwEffect = dropEffect2OleEnum(effect);
return S_OK;
}
#else
DragDropEffect Window::DoDragDrop(const StringView& data)
{
return DragDropEffect::None;
}
#endif
#endif

View File

@@ -1,6 +1,6 @@
// Copyright (c) 2012-2024 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 <propidl.h>
#if USE_EDITOR
@@ -118,7 +111,7 @@ WindowsWindow::WindowsWindow(const CreateWindowSettings& settings)
if (settings.IsRegularWindow)
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);
@@ -295,7 +288,7 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool maximized)
if (_settings.IsRegularWindow)
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
@@ -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)
@@ -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<char*>(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<DVTARGETDEVICE*>(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);
/// <summary>
/// GUI data for Windows platform
/// </summary>
class WindowsGuiData : public IGuiData
{
private:
Type _type;
Array<String> _data;
public:
/// <summary>
/// Init
/// </summary>
WindowsGuiData()
: _type(Type::Unknown)
, _data(1)
{
}
public:
/// <summary>
/// Init from Ole IDataObject
/// </summary>
/// <param name="pDataObj">Object</param>
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<char*>(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<Char*>(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<HDROP>(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<String>* files) const override
{
if (_type == Type::Files)
{
files->Add(_data);
}
}
};
/// <summary>
/// Tool class for Windows Ole support
/// </summary>
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<WindowsEnumFormatEtc*>(*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;
}
/// <summary>
/// Drag drop source and data container for Ole
/// </summary>
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<DoDragDropJob>();
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<float>(p.x), static_cast<float>(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<float>(p.x), static_cast<float>(p.y)), effect);
*pdwEffect = dropEffect2OleEnum(effect);
return S_OK;
}
HRESULT WindowsWindow::DragLeave()
{
OnDragLeave();
return S_OK;
}
HRESULT WindowsWindow::Drop(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;
OnDragDrop(&GuiDragDropData, Float2(static_cast<float>(p.x), static_cast<float>(p.y)), effect);
*pdwEffect = dropEffect2OleEnum(effect);
return S_OK;
}
#else
DragDropEffect WindowsWindow::DoDragDrop(const StringView& data)
{
return DragDropEffect::None;
}
#endif
#endif

View File

@@ -2,7 +2,7 @@
#pragma once
#if PLATFORM_WINDOWS
#if PLATFORM_WINDOWS && !PLATFORM_SDL
#include "Engine/Platform/Base/WindowBase.h"
#include "Engine/Platform/Platform.h"
@@ -136,7 +136,7 @@ public:
Windows::ULONG __stdcall AddRef() override;
Windows::ULONG __stdcall Release() override;
// [IDropTarget]
// [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;

View File

@@ -771,6 +771,15 @@ namespace FlaxEngine.GUI
Tooltip.OnMouseLeaveControl(this);
}
}
/// <summary>
/// When mouse moves over control's area while mouse is in relative mode
/// </summary>
/// <param name="mouseMotion">Mouse relative motion</param>
[NoAnimate]
public virtual void OnMouseMoveRelative(Float2 mouseMotion)
{
}
/// <summary>
/// When mouse leaves control's area

View File

@@ -17,6 +17,11 @@ namespace FlaxEngine.GUI
private string _currentText;
private Window _window;
/// <summary>
/// The mouse offset from top-left corner of tooltip.
/// </summary>
private static readonly Float2 TooltipOffset = new Float2(15, 10);
/// <summary>
/// The horizontal alignment of the text.
/// </summary>
@@ -69,16 +74,24 @@ namespace FlaxEngine.GUI
UnlockChildrenRecursive();
PerformLayout();
// Calculate popup direction and initial location
var parentWin = target.Root;
if (parentWin == null)
return;
// Showing a popup window might bring up the parent window on top
if (!parentWin.IsFocused)
return;
// Calculate popup direction and initial location
var dpiScale = target.RootWindow.DpiScale;
var dpiSize = Size * dpiScale;
var locationWS = target.PointToWindow(location);
var locationSS = parentWin.PointToScreen(locationWS);
var mousePos = Input.MouseScreenPosition;
_showTarget = target;
WrapPosition(ref locationSS);
//WrapPosition(ref locationSS);
WrapPosition(ref mousePos, 10);
locationSS = mousePos + TooltipOffset;
// Create window
var desc = CreateWindowSettings.Default;
@@ -95,9 +108,11 @@ namespace FlaxEngine.GUI
desc.AllowMaximize = false;
desc.AllowDragAndDrop = false;
desc.IsTopmost = true;
desc.IsRegularWindow = false;
desc.Type = WindowType.Tooltip;
desc.Title = "Tooltip";
desc.HasSizingFrame = false;
desc.ShowAfterFirstPaint = true;
desc.Parent = parentWin.RootWindow.Window;
_window = Platform.CreateWindow(ref desc);
if (_window == null)
throw new InvalidOperationException("Failed to create tooltip window.");
@@ -197,7 +212,7 @@ namespace FlaxEngine.GUI
var rightBottomLocationSS = locationSS + dpiSize;
// Prioritize tooltip placement within parent window, fall back to virtual desktop
if (rightBottomMonitorBounds.Y < rightBottomLocationSS.Y)
/*if (rightBottomMonitorBounds.Y < rightBottomLocationSS.Y)
{
// Direction: up
locationSS.Y -= dpiSize.Y + flipOffset;
@@ -206,7 +221,7 @@ namespace FlaxEngine.GUI
{
// Direction: left
locationSS.X -= dpiSize.X + flipOffset * 2;
}
}*/
}
/// <inheritdoc />
@@ -224,7 +239,7 @@ namespace FlaxEngine.GUI
// Position tooltip when mouse moves
WrapPosition(ref mousePos, 10);
if (_window)
_window.Position = mousePos + new Float2(15, 10);
_window.Position = mousePos + TooltipOffset;
}
base.Update(deltaTime);

View File

@@ -338,5 +338,17 @@ namespace FlaxEngine.GUI
base.OnMouseMove(location);
}
/// <inheritdoc />
public override void OnMouseMoveRelative(Float2 mouseMotion)
{
if (_trackingControl != null)
{
_trackingControl.OnMouseMoveRelative(mouseMotion);
return;
}
base.OnMouseMoveRelative(mouseMotion);
}
}
}

18
Source/ThirdParty/SDL/LICENSE.txt vendored Normal file
View File

@@ -0,0 +1,18 @@
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

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