67 Commits

Author SHA1 Message Date
5274a96a42 _begin refactor, windows fixes
Some checks are pending
Build Android / Game (Android, Release ARM64) (push) Waiting to run
Build iOS / Game (iOS, Release ARM64) (push) Waiting to run
Build Linux / Editor (Linux, Development x64) (push) Waiting to run
Build Linux / Game (Linux, Release x64) (push) Waiting to run
Build macOS / Editor (Mac, Development ARM64) (push) Waiting to run
Build macOS / Game (Mac, Release ARM64) (push) Waiting to run
Build Windows / Editor (Windows, Development x64) (push) Waiting to run
Build Windows / Game (Windows, Release x64) (push) Waiting to run
Cooker / Cook (Mac) (push) Waiting to run
Tests / Tests (Linux) (push) Waiting to run
Tests / Tests (Windows) (push) Waiting to run
2025-01-01 02:05:15 +02:00
aac2920f03 Update SDL3 2025-01-01 02:04:30 +02:00
18711a2a14 Fix window ShowInTaskbar setting 2025-01-01 02:01:13 +02:00
87e6c465a0 _sdl binary 2024-12-31 01:16:36 +02:00
e44b2a292a _update sdl 2024-12-31 01:16:31 +02:00
d1d148d5a7 _sdl build fix 2024-12-31 01:16:25 +02:00
3df9602ac5 _refix x11 popu positioning
Some checks failed
Build Android / Game (Android, Release ARM64) (push) Has been cancelled
Build iOS / Game (iOS, Release ARM64) (push) Has been cancelled
Build Linux / Editor (Linux, Development x64) (push) Has been cancelled
Build Linux / Game (Linux, Release x64) (push) Has been cancelled
Build macOS / Editor (Mac, Development ARM64) (push) Has been cancelled
Build macOS / Game (Mac, Release ARM64) (push) Has been cancelled
Build Windows / Editor (Windows, Development x64) (push) Has been cancelled
Build Windows / Game (Windows, Release x64) (push) Has been cancelled
Cooker / Cook (Mac) (push) Has been cancelled
Tests / Tests (Linux) (push) Has been cancelled
Tests / Tests (Windows) (push) Has been cancelled
2024-12-30 03:52:40 +02:00
003952dfec _final wayland fixes, tooltip move fixes
Some checks are pending
Build Android / Game (Android, Release ARM64) (push) Waiting to run
Build iOS / Game (iOS, Release ARM64) (push) Waiting to run
Build Linux / Editor (Linux, Development x64) (push) Waiting to run
Build Linux / Game (Linux, Release x64) (push) Waiting to run
Build macOS / Editor (Mac, Development ARM64) (push) Waiting to run
Build macOS / Game (Mac, Release ARM64) (push) Waiting to run
Build Windows / Editor (Windows, Development x64) (push) Waiting to run
Build Windows / Game (Windows, Release x64) (push) Waiting to run
Cooker / Cook (Mac) (push) Waiting to run
Tests / Tests (Linux) (push) Waiting to run
Tests / Tests (Windows) (push) Waiting to run
2024-12-30 02:38:06 +02:00
3190c22295 Update SDL3 2024-12-30 01:57:53 +02:00
366404f251 Update SDL3 2024-12-28 21:39:48 +02:00
484c8ce146 _wayland working minus sdl bug 2024-12-28 20:45:34 +02:00
c9f1a45f40 _x11 maybe working? 2024-12-28 20:03:44 +02:00
089346b296 _x11 child tooltip fixes progress 2024-12-28 19:41:06 +02:00
ac0ce8fa74 Use SDL locale 2024-12-28 13:31:37 +02:00
9331c35ab3 _update sdl binary 2024-12-27 16:18:31 +02:00
45f67c4d8c Fix SDL build process on Linux 2024-12-27 16:18:15 +02:00
cd2e7140af _windows subwindow popup fix
Some checks failed
Build Android / Game (Android, Release ARM64) (push) Has been cancelled
Build iOS / Game (iOS, Release ARM64) (push) Has been cancelled
Build Linux / Editor (Linux, Development x64) (push) Has been cancelled
Build Linux / Game (Linux, Release x64) (push) Has been cancelled
Build macOS / Editor (Mac, Development ARM64) (push) Has been cancelled
Build macOS / Game (Mac, Release ARM64) (push) Has been cancelled
Build Windows / Editor (Windows, Development x64) (push) Has been cancelled
Build Windows / Game (Windows, Release x64) (push) Has been cancelled
Cooker / Cook (Mac) (push) Has been cancelled
Tests / Tests (Linux) (push) Has been cancelled
Tests / Tests (Windows) (push) Has been cancelled
2024-12-27 12:58:04 +02:00
d5b64aece4 _sdl binary 2024-12-27 12:22:50 +02:00
146cc13bad Update SDL3 2024-12-27 12:22:45 +02:00
865b430ab0 _sdl3 binary 2024-12-27 00:19:23 +02:00
a5ad7a4398 Update SDL3 2024-12-27 00:19:17 +02:00
89d1cc4270 _borderless maximize workarounds WIP 2024-12-26 22:12:02 +02:00
bd81e3e89e Fix workaround for maximizing Editor window 2024-12-25 19:45:17 +02:00
37acdc29ff _sdl3 binary 2024-12-25 19:44:25 +02:00
ccdc7a5e1d Update SDL3 2024-12-25 19:44:19 +02:00
fe313773d1 Fix casting error in CPU profiler tab
Some checks failed
Build Android / Game (Android, Release ARM64) (push) Has been cancelled
Build iOS / Game (iOS, Release ARM64) (push) Has been cancelled
Build Linux / Editor (Linux, Development x64) (push) Has been cancelled
Build Linux / Game (Linux, Release x64) (push) Has been cancelled
Build macOS / Editor (Mac, Development ARM64) (push) Has been cancelled
Build macOS / Game (Mac, Release ARM64) (push) Has been cancelled
Build Windows / Editor (Windows, Development x64) (push) Has been cancelled
Build Windows / Game (Windows, Release x64) (push) Has been cancelled
Cooker / Cook (Mac) (push) Has been cancelled
Tests / Tests (Linux) (push) Has been cancelled
Tests / Tests (Windows) (push) Has been cancelled
2024-12-19 20:09:43 +02:00
34346e4111 Force cursor to center of Game Window when tab handle is clicked 2024-12-19 20:09:43 +02:00
7cbe44c4e5 _update sdl 2024-12-19 20:09:43 +02:00
846f44b882 _sdl binary 2024-12-19 20:09:42 +02:00
68764580f2 Update SDL 2024-12-19 20:09:42 +02:00
Chandler Cox
cdf0e24c12 Fix rotation using SDL 2024-12-19 20:09:42 +02:00
ac7087c4ce Update SDL3 binaries 2024-12-19 20:09:41 +02:00
56e6e53d0c Update SDL to 3.1.3 preview 2024-12-19 20:09:41 +02:00
25b6605027 _window positioning that works on windows 2024-12-19 20:09:41 +02:00
c97f6f3b7e _lfsconfig 2024-12-19 20:09:40 +02:00
bc33d1a0f5 _noprog? 2024-12-19 20:09:40 +02:00
5e950833ff _works 0,0 primary screen, returned positions correct but visually wrong 2024-12-19 20:09:40 +02:00
9a04cee71f _works 2024-12-19 20:09:39 +02:00
b79c9402fd _wip parent-child positioning fixes 2024-12-19 20:09:39 +02:00
b0aed96d18 Fix Linux compilation without SDL 2024-12-19 20:09:39 +02:00
c003506906 Fix compilation 2024-12-19 20:09:38 +02:00
473dc4a1a0 Update SDL3 2024-12-19 20:09:38 +02:00
8d97e21b28 Fix compilation issues 2024-12-19 20:09:38 +02:00
ce932b1739 Fix windows not being hidden initially 2024-12-19 20:09:37 +02:00
506ad85cad Fix parent window position handling with popup/tooltip windows 2024-12-19 20:09:37 +02:00
e86ccefd3c Fix compilation errors in other platforms 2024-12-19 20:09:37 +02:00
71147cd4e9 Fix CI for Linux 2024-12-19 20:09:36 +02:00
d6fe1f0519 Prevent building with SDL in unsupported platforms 2024-12-19 20:09:36 +02:00
ed3d1ce366 Fallback to X11 message box implementation when SDL fails 2024-12-19 20:09:36 +02:00
37177edeaa Fix popup and context menus not working on Wayland 2024-12-19 20:09:35 +02:00
7981483a5b Hide warnings for unsupported SDL operations on Wayland 2024-12-19 20:09:35 +02:00
c3ca359b4b Log a warning for not implemented Wayland functionality 2024-12-19 20:09:35 +02:00
99cf7292aa Fix compilation in Linux 2024-12-19 20:09:34 +02:00
4c6f39d489 Enable warning sound in question dialogs 2024-12-19 20:09:34 +02:00
dcfc265433 Enable modern Windows dialog boxes 2024-12-19 20:09:34 +02:00
4a7f6c34f3 Implement relative mouse mode (raw input) for SDL platform 2024-12-19 20:09:33 +02:00
ba248a61bd Add flag for Window types 2024-12-19 20:09:33 +02:00
eacd23bdbb Enable native windowing system settings with SDL platform 2024-12-19 20:09:33 +02:00
62df728452 Add command-line switches to force X11 and Wayland SDL drivers 2024-12-19 20:09:32 +02:00
fe0d97aad1 Implement SDL platform, windowing and input handling 2024-12-19 20:09:32 +02:00
7f7d839f23 Refactor application window class name 2024-12-19 20:09:32 +02:00
c445bfd5d4 Move Window related enums to separate header file 2024-12-19 20:09:31 +02:00
62843476b7 Refactor Windows drag and drop implementation 2024-12-19 20:09:31 +02:00
7595c572d7 Refactor ScreenUtilities 2024-12-19 20:09:31 +02:00
13ae091273 Add more helper methods for managing Git repos 2024-12-19 20:09:31 +02:00
a9dccd2949 Fix centered window location on X11 2024-12-19 20:09:30 +02:00
f8dd3f23c6 Fix initial position of Tooltips 2024-12-19 20:09:30 +02:00
188 changed files with 84378 additions and 1272 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

@@ -1,4 +1,4 @@
# Redirect to our own Git LFS server
[lfs]
url="https://gitlab.flaxengine.com/flax/flaxengine.git/info/lfs"
#url="https://gitlab.flaxengine.com/flax/flaxengine.git/info/lfs"
locksverify = false

View File

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

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;
@@ -111,7 +114,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>
@@ -203,6 +206,7 @@ namespace FlaxEditor.GUI.ContextMenu
// Create window
var desc = CreateWindowSettings.Default;
desc.Position = locationSS;
//Editor.Log($"contextmenu loc: {locationSS}, in parentloc: {location}");
desc.StartPosition = WindowStartPosition.Manual;
desc.Size = dpiSize;
desc.Fullscreen = false;
@@ -215,21 +219,27 @@ 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);
_window.GotFocus += OnWindowGotFocus;
_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();
PerformLayout();
_previouslyFocused = parentWin.FocusedControl;
@@ -378,6 +388,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;
@@ -391,6 +412,7 @@ namespace FlaxEditor.GUI.ContextMenu
});
}
}
#endif
private void OnWindowLostFocus()
{
@@ -489,7 +511,12 @@ namespace FlaxEditor.GUI.ContextMenu
// Let root context menu to check if none of the popup windows
if (_parentCM == null && !IsForeground)
{
#if USE_SDL_WORKAROUNDS
if (!IsMouseOver)
Hide();
#else
Hide();
#endif
}
}
#endif

View File

@@ -67,9 +67,7 @@ namespace FlaxEditor.GUI.Docking
Proxy.Window.MouseUp += OnMouseUp;
Proxy.Window.MouseMove += OnMouseMove;
Proxy.Window.LostFocus += OnLostFocus;
// Start tracking mouse
Proxy.Window.StartTrackingMouse(false);
_toMove.Window.Window.MouseUp += OnMouseUp; // Intercept the drag release mouse event from source window
// Update window GUI
Proxy.Window.GUI.PerformLayout();
@@ -77,13 +75,16 @@ namespace FlaxEditor.GUI.Docking
// Update rectangles
UpdateRects();
// Hide base window
window.Hide();
// Enable hit window presentation
Proxy.Window.RenderingEnabled = true;
Proxy.Window.Show();
Proxy.Window.Focus();
// Hide base window
window.Hide();
// Start tracking mouse
Proxy.Window.StartTrackingMouse(false);
}
/// <summary>
@@ -101,6 +102,8 @@ namespace FlaxEditor.GUI.Docking
Proxy.Window.MouseUp -= OnMouseUp;
Proxy.Window.MouseMove -= OnMouseMove;
Proxy.Window.LostFocus -= OnLostFocus;
if (_toMove?.Window?.Window)
_toMove.Window.Window.MouseUp -= OnMouseUp;
// Hide the proxy
Proxy.Hide();
@@ -240,6 +243,8 @@ namespace FlaxEditor.GUI.Docking
{
var baseWinPos = _toMove.Window.Window.Position;
_dragOffset = mouseScreenPosition - baseWinPos;
Editor.Log($"_dragOffset: {_dragOffset}, mouse: {mouseScreenPosition}, basewinpos: {baseWinPos}");
}
private void UpdateRects()
@@ -438,7 +443,7 @@ namespace FlaxEditor.GUI.Docking
settings.AllowMinimize = false;
settings.HasBorder = false;
settings.HasSizingFrame = false;
settings.IsRegularWindow = false;
settings.Type = WindowType.Utility;
settings.SupportsTransparency = true;
settings.ShowInTaskbar = false;
settings.ShowAfterFirstPaint = false;
@@ -470,7 +475,7 @@ namespace FlaxEditor.GUI.Docking
settings.AllowMinimize = false;
settings.HasBorder = false;
settings.HasSizingFrame = false;
settings.IsRegularWindow = false;
settings.Type = WindowType.Utility;
settings.SupportsTransparency = true;
settings.ShowInTaskbar = false;
settings.ActivateWhenFirstShown = false;

View File

@@ -82,7 +82,7 @@ namespace FlaxEditor.GUI.Docking
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

@@ -266,6 +266,7 @@ namespace FlaxEditor.GUI.Input
return base.OnMouseDown(location, button);
}
#if !PLATFORM_SDL
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
@@ -292,6 +293,36 @@ 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)
{

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)
{
@@ -166,7 +166,7 @@ namespace FlaxEditor.GUI
}
}
#if PLATFORM_WINDOWS
#if PLATFORM_WINDOWS || PLATFORM_SDL
/// <inheritdoc />
public override void Update(float deltaTime)
{
@@ -291,7 +291,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 +321,7 @@ namespace FlaxEditor.GUI
{
float x = 0;
#if PLATFORM_WINDOWS
#if PLATFORM_WINDOWS || PLATFORM_SDL
if (_useCustomWindowSystem)
{
// Icon
@@ -349,7 +349,7 @@ namespace FlaxEditor.GUI
}
}
#if PLATFORM_WINDOWS
#if PLATFORM_WINDOWS || PLATFORM_SDL
if (_useCustomWindowSystem)
{
// Buttons
@@ -367,7 +367,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

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

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

@@ -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).
@@ -525,10 +529,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;
@@ -1456,7 +1461,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);
@@ -1465,6 +1472,7 @@ namespace FlaxEditor.Viewport
_viewMousePos = center;
win.MousePosition = PointToWindow(_viewMousePos);
}
#endif
}
/// <summary>
@@ -1476,6 +1484,7 @@ namespace FlaxEditor.Viewport
// Restore cursor and stop tracking mouse movement
win.Cursor = CursorType.Default;
win.EndTrackingMouse();
win.MouseMoveRelative -= OnMouseMoveRelative;
}
/// <summary>
@@ -1580,6 +1589,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));
@@ -1587,6 +1604,7 @@ namespace FlaxEditor.Viewport
_input.Gather(win.Window, useMouse, ref _prevInput);
else
_input.Clear();
#endif
// Track controlling mouse state change
bool wasControllingMouse = _prevInput.IsControllingMouse;
@@ -1695,6 +1713,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)
@@ -1736,6 +1758,7 @@ namespace FlaxEditor.Viewport
mouseDelta += _mouseDeltaLast * _mouseAccelerationScale;
_mouseDeltaLast = currentDelta;
}
#endif
// Update
moveDelta *= dt * (60.0f * 4.0f);
@@ -1744,12 +1767,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)
@@ -1761,6 +1786,8 @@ namespace FlaxEditor.Viewport
}
else
{
#if PLATFORM_SDL
#else
if (_input.IsMouseLeftDown || _input.IsMouseRightDown || _isVirtualMouseRightDown)
{
// Calculate smooth mouse delta not dependant on viewport size
@@ -1775,6 +1802,7 @@ namespace FlaxEditor.Viewport
_mouseDelta = Float2.Zero;
}
_mouseDeltaLast = Float2.Zero;
#endif
if (ContainsFocus)
{
@@ -1824,6 +1852,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

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

@@ -175,7 +175,7 @@ namespace FlaxEditor.Windows.Profiler
private string FormatCellBytes(object x)
{
return Utilities.Utils.FormatBytesCount((ulong)x);
return Utilities.Utils.FormatBytesCount((ulong)(int)x);
}
/// <inheritdoc />

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

@@ -95,13 +95,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

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

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

@@ -79,6 +79,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;
@@ -209,6 +210,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();
@@ -274,6 +283,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;
@@ -731,6 +745,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;
@@ -787,6 +804,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;
@@ -1000,12 +1020,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

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

View File

@@ -166,6 +166,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

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();
@@ -257,6 +257,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>
@@ -837,6 +592,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;
@@ -316,7 +315,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!"));
@@ -474,7 +473,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

@@ -93,7 +93,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
@@ -651,7 +653,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)
return DialogResult::None;
@@ -838,6 +844,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)
@@ -848,6 +856,8 @@ int X11ErrorHandler(X11::Display* display, X11::XErrorEvent* event)
return 0;
}
#endif
int32 CalculateDpi()
{
int dpi = 96;
@@ -1203,17 +1213,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)
@@ -1328,8 +1341,10 @@ namespace Impl
X11::XQueryPointer(display, w, &wtmp, &child, &tmp, &tmp, &tmp, &tmp, &utmp);
return FindAppWindow(display, child);
}
#endif
}
#if !PLATFORM_SDL
class LinuxDropFilesData : public IGuiData
{
public:
@@ -1367,7 +1382,7 @@ public:
}
};
DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
DragDropEffect Window::DoDragDrop(const StringView& data)
{
if (CommandLine::Options.Headless)
return DragDropEffect::None;
@@ -1381,13 +1396,18 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
StringAnsi dataAnsi(data);
LinuxDropTextData dropData;
dropData.Text = data;
#if !PLATFORM_SDL
unsigned long mainWindow = _window;
#else
unsigned long mainWindow = (unsigned long)_handle;
#endif
// 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;
@@ -1495,7 +1515,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;
@@ -1524,7 +1544,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;
@@ -1559,7 +1579,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;
@@ -1602,7 +1622,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;
@@ -1649,7 +1669,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;
@@ -1675,7 +1695,7 @@ void LinuxClipboard::SetText(const StringView& text)
{
if (CommandLine::Options.Headless)
return;
auto mainWindow = (LinuxWindow*)Engine::MainWindow;
auto mainWindow = Engine::MainWindow;
if (!mainWindow)
return;
X11::Window window = (X11::Window)mainWindow->GetNativePtr();
@@ -1698,7 +1718,7 @@ String LinuxClipboard::GetText()
if (CommandLine::Options.Headless)
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();
@@ -1727,9 +1747,11 @@ Array<String> LinuxClipboard::GetFiles()
return Array<String>();
}
#endif
void* LinuxPlatform::GetXDisplay()
{
return xDisplay;
return xDisplay;
}
bool LinuxPlatform::CreateMutex(const Char* name)
@@ -2090,6 +2112,7 @@ bool LinuxPlatform::Init()
DeviceId.D = (uint32)UnixCpu.ClockSpeed * UnixCpu.LogicalProcessorCount * UnixCpu.ProcessorCoreCount * UnixCpu.CacheLineSize;
}
#if !PLATFORM_SDL
// Get user locale string
setlocale(LC_ALL, "");
const char* locale = setlocale(LC_CTYPE, NULL);
@@ -2099,6 +2122,7 @@ bool LinuxPlatform::Init()
UserLocale.Replace('_', '-');
if (UserLocale == TEXT("C"))
UserLocale = TEXT("en");
#endif
// Get computer name string
gethostname(buffer, UNIX_APP_BUFF_SIZE);
@@ -2117,10 +2141,15 @@ bool LinuxPlatform::Init()
Platform::MemoryClear(Cursors, sizeof(Cursors));
Platform::MemoryClear(CursorsImg, sizeof(CursorsImg));
// Skip setup if running in headless mode (X11 might not be available on servers)
if (CommandLine::Options.Headless)
return false;
#if PLATFORM_SDL
xDisplay = X11::XOpenDisplay(nullptr);
#endif
#if !PLATFORM_SDL
X11::XInitThreads();
xDisplay = X11::XOpenDisplay(nullptr);
@@ -2259,7 +2288,7 @@ bool LinuxPlatform::Init()
Input::Mouse = Impl::Mouse = New<LinuxMouse>();
Input::Keyboard = Impl::Keyboard = New<LinuxKeyboard>();
LinuxInput::Init();
#endif
return false;
}
@@ -2269,6 +2298,7 @@ void LinuxPlatform::BeforeRun()
void LinuxPlatform::Tick()
{
#if !PLATFORM_SDL
UnixPlatform::Tick();
LinuxInput::UpdateState();
@@ -2285,9 +2315,9 @@ void LinuxPlatform::Tick()
continue;
// External event handling
xEventRecieved(&event);
xEventReceived(&event);
LinuxWindow* window;
Window* window;
switch (event.type)
{
case ClientMessage:
@@ -2619,6 +2649,7 @@ void LinuxPlatform::Tick()
}
//X11::XFlush(xDisplay);
#endif
}
void LinuxPlatform::BeforeExit()
@@ -2627,6 +2658,7 @@ void LinuxPlatform::BeforeExit()
void LinuxPlatform::Exit()
{
#if !PLATFORM_SDL
for (int32 i = 0; i < (int32)CursorType::MAX; i++)
{
if (Cursors[i])
@@ -2652,8 +2684,10 @@ void LinuxPlatform::Exit()
X11::XCloseDisplay(xDisplay);
xDisplay = nullptr;
}
#endif
}
#if !PLATFORM_SDL
int32 LinuxPlatform::GetDpi()
{
return SystemDpi;
@@ -2663,6 +2697,7 @@ String LinuxPlatform::GetUserLocaleName()
{
return UserLocale;
}
#endif
String LinuxPlatform::GetComputerName()
{
@@ -2870,10 +2905,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:
@@ -122,8 +122,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);
@@ -138,7 +140,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 USE_EDITOR && 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 USE_EDITOR && 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"
@@ -617,7 +617,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,19 @@ 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.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,792 @@
// 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 <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;
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
{
return oldPosition;
}
// [Mouse]
void SetMousePosition(const Float2& newPosition) final override
{
SDL_WarpMouseGlobal(newPosition.X, newPosition.Y);
OnMouseMoved(newPosition);
}
void SetRelativeMode(bool relativeMode, Window* window) final override
{
if (relativeMode == _relativeMode)
return;
if (relativeMode)
SDL_GetGlobalMouseState(&oldPosition.X, &oldPosition.Y);
Mouse::SetRelativeMode(relativeMode, window);
if (!SDL_SetWindowRelativeMouseMode(static_cast<SDLWindow*>(window)->_window, relativeMode))
LOG(Error, "Failed to set mouse relative mode: {0}", String(SDL_GetError()));
if (!relativeMode)
SetMousePosition(oldPosition);
}
};
/// <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 });
Int2 p;
Float2 wp = window->ClientToScreen({0, 0});
//SDL_GetWindowPosition(window->GetSDLWindow(), &p.X, &p.Y);
p.X = wp.X;
p.Y = wp.Y;
//LOG(Info, "motion {},{}, mouse: {}, win: {}, winpos {},{}", event.motion.x, event.motion.y, mousePos, String(window->GetTitle()), p.X, p.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

View File

@@ -0,0 +1,937 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#if PLATFORM_SDL && PLATFORM_LINUX
#include "Engine/Platform/Platform.h"
#include "SDLWindow.h"
#include "Engine/Platform/IGuiData.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Platform/SDL/SDLClipboard.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Platform/WindowsManager.h"
#include "Engine/Platform/Linux/IncludeX11.h"
#include <SDL3/SDL_video.h>
#include <SDL3/SDL_system.h>
#include <SDL3/SDL_hints.h>
#include <SDL3/SDL_timer.h>
Delegate<void*> LinuxPlatform::xEventReceived;
// Missing Wayland features:
// - Application icon (xdg-toplevel-icon-v1) https://github.com/libsdl-org/SDL/pull/9584
// - Window positioning and position tracking
// - Color picker (xdg-desktop-portal?) https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Screenshot.html
// -
namespace
{
bool UseWayland = false;
bool UseXWayland = false;
X11::Display* xDisplay = nullptr;
X11::XIM IM = nullptr;
X11::XIC IC = nullptr;
X11::Atom xAtomDeleteWindow;
X11::Atom xAtomXdndEnter;
X11::Atom xAtomXdndPosition;
X11::Atom xAtomXdndLeave;
X11::Atom xAtomXdndDrop;
X11::Atom xAtomXdndActionCopy;
X11::Atom xAtomXdndStatus;
X11::Atom xAtomXdndSelection;
X11::Atom xAtomXdndFinished;
X11::Atom xAtomXdndAware;
X11::Atom xAtomWmState;
X11::Atom xAtomWmStateHidden;
X11::Atom xAtomWmStateMaxVert;
X11::Atom xAtomWmStateMaxHorz;
X11::Atom xAtomWmWindowOpacity;
X11::Atom xAtomWmName;
X11::Atom xAtomAtom;
X11::Atom xAtomClipboard;
X11::Atom xAtomPrimary;
X11::Atom xAtomTargets;
X11::Atom xAtomText;
X11::Atom xAtomString;
X11::Atom xAtomUTF8String;
X11::Atom xAtomXselData;
X11::Atom xDnDRequested = 0;
X11::Window xDndSourceWindow = 0;
DragDropEffect xDndResult;
Float2 xDndPos;
int32 xDnDVersion = 0;
int32 XFixesSelectionNotifyEvent = 0;
}
class LinuxDropFilesData : public IGuiData
{
public:
Array<String> Files;
Type GetType() const override
{
return Type::Files;
}
String GetAsText() const override
{
return String::Empty;
}
void GetAsFiles(Array<String>* files) const override
{
files->Add(Files);
}
};
class LinuxDropTextData : public IGuiData
{
public:
StringView Text;
Type GetType() const override
{
return Type::Text;
}
String GetAsText() const override
{
return String(Text);
}
void GetAsFiles(Array<String>* files) const override
{
}
};
struct Property
{
unsigned char* data;
int format, nitems;
X11::Atom type;
};
namespace Impl
{
StringAnsi ClipboardText;
void ClipboardGetText(String& result, X11::Atom source, X11::Atom atom, X11::Window window)
{
X11::Window selectionOwner = X11::XGetSelectionOwner(xDisplay, source);
if (selectionOwner == 0)
{
// No copy owner
return;
}
if (selectionOwner == window)
{
// Copy/paste from self
result.Set(ClipboardText.Get(), ClipboardText.Length());
return;
}
// Send event to get data from the owner
int format;
unsigned long N, size;
char* data;
X11::Atom target;
X11::XEvent event;
X11::XConvertSelection(xDisplay, xAtomClipboard, atom, xAtomXselData, window, CurrentTime);
X11::XSync(xDisplay, 0);
if (X11::XCheckTypedEvent(xDisplay, SelectionNotify, &event))
{
if (event.xselection.selection != xAtomClipboard)
return;
if (event.xselection.property)
{
X11::XGetWindowProperty(event.xselection.display, event.xselection.requestor, event.xselection.property, 0L,(~0L), 0, AnyPropertyType, &target, &format, &size, &N,(unsigned char**)&data);
if (target == xAtomUTF8String || target == xAtomString)
{
// Got text to paste
result.Set(data , size);
X11::XFree(data);
}
X11::XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property);
}
}
}
Property ReadProperty(X11::Display* display, X11::Window window, X11::Atom property)
{
X11::Atom readType = 0;
int readFormat = 0;
unsigned long nitems = 0;
unsigned long readBytes = 0;
unsigned char* result = nullptr;
int bytesCount = 1024;
if (property != 0)
{
do
{
if (result != nullptr)
X11::XFree(result);
XGetWindowProperty(display, window, property, 0, bytesCount, 0, AnyPropertyType, &readType, &readFormat, &nitems, &readBytes, &result);
bytesCount *= 2;
} while (readBytes != 0);
}
Property p = { result, readFormat, (int)nitems, readType };
return p;
}
static X11::Atom SelectTargetFromList(X11::Display* display, const char* targetType, X11::Atom* list, int count)
{
for (int i = 0; i < count; i++)
{
X11::Atom atom = list[i];
if (atom != 0 && StringAnsi(XGetAtomName(display, atom)) == targetType)
return atom;
}
return 0;
}
static X11::Atom SelectTargetFromAtoms(X11::Display* display, const char* targetType, X11::Atom t1, X11::Atom t2, X11::Atom t3)
{
if (t1 != 0 && StringAnsi(XGetAtomName(display, t1)) == targetType)
return t1;
if (t2 != 0 && StringAnsi(XGetAtomName(display, t2)) == targetType)
return t2;
if (t3 != 0 && StringAnsi(XGetAtomName(display, t3)) == targetType)
return t3;
return 0;
}
static X11::Window FindAppWindow(X11::Display* display, X11::Window w)
{
int nprops, i = 0;
X11::Atom* a;
if (w == 0)
return 0;
a = X11::XListProperties(display, w, &nprops);
for (i = 0; i < nprops; i++)
{
if (a[i] == xAtomXdndAware)
break;
}
if (nprops)
X11::XFree(a);
if (i != nprops)
return w;
X11::Window child, wtmp;
int tmp;
unsigned int utmp;
X11::XQueryPointer(display, w, &wtmp, &child, &tmp, &tmp, &tmp, &tmp, &utmp);
return FindAppWindow(display, child);
}
static Float2 GetX11MousePosition()
{
if (!xDisplay)
return Float2::Zero;
int32 x = 0, y = 0;
uint32 screenCount = (uint32)X11::XScreenCount(xDisplay);
for (uint32 i = 0; i < screenCount; i++)
{
X11::Window outRoot, outChild;
int32 childX, childY;
uint32 mask;
if (X11::XQueryPointer(xDisplay, X11::XRootWindow(xDisplay, i), &outRoot, &outChild, &x, &y, &childX, &childY, &mask))
break;
}
return Float2((float)x, (float)y);
}
}
DragDropEffect Window::DoDragDrop(const StringView& data)
{
if (CommandLine::Options.Headless)
return DragDropEffect::None;
if (UseWayland)
return DoDragDropWayland(data);
else
return DoDragDropX11(data);
}
DragDropEffect Window::DoDragDropWayland(const StringView& data)
{
// TODO: Wayland
LOG(Warning, "Wayland Drag and drop is not implemented yet.");
return DragDropEffect::None;
}
DragDropEffect Window::DoDragDropX11(const StringView& data)
{
auto cursorWrong = X11::XCreateFontCursor(xDisplay, 54);
auto cursorTransient = X11::XCreateFontCursor(xDisplay, 24);
auto cursorGood = X11::XCreateFontCursor(xDisplay, 4);
Array<X11::Atom, FixedAllocation<3>> formats;
formats.Add(X11::XInternAtom(xDisplay, "text/plain", 0));
formats.Add(xAtomText);
formats.Add(xAtomString);
StringAnsi dataAnsi(data);
LinuxDropTextData dropData;
dropData.Text = data;
#if !PLATFORM_SDL
X11::Window mainWindow = _window;
#else
X11::Window mainWindow = static_cast<X11::Window>(GetX11WindowHandle());
#endif
// Make sure SDL hasn't grabbed the pointer, and force ungrab it
XUngrabPointer(xDisplay, CurrentTime);
SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
// Begin dragging
auto screen = X11::XDefaultScreen(xDisplay);
auto rootWindow = X11::XRootWindow(xDisplay, screen);
if (X11::XGrabPointer(xDisplay, mainWindow, 1, Button1MotionMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, rootWindow, cursorWrong, CurrentTime) != GrabSuccess)
{
SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "1");
return DragDropEffect::None;
}
X11::XSetSelectionOwner(xDisplay, xAtomXdndSelection, mainWindow, CurrentTime);
// Process events
X11::XEvent event;
enum Status
{
Unaware,
Unreceptive,
CanDrop,
};
int status = Unaware, previousVersion = -1;
X11::Window previousWindow = 0;
DragDropEffect result = DragDropEffect::None;
float lastDraw = Platform::GetTimeSeconds();
float startTime = lastDraw;
while (true)
{
X11::XNextEvent(xDisplay, &event);
if (event.type == SelectionClear)
break;
if (event.type == SelectionRequest)
{
// Extract the relavent data
X11::Window owner = event.xselectionrequest.owner;
X11::Atom selection = event.xselectionrequest.selection;
X11::Atom target = event.xselectionrequest.target;
X11::Atom property = event.xselectionrequest.property;
X11::Window requestor = event.xselectionrequest.requestor;
X11::Time timestamp = event.xselectionrequest.time;
X11::Display* disp = event.xselection.display;
X11::XEvent s;
s.xselection.type = SelectionNotify;
s.xselection.requestor = requestor;
s.xselection.selection = selection;
s.xselection.target = target;
s.xselection.property = 0;
s.xselection.time = timestamp;
if (target == xAtomTargets)
{
Array<X11::Atom> targets;
targets.Add(target);
targets.Add(X11::XInternAtom(disp, "MULTIPLE", 0));
targets.Add(formats.Get(), formats.Count());
X11::XChangeProperty(disp, requestor, property, xAtomAtom, 32, PropModeReplace, (unsigned char*)targets.Get(), targets.Count());
s.xselection.property = property;
}
else if (formats.Contains(target))
{
s.xselection.property = property;
X11::XChangeProperty(disp, requestor, property, target, 8, PropModeReplace, reinterpret_cast<const unsigned char*>(dataAnsi.Get()), dataAnsi.Length());
}
X11::XSendEvent(event.xselection.display, event.xselectionrequest.requestor, 1, 0, &s);
}
else if (event.type == MotionNotify)
{
// Find window under mouse
auto window = Impl::FindAppWindow(xDisplay, rootWindow);
int fmt, version = -1;
X11::Atom atmp;
unsigned long nitems, bytesLeft;
unsigned char* data = nullptr;
if (window == previousWindow)
version = previousVersion;
else if(window == 0)
;
else if (X11::XGetWindowProperty(xDisplay, window, xAtomXdndAware, 0, 2, 0, AnyPropertyType, &atmp, &fmt, &nitems, &bytesLeft, &data) != Success)
continue;
else if (data == 0)
continue;
else if (fmt != 32)
continue;
else if (nitems != 1)
continue;
else
version = data[0];
if (status == Unaware && version != -1)
status = Unreceptive;
else if(version == -1)
status = Unaware;
xDndPos = Float2((float)event.xmotion.x_root, (float)event.xmotion.y_root);
// Update mouse grab
if (status == Unaware)
X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, cursorWrong, CurrentTime);
else if(status == Unreceptive)
X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, cursorTransient, CurrentTime);
else
X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, cursorGood, CurrentTime);
if (window != previousWindow && previousVersion != -1)
{
// Send drag left event
auto ww = WindowsManager::GetByNativePtr((void*)previousWindow);
if (ww)
{
ww->_dragOver = false;
ww->OnDragLeave();
}
else
{
X11::XClientMessageEvent m;
memset(&m, 0, sizeof(m));
m.type = ClientMessage;
m.display = event.xclient.display;
m.window = previousWindow;
m.message_type = xAtomXdndLeave;
m.format = 32;
m.data.l[0] = mainWindow;
m.data.l[1] = 0;
m.data.l[2] = 0;
m.data.l[3] = 0;
m.data.l[4] = 0;
X11::XSendEvent(xDisplay, previousWindow, 0, NoEventMask, (X11::XEvent*)&m);
X11::XFlush(xDisplay);
}
}
if (window != previousWindow && version != -1)
{
// Send drag enter event
auto ww = WindowsManager::GetByNativePtr((void*)window);
if (ww)
{
xDndPos = ww->ScreenToClient(Impl::GetX11MousePosition());
xDndResult = DragDropEffect::None;
ww->OnDragEnter(&dropData, xDndPos, xDndResult);
}
else
{
X11::XClientMessageEvent m;
memset(&m, 0, sizeof(m));
m.type = ClientMessage;
m.display = event.xclient.display;
m.window = window;
m.message_type = xAtomXdndEnter;
m.format = 32;
m.data.l[0] = mainWindow;
m.data.l[1] = Math::Min(5, version) << 24 | (formats.Count() > 3);
m.data.l[2] = formats.Count() > 0 ? formats[0] : 0;
m.data.l[3] = formats.Count() > 1 ? formats[1] : 0;
m.data.l[4] = formats.Count() > 2 ? formats[2] : 0;
X11::XSendEvent(xDisplay, window, 0, NoEventMask, (X11::XEvent*)&m);
X11::XFlush(xDisplay);
}
}
if (version != -1)
{
// Send position event
auto ww = WindowsManager::GetByNativePtr((void*)window);
if (ww)
{
xDndPos = ww->ScreenToClient(Impl::GetX11MousePosition());
ww->_dragOver = true;
xDndResult = DragDropEffect::None;
ww->OnDragOver(&dropData, xDndPos, xDndResult);
status = CanDrop;
}
else
{
int x, y, tmp;
unsigned int utmp;
X11::Window wtmp;
X11::XQueryPointer(xDisplay, window, &wtmp, &wtmp, &tmp, &tmp, &x, &y, &utmp);
X11::XClientMessageEvent m;
memset(&m, 0, sizeof(m));
m.type = ClientMessage;
m.display = event.xclient.display;
m.window = window;
m.message_type = xAtomXdndPosition;
m.format = 32;
m.data.l[0] = mainWindow;
m.data.l[1] = 0;
m.data.l[2] = (x << 16) | y;
m.data.l[3] = CurrentTime;
m.data.l[4] = xAtomXdndActionCopy;
X11::XSendEvent(xDisplay, window, 0, NoEventMask, (X11::XEvent*)&m);
X11::XFlush(xDisplay);
}
}
previousWindow = window;
previousVersion = version;
}
else if (event.type == ClientMessage && event.xclient.message_type == xAtomXdndStatus)
{
if ((event.xclient.data.l[1]&1) && status != Unaware)
status = CanDrop;
if (!(event.xclient.data.l[1]&1) && status != Unaware)
status = Unreceptive;
}
else if (event.type == ButtonRelease && event.xbutton.button == Button1)
{
if (status == CanDrop)
{
// Send drop event
auto ww = WindowsManager::GetByNativePtr((void*)previousWindow);
if (ww)
{
xDndPos = ww->ScreenToClient(Impl::GetX11MousePosition());
xDndResult = DragDropEffect::None;
ww->OnDragDrop(&dropData, xDndPos, xDndResult);
ww->Focus();
result = xDndResult;
}
else
{
X11::XClientMessageEvent m;
memset(&m, 0, sizeof(m));
m.type = ClientMessage;
m.display = event.xclient.display;
m.window = previousWindow;
m.message_type = xAtomXdndDrop;
m.format = 32;
m.data.l[0] = mainWindow;
m.data.l[1] = 0;
m.data.l[2] = CurrentTime;
m.data.l[3] = 0;
m.data.l[4] = 0;
X11::XSendEvent(xDisplay, previousWindow, 0, NoEventMask, (X11::XEvent*)&m);
X11::XFlush(xDisplay);
result = DragDropEffect::Copy;
}
}
break;
}
// Redraw
const float time = Platform::GetTimeSeconds();
if (time - lastDraw >= 1.0f / 20.0f)
{
lastDraw = time;
Engine::OnDraw();
}
// Prevent dead-loop
if (time - startTime >= 10.0f)
{
LOG(Warning, "DoDragDrop timed out after 10 seconds.");
break;
}
}
// Drag end
if (previousWindow != 0 && previousVersion != -1)
{
// Send drag left event
auto ww = WindowsManager::GetByNativePtr((void*)previousWindow);
if (ww)
{
ww->_dragOver = false;
ww->OnDragLeave();
}
else
{
X11::XClientMessageEvent m;
memset(&m, 0, sizeof(m));
m.type = ClientMessage;
m.display = event.xclient.display;
m.window = previousWindow;
m.message_type = xAtomXdndLeave;
m.format = 32;
m.data.l[0] = mainWindow;
m.data.l[1] = 0;
m.data.l[2] = 0;
m.data.l[3] = 0;
m.data.l[4] = 0;
X11::XSendEvent(xDisplay, previousWindow, 0, NoEventMask, (X11::XEvent*)&m);
X11::XFlush(xDisplay);
}
}
// End grabbing
X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, 0, CurrentTime);
XUngrabPointer(xDisplay, CurrentTime);
X11::XFlush(xDisplay);
SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "1");
return result;
}
void SDLClipboard::Clear()
{
SetText(StringView::Empty);
}
void SDLClipboard::SetText(const StringView& text)
{
if (CommandLine::Options.Headless)
return;
auto mainWindow = Engine::MainWindow;
if (!mainWindow)
return;
if (xDisplay)
{
X11::Window window = (X11::Window)(mainWindow->GetX11WindowHandle());
Impl::ClipboardText.Set(text.Get(), text.Length());
X11::XSetSelectionOwner(xDisplay, xAtomClipboard, window, CurrentTime); // CLIPBOARD
//X11::XSetSelectionOwner(xDisplay, xAtomPrimary, window, CurrentTime); // XA_PRIMARY
X11::XFlush(xDisplay);
X11::XGetSelectionOwner(xDisplay, xAtomClipboard);
//X11::XGetSelectionOwner(xDisplay, xAtomPrimary);
}
else
{
LOG(Warning, "Wayland clipboard support is not implemented yet."); // TODO: Wayland
}
}
void SDLClipboard::SetRawData(const Span<byte>& data)
{
}
void SDLClipboard::SetFiles(const Array<String>& files)
{
}
String SDLClipboard::GetText()
{
if (CommandLine::Options.Headless)
return String::Empty;
String result;
auto mainWindow = Engine::MainWindow;
if (!mainWindow)
return result;
if (xDisplay)
{
X11::Window window = (X11::Window)mainWindow->GetX11WindowHandle();
Impl::ClipboardGetText(result, xAtomClipboard, xAtomUTF8String, window);
if (result.HasChars())
return result;
Impl::ClipboardGetText(result, xAtomClipboard, xAtomString, window);
if (result.HasChars())
return result;
Impl::ClipboardGetText(result, xAtomPrimary, xAtomUTF8String, window);
if (result.HasChars())
return result;
Impl::ClipboardGetText(result, xAtomPrimary, xAtomString, window);
return result;
}
else
{
LOG(Warning, "Wayland clipboard is not implemented yet."); // TODO: Wayland
return String::Empty;
}
}
Array<byte> SDLClipboard::GetRawData()
{
return Array<byte>();
}
Array<String> SDLClipboard::GetFiles()
{
return Array<String>();
}
bool SDLCALL SDLPlatform::X11EventHook(void *userdata, _XEvent *xevent)
{
const X11::XEvent& event = *(X11::XEvent*)xevent;
Window* window;
// External event handling
xEventReceived(xevent);
if (event.type == ClientMessage)
{
if ((uint32)event.xclient.message_type == (uint32)xAtomXdndEnter)
{
// Drag&drop enter
X11::Window source = event.xclient.data.l[0];
xDnDVersion = (int32)(event.xclient.data.l[1] >> 24);
const char* targetTypeFiles = "text/uri-list";
if (event.xclient.data.l[1] & 1)
{
Property p = Impl::ReadProperty(xDisplay, source, XInternAtom(xDisplay, "XdndTypeList", 0));
xDnDRequested = Impl::SelectTargetFromList(xDisplay, targetTypeFiles, (X11::Atom*)p.data, p.nitems);
X11::XFree(p.data);
}
else
{
xDnDRequested = Impl::SelectTargetFromAtoms(xDisplay, targetTypeFiles, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]);
}
return false;
}
else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndPosition)
{
// Drag&drop move
X11::XClientMessageEvent m;
memset(&m, 0, sizeof(m));
m.type = ClientMessage;
m.display = event.xclient.display;
m.window = event.xclient.data.l[0];
m.message_type = xAtomXdndStatus;
m.format = 32;
m.data.l[0] = event.xany.window;
m.data.l[1] = (xDnDRequested != 0);
m.data.l[2] = 0;
m.data.l[3] = 0;
m.data.l[4] = xAtomXdndActionCopy;
X11::XSendEvent(xDisplay, event.xclient.data.l[0], 0, NoEventMask, (X11::XEvent*)&m);
X11::XFlush(xDisplay);
xDndPos = Float2((float)(event.xclient.data.l[2] >> 16), (float)(event.xclient.data.l[2] & 0xffff));
window = WindowsManager::GetByNativePtr((void*)event.xany.window);
if (window)
{
LinuxDropFilesData dropData;
xDndResult = DragDropEffect::None;
if (window->_dragOver)
{
window->OnDragOver(&dropData, xDndPos, xDndResult);
}
else
{
window->_dragOver = true;
window->OnDragEnter(&dropData, xDndPos, xDndResult);
}
}
return false;
}
else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndLeave)
{
window = WindowsManager::GetByNativePtr((void*)event.xany.window);
if (window && window->_dragOver)
{
window->_dragOver = false;
window->OnDragLeave();
}
return false;
}
else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndDrop)
{
auto w = event.xany.window;
if (xDnDRequested != 0)
{
xDndSourceWindow = event.xclient.data.l[0];
if (xDnDVersion >= 1)
XConvertSelection(xDisplay, xAtomXdndSelection, xDnDRequested, xAtomPrimary, w, event.xclient.data.l[2]);
else
XConvertSelection(xDisplay, xAtomXdndSelection, xDnDRequested, xAtomPrimary, w, CurrentTime);
}
else
{
X11::XClientMessageEvent m;
memset(&m, 0, sizeof(m));
m.type = ClientMessage;
m.display = event.xclient.display;
m.window = event.xclient.data.l[0];
m.message_type = xAtomXdndFinished;
m.format = 32;
m.data.l[0] = w;
m.data.l[1] = 0;
m.data.l[2] = 0;
X11::XSendEvent(xDisplay, event.xclient.data.l[0], 0, NoEventMask, (X11::XEvent*)&m);
}
return false;
}
}
else if (event.type == SelectionNotify)
{
if (event.xselection.target == xDnDRequested)
{
// Drag&drop
window = WindowsManager::GetByNativePtr((void*)event.xany.window);
if (window)
{
Property p = Impl::ReadProperty(xDisplay, event.xany.window, xAtomPrimary);
if (xDndResult != DragDropEffect::None)
{
LinuxDropFilesData dropData;
const String filesList((const char*)p.data);
filesList.Split('\n', dropData.Files);
for (auto& e : dropData.Files)
{
e.Replace(TEXT("file://"), TEXT(""));
e.Replace(TEXT("%20"), TEXT(" "));
e = e.TrimTrailing();
}
xDndResult = DragDropEffect::None;
window->OnDragDrop(&dropData, xDndPos, xDndResult);
}
}
X11::XClientMessageEvent m;
memset(&m, 0, sizeof(m));
m.type = ClientMessage;
m.display = xDisplay;
m.window = xDndSourceWindow;
m.message_type = xAtomXdndFinished;
m.format = 32;
m.data.l[0] = event.xany.window;
m.data.l[1] = 1;
m.data.l[2] = xAtomXdndActionCopy;
XSendEvent(xDisplay, xDndSourceWindow, 0, NoEventMask, (X11::XEvent*)&m);
return false;
}
return false;
}
else if (event.type == SelectionRequest)
{
if (event.xselectionrequest.selection != xAtomClipboard)
return false;
const X11::XSelectionRequestEvent* xsr = &event.xselectionrequest;
X11::XSelectionEvent ev = { 0 };
ev.type = SelectionNotify;
ev.display = xsr->display;
ev.requestor = xsr->requestor;
ev.selection = xsr->selection;
ev.time = xsr->time;
ev.target = xsr->target;
ev.property = xsr->property;
int result = 0;
if (ev.target == xAtomTargets)
{
Array<X11::Atom, FixedAllocation<2>> types(2);
types.Add(xAtomTargets);
types.Add(xAtomUTF8String);
result = X11::XChangeProperty(xDisplay, ev.requestor, ev.property, xAtomAtom, 32, PropModeReplace, (unsigned char*)types.Get(), types.Count());
}
else if (ev.target == xAtomString || ev.target == xAtomText)
result = X11::XChangeProperty(xDisplay, ev.requestor, ev.property, xAtomString, 8, PropModeReplace, (unsigned char*)Impl::ClipboardText.Get(), Impl::ClipboardText.Length());
else if (ev.target == xAtomUTF8String)
result = X11::XChangeProperty(xDisplay, ev.requestor, ev.property, xAtomUTF8String, 8, PropModeReplace, (unsigned char*)Impl::ClipboardText.Get(), Impl::ClipboardText.Length());
else
ev.property = 0;
if ((result & 2) == 0)
X11::XSendEvent(xDisplay, ev.requestor, 0, 0, (X11::XEvent*)&ev);
return false;
}
else if (event.type == SelectionClear)
return false;
else if (event.type == XFixesSelectionNotifyEvent)
return false;
return true;
}
int X11ErrorHandler(X11::Display* display, X11::XErrorEvent* event)
{
if (event->error_code == 5)
return 0; // BadAtom (invalid Atom parameter)
char buffer[256];
XGetErrorText(display, event->error_code, buffer, sizeof(buffer));
LOG(Error, "X11 Error: {0}", String(buffer));
return 0;
}
bool SDLPlatform::InitPlatform()
{
if (LinuxPlatform::Init())
return true;
if (!CommandLine::Options.Headless)
{
UseWayland = strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0;
String waylandDisplay;
if (/*!UseWayland &&*/ !Platform::GetEnvironmentVariable(TEXT("WAYLAND_DISPLAY"), waylandDisplay))
UseXWayland = waylandDisplay.Length() > 1;
}
return false;
}
bool SDLPlatform::InitPlatformX11(void* display)
{
if (xDisplay || UseWayland)
return false;
// The Display instance must be the same one SDL uses internally
xDisplay = (X11::Display*)display;
SDL_SetX11EventHook((SDL_X11EventHook)&X11EventHook, nullptr);
X11::XSetErrorHandler(X11ErrorHandler);
//xDisplay = X11::XOpenDisplay(nullptr);
xAtomDeleteWindow = X11::XInternAtom(xDisplay, "WM_DELETE_WINDOW", 0);
xAtomXdndEnter = X11::XInternAtom(xDisplay, "XdndEnter", 0);
xAtomXdndPosition = X11::XInternAtom(xDisplay, "XdndPosition", 0);
xAtomXdndLeave = X11::XInternAtom(xDisplay, "XdndLeave", 0);
xAtomXdndDrop = X11::XInternAtom(xDisplay, "XdndDrop", 0);
xAtomXdndActionCopy = X11::XInternAtom(xDisplay, "XdndActionCopy", 0);
xAtomXdndStatus = X11::XInternAtom(xDisplay, "XdndStatus", 0);
xAtomXdndSelection = X11::XInternAtom(xDisplay, "XdndSelection", 0);
xAtomXdndFinished = X11::XInternAtom(xDisplay, "XdndFinished", 0);
xAtomXdndAware = X11::XInternAtom(xDisplay, "XdndAware", 0);
xAtomWmStateHidden = X11::XInternAtom(xDisplay, "_NET_WM_STATE_HIDDEN", 0);
xAtomWmStateMaxHorz = X11::XInternAtom(xDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", 0);
xAtomWmStateMaxVert = X11::XInternAtom(xDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", 0);
xAtomWmWindowOpacity = X11::XInternAtom(xDisplay, "_NET_WM_WINDOW_OPACITY", 0);
xAtomWmName = X11::XInternAtom(xDisplay, "_NET_WM_NAME", 0);
xAtomAtom = static_cast<X11::Atom>(4); // XA_ATOM
xAtomClipboard = X11::XInternAtom(xDisplay, "CLIPBOARD", 0);
xAtomPrimary = static_cast<X11::Atom>(1); // XA_PRIMARY
xAtomTargets = X11::XInternAtom(xDisplay, "TARGETS", 0);
xAtomText = X11::XInternAtom(xDisplay, "TEXT", 0);
xAtomString = static_cast<X11::Atom>(31); // XA_STRING
xAtomUTF8String = X11::XInternAtom(xDisplay, "UTF8_STRING", 1);
if (xAtomUTF8String == 0)
xAtomUTF8String = xAtomString;
xAtomXselData = X11::XInternAtom(xDisplay, "XSEL_DATA", 0);
// We need to override handling of the XFixes selection tracking events from SDL
auto screen = X11::XDefaultScreen(xDisplay);
auto rootWindow = X11::XRootWindow(xDisplay, screen);
int eventBase = 0, errorBase = 0;
if (X11::XFixesQueryExtension(xDisplay, &eventBase, &errorBase))
{
XFixesSelectionNotifyEvent = eventBase + XFixesSelectionNotify;
X11::XFixesSelectSelectionInput(xDisplay, rootWindow, xAtomClipboard, XFixesSetSelectionOwnerNotifyMask);
X11::XFixesSelectSelectionInput(xDisplay, rootWindow, xAtomPrimary, XFixesSetSelectionOwnerNotifyMask);
}
return false;
}
void* SDLPlatform::GetXDisplay()
{
return xDisplay;
}
void SDLPlatform::SetHighDpiAwarenessEnabled(bool enable)
{
base::SetHighDpiAwarenessEnabled(enable);
}
bool SDLPlatform::UsesWayland()
{
return UseWayland;
}
bool SDLPlatform::UsesXWayland()
{
return UseXWayland;
}
bool SDLPlatform::UsesX11()
{
return !UseWayland;
}
#endif

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,64 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#if PLATFORM_SDL && PLATFORM_WINDOWS
#include "SDLPlatform.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Platform/WindowsManager.h"
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
#include <SDL3/SDL_hints.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_system.h>
// 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);
auto hit = static_cast<WindowHitCodes>(msg->wParam);
if (SDLPlatform::CheckWindowDragging(window, hit))
return false;
}
return true;
#undef GET_WINDOW_WITH_HWND
}
bool SDLPlatform::InitPlatform()
{
// Workaround required for handling window dragging events properly for DockHintWindow
SDL_SetWindowsMessageHook(&EventMessageHook, nullptr);
if (WindowsPlatform::Init())
return true;
return false;
}
void SDLPlatform::SetHighDpiAwarenessEnabled(bool enable)
{
// Other supported values: "permonitor", "permonitorv2"
SDL_SetHint("SDL_WINDOWS_DPI_AWARENESS", enable ? "system" : "unaware");
}
#endif

View File

@@ -0,0 +1,493 @@
// 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 <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"
#include "Engine/Platform/MessageBox.h"
#include <SDL3/SDL_messagebox.h>
#endif
#define DefaultDPI 96
uint32 SDLPlatform::DraggedWindowId = 0;
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);
//SDL_SetHintWithPriority(SDL_HINT_VIDEO_DRIVER, "wayland", SDL_HINT_OVERRIDE);
// If the hint is not present, SDL will prefer more stable X11 driver over Wayland
#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, "1"); // Is this needed?
//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");
// 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);
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 (InitPlatform())
return true;
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()));
}
bool SDLPlatform::CheckWindowDragging(Window* window, WindowHitCodes hit)
{
bool handled = false;
window->OnLeftButtonHit(hit, handled);
if (handled)
DraggedWindowId = window->_windowId;
return handled;
}
void SDLPlatform::Tick()
{
SDLInput::Update();
if (DraggedWindowId != 0)
{
Float2 mousePos;
auto buttons = SDL_GetGlobalMouseState(&mousePos.X, &mousePos.Y);
if (!(buttons & SDL_BUTTON_MASK(SDL_BUTTON_LEFT)))
{
Window* window = nullptr;
WindowsManager::WindowsLocker.Lock();
for (int32 i = 0; i < WindowsManager::Windows.Count(); i++)
{
if (WindowsManager::Windows[i]->_windowId == DraggedWindowId)
{
window = WindowsManager::Windows[i];
break;
}
}
WindowsManager::WindowsLocker.Unlock();
if (window != nullptr)
{
int top, left, bottom, right;
SDL_GetWindowBordersSize(window->_window, &top, &left, &bottom, &right);
mousePos += Float2(static_cast<float>(left), static_cast<float>(-top));
Input::Mouse->OnMouseUp(mousePos, MouseButton::Left, window);
}
DraggedWindowId = 0;
}
else
{
#if PLATFORM_LINUX
String dockHintWindow("DockHint.Window");
Window* window = nullptr;
WindowsManager::WindowsLocker.Lock();
for (int32 i = 0; i < WindowsManager::Windows.Count(); i++)
{
if (WindowsManager::Windows[i]->_title.Compare(dockHintWindow) == 0)
//if (WindowsManager::Windows[i]->_windowId == DraggedWindowId)
{
window = WindowsManager::Windows[i];
break;
}
}
WindowsManager::WindowsLocker.Unlock();
if (window != nullptr)
{
int top, left, bottom, right;
SDL_GetWindowBordersSize(window->_window, &top, &left, &bottom, &right);
mousePos += Float2(static_cast<float>(left), static_cast<float>(-top));
Input::Mouse->OnMouseMove(mousePos, window);
}
#endif
}
}
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
SDLPlatform::HandleEvent(events[i]);
}
}
bool SDLPlatform::HandleEvent(SDL_Event& event)
{
return true;
}
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;
SDL_GetGlobalMouseState(&pos.X, &pos.Y);
return pos;
}
void SDLPlatform::SetMousePosition(const Float2& pos)
{
SDL_WarpMouseGlobal(pos.X, pos.Y);
}
Float2 SDLPlatform::GetDesktopSize()
{
SDL_Rect rect;
SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &rect);
return Float2(static_cast<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);
}
#if !PLATFORM_LINUX
bool SDLPlatform::UsesWayland()
{
return false;
}
bool SDLPlatform::UsesXWayland()
{
return false;
}
bool SDLPlatform::UsesX11()
{
return false;
}
#endif
#if PLATFORM_LINUX
DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon)
{
StringAnsi textAnsi(text);
StringAnsi captionAnsi(caption);
SDL_MessageBoxData data;
SDL_MessageBoxButtonData dataButtons[3];
data.window = parent ? static_cast<SDLWindow*>(parent)->_window : nullptr;
data.title = captionAnsi.GetText();
data.message = textAnsi.GetText();
data.colorScheme = nullptr;
switch (icon)
{
case MessageBoxIcon::Error:
case MessageBoxIcon::Hand:
case MessageBoxIcon::Stop:
data.flags |= SDL_MESSAGEBOX_ERROR;
break;
case MessageBoxIcon::Asterisk:
case MessageBoxIcon::Information:
case MessageBoxIcon::Question:
data.flags |= SDL_MESSAGEBOX_INFORMATION;
break;
case MessageBoxIcon::Exclamation:
case MessageBoxIcon::Warning:
data.flags |= SDL_MESSAGEBOX_WARNING;
break;
default:
break;
}
switch (buttons)
{
case MessageBoxButtons::AbortRetryIgnore:
dataButtons[0] =
{
SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
(int)DialogResult::Abort,
"Abort"
};
dataButtons[1] =
{
SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
(int)DialogResult::Retry,
"Retry"
};
dataButtons[2] =
{
0,
(int)DialogResult::Ignore,
"Ignore"
};
data.numbuttons = 3;
break;
case MessageBoxButtons::OK:
dataButtons[0] =
{
SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT | SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
(int)DialogResult::OK,
"OK"
};
data.numbuttons = 1;
break;
case MessageBoxButtons::OKCancel:
dataButtons[0] =
{
SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
(int)DialogResult::OK,
"OK"
};
dataButtons[1] =
{
SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
(int)DialogResult::Cancel,
"Cancel"
};
data.numbuttons = 2;
break;
case MessageBoxButtons::RetryCancel:
dataButtons[0] =
{
SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
(int)DialogResult::Retry,
"Retry"
};
dataButtons[1] =
{
SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
(int)DialogResult::Cancel,
"Cancel"
};
data.numbuttons = 2;
break;
case MessageBoxButtons::YesNo:
dataButtons[0] =
{
SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
(int)DialogResult::Yes,
"Yes"
};
dataButtons[1] =
{
SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
(int)DialogResult::No,
"No"
};
data.numbuttons = 2;
break;
case MessageBoxButtons::YesNoCancel:
{
dataButtons[0] =
{
SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
(int)DialogResult::Yes,
"Yes"
};
dataButtons[1] =
{
0,
(int)DialogResult::No,
"No"
};
dataButtons[2] =
{
SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
(int)DialogResult::Cancel,
"Cancel"
};
data.numbuttons = 3;
break;
}
default:
break;
}
data.buttons = dataButtons;
int result = -1;
if (!SDL_ShowMessageBox(&data, &result))
{
#if PLATFORM_LINUX
// Fallback to native messagebox implementation in case some system fonts are missing
if (SDLPlatform::UsesX11())
{
LOG(Warning, "Failed to show SDL message box: {0}", String(SDL_GetError()));
return ShowFallback(parent, text, caption, buttons, icon);
}
#endif
LOG(Error, "Failed to show SDL message box: {0}", String(SDL_GetError()));
return DialogResult::Abort;
}
if (result < 0)
return DialogResult::None;
return (DialogResult)result;
}
#endif
#endif

View File

@@ -0,0 +1,83 @@
// 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"
#elif PLATFORM_LINUX
#include "Engine/Platform/Linux/LinuxPlatform.h"
#else
#endif
class SDLWindow;
union SDL_Event;
#if PLATFORM_WINDOWS
typedef struct tagMSG MSG;
#elif PLATFORM_LINUX
union _XEvent;
#endif
/// <summary>
/// The Windows 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 uint32 DraggedWindowId;
private:
static bool InitPlatform();
#if PLATFORM_LINUX
static bool InitPlatformX11(void* display);
#endif
static bool HandleEvent(SDL_Event& event);
#if PLATFORM_WINDOWS
static bool __cdecl EventMessageHook(void* userdata, MSG* msg);
#elif PLATFORM_LINUX
static bool __cdecl X11EventHook(void *userdata, _XEvent *xevent);
#endif
public:
static bool CheckWindowDragging(Window* window, WindowHitCodes hit);
#if PLATFORM_LINUX
static void* GetXDisplay();
#endif
static bool UsesWayland();
static bool UsesXWayland();
static bool UsesX11();
public:
// [PlatformBase]
static bool Init();
static void LogInfo();
static void Tick();
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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,126 @@
// 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;
bool _forcedFocus;
#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);
void CheckForWindowResize();
void UpdateCursor();
#if PLATFORM_LINUX
DragDropEffect DoDragDropWayland(const StringView& data);
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;
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;
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)
@@ -661,11 +669,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))
@@ -681,7 +691,9 @@ bool WindowsPlatform::Init()
}
OnPlatformUserAdd(New<User>(userName));
#if !PLATFORM_SDL
WindowsInput::Init();
#endif
return false;
}
@@ -709,7 +721,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;
@@ -740,8 +754,10 @@ void WindowsPlatform::Exit()
DbgHelpUnlock();
#endif
#if !PLATFORM_SDL
// Unregister app class
UnregisterClassW(ApplicationWindowClass, nullptr);
UnregisterClassW(ApplicationClassName, nullptr);
#endif
Win32Platform::Exit();
}
@@ -808,6 +824,7 @@ BatteryInfo WindowsPlatform::GetBatteryInfo()
return info;
}
#if !PLATFORM_SDL
int32 WindowsPlatform::GetDpi()
{
return SystemDpi;
@@ -817,6 +834,7 @@ String WindowsPlatform::GetUserLocaleName()
{
return UserLocale;
}
#endif
String WindowsPlatform::GetComputerName()
{
@@ -1186,10 +1204,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>
@@ -68,8 +63,10 @@ public:
#endif
static void SetHighDpiAwarenessEnabled(bool enable);
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);
@@ -83,7 +80,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 USE_EDITOR && 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 USE_EDITOR && 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,661 @@
// 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
Input::Mouse->OnMouseUp(Float2((float)point.x, (float)point.y), MouseButton::Left, (Window*)this);
#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

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using FlaxEditor;
namespace FlaxEngine.GUI
{
@@ -77,13 +78,17 @@ namespace FlaxEngine.GUI
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;
// Create window
var desc = CreateWindowSettings.Default;
desc.StartPosition = WindowStartPosition.Manual;
desc.Position = locationSS;
//Editor.Log($"tooltip pos: {Input.MouseScreenPosition}, in parentloc: {location}");
desc.Size = dpiSize;
desc.Fullscreen = false;
desc.HasBorder = false;
@@ -95,9 +100,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.");
@@ -107,6 +114,7 @@ namespace FlaxEngine.GUI
Visible = true;
_window.Show();
_showTarget.OnTooltipShown(this);
once = true;
}
/// <summary>
@@ -197,7 +205,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,9 +214,11 @@ namespace FlaxEngine.GUI
{
// Direction: left
locationSS.X -= dpiSize.X + flipOffset * 2;
}
}*/
}
bool once = true;
/// <inheritdoc />
public override void Update(float deltaTime)
{
@@ -219,12 +229,24 @@ namespace FlaxEngine.GUI
// Auto hide if mouse leaves control area
Hide();
}
else
else if (true)
{
if (_window)
_window.Position = _window.Position;
if (once)
{
once = false;
_window.ClientSize = _window.ClientSize + new Float2(2, 2);
}
}
else if (false)
{
// Position tooltip when mouse moves
WrapPosition(ref mousePos, 10);
//Editor.Log($"tooltip newpos: {Input.MouseScreenPosition}, in parentloc: {location}");
if (_window)
_window.Position = mousePos + new Float2(15, 10);
_window.Position = mousePos;
}
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);
}
}
}

BIN
Source/Platforms/Linux/Binaries/ThirdParty/x64/libSDL3.a (Stored with Git LFS) vendored Normal file

Binary file not shown.

BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/SDL3.lib (Stored with Git LFS) vendored Normal file

Binary file not shown.

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

@@ -0,0 +1,18 @@
Copyright (C) 1997-2024 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.

53
Source/ThirdParty/SDL/SDL.Build.cs vendored Normal file
View File

@@ -0,0 +1,53 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.IO;
using Flax.Build;
using Flax.Build.NativeCpp;
/// <summary>
/// https://www.libsdl.org/
/// </summary>
public class SDL : DepsModule
{
/// <inheritdoc />
public override void Init()
{
base.Init();
LicenseType = LicenseTypes.Custom;
LicenseFilePath = "LICENSE.txt";
// Merge third-party modules into engine binary
BinaryModuleName = "FlaxEngine";
}
/// <inheritdoc />
public override void Setup(BuildOptions options)
{
base.Setup(options);
var depsRoot = options.DepsFolder;
switch (options.Platform.Target)
{
case TargetPlatform.Windows:
options.OutputFiles.Add(Path.Combine(depsRoot, "SDL3.lib"));
options.OptionalDependencyFiles.Add(Path.Combine(depsRoot, "SDL3.pdb"));
options.OptionalDependencyFiles.Add(Path.Combine(depsRoot, "SDL3.dll"));
// For static linkage
options.OutputFiles.Add("Setupapi.lib");
options.OutputFiles.Add("Version.lib");
options.OutputFiles.Add("Imm32.lib");
options.OutputFiles.Add("Gdi32.lib");
break;
case TargetPlatform.Linux:
case TargetPlatform.Mac:
options.OutputFiles.Add(Path.Combine(depsRoot, "libSDL3.a"));
break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
options.PublicIncludePaths.Add(Path.Combine(Globals.EngineRoot, @"Source\ThirdParty\SDL"));
}
}

90
Source/ThirdParty/SDL/SDL3/SDL.h vendored Normal file
View File

@@ -0,0 +1,90 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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.
*/
/**
* Main include header for the SDL library, version 3.1.7
*
* It is almost always best to include just this one header instead of
* picking out individual headers included here. There are exceptions to
* this rule--SDL_main.h is special and not included here--but usually
* letting SDL.h include the kitchen sink for you is the correct approach.
*/
#ifndef SDL_h_
#define SDL_h_
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_assert.h>
#include <SDL3/SDL_asyncio.h>
#include <SDL3/SDL_atomic.h>
#include <SDL3/SDL_audio.h>
#include <SDL3/SDL_bits.h>
#include <SDL3/SDL_blendmode.h>
#include <SDL3/SDL_camera.h>
#include <SDL3/SDL_clipboard.h>
#include <SDL3/SDL_cpuinfo.h>
#include <SDL3/SDL_dialog.h>
#include <SDL3/SDL_endian.h>
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_filesystem.h>
#include <SDL3/SDL_gamepad.h>
#include <SDL3/SDL_gpu.h>
#include <SDL3/SDL_guid.h>
#include <SDL3/SDL_haptic.h>
#include <SDL3/SDL_hidapi.h>
#include <SDL3/SDL_hints.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_iostream.h>
#include <SDL3/SDL_joystick.h>
#include <SDL3/SDL_keyboard.h>
#include <SDL3/SDL_keycode.h>
#include <SDL3/SDL_loadso.h>
#include <SDL3/SDL_locale.h>
#include <SDL3/SDL_log.h>
#include <SDL3/SDL_messagebox.h>
#include <SDL3/SDL_metal.h>
#include <SDL3/SDL_misc.h>
#include <SDL3/SDL_mouse.h>
#include <SDL3/SDL_mutex.h>
#include <SDL3/SDL_pen.h>
#include <SDL3/SDL_pixels.h>
#include <SDL3/SDL_platform.h>
#include <SDL3/SDL_power.h>
#include <SDL3/SDL_process.h>
#include <SDL3/SDL_properties.h>
#include <SDL3/SDL_rect.h>
#include <SDL3/SDL_render.h>
#include <SDL3/SDL_scancode.h>
#include <SDL3/SDL_sensor.h>
#include <SDL3/SDL_storage.h>
#include <SDL3/SDL_surface.h>
#include <SDL3/SDL_system.h>
#include <SDL3/SDL_thread.h>
#include <SDL3/SDL_time.h>
#include <SDL3/SDL_timer.h>
#include <SDL3/SDL_tray.h>
#include <SDL3/SDL_touch.h>
#include <SDL3/SDL_version.h>
#include <SDL3/SDL_video.h>
#include <SDL3/SDL_oldnames.h>
#endif /* SDL_h_ */

658
Source/ThirdParty/SDL/SDL3/SDL_assert.h vendored Normal file
View File

@@ -0,0 +1,658 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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.
*/
/**
* # CategoryAssert
*
* A helpful assertion macro!
*
* SDL assertions operate like your usual `assert` macro, but with some added
* features:
*
* - It uses a trick with the `sizeof` operator, so disabled assertions
* vaporize out of the compiled code, but variables only referenced in the
* assertion won't trigger compiler warnings about being unused.
* - It is safe to use with a dangling-else: `if (x) SDL_assert(y); else
* do_something();`
* - It works the same everywhere, instead of counting on various platforms'
* compiler and C runtime to behave.
* - It provides multiple levels of assertion (SDL_assert, SDL_assert_release,
* SDL_assert_paranoid) instead of a single all-or-nothing option.
* - It offers a variety of responses when an assertion fails (retry, trigger
* the debugger, abort the program, ignore the failure once, ignore it for
* the rest of the program's run).
* - It tries to show the user a dialog by default, if possible, but the app
* can provide a callback to handle assertion failures however they like.
* - It lets failed assertions be retried. Perhaps you had a network failure
* and just want to retry the test after plugging your network cable back
* in? You can.
* - It lets the user ignore an assertion failure, if there's a harmless
* problem that one can continue past.
* - It lets the user mark an assertion as ignored for the rest of the
* program's run; if there's a harmless problem that keeps popping up.
* - It provides statistics and data on all failed assertions to the app.
* - It allows the default assertion handler to be controlled with environment
* variables, in case an automated script needs to control it.
* - It can be used as an aid to Clang's static analysis; it will treat SDL
* assertions as universally true (under the assumption that you are serious
* about the asserted claims and that your debug builds will detect when
* these claims were wrong). This can help the analyzer avoid false
* positives.
*
* To use it: compile a debug build and just sprinkle around tests to check
* your code!
*/
#ifndef SDL_assert_h_
#define SDL_assert_h_
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_begin_code.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
#ifdef SDL_WIKI_DOCUMENTATION_SECTION
/**
* The level of assertion aggressiveness.
*
* This value changes depending on compiler options and other preprocessor
* defines.
*
* It is currently one of the following values, but future SDL releases might
* add more:
*
* - 0: All SDL assertion macros are disabled.
* - 1: Release settings: SDL_assert disabled, SDL_assert_release enabled.
* - 2: Debug settings: SDL_assert and SDL_assert_release enabled.
* - 3: Paranoid settings: All SDL assertion macros enabled, including
* SDL_assert_paranoid.
*
* \since This macro is available since SDL 3.1.3.
*/
#define SDL_ASSERT_LEVEL SomeNumberBasedOnVariousFactors
#elif !defined(SDL_ASSERT_LEVEL)
#ifdef SDL_DEFAULT_ASSERT_LEVEL
#define SDL_ASSERT_LEVEL SDL_DEFAULT_ASSERT_LEVEL
#elif defined(_DEBUG) || defined(DEBUG) || \
(defined(__GNUC__) && !defined(__OPTIMIZE__))
#define SDL_ASSERT_LEVEL 2
#else
#define SDL_ASSERT_LEVEL 1
#endif
#endif
#ifdef SDL_WIKI_DOCUMENTATION_SECTION
/**
* Attempt to tell an attached debugger to pause.
*
* This allows an app to programmatically halt ("break") the debugger as if it
* had hit a breakpoint, allowing the developer to examine program state, etc.
*
* This is a macro--not a function--so that the debugger breaks on the source
* code line that used SDL_TriggerBreakpoint and not in some random guts of
* SDL. SDL_assert uses this macro for the same reason.
*
* If the program is not running under a debugger, SDL_TriggerBreakpoint will
* likely terminate the app, possibly without warning. If the current platform
* isn't supported (SDL doesn't know how to trigger a breakpoint), this macro
* does nothing.
*
* \threadsafety It is safe to call this macro from any thread.
*
* \since This macro is available since SDL 3.1.3.
*/
#define SDL_TriggerBreakpoint() TriggerABreakpointInAPlatformSpecificManner
#elif defined(_MSC_VER)
/* Don't include intrin.h here because it contains C++ code */
extern void __cdecl __debugbreak(void);
#define SDL_TriggerBreakpoint() __debugbreak()
#elif defined(ANDROID)
#include <assert.h>
#define SDL_TriggerBreakpoint() assert(0)
#elif SDL_HAS_BUILTIN(__builtin_debugtrap)
#define SDL_TriggerBreakpoint() __builtin_debugtrap()
#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))
#define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "int $3\n\t" )
#elif (defined(__GNUC__) || defined(__clang__)) && defined(__riscv)
#define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "ebreak\n\t" )
#elif ( defined(SDL_PLATFORM_APPLE) && (defined(__arm64__) || defined(__aarch64__)) ) /* this might work on other ARM targets, but this is a known quantity... */
#define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "brk #22\n\t" )
#elif defined(SDL_PLATFORM_APPLE) && defined(__arm__)
#define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "bkpt #22\n\t" )
#elif defined(_WIN32) && ((defined(__GNUC__) || defined(__clang__)) && (defined(__arm64__) || defined(__aarch64__)) )
#define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "brk #0xF000\n\t" )
#elif defined(__386__) && defined(__WATCOMC__)
#define SDL_TriggerBreakpoint() { _asm { int 0x03 } }
#elif defined(HAVE_SIGNAL_H) && !defined(__WATCOMC__)
#include <signal.h>
#define SDL_TriggerBreakpoint() raise(SIGTRAP)
#else
/* How do we trigger breakpoints on this platform? */
#define SDL_TriggerBreakpoint()
#endif
#ifdef SDL_WIKI_DOCUMENTATION_SECTION
/**
* A macro that reports the current function being compiled.
*
* If SDL can't figure how the compiler reports this, it will use "???".
*
* \since This macro is available since SDL 3.1.3.
*/
#define SDL_FUNCTION __FUNCTION__
#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 supports __func__ as a standard. */
# define SDL_FUNCTION __func__
#elif ((defined(__GNUC__) && (__GNUC__ >= 2)) || defined(_MSC_VER) || defined (__WATCOMC__))
# define SDL_FUNCTION __FUNCTION__
#else
# define SDL_FUNCTION "???"
#endif
/**
* A macro that reports the current file being compiled.
*
* \since This macro is available since SDL 3.1.3.
*/
#define SDL_FILE __FILE__
/**
* A macro that reports the current line number of the file being compiled.
*
* \since This macro is available since SDL 3.1.3.
*/
#define SDL_LINE __LINE__
/*
sizeof (x) makes the compiler still parse the expression even without
assertions enabled, so the code is always checked at compile time, but
doesn't actually generate code for it, so there are no side effects or
expensive checks at run time, just the constant size of what x WOULD be,
which presumably gets optimized out as unused.
This also solves the problem of...
int somevalue = blah();
SDL_assert(somevalue == 1);
...which would cause compiles to complain that somevalue is unused if we
disable assertions.
*/
#ifdef SDL_WIKI_DOCUMENTATION_SECTION
/**
* A macro for wrapping code in `do {} while (0);` without compiler warnings.
*
* Visual Studio with really aggressive warnings enabled needs this to avoid
* compiler complaints.
*
* the `do {} while (0);` trick is useful for wrapping code in a macro that
* may or may not be a single statement, to avoid various C language
* accidents.
*
* To use:
*
* ```c
* do { SomethingOnce(); } while (SDL_NULL_WHILE_LOOP_CONDITION (0));
* ```
*
* \since This macro is available since SDL 3.1.3.
*/
#define SDL_NULL_WHILE_LOOP_CONDITION (0)
#elif defined(_MSC_VER) /* Avoid /W4 warnings. */
/* "while (0,0)" fools Microsoft's compiler's /W4 warning level into thinking
this condition isn't constant. And looks like an owl's face! */
#define SDL_NULL_WHILE_LOOP_CONDITION (0,0)
#else
#define SDL_NULL_WHILE_LOOP_CONDITION (0)
#endif
/**
* The macro used when an assertion is disabled.
*
* This isn't for direct use by apps, but this is the code that is inserted
* when an SDL_assert is disabled (perhaps in a release build).
*
* The code does nothing, but wraps `condition` in a sizeof operator, which
* generates no code and has no side effects, but avoid compiler warnings
* about unused variables.
*
* \param condition the condition to assert (but not actually run here).
*
* \since This macro is available since SDL 3.1.3.
*/
#define SDL_disabled_assert(condition) \
do { (void) sizeof ((condition)); } while (SDL_NULL_WHILE_LOOP_CONDITION)
/**
* Possible outcomes from a triggered assertion.
*
* When an enabled assertion triggers, it may call the assertion handler
* (possibly one provided by the app via SDL_SetAssertionHandler), which will
* return one of these values, possibly after asking the user.
*
* Then SDL will respond based on this outcome (loop around to retry the
* condition, try to break in a debugger, kill the program, or ignore the
* problem).
*
* \since This enum is available since SDL 3.1.3.
*/
typedef enum SDL_AssertState
{
SDL_ASSERTION_RETRY, /**< Retry the assert immediately. */
SDL_ASSERTION_BREAK, /**< Make the debugger trigger a breakpoint. */
SDL_ASSERTION_ABORT, /**< Terminate the program. */
SDL_ASSERTION_IGNORE, /**< Ignore the assert. */
SDL_ASSERTION_ALWAYS_IGNORE /**< Ignore the assert from now on. */
} SDL_AssertState;
/**
* Information about an assertion failure.
*
* This structure is filled in with information about a triggered assertion,
* used by the assertion handler, then added to the assertion report. This is
* returned as a linked list from SDL_GetAssertionReport().
*
* \since This struct is available since SDL 3.1.3.
*/
typedef struct SDL_AssertData
{
bool always_ignore; /**< true if app should always continue when assertion is triggered. */
unsigned int trigger_count; /**< Number of times this assertion has been triggered. */
const char *condition; /**< A string of this assert's test code. */
const char *filename; /**< The source file where this assert lives. */
int linenum; /**< The line in `filename` where this assert lives. */
const char *function; /**< The name of the function where this assert lives. */
const struct SDL_AssertData *next; /**< next item in the linked list. */
} SDL_AssertData;
/**
* Never call this directly.
*
* Use the SDL_assert macros instead.
*
* \param data assert data structure.
* \param func function name.
* \param file file name.
* \param line line number.
* \returns assert state.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*/
extern SDL_DECLSPEC SDL_AssertState SDLCALL SDL_ReportAssertion(SDL_AssertData *data,
const char *func,
const char *file, int line) SDL_ANALYZER_NORETURN;
#ifdef SDL_WIKI_DOCUMENTATION_SECTION
/**
* The macro used when an assertion triggers a breakpoint.
*
* This isn't for direct use by apps; use SDL_assert or SDL_TriggerBreakpoint
* instead.
*
* \since This macro is available since SDL 3.1.3.
*/
#define SDL_AssertBreakpoint() SDL_TriggerBreakpoint()
#elif !defined(SDL_AssertBreakpoint)
# if defined(ANDROID) && defined(assert)
/* Define this as empty in case assert() is defined as SDL_assert */
# define SDL_AssertBreakpoint()
# else
# define SDL_AssertBreakpoint() SDL_TriggerBreakpoint()
# endif
#endif /* !SDL_AssertBreakpoint */
/**
* The macro used when an assertion is enabled.
*
* This isn't for direct use by apps, but this is the code that is inserted
* when an SDL_assert is enabled.
*
* The `do {} while(0)` avoids dangling else problems:
*
* ```c
* if (x) SDL_assert(y); else blah();
* ```
*
* ... without the do/while, the "else" could attach to this macro's "if". We
* try to handle just the minimum we need here in a macro...the loop, the
* static vars, and break points. The heavy lifting is handled in
* SDL_ReportAssertion().
*
* \param condition the condition to assert.
*
* \since This macro is available since SDL 3.1.3.
*/
#define SDL_enabled_assert(condition) \
do { \
while ( !(condition) ) { \
static struct SDL_AssertData sdl_assert_data = { 0, 0, #condition, 0, 0, 0, 0 }; \
const SDL_AssertState sdl_assert_state = SDL_ReportAssertion(&sdl_assert_data, SDL_FUNCTION, SDL_FILE, SDL_LINE); \
if (sdl_assert_state == SDL_ASSERTION_RETRY) { \
continue; /* go again. */ \
} else if (sdl_assert_state == SDL_ASSERTION_BREAK) { \
SDL_AssertBreakpoint(); \
} \
break; /* not retrying. */ \
} \
} while (SDL_NULL_WHILE_LOOP_CONDITION)
#ifdef SDL_WIKI_DOCUMENTATION_SECTION
/**
* An assertion test that is normally performed only in debug builds.
*
* This macro is enabled when the SDL_ASSERT_LEVEL is >= 2, otherwise it is
* disabled. This is meant to only do these tests in debug builds, so they can
* tend to be more expensive, and they are meant to bring everything to a halt
* when they fail, with the programmer there to assess the problem.
*
* In short: you can sprinkle these around liberally and assume they will
* evaporate out of the build when building for end-users.
*
* When assertions are disabled, this wraps `condition` in a `sizeof`
* operator, which means any function calls and side effects will not run, but
* the compiler will not complain about any otherwise-unused variables that
* are only referenced in the assertion.
*
* One can set the environment variable "SDL_ASSERT" to one of several strings
* ("abort", "break", "retry", "ignore", "always_ignore") to force a default
* behavior, which may be desirable for automation purposes. If your platform
* requires GUI interfaces to happen on the main thread but you're debugging
* an assertion in a background thread, it might be desirable to set this to
* "break" so that your debugger takes control as soon as assert is triggered,
* instead of risking a bad UI interaction (deadlock, etc) in the application.
*
* \param condition boolean value to test.
*
* \threadsafety It is safe to call this macro from any thread.
*
* \since This macro is available since SDL 3.1.3.
*/
#define SDL_assert(condition) if (assertion_enabled && (condition)) { trigger_assertion; }
/**
* An assertion test that is performed even in release builds.
*
* This macro is enabled when the SDL_ASSERT_LEVEL is >= 1, otherwise it is
* disabled. This is meant to be for tests that are cheap to make and
* extremely unlikely to fail; generally it is frowned upon to have an
* assertion failure in a release build, so these assertions generally need to
* be of more than life-and-death importance if there's a chance they might
* trigger. You should almost always consider handling these cases more
* gracefully than an assert allows.
*
* When assertions are disabled, this wraps `condition` in a `sizeof`
* operator, which means any function calls and side effects will not run, but
* the compiler will not complain about any otherwise-unused variables that
* are only referenced in the assertion.
*
* One can set the environment variable "SDL_ASSERT" to one of several strings
* ("abort", "break", "retry", "ignore", "always_ignore") to force a default
* behavior, which may be desirable for automation purposes. If your platform
* requires GUI interfaces to happen on the main thread but you're debugging
* an assertion in a background thread, it might be desirable to set this to
* "break" so that your debugger takes control as soon as assert is triggered,
* instead of risking a bad UI interaction (deadlock, etc) in the application.
* *
*
* \param condition boolean value to test.
*
* \threadsafety It is safe to call this macro from any thread.
*
* \since This macro is available since SDL 3.1.3.
*/
#define SDL_assert_release(condition) SDL_disabled_assert(condition)
/**
* An assertion test that is performed only when built with paranoid settings.
*
* This macro is enabled when the SDL_ASSERT_LEVEL is >= 3, otherwise it is
* disabled. This is a higher level than both release and debug, so these
* tests are meant to be expensive and only run when specifically looking for
* extremely unexpected failure cases in a special build.
*
* When assertions are disabled, this wraps `condition` in a `sizeof`
* operator, which means any function calls and side effects will not run, but
* the compiler will not complain about any otherwise-unused variables that
* are only referenced in the assertion.
*
* One can set the environment variable "SDL_ASSERT" to one of several strings
* ("abort", "break", "retry", "ignore", "always_ignore") to force a default
* behavior, which may be desirable for automation purposes. If your platform
* requires GUI interfaces to happen on the main thread but you're debugging
* an assertion in a background thread, it might be desirable to set this to
* "break" so that your debugger takes control as soon as assert is triggered,
* instead of risking a bad UI interaction (deadlock, etc) in the application.
*
* \param condition boolean value to test.
*
* \threadsafety It is safe to call this macro from any thread.
*
* \since This macro is available since SDL 3.1.3.
*/
#define SDL_assert_paranoid(condition) SDL_disabled_assert(condition)
/* Enable various levels of assertions. */
#elif SDL_ASSERT_LEVEL == 0 /* assertions disabled */
# define SDL_assert(condition) SDL_disabled_assert(condition)
# define SDL_assert_release(condition) SDL_disabled_assert(condition)
# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition)
#elif SDL_ASSERT_LEVEL == 1 /* release settings. */
# define SDL_assert(condition) SDL_disabled_assert(condition)
# define SDL_assert_release(condition) SDL_enabled_assert(condition)
# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition)
#elif SDL_ASSERT_LEVEL == 2 /* debug settings. */
# define SDL_assert(condition) SDL_enabled_assert(condition)
# define SDL_assert_release(condition) SDL_enabled_assert(condition)
# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition)
#elif SDL_ASSERT_LEVEL == 3 /* paranoid settings. */
# define SDL_assert(condition) SDL_enabled_assert(condition)
# define SDL_assert_release(condition) SDL_enabled_assert(condition)
# define SDL_assert_paranoid(condition) SDL_enabled_assert(condition)
#else
# error Unknown assertion level.
#endif
/**
* An assertion test that is always performed.
*
* This macro is always enabled no matter what SDL_ASSERT_LEVEL is set to. You
* almost never want to use this, as it could trigger on an end-user's system,
* crashing your program.
*
* One can set the environment variable "SDL_ASSERT" to one of several strings
* ("abort", "break", "retry", "ignore", "always_ignore") to force a default
* behavior, which may be desirable for automation purposes. If your platform
* requires GUI interfaces to happen on the main thread but you're debugging
* an assertion in a background thread, it might be desirable to set this to
* "break" so that your debugger takes control as soon as assert is triggered,
* instead of risking a bad UI interaction (deadlock, etc) in the application.
*
* \param condition boolean value to test.
*
* \threadsafety It is safe to call this macro from any thread.
*
* \since This macro is available since SDL 3.1.3.
*/
#define SDL_assert_always(condition) SDL_enabled_assert(condition)
/**
* A callback that fires when an SDL assertion fails.
*
* \param data a pointer to the SDL_AssertData structure corresponding to the
* current assertion.
* \param userdata what was passed as `userdata` to SDL_SetAssertionHandler().
* \returns an SDL_AssertState value indicating how to handle the failure.
*
* \threadsafety This callback may be called from any thread that triggers an
* assert at any time.
*
* \since This datatype is available since SDL 3.1.3.
*/
typedef SDL_AssertState (SDLCALL *SDL_AssertionHandler)(
const SDL_AssertData *data, void *userdata);
/**
* Set an application-defined assertion handler.
*
* This function allows an application to show its own assertion UI and/or
* force the response to an assertion failure. If the application doesn't
* provide this, SDL will try to do the right thing, popping up a
* system-specific GUI dialog, and probably minimizing any fullscreen windows.
*
* This callback may fire from any thread, but it runs wrapped in a mutex, so
* it will only fire from one thread at a time.
*
* This callback is NOT reset to SDL's internal handler upon SDL_Quit()!
*
* \param handler the SDL_AssertionHandler function to call when an assertion
* fails or NULL for the default handler.
* \param userdata a pointer that is passed to `handler`.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_GetAssertionHandler
*/
extern SDL_DECLSPEC void SDLCALL SDL_SetAssertionHandler(
SDL_AssertionHandler handler,
void *userdata);
/**
* Get the default assertion handler.
*
* This returns the function pointer that is called by default when an
* assertion is triggered. This is an internal function provided by SDL, that
* is used for assertions when SDL_SetAssertionHandler() hasn't been used to
* provide a different function.
*
* \returns the default SDL_AssertionHandler that is called when an assert
* triggers.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_GetAssertionHandler
*/
extern SDL_DECLSPEC SDL_AssertionHandler SDLCALL SDL_GetDefaultAssertionHandler(void);
/**
* Get the current assertion handler.
*
* This returns the function pointer that is called when an assertion is
* triggered. This is either the value last passed to
* SDL_SetAssertionHandler(), or if no application-specified function is set,
* is equivalent to calling SDL_GetDefaultAssertionHandler().
*
* The parameter `puserdata` is a pointer to a void*, which will store the
* "userdata" pointer that was passed to SDL_SetAssertionHandler(). This value
* will always be NULL for the default handler. If you don't care about this
* data, it is safe to pass a NULL pointer to this function to ignore it.
*
* \param puserdata pointer which is filled with the "userdata" pointer that
* was passed to SDL_SetAssertionHandler().
* \returns the SDL_AssertionHandler that is called when an assert triggers.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_SetAssertionHandler
*/
extern SDL_DECLSPEC SDL_AssertionHandler SDLCALL SDL_GetAssertionHandler(void **puserdata);
/**
* Get a list of all assertion failures.
*
* This function gets all assertions triggered since the last call to
* SDL_ResetAssertionReport(), or the start of the program.
*
* The proper way to examine this data looks something like this:
*
* ```c
* const SDL_AssertData *item = SDL_GetAssertionReport();
* while (item) {
* printf("'%s', %s (%s:%d), triggered %u times, always ignore: %s.\\n",
* item->condition, item->function, item->filename,
* item->linenum, item->trigger_count,
* item->always_ignore ? "yes" : "no");
* item = item->next;
* }
* ```
*
* \returns a list of all failed assertions or NULL if the list is empty. This
* memory should not be modified or freed by the application. This
* pointer remains valid until the next call to SDL_Quit() or
* SDL_ResetAssertionReport().
*
* \threadsafety This function is not thread safe. Other threads calling
* SDL_ResetAssertionReport() simultaneously, may render the
* returned pointer invalid.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_ResetAssertionReport
*/
extern SDL_DECLSPEC const SDL_AssertData * SDLCALL SDL_GetAssertionReport(void);
/**
* Clear the list of all assertion failures.
*
* This function will clear the list of all assertions triggered up to that
* point. Immediately following this call, SDL_GetAssertionReport will return
* no items. In addition, any previously-triggered assertions will be reset to
* a trigger_count of zero, and their always_ignore state will be false.
*
* \threadsafety This function is not thread safe. Other threads triggering an
* assertion, or simultaneously calling this function may cause
* memory leaks or crashes.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_GetAssertionReport
*/
extern SDL_DECLSPEC void SDLCALL SDL_ResetAssertionReport(void);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include <SDL3/SDL_close_code.h>
#endif /* SDL_assert_h_ */

546
Source/ThirdParty/SDL/SDL3/SDL_asyncio.h vendored Normal file
View File

@@ -0,0 +1,546 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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.
*/
/* WIKI CATEGORY: AsyncIO */
/**
* # CategoryAsyncIO
*
* SDL offers a way to perform I/O asynchronously. This allows an app to read
* or write files without waiting for data to actually transfer; the functions
* that request I/O never block while the request is fulfilled.
*
* Instead, the data moves in the background and the app can check for results
* at their leisure.
*
* This is more complicated than just reading and writing files in a
* synchronous way, but it can allow for more efficiency, and never having
* framerate drops as the hard drive catches up, etc.
*
* The general usage pattern for async I/O is:
*
* - Create one or more SDL_AsyncIOQueue objects.
* - Open files with SDL_AsyncIOFromFile.
* - Start I/O tasks to the files with SDL_ReadAsyncIO or SDL_WriteAsyncIO,
* putting those tasks into one of the queues.
* - Later on, use SDL_GetAsyncIOResult on a queue to see if any task is
* finished without blocking. Tasks might finish in any order with success
* or failure.
* - When all your tasks are done, close the file with SDL_CloseAsyncIO. This
* also generates a task, since it might flush data to disk!
*
* This all works, without blocking, in a single thread, but one can also wait
* on a queue in a background thread, sleeping until new results have arrived:
*
* - Call SDL_WaitAsyncIOResult from one or more threads to efficiently block
* until new tasks complete.
* - When shutting down, call SDL_SignalAsyncIOQueue to unblock any sleeping
* threads despite there being no new tasks completed.
*
* And, of course, to match the synchronous SDL_LoadFile, we offer
* SDL_LoadFileAsync as a convenience function. This will handle allocating a
* buffer, slurping in the file data, and null-terminating it; you still check
* for results later.
*
* Behind the scenes, SDL will use newer, efficient APIs on platforms that
* support them: Linux's io_uring and Windows 11's IoRing, for example. If
* those technologies aren't available, SDL will offload the work to a thread
* pool that will manage otherwise-synchronous loads without blocking the app.
*
* ## Best Practices
*
* Simple non-blocking i/o--for an app that just wants to pick up data
* whenever it's ready without losing framerate waiting on disks to spin--can
* use whatever pattern works well for the program. In this case, simply call
* SDL_ReadAsyncIO, or maybe SDL_LoadFileAsync, as needed. Once a frame, call
* SDL_GetAsyncIOResult to check for any completed tasks and deal with the
* data as it arrives.
*
* If two separate pieces of the same program need their own i/o, it is legal
* for each to create their own queue. This will prevent either piece from
* accidentally consuming the other's completed tasks. Each queue does require
* some amount of resources, but it is not an overwhelming cost. Do not make a
* queue for each task, however. It is better to put many tasks into a single
* queue. They will be reported in order of completion, not in the order they
* were submitted, so it doesn't generally matter what order tasks are
* started.
*
* One async i/o queue can be shared by multiple threads, or one thread can
* have more than one queue, but the most efficient way--if ruthless
* efficiency is the goal--is to have one queue per thread, with multiple
* threads working in parallel, and attempt to keep each queue loaded with
* tasks that are both started by and consumed by the same thread. On modern
* platforms that can use newer interfaces, this can keep data flowing as
* efficiently as possible all the way from storage hardware to the app, with
* no contention between threads for access to the same queue.
*
* Written data is not guaranteed to make it to physical media by the time a
* closing task is completed, unless SDL_CloseAsyncIO is called with its
* `flush` parameter set to true, which is to say that a successful result
* here can still result in lost data during an unfortunately-timed power
* outage if not flushed. However, flushing will take longer and may be
* unnecessary, depending on the app's needs.
*/
#ifndef SDL_asyncio_h_
#define SDL_asyncio_h_
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_begin_code.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* The asynchronous I/O operation structure.
*
* This operates as an opaque handle. One can then request read or write
* operations on it.
*
* \since This struct is available since SDL 3.0.0.
*
* \sa SDL_AsyncIOFromFile
*/
typedef struct SDL_AsyncIO SDL_AsyncIO;
/**
* Types of asynchronous I/O tasks.
*
* \since This enum is available since SDL 3.0.0.
*/
typedef enum SDL_AsyncIOTaskType
{
SDL_ASYNCIO_TASK_READ, /**< A read operation. */
SDL_ASYNCIO_TASK_WRITE, /**< A write operation. */
SDL_ASYNCIO_TASK_CLOSE /**< A close operation. */
} SDL_AsyncIOTaskType;
/**
* Possible outcomes of an asynchronous I/O task.
*
* \since This enum is available since SDL 3.0.0.
*/
typedef enum SDL_AsyncIOResult
{
SDL_ASYNCIO_COMPLETE, /**< request was completed without error */
SDL_ASYNCIO_FAILURE, /**< request failed for some reason; check SDL_GetError()! */
SDL_ASYNCIO_CANCELED /**< request was canceled before completing. */
} SDL_AsyncIOResult;
/**
* Information about a completed asynchronous I/O request.
*
* \since This struct is available since SDL 3.0.0.
*/
typedef struct SDL_AsyncIOOutcome
{
SDL_AsyncIO *asyncio; /**< what generated this task. This pointer will be invalid if it was closed! */
SDL_AsyncIOTaskType type; /**< What sort of task was this? Read, write, etc? */
SDL_AsyncIOResult result; /**< the result of the work (success, failure, cancellation). */
void *buffer; /**< buffer where data was read/written. */
Uint64 offset; /**< offset in the SDL_AsyncIO where data was read/written. */
Uint64 bytes_requested; /**< number of bytes the task was to read/write. */
Uint64 bytes_transferred; /**< actual number of bytes that were read/written. */
void *userdata; /**< pointer provided by the app when starting the task */
} SDL_AsyncIOOutcome;
/**
* A queue of completed asynchronous I/O tasks.
*
* When starting an asynchronous operation, you specify a queue for the new
* task. A queue can be asked later if any tasks in it have completed,
* allowing an app to manage multiple pending tasks in one place, in whatever
* order they complete.
*
* \since This struct is available since SDL 3.0.0.
*
* \sa SDL_CreateAsyncIOQueue
* \sa SDL_ReadAsyncIO
* \sa SDL_WriteAsyncIO
* \sa SDL_GetAsyncIOResult
* \sa SDL_WaitAsyncIOResult
*/
typedef struct SDL_AsyncIOQueue SDL_AsyncIOQueue;
/**
* Use this function to create a new SDL_AsyncIO object for reading from
* and/or writing to a named file.
*
* The `mode` string understands the following values:
*
* - "r": Open a file for reading only. It must exist.
* - "w": Open a file for writing only. It will create missing files or
* truncate existing ones.
* - "r+": Open a file for update both reading and writing. The file must
* exist.
* - "w+": Create an empty file for both reading and writing. If a file with
* the same name already exists its content is erased and the file is
* treated as a new empty file.
*
* There is no "b" mode, as there is only "binary" style I/O, and no "a" mode
* for appending, since you specify the position when starting a task.
*
* This function supports Unicode filenames, but they must be encoded in UTF-8
* format, regardless of the underlying operating system.
*
* This call is _not_ asynchronous; it will open the file before returning,
* under the assumption that doing so is generally a fast operation. Future
* reads and writes to the opened file will be async, however.
*
* \param file a UTF-8 string representing the filename to open.
* \param mode an ASCII string representing the mode to be used for opening
* the file.
* \returns a pointer to the SDL_AsyncIO structure that is created or NULL on
* failure; call SDL_GetError() for more information.
*
* \since This function is available since SDL 3.2.0.
*
* \sa SDL_CloseAsyncIO
* \sa SDL_ReadAsyncIO
* \sa SDL_WriteAsyncIO
*/
extern SDL_DECLSPEC SDL_AsyncIO * SDLCALL SDL_AsyncIOFromFile(const char *file, const char *mode);
/**
* Use this function to get the size of the data stream in an SDL_AsyncIO.
*
* This call is _not_ asynchronous; it assumes that obtaining this info is a
* non-blocking operation in most reasonable cases.
*
* \param asyncio the SDL_AsyncIO to get the size of the data stream from.
* \returns the size of the data stream in the SDL_IOStream on success or a
* negative error code on failure; call SDL_GetError() for more
* information.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.2.0.
*/
extern SDL_DECLSPEC Sint64 SDLCALL SDL_GetAsyncIOSize(SDL_AsyncIO *asyncio);
/**
* Start an async read.
*
* This function reads up to `size` bytes from `offset` position in the data
* source to the area pointed at by `ptr`. This function may read less bytes
* than requested.
*
* This function returns as quickly as possible; it does not wait for the read
* to complete. On a successful return, this work will continue in the
* background. If the work begins, even failure is asynchronous: a failing
* return value from this function only means the work couldn't start at all.
*
* `ptr` must remain available until the work is done, and may be accessed by
* the system at any time until then. Do not allocate it on the stack, as this
* might take longer than the life of the calling function to complete!
*
* An SDL_AsyncIOQueue must be specified. The newly-created task will be added
* to it when it completes its work.
*
* \param asyncio a pointer to an SDL_AsyncIO structure.
* \param ptr a pointer to a buffer to read data into.
* \param offset the position to start reading in the data source.
* \param size the number of bytes to read from the data source.
* \param queue a queue to add the new SDL_AsyncIO to.
* \param userdata an app-defined pointer that will be provided with the task
* results.
* \returns true on success or false on failure; call SDL_GetError() for more
* information.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.2.0.
*
* \sa SDL_WriteAsyncIO
* \sa SDL_CreateAsyncIOQueue
*/
extern SDL_DECLSPEC bool SDLCALL SDL_ReadAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata);
/**
* Start an async write.
*
* This function writes `size` bytes from `offset` position in the data source
* to the area pointed at by `ptr`.
*
* This function returns as quickly as possible; it does not wait for the
* write to complete. On a successful return, this work will continue in the
* background. If the work begins, even failure is asynchronous: a failing
* return value from this function only means the work couldn't start at all.
*
* `ptr` must remain available until the work is done, and may be accessed by
* the system at any time until then. Do not allocate it on the stack, as this
* might take longer than the life of the calling function to complete!
*
* An SDL_AsyncIOQueue must be specified. The newly-created task will be added
* to it when it completes its work.
*
* \param asyncio a pointer to an SDL_AsyncIO structure.
* \param ptr a pointer to a buffer to write data from.
* \param offset the position to start writing to the data source.
* \param size the number of bytes to write to the data source.
* \param queue a queue to add the new SDL_AsyncIO to.
* \param userdata an app-defined pointer that will be provided with the task
* results.
* \returns true on success or false on failure; call SDL_GetError() for more
* information.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.2.0.
*
* \sa SDL_ReadAsyncIO
* \sa SDL_CreateAsyncIOQueue
*/
extern SDL_DECLSPEC bool SDLCALL SDL_WriteAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata);
/**
* Close and free any allocated resources for an async I/O object.
*
* Closing a file is _also_ an asynchronous task! If a write failure were to
* happen during the closing process, for example, the task results will
* report it as usual.
*
* Closing a file that has been written to does not guarantee the data has
* made it to physical media; it may remain in the operating system's file
* cache, for later writing to disk. This means that a successfully-closed
* file can be lost if the system crashes or loses power in this small window.
* To prevent this, call this function with the `flush` parameter set to true.
* This will make the operation take longer, and perhaps increase system load
* in general, but a successful result guarantees that the data has made it to
* physical storage. Don't use this for temporary files, caches, and
* unimportant data, and definitely use it for crucial irreplaceable files,
* like game saves.
*
* This function guarantees that the close will happen after any other pending
* tasks to `asyncio`, so it's safe to open a file, start several operations,
* close the file immediately, then check for all results later. This function
* will not block until the tasks have completed.
*
* Once this function returns true, `asyncio` is no longer valid, regardless
* of any future outcomes. Any completed tasks might still contain this
* pointer in their SDL_AsyncIOOutcome data, in case the app was using this
* value to track information, but it should not be used again.
*
* If this function returns false, the close wasn't started at all, and it's
* safe to attempt to close again later.
*
* An SDL_AsyncIOQueue must be specified. The newly-created task will be added
* to it when it completes its work.
*
* \param asyncio a pointer to an SDL_AsyncIO structure to close.
* \param flush true if data should sync to disk before the task completes.
* \param queue a queue to add the new SDL_AsyncIO to.
* \param userdata an app-defined pointer that will be provided with the task
* results.
* \returns true on success or false on failure; call SDL_GetError() for more
* information.
*
* \threadsafety It is safe to call this function from any thread, but two
* threads should not attempt to close the same object.
*
* \since This function is available since SDL 3.2.0.
*/
extern SDL_DECLSPEC bool SDLCALL SDL_CloseAsyncIO(SDL_AsyncIO *asyncio, bool flush, SDL_AsyncIOQueue *queue, void *userdata);
/**
* Create a task queue for tracking multiple I/O operations.
*
* Async I/O operations are assigned to a queue when started. The queue can be
* checked for completed tasks thereafter.
*
* \returns a new task queue object or NULL if there was an error; call
* SDL_GetError() for more information.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.2.0.
*
* \sa SDL_DestroyAsyncIOQueue
* \sa SDL_GetAsyncIOResult
* \sa SDL_WaitAsyncIOResult
*/
extern SDL_DECLSPEC SDL_AsyncIOQueue * SDLCALL SDL_CreateAsyncIOQueue(void);
/**
* Destroy a previously-created async I/O task queue.
*
* If there are still tasks pending for this queue, this call will block until
* those tasks are finished. All those tasks will be deallocated. Their
* results will be lost to the app.
*
* Any pending reads from SDL_LoadFileAsync() that are still in this queue
* will have their buffers deallocated by this function, to prevent a memory
* leak.
*
* Once this function is called, the queue is no longer valid and should not
* be used, including by other threads that might access it while destruction
* is blocking on pending tasks.
*
* Do not destroy a queue that still has threads waiting on it through
* SDL_WaitAsyncIOResult(). You can call SDL_SignalAsyncIOQueue() first to
* unblock those threads, and take measures (such as SDL_WaitThread()) to make
* sure they have finished their wait and won't wait on the queue again.
*
* \param queue the task queue to destroy.
*
* \threadsafety It is safe to call this function from any thread, so long as
* no other thread is waiting on the queue with
* SDL_WaitAsyncIOResult.
*
* \since This function is available since SDL 3.2.0.
*/
extern SDL_DECLSPEC void SDLCALL SDL_DestroyAsyncIOQueue(SDL_AsyncIOQueue *queue);
/**
* Query an async I/O task queue for completed tasks.
*
* If a task assigned to this queue has finished, this will return true and
* fill in `outcome` with the details of the task. If no task in the queue has
* finished, this function will return false. This function does not block.
*
* If a task has completed, this function will free its resources and the task
* pointer will no longer be valid. The task will be removed from the queue.
*
* It is safe for multiple threads to call this function on the same queue at
* once; a completed task will only go to one of the threads.
*
* \param queue the async I/O task queue to query.
* \param outcome details of a finished task will be written here. May not be
* NULL.
* \returns true if a task has completed, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.2.0.
*
* \sa SDL_WaitAsyncIOResult
*/
extern SDL_DECLSPEC bool SDLCALL SDL_GetAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome);
/**
* Block until an async I/O task queue has a completed task.
*
* This function puts the calling thread to sleep until there a task assigned
* to the queue that has finished.
*
* If a task assigned to the queue has finished, this will return true and
* fill in `outcome` with the details of the task. If no task in the queue has
* finished, this function will return false.
*
* If a task has completed, this function will free its resources and the task
* pointer will no longer be valid. The task will be removed from the queue.
*
* It is safe for multiple threads to call this function on the same queue at
* once; a completed task will only go to one of the threads.
*
* Note that by the nature of various platforms, more than one waiting thread
* may wake to handle a single task, but only one will obtain it, so
* `timeoutMS` is a _maximum_ wait time, and this function may return false
* sooner.
*
* This function may return false if there was a system error, the OS
* inadvertently awoke multiple threads, or if SDL_SignalAsyncIOQueue() was
* called to wake up all waiting threads without a finished task.
*
* A timeout can be used to specify a maximum wait time, but rather than
* polling, it is possible to have a timeout of -1 to wait forever, and use
* SDL_SignalAsyncIOQueue() to wake up the waiting threads later.
*
* \param queue the async I/O task queue to wait on.
* \param outcome details of a finished task will be written here. May not be
* NULL.
* \param timeoutMS the maximum time to wait, in milliseconds, or -1 to wait
* indefinitely.
* \returns true if task has completed, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.2.0.
*
* \sa SDL_SignalAsyncIOQueue
*/
extern SDL_DECLSPEC bool SDLCALL SDL_WaitAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome, Sint32 timeoutMS);
/**
* Wake up any threads that are blocking in SDL_WaitAsyncIOResult().
*
* This will unblock any threads that are sleeping in a call to
* SDL_WaitAsyncIOResult for the specified queue, and cause them to return
* from that function.
*
* This can be useful when destroying a queue to make sure nothing is touching
* it indefinitely. In this case, once this call completes, the caller should
* take measures to make sure any previously-blocked threads have returned
* from their wait and will not touch the queue again (perhaps by setting a
* flag to tell the threads to terminate and then using SDL_WaitThread() to
* make sure they've done so).
*
* \param queue the async I/O task queue to signal.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.2.0.
*
* \sa SDL_WaitAsyncIOResult
*/
extern SDL_DECLSPEC void SDLCALL SDL_SignalAsyncIOQueue(SDL_AsyncIOQueue *queue);
/**
* Load all the data from a file path, asynchronously.
*
* This function returns as quickly as possible; it does not wait for the read
* to complete. On a successful return, this work will continue in the
* background. If the work begins, even failure is asynchronous: a failing
* return value from this function only means the work couldn't start at all.
*
* The data is allocated with a zero byte at the end (null terminated) for
* convenience. This extra byte is not included in SDL_AsyncIOOutcome's
* bytes_transferred value.
*
* This function will allocate the buffer to contain the file. It must be
* deallocated by calling SDL_free() on SDL_AsyncIOOutcome's buffer field
* after completion.
*
* An SDL_AsyncIOQueue must be specified. The newly-created task will be added
* to it when it completes its work.
*
* \param file the path to read all available data from.
* \param queue a queue to add the new SDL_AsyncIO to.
* \param userdata an app-defined pointer that will be provided with the task
* results.
* \returns true on success or false on failure; call SDL_GetError() for more
* information.
*
* \since This function is available since SDL 3.2.0.
*
* \sa SDL_LoadFile_IO
*/
extern SDL_DECLSPEC bool SDLCALL SDL_LoadFileAsync(const char *file, SDL_AsyncIOQueue *queue, void *userdata);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include <SDL3/SDL_close_code.h>
#endif /* SDL_asyncio_h_ */

664
Source/ThirdParty/SDL/SDL3/SDL_atomic.h vendored Normal file
View File

@@ -0,0 +1,664 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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.
*/
/**
* # CategoryAtomic
*
* Atomic operations.
*
* IMPORTANT: If you are not an expert in concurrent lockless programming, you
* should not be using any functions in this file. You should be protecting
* your data structures with full mutexes instead.
*
* ***Seriously, here be dragons!***
*
* You can find out a little more about lockless programming and the subtle
* issues that can arise here:
* https://learn.microsoft.com/en-us/windows/win32/dxtecharts/lockless-programming
*
* There's also lots of good information here:
*
* - https://www.1024cores.net/home/lock-free-algorithms
* - https://preshing.com/
*
* These operations may or may not actually be implemented using processor
* specific atomic operations. When possible they are implemented as true
* processor specific atomic operations. When that is not possible the are
* implemented using locks that *do* use the available atomic operations.
*
* All of the atomic operations that modify memory are full memory barriers.
*/
#ifndef SDL_atomic_h_
#define SDL_atomic_h_
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_platform_defines.h>
#include <SDL3/SDL_begin_code.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* An atomic spinlock.
*
* The atomic locks are efficient spinlocks using CPU instructions, but are
* vulnerable to starvation and can spin forever if a thread holding a lock
* has been terminated. For this reason you should minimize the code executed
* inside an atomic lock and never do expensive things like API or system
* calls while holding them.
*
* They are also vulnerable to starvation if the thread holding the lock is
* lower priority than other threads and doesn't get scheduled. In general you
* should use mutexes instead, since they have better performance and
* contention behavior.
*
* The atomic locks are not safe to lock recursively.
*
* Porting Note: The spin lock functions and type are required and can not be
* emulated because they are used in the atomic emulation code.
*/
typedef int SDL_SpinLock;
/**
* Try to lock a spin lock by setting it to a non-zero value.
*
* ***Please note that spinlocks are dangerous if you don't know what you're
* doing. Please be careful using any sort of spinlock!***
*
* \param lock a pointer to a lock variable.
* \returns true if the lock succeeded, false if the lock is already held.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_LockSpinlock
* \sa SDL_UnlockSpinlock
*/
extern SDL_DECLSPEC bool SDLCALL SDL_TryLockSpinlock(SDL_SpinLock *lock);
/**
* Lock a spin lock by setting it to a non-zero value.
*
* ***Please note that spinlocks are dangerous if you don't know what you're
* doing. Please be careful using any sort of spinlock!***
*
* \param lock a pointer to a lock variable.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_TryLockSpinlock
* \sa SDL_UnlockSpinlock
*/
extern SDL_DECLSPEC void SDLCALL SDL_LockSpinlock(SDL_SpinLock *lock);
/**
* Unlock a spin lock by setting it to 0.
*
* Always returns immediately.
*
* ***Please note that spinlocks are dangerous if you don't know what you're
* doing. Please be careful using any sort of spinlock!***
*
* \param lock a pointer to a lock variable.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_LockSpinlock
* \sa SDL_TryLockSpinlock
*/
extern SDL_DECLSPEC void SDLCALL SDL_UnlockSpinlock(SDL_SpinLock *lock);
#ifdef SDL_WIKI_DOCUMENTATION_SECTION
/**
* Mark a compiler barrier.
*
* A compiler barrier prevents the compiler from reordering reads and writes
* to globally visible variables across the call.
*
* This macro only prevents the compiler from reordering reads and writes, it
* does not prevent the CPU from reordering reads and writes. However, all of
* the atomic operations that modify memory are full memory barriers.
*
* \threadsafety Obviously this macro is safe to use from any thread at any
* time, but if you find yourself needing this, you are probably
* dealing with some very sensitive code; be careful!
*
* \since This macro is available since SDL 3.1.3.
*/
#define SDL_CompilerBarrier() DoCompilerSpecificReadWriteBarrier()
#elif defined(_MSC_VER) && (_MSC_VER > 1200) && !defined(__clang__)
void _ReadWriteBarrier(void);
#pragma intrinsic(_ReadWriteBarrier)
#define SDL_CompilerBarrier() _ReadWriteBarrier()
#elif (defined(__GNUC__) && !defined(SDL_PLATFORM_EMSCRIPTEN)) || (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x5120))
/* This is correct for all CPUs when using GCC or Solaris Studio 12.1+. */
#define SDL_CompilerBarrier() __asm__ __volatile__ ("" : : : "memory")
#elif defined(__WATCOMC__)
extern __inline void SDL_CompilerBarrier(void);
#pragma aux SDL_CompilerBarrier = "" parm [] modify exact [];
#else
#define SDL_CompilerBarrier() \
{ SDL_SpinLock _tmp = 0; SDL_LockSpinlock(&_tmp); SDL_UnlockSpinlock(&_tmp); }
#endif
/**
* Insert a memory release barrier (function version).
*
* Please refer to SDL_MemoryBarrierRelease for details. This is a function
* version, which might be useful if you need to use this functionality from a
* scripting language, etc. Also, some of the macro versions call this
* function behind the scenes, where more heavy lifting can happen inside of
* SDL. Generally, though, an app written in C/C++/etc should use the macro
* version, as it will be more efficient.
*
* \threadsafety Obviously this function is safe to use from any thread at any
* time, but if you find yourself needing this, you are probably
* dealing with some very sensitive code; be careful!
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_MemoryBarrierRelease
*/
extern SDL_DECLSPEC void SDLCALL SDL_MemoryBarrierReleaseFunction(void);
/**
* Insert a memory acquire barrier (function version).
*
* Please refer to SDL_MemoryBarrierRelease for details. This is a function
* version, which might be useful if you need to use this functionality from a
* scripting language, etc. Also, some of the macro versions call this
* function behind the scenes, where more heavy lifting can happen inside of
* SDL. Generally, though, an app written in C/C++/etc should use the macro
* version, as it will be more efficient.
*
* \threadsafety Obviously this function is safe to use from any thread at any
* time, but if you find yourself needing this, you are probably
* dealing with some very sensitive code; be careful!
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_MemoryBarrierAcquire
*/
extern SDL_DECLSPEC void SDLCALL SDL_MemoryBarrierAcquireFunction(void);
#ifdef SDL_WIKI_DOCUMENTATION_SECTION
/**
* Insert a memory release barrier (macro version).
*
* Memory barriers are designed to prevent reads and writes from being
* reordered by the compiler and being seen out of order on multi-core CPUs.
*
* A typical pattern would be for thread A to write some data and a flag, and
* for thread B to read the flag and get the data. In this case you would
* insert a release barrier between writing the data and the flag,
* guaranteeing that the data write completes no later than the flag is
* written, and you would insert an acquire barrier between reading the flag
* and reading the data, to ensure that all the reads associated with the flag
* have completed.
*
* In this pattern you should always see a release barrier paired with an
* acquire barrier and you should gate the data reads/writes with a single
* flag variable.
*
* For more information on these semantics, take a look at the blog post:
* http://preshing.com/20120913/acquire-and-release-semantics
*
* This is the macro version of this functionality; if possible, SDL will use
* compiler intrinsics or inline assembly, but some platforms might need to
* call the function version of this, SDL_MemoryBarrierReleaseFunction to do
* the heavy lifting. Apps that can use the macro should favor it over the
* function.
*
* \threadsafety Obviously this macro is safe to use from any thread at any
* time, but if you find yourself needing this, you are probably
* dealing with some very sensitive code; be careful!
*
* \since This macro is available since SDL 3.1.3.
*
* \sa SDL_MemoryBarrierAcquire
* \sa SDL_MemoryBarrierReleaseFunction
*/
#define SDL_MemoryBarrierRelease() SDL_MemoryBarrierReleaseFunction()
/**
* Insert a memory acquire barrier (macro version).
*
* Please see SDL_MemoryBarrierRelease for the details on what memory barriers
* are and when to use them.
*
* This is the macro version of this functionality; if possible, SDL will use
* compiler intrinsics or inline assembly, but some platforms might need to
* call the function version of this, SDL_MemoryBarrierAcquireFunction, to do
* the heavy lifting. Apps that can use the macro should favor it over the
* function.
*
* \threadsafety Obviously this macro is safe to use from any thread at any
* time, but if you find yourself needing this, you are probably
* dealing with some very sensitive code; be careful!
*
* \since This macro is available since SDL 3.1.3.
*
* \sa SDL_MemoryBarrierRelease
* \sa SDL_MemoryBarrierAcquireFunction
*/
#define SDL_MemoryBarrierAcquire() SDL_MemoryBarrierAcquireFunction()
#elif defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__))
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("lwsync" : : : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("lwsync" : : : "memory")
#elif defined(__GNUC__) && defined(__aarch64__)
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory")
#elif defined(__GNUC__) && defined(__arm__)
#if 0 /* defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) */
/* Information from:
https://chromium.googlesource.com/chromium/chromium/+/trunk/base/atomicops_internals_arm_gcc.h#19
The Linux kernel provides a helper function which provides the right code for a memory barrier,
hard-coded at address 0xffff0fa0
*/
typedef void (*SDL_KernelMemoryBarrierFunc)();
#define SDL_MemoryBarrierRelease() ((SDL_KernelMemoryBarrierFunc)0xffff0fa0)()
#define SDL_MemoryBarrierAcquire() ((SDL_KernelMemoryBarrierFunc)0xffff0fa0)()
#else
#if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) || defined(__ARM_ARCH_8A__)
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory")
#elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__)
#ifdef __thumb__
/* The mcr instruction isn't available in thumb mode, use real functions */
#define SDL_MEMORY_BARRIER_USES_FUNCTION
#define SDL_MemoryBarrierRelease() SDL_MemoryBarrierReleaseFunction()
#define SDL_MemoryBarrierAcquire() SDL_MemoryBarrierAcquireFunction()
#else
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" : : "r"(0) : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" : : "r"(0) : "memory")
#endif /* __thumb__ */
#else
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("" : : : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("" : : : "memory")
#endif /* SDL_PLATFORM_LINUX || SDL_PLATFORM_ANDROID */
#endif /* __GNUC__ && __arm__ */
#else
#if (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x5120))
/* This is correct for all CPUs on Solaris when using Solaris Studio 12.1+. */
#include <mbarrier.h>
#define SDL_MemoryBarrierRelease() __machine_rel_barrier()
#define SDL_MemoryBarrierAcquire() __machine_acq_barrier()
#else
/* This is correct for the x86 and x64 CPUs, and we'll expand this over time. */
#define SDL_MemoryBarrierRelease() SDL_CompilerBarrier()
#define SDL_MemoryBarrierAcquire() SDL_CompilerBarrier()
#endif
#endif
/* "REP NOP" is PAUSE, coded for tools that don't know it by that name. */
#ifdef SDL_WIKI_DOCUMENTATION_SECTION
/**
* A macro to insert a CPU-specific "pause" instruction into the program.
*
* This can be useful in busy-wait loops, as it serves as a hint to the CPU as
* to the program's intent; some CPUs can use this to do more efficient
* processing. On some platforms, this doesn't do anything, so using this
* macro might just be a harmless no-op.
*
* Note that if you are busy-waiting, there are often more-efficient
* approaches with other synchronization primitives: mutexes, semaphores,
* condition variables, etc.
*
* \threadsafety This macro is safe to use from any thread.
*
* \since This macro is available since SDL 3.1.3.
*/
#define SDL_CPUPauseInstruction() DoACPUPauseInACompilerAndArchitectureSpecificWay
#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))
#define SDL_CPUPauseInstruction() __asm__ __volatile__("pause\n") /* Some assemblers can't do REP NOP, so go with PAUSE. */
#elif (defined(__arm__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7) || defined(__aarch64__)
#define SDL_CPUPauseInstruction() __asm__ __volatile__("yield" ::: "memory")
#elif (defined(__powerpc__) || defined(__powerpc64__))
#define SDL_CPUPauseInstruction() __asm__ __volatile__("or 27,27,27");
#elif (defined(__riscv) && __riscv_xlen == 64)
#define SDL_CPUPauseInstruction() __asm__ __volatile__(".insn i 0x0F, 0, x0, x0, 0x010");
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
#define SDL_CPUPauseInstruction() _mm_pause() /* this is actually "rep nop" and not a SIMD instruction. No inline asm in MSVC x86-64! */
#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64))
#define SDL_CPUPauseInstruction() __yield()
#elif defined(__WATCOMC__) && defined(__386__)
extern __inline void SDL_CPUPauseInstruction(void);
#pragma aux SDL_CPUPauseInstruction = ".686p" ".xmm2" "pause"
#else
#define SDL_CPUPauseInstruction()
#endif
/**
* A type representing an atomic integer value.
*
* This can be used to manage a value that is synchronized across multiple
* CPUs without a race condition; when an app sets a value with
* SDL_SetAtomicInt all other threads, regardless of the CPU it is running on,
* will see that value when retrieved with SDL_GetAtomicInt, regardless of CPU
* caches, etc.
*
* This is also useful for atomic compare-and-swap operations: a thread can
* change the value as long as its current value matches expectations. When
* done in a loop, one can guarantee data consistency across threads without a
* lock (but the usual warnings apply: if you don't know what you're doing, or
* you don't do it carefully, you can confidently cause any number of
* disasters with this, so in most cases, you _should_ use a mutex instead of
* this!).
*
* This is a struct so people don't accidentally use numeric operations on it
* directly. You have to use SDL atomic functions.
*
* \since This struct is available since SDL 3.1.3.
*
* \sa SDL_CompareAndSwapAtomicInt
* \sa SDL_GetAtomicInt
* \sa SDL_SetAtomicInt
* \sa SDL_AddAtomicInt
*/
typedef struct SDL_AtomicInt { int value; } SDL_AtomicInt;
/**
* Set an atomic variable to a new value if it is currently an old value.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to an SDL_AtomicInt variable to be modified.
* \param oldval the old value.
* \param newval the new value.
* \returns true if the atomic variable was set, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_GetAtomicInt
* \sa SDL_SetAtomicInt
*/
extern SDL_DECLSPEC bool SDLCALL SDL_CompareAndSwapAtomicInt(SDL_AtomicInt *a, int oldval, int newval);
/**
* Set an atomic variable to a value.
*
* This function also acts as a full memory barrier.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to an SDL_AtomicInt variable to be modified.
* \param v the desired value.
* \returns the previous value of the atomic variable.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_GetAtomicInt
*/
extern SDL_DECLSPEC int SDLCALL SDL_SetAtomicInt(SDL_AtomicInt *a, int v);
/**
* Get the value of an atomic variable.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to an SDL_AtomicInt variable.
* \returns the current value of an atomic variable.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_SetAtomicInt
*/
extern SDL_DECLSPEC int SDLCALL SDL_GetAtomicInt(SDL_AtomicInt *a);
/**
* Add to an atomic variable.
*
* This function also acts as a full memory barrier.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to an SDL_AtomicInt variable to be modified.
* \param v the desired value to add.
* \returns the previous value of the atomic variable.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_AtomicDecRef
* \sa SDL_AtomicIncRef
*/
extern SDL_DECLSPEC int SDLCALL SDL_AddAtomicInt(SDL_AtomicInt *a, int v);
#ifndef SDL_AtomicIncRef
/**
* Increment an atomic variable used as a reference count.
*
* ***Note: If you don't know what this macro is for, you shouldn't use it!***
*
* \param a a pointer to an SDL_AtomicInt to increment.
* \returns the previous value of the atomic variable.
*
* \threadsafety It is safe to call this macro from any thread.
*
* \since This macro is available since SDL 3.1.3.
*
* \sa SDL_AtomicDecRef
*/
#define SDL_AtomicIncRef(a) SDL_AddAtomicInt(a, 1)
#endif
#ifndef SDL_AtomicDecRef
/**
* Decrement an atomic variable used as a reference count.
*
* ***Note: If you don't know what this macro is for, you shouldn't use it!***
*
* \param a a pointer to an SDL_AtomicInt to increment.
* \returns true if the variable reached zero after decrementing, false
* otherwise.
*
* \threadsafety It is safe to call this macro from any thread.
*
* \since This macro is available since SDL 3.1.3.
*
* \sa SDL_AtomicIncRef
*/
#define SDL_AtomicDecRef(a) (SDL_AddAtomicInt(a, -1) == 1)
#endif
/**
* A type representing an atomic unsigned 32-bit value.
*
* This can be used to manage a value that is synchronized across multiple
* CPUs without a race condition; when an app sets a value with
* SDL_SetAtomicU32 all other threads, regardless of the CPU it is running on,
* will see that value when retrieved with SDL_GetAtomicU32, regardless of CPU
* caches, etc.
*
* This is also useful for atomic compare-and-swap operations: a thread can
* change the value as long as its current value matches expectations. When
* done in a loop, one can guarantee data consistency across threads without a
* lock (but the usual warnings apply: if you don't know what you're doing, or
* you don't do it carefully, you can confidently cause any number of
* disasters with this, so in most cases, you _should_ use a mutex instead of
* this!).
*
* This is a struct so people don't accidentally use numeric operations on it
* directly. You have to use SDL atomic functions.
*
* \since This struct is available since SDL 3.1.3.
*
* \sa SDL_CompareAndSwapAtomicU32
* \sa SDL_GetAtomicU32
* \sa SDL_SetAtomicU32
*/
typedef struct SDL_AtomicU32 { Uint32 value; } SDL_AtomicU32;
/**
* Set an atomic variable to a new value if it is currently an old value.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to an SDL_AtomicU32 variable to be modified.
* \param oldval the old value.
* \param newval the new value.
* \returns true if the atomic variable was set, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_GetAtomicU32
* \sa SDL_SetAtomicU32
*/
extern SDL_DECLSPEC bool SDLCALL SDL_CompareAndSwapAtomicU32(SDL_AtomicU32 *a, Uint32 oldval, Uint32 newval);
/**
* Set an atomic variable to a value.
*
* This function also acts as a full memory barrier.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to an SDL_AtomicU32 variable to be modified.
* \param v the desired value.
* \returns the previous value of the atomic variable.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_GetAtomicU32
*/
extern SDL_DECLSPEC Uint32 SDLCALL SDL_SetAtomicU32(SDL_AtomicU32 *a, Uint32 v);
/**
* Get the value of an atomic variable.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to an SDL_AtomicU32 variable.
* \returns the current value of an atomic variable.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_SetAtomicU32
*/
extern SDL_DECLSPEC Uint32 SDLCALL SDL_GetAtomicU32(SDL_AtomicU32 *a);
/**
* Set a pointer to a new value if it is currently an old value.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to a pointer.
* \param oldval the old pointer value.
* \param newval the new pointer value.
* \returns true if the pointer was set, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_CompareAndSwapAtomicInt
* \sa SDL_GetAtomicPointer
* \sa SDL_SetAtomicPointer
*/
extern SDL_DECLSPEC bool SDLCALL SDL_CompareAndSwapAtomicPointer(void **a, void *oldval, void *newval);
/**
* Set a pointer to a value atomically.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to a pointer.
* \param v the desired pointer value.
* \returns the previous value of the pointer.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_CompareAndSwapAtomicPointer
* \sa SDL_GetAtomicPointer
*/
extern SDL_DECLSPEC void * SDLCALL SDL_SetAtomicPointer(void **a, void *v);
/**
* Get the value of a pointer atomically.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to a pointer.
* \returns the current value of a pointer.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.1.3.
*
* \sa SDL_CompareAndSwapAtomicPointer
* \sa SDL_SetAtomicPointer
*/
extern SDL_DECLSPEC void * SDLCALL SDL_GetAtomicPointer(void **a);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include <SDL3/SDL_close_code.h>
#endif /* SDL_atomic_h_ */

2159
Source/ThirdParty/SDL/SDL3/SDL_audio.h vendored Normal file

File diff suppressed because it is too large Load Diff

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