1121 lines
33 KiB
C++
1121 lines
33 KiB
C++
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
|
|
|
#if PLATFORM_SDL
|
|
|
|
#include "SDLWindow.h"
|
|
#include "SDLInput.h"
|
|
#include "Engine/Core/Log.h"
|
|
#include "Engine/Core/Math/Math.h"
|
|
#include "Engine/Core/Math/Rectangle.h"
|
|
#include "Engine/Engine/Engine.h"
|
|
#include "Engine/Graphics/GPUDevice.h"
|
|
#include "Engine/Graphics/GPUSwapChain.h"
|
|
#include "Engine/Graphics/RenderTask.h"
|
|
#include "Engine/Input/Input.h"
|
|
#include "Engine/Input/Keyboard.h"
|
|
#include "Engine/Input/Mouse.h"
|
|
#include "Engine/Platform/IGuiData.h"
|
|
#include "Engine/Platform/WindowsManager.h"
|
|
|
|
#define NOGDI
|
|
#include <SDL3/SDL_events.h>
|
|
#include <SDL3/SDL_hints.h>
|
|
#include <SDL3/SDL_properties.h>
|
|
#include <SDL3/SDL_video.h>
|
|
#undef CreateWindow
|
|
|
|
#if PLATFORM_WINDOWS
|
|
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
|
|
#define STYLE_RESIZABLE (WS_THICKFRAME | WS_MAXIMIZEBOX)
|
|
#define BORDERLESS_MAXIMIZE_WORKAROUND 2
|
|
#if USE_EDITOR
|
|
#include <oleidl.h>
|
|
#endif
|
|
#elif PLATFORM_LINUX
|
|
#include "Engine/Platform/Linux/IncludeX11.h"
|
|
#endif
|
|
|
|
#define DefaultDPI 96
|
|
|
|
namespace
|
|
{
|
|
SDLWindow* LastEventWindow = nullptr;
|
|
static SDL_Cursor* Cursors[SDL_SYSTEM_CURSOR_COUNT] = { nullptr };
|
|
#if BORDERLESS_MAXIMIZE_WORKAROUND == 2
|
|
int SkipMaximizeEventsCount = 0;
|
|
#endif
|
|
}
|
|
|
|
void* GetNativeWindowPointer(SDL_Window* window);
|
|
SDL_HitTestResult OnWindowHitTest(SDL_Window* win, const SDL_Point* area, void* data);
|
|
void GetRelativeWindowOffset(WindowType type, SDLWindow* parentWindow, Int2& positionOffset);
|
|
Int2 GetSDLWindowScreenPosition(const SDLWindow* window);
|
|
void SetSDLWindowScreenPosition(const SDLWindow* window, const int x, const int y);
|
|
|
|
class SDLDropFilesData : 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 SDLDropTextData : 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
|
|
{
|
|
}
|
|
};
|
|
|
|
SDLWindow::SDLWindow(const CreateWindowSettings& settings)
|
|
: WindowBase(settings)
|
|
, _handle(nullptr)
|
|
, _cachedClientRectangle(Rectangle())
|
|
#if PLATFORM_LINUX
|
|
, _dragOver(false)
|
|
#endif
|
|
{
|
|
Int2 clientSize(Math::TruncToInt(settings.Size.X), Math::TruncToInt(settings.Size.Y));
|
|
_clientSize = Float2(clientSize);
|
|
|
|
if (SDLPlatform::UsesWayland())
|
|
{
|
|
// The compositor seems to crash when something is rendered to the hidden popup window surface
|
|
_settings.ShowAfterFirstPaint = _showAfterFirstPaint = false;
|
|
}
|
|
|
|
uint32 flags = SDL_WINDOW_HIDDEN;
|
|
if (_settings.Type == WindowType::Utility)
|
|
flags |= SDL_WINDOW_UTILITY;
|
|
else if (_settings.Type == WindowType::Regular && !_settings.ShowInTaskbar)
|
|
flags |= SDL_WINDOW_UTILITY;
|
|
else if (_settings.Type == WindowType::Tooltip)
|
|
flags |= SDL_WINDOW_TOOLTIP;
|
|
else if (_settings.Type == WindowType::Popup)
|
|
flags |= SDL_WINDOW_POPUP_MENU;
|
|
|
|
if (!_settings.HasBorder)
|
|
flags |= SDL_WINDOW_BORDERLESS;
|
|
if (_settings.AllowInput)
|
|
flags |= SDL_WINDOW_INPUT_FOCUS;
|
|
else
|
|
flags |= SDL_WINDOW_NOT_FOCUSABLE;
|
|
if (_settings.HasSizingFrame)
|
|
flags |= SDL_WINDOW_RESIZABLE;
|
|
if (_settings.IsTopmost)
|
|
flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
|
if (_settings.SupportsTransparency)
|
|
flags |= SDL_WINDOW_TRANSPARENT;
|
|
|
|
// Disable parenting of child windows as those are always on top of the parent window and never show up in taskbar
|
|
//if (_settings.Parent != nullptr && (_settings.Type != WindowType::Tooltip && _settings.Type != WindowType::Popup))
|
|
// _settings.Parent = nullptr;
|
|
|
|
// The window position needs to be relative to the parent window
|
|
Int2 relativePosition(Math::TruncToInt(settings.Position.X), Math::TruncToInt(settings.Position.Y));
|
|
GetRelativeWindowOffset(_settings.Type, _settings.Parent, relativePosition);
|
|
|
|
SDL_PropertiesID props = SDL_CreateProperties();
|
|
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, flags);
|
|
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, settings.Title.ToStringAnsi().Get());
|
|
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, relativePosition.X);
|
|
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, relativePosition.Y);
|
|
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, clientSize.X);
|
|
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, clientSize.Y);
|
|
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN, true);
|
|
if ((flags & SDL_WINDOW_TOOLTIP) != 0)
|
|
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_TOOLTIP_BOOLEAN, true);
|
|
else if ((flags & SDL_WINDOW_POPUP_MENU) != 0)
|
|
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MENU_BOOLEAN, true);
|
|
if (_settings.Parent != nullptr)
|
|
SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, _settings.Parent->_window);
|
|
|
|
_window = SDL_CreateWindowWithProperties(props);
|
|
SDL_DestroyProperties(props);
|
|
if (_window == nullptr)
|
|
Platform::Fatal(String::Format(TEXT("Cannot create SDL window: {0}"), String(SDL_GetError())));
|
|
|
|
_windowId = SDL_GetWindowID(_window);
|
|
_handle = GetNativeWindowPointer(_window);
|
|
ASSERT(_handle != nullptr);
|
|
|
|
SDL_DisplayID display = SDL_GetDisplayForWindow(_window);
|
|
_dpiScale = SDL_GetWindowDisplayScale(_window);
|
|
_dpi = Math::TruncToInt(_dpiScale * DefaultDPI);
|
|
|
|
SDL_SetWindowMinimumSize(_window, Math::TruncToInt(_settings.MinimumSize.X), Math::TruncToInt(_settings.MinimumSize.Y));
|
|
SDL_SetWindowMaximumSize(_window, Math::TruncToInt(_settings.MaximumSize.X), Math::TruncToInt(_settings.MaximumSize.Y));
|
|
|
|
SDL_SetWindowHitTest(_window, &OnWindowHitTest, this);
|
|
InitSwapChain();
|
|
|
|
#if USE_EDITOR
|
|
// Enable file drag & drop support
|
|
if (_settings.AllowDragAndDrop)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
const auto result = RegisterDragDrop((HWND)_handle, (LPDROPTARGET)(Windows::IDropTarget*)this);
|
|
if (result != S_OK)
|
|
{
|
|
LOG(Warning, "Window drag and drop service error: 0x{0:x}:{1}", result, 1);
|
|
}
|
|
#elif PLATFORM_LINUX
|
|
auto xDisplay = (X11::Display*)GetX11Display();
|
|
if (xDisplay)
|
|
{
|
|
auto xdndVersion = 5;
|
|
auto xdndAware = X11::XInternAtom(xDisplay, "XdndAware", 0);
|
|
if (xdndAware != 0)
|
|
X11::XChangeProperty(xDisplay, (X11::Window)_handle, xdndAware, (X11::Atom)4, 32, PropModeReplace, (unsigned char*)&xdndVersion, 1);
|
|
}
|
|
else
|
|
{
|
|
// TODO: Wayland
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
LastEventWindow = this;
|
|
|
|
#if PLATFORM_LINUX
|
|
// Initialize using the shared Display instance from SDL
|
|
if (SDLPlatform::UsesX11() && SDLPlatform::GetXDisplay() == nullptr)
|
|
SDLPlatform::InitPlatformX11(GetX11Display());
|
|
#endif
|
|
}
|
|
|
|
void* GetNativeWindowPointer(SDL_Window* window)
|
|
{
|
|
void* windowPtr;
|
|
auto props = SDL_GetWindowProperties(window);
|
|
#if PLATFORM_WINDOWS
|
|
windowPtr = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
|
|
#elif PLATFORM_LINUX
|
|
windowPtr = SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr);
|
|
if (windowPtr == nullptr)
|
|
windowPtr = (void*)SDL_GetNumberProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
|
|
#elif PLATFORM_MAC
|
|
windowPtr = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr);
|
|
#elif PLATFORM_ANDROID
|
|
windowPtr = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, nullptr);
|
|
#elif PLATFORM_IOS
|
|
windowPtr = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, nullptr);
|
|
#else
|
|
static_assert(false, "unsupported platform");
|
|
#endif
|
|
return windowPtr;
|
|
}
|
|
|
|
SDL_Window* SDLWindow::GetSDLWindow() const
|
|
{
|
|
return _window;
|
|
}
|
|
|
|
#if PLATFORM_LINUX
|
|
|
|
void* SDLWindow::GetWaylandSurfacePtr() const
|
|
{
|
|
return SDL_GetPointerProperty(SDL_GetWindowProperties(_window), SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr);
|
|
}
|
|
|
|
void* SDLWindow::GetWaylandDisplay() const
|
|
{
|
|
return SDL_GetPointerProperty(SDL_GetWindowProperties(_window), SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr);
|
|
}
|
|
|
|
uintptr SDLWindow::GetX11WindowHandle() const
|
|
{
|
|
return (uintptr)SDL_GetNumberProperty(SDL_GetWindowProperties(_window), SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
|
|
}
|
|
|
|
void* SDLWindow::GetX11Display() const
|
|
{
|
|
return SDL_GetPointerProperty(SDL_GetWindowProperties(_window), SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr);
|
|
}
|
|
|
|
#endif
|
|
|
|
SDLWindow::~SDLWindow()
|
|
{
|
|
if (LastEventWindow == this)
|
|
LastEventWindow = nullptr;
|
|
|
|
if (_window == nullptr)
|
|
return;
|
|
|
|
SDL_StopTextInput(_window);
|
|
SDL_DestroyWindow(_window);
|
|
|
|
_window = nullptr;
|
|
_handle = nullptr;
|
|
_windowId = 0;
|
|
_visible = false;
|
|
}
|
|
|
|
SDL_HitTestResult OnWindowHitTest(SDL_Window* win, const SDL_Point* area, void* data)
|
|
{
|
|
SDLWindow* window = (SDLWindow*)data;
|
|
if (window->IsFullscreen())
|
|
return SDL_HITTEST_NORMAL;
|
|
|
|
Float2 clientPosition = Float2(static_cast<float>(area->x), static_cast<float>(area->y));
|
|
Float2 screenPosition = window->ClientToScreen(clientPosition);
|
|
|
|
WindowHitCodes hit = WindowHitCodes::Client;
|
|
bool handled = false;
|
|
window->OnHitTest(screenPosition, hit, handled);
|
|
|
|
if (!handled)
|
|
{
|
|
int margin = window->GetSettings().HasBorder ? 0 : 0;
|
|
auto size = window->GetClientSize();
|
|
//if (clientPosition.Y < 0)
|
|
// return SDL_HITTEST_DRAGGABLE;
|
|
if (clientPosition.Y < margin && clientPosition.X < margin)
|
|
return SDL_HITTEST_RESIZE_TOPLEFT;
|
|
else if (clientPosition.Y < margin && clientPosition.X > size.X - margin)
|
|
return SDL_HITTEST_RESIZE_TOPRIGHT;
|
|
else if (clientPosition.Y < margin)
|
|
return SDL_HITTEST_RESIZE_TOP;
|
|
else if (clientPosition.X < margin && clientPosition.Y > size.Y - margin)
|
|
return SDL_HITTEST_RESIZE_BOTTOMLEFT;
|
|
else if (clientPosition.X < margin)
|
|
return SDL_HITTEST_RESIZE_LEFT;
|
|
else if (clientPosition.X > size.X - margin && clientPosition.Y > size.Y - margin)
|
|
return SDL_HITTEST_RESIZE_BOTTOMRIGHT;
|
|
else if (clientPosition.X > size.X - margin)
|
|
return SDL_HITTEST_RESIZE_RIGHT;
|
|
else if (clientPosition.Y > size.Y - margin)
|
|
return SDL_HITTEST_RESIZE_BOTTOM;
|
|
else
|
|
return SDL_HITTEST_NORMAL;
|
|
}
|
|
|
|
switch (hit)
|
|
{
|
|
case WindowHitCodes::Caption:
|
|
return SDL_HITTEST_DRAGGABLE;
|
|
case WindowHitCodes::TopLeft:
|
|
return SDL_HITTEST_RESIZE_TOPLEFT;
|
|
case WindowHitCodes::Top:
|
|
return SDL_HITTEST_RESIZE_TOP;
|
|
case WindowHitCodes::TopRight:
|
|
return SDL_HITTEST_RESIZE_TOPRIGHT;
|
|
case WindowHitCodes::Right:
|
|
return SDL_HITTEST_RESIZE_RIGHT;
|
|
case WindowHitCodes::BottomRight:
|
|
return SDL_HITTEST_RESIZE_BOTTOMRIGHT;
|
|
case WindowHitCodes::Bottom:
|
|
return SDL_HITTEST_RESIZE_BOTTOM;
|
|
case WindowHitCodes::BottomLeft:
|
|
return SDL_HITTEST_RESIZE_BOTTOMLEFT;
|
|
case WindowHitCodes::Left:
|
|
return SDL_HITTEST_RESIZE_LEFT;
|
|
default:
|
|
return SDL_HITTEST_NORMAL;
|
|
}
|
|
}
|
|
|
|
SDLWindow* SDLWindow::GetWindowFromEvent(const SDL_Event& event)
|
|
{
|
|
SDL_Window* window = SDL_GetWindowFromEvent(&event);
|
|
if (window == nullptr)
|
|
return nullptr;
|
|
if (LastEventWindow == nullptr || window != LastEventWindow->_window)
|
|
LastEventWindow = GetWindowWithSDLWindow(window);
|
|
return LastEventWindow;
|
|
}
|
|
|
|
SDLWindow* SDLWindow::GetWindowWithSDLWindow(SDL_Window* window)
|
|
{
|
|
WindowsManager::WindowsLocker.Lock();
|
|
for (auto win : WindowsManager::Windows)
|
|
{
|
|
if (win->_window == window)
|
|
return win;
|
|
}
|
|
WindowsManager::WindowsLocker.Unlock();
|
|
return nullptr;
|
|
}
|
|
|
|
void SDLWindow::HandleEvent(SDL_Event& event)
|
|
{
|
|
if (_isClosing)
|
|
return;
|
|
|
|
switch (event.type)
|
|
{
|
|
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
|
{
|
|
Close(ClosingReason::User);
|
|
return;
|
|
}
|
|
case SDL_EVENT_WINDOW_DESTROYED:
|
|
{
|
|
#if USE_EDITOR && PLATFORM_WINDOWS
|
|
// Disable file dropping
|
|
if (_settings.AllowDragAndDrop)
|
|
{
|
|
const auto result = RevokeDragDrop((HWND)_handle);
|
|
if (result != S_OK)
|
|
LOG(Warning, "Window drag and drop service error: 0x{0:x}:{1}", result, 2);
|
|
}
|
|
#endif
|
|
|
|
// Quit
|
|
#if PLATFORM_WINDOWS
|
|
PostQuitMessage(0);
|
|
#endif
|
|
return;
|
|
}
|
|
case SDL_EVENT_MOUSE_MOTION:
|
|
{
|
|
if (_isTrackingMouse && _isUsingMouseOffset)
|
|
{
|
|
Float2 delta(event.motion.xrel, event.motion.yrel);
|
|
_trackingMouseOffset += delta;
|
|
}
|
|
break;
|
|
}
|
|
case SDL_EVENT_KEY_DOWN:
|
|
{
|
|
if (event.key.scancode == SDL_SCANCODE_RETURN && Input::Keyboard->GetKey(KeyboardKeys::Alt))
|
|
{
|
|
LOG(Info, "Alt+Enter pressed");
|
|
SetIsFullscreen(!IsFullscreen());
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case SDL_EVENT_WINDOW_MOVED:
|
|
{
|
|
_cachedClientRectangle.Location = Float2(static_cast<float>(event.window.data1), static_cast<float>(event.window.data2));
|
|
#if PLATFORM_LINUX
|
|
if (SDLPlatform::UsesX11())
|
|
{
|
|
// X11 doesn't report any mouse events when mouse is over the caption area, send a simulated event instead...
|
|
Float2 mousePosition;
|
|
auto buttons = SDL_GetGlobalMouseState(&mousePosition.X, &mousePosition.Y);
|
|
if ((buttons & SDL_BUTTON_MASK(SDL_BUTTON_LEFT)) != 0)
|
|
SDLPlatform::CheckWindowDragging(this, WindowHitCodes::Caption);
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
case SDL_EVENT_WINDOW_HIT_TEST:
|
|
{
|
|
return;
|
|
}
|
|
case SDL_EVENT_WINDOW_MINIMIZED:
|
|
{
|
|
_minimized = true;
|
|
_maximized = false;
|
|
return;
|
|
}
|
|
case SDL_EVENT_WINDOW_MAXIMIZED:
|
|
{
|
|
_minimized = false;
|
|
_maximized = true;
|
|
|
|
#if PLATFORM_WINDOWS && BORDERLESS_MAXIMIZE_WORKAROUND == 2
|
|
if (SkipMaximizeEventsCount > 0)
|
|
{
|
|
SkipMaximizeEventsCount--;
|
|
return;
|
|
}
|
|
|
|
if (!_settings.HasBorder && _settings.HasSizingFrame)
|
|
{
|
|
// Restore the window back to previous state
|
|
SDL_RestoreWindow(_window);
|
|
|
|
// Remove the resizable flags from borderless windows and maximize the window again
|
|
auto style = ::GetWindowLong((HWND)_handle, GWL_STYLE);
|
|
style &= ~STYLE_RESIZABLE;
|
|
::SetWindowLong((HWND)_handle, GWL_STYLE, style);
|
|
|
|
SDL_MaximizeWindow(_window);
|
|
|
|
// Re-enable the resizable borderless flags
|
|
style = ::GetWindowLong((HWND)_handle, GWL_STYLE) | STYLE_RESIZABLE;
|
|
::SetWindowLong((HWND)_handle, GWL_STYLE, style);
|
|
|
|
// The next SDL_EVENT_WINDOW_RESTORED and SDL_EVENT_WINDOW_MAXIMIZED events should be ignored
|
|
SkipMaximizeEventsCount = 2;
|
|
}
|
|
#endif
|
|
CheckForWindowResize();
|
|
return;
|
|
}
|
|
case SDL_EVENT_WINDOW_RESTORED:
|
|
{
|
|
#if BORDERLESS_MAXIMIZE_WORKAROUND == 2
|
|
if (SkipMaximizeEventsCount > 0)
|
|
{
|
|
SkipMaximizeEventsCount--;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (_maximized)
|
|
{
|
|
_maximized = false;
|
|
// We assume SDL_EVENT_WINDOW_RESIZED is called right afterwards, no need to check for resize here
|
|
//CheckForWindowResize();
|
|
}
|
|
else if (_minimized)
|
|
{
|
|
_minimized = false;
|
|
CheckForWindowResize();
|
|
}
|
|
return;
|
|
}
|
|
case SDL_EVENT_WINDOW_RESIZED:
|
|
{
|
|
int32 width = event.window.data1;
|
|
int32 height = event.window.data2;
|
|
|
|
_clientSize = Float2(static_cast<float>(width), static_cast<float>(height));
|
|
_cachedClientRectangle.Size = _clientSize;
|
|
|
|
// Check if window size has been changed
|
|
if (width > 0 && height > 0 && (_swapChain == nullptr || width != _swapChain->GetWidth() || height != _swapChain->GetHeight()))
|
|
OnResize(width, height);
|
|
return;
|
|
}
|
|
case SDL_EVENT_WINDOW_FOCUS_GAINED:
|
|
{
|
|
OnGotFocus();
|
|
SDL_StartTextInput(_window);
|
|
const SDL_Rect* currentClippingRect = SDL_GetWindowMouseRect(_window);
|
|
if (_isClippingCursor && currentClippingRect == nullptr)
|
|
{
|
|
SDL_Rect rect{ (int)_clipCursorRect.GetX(), (int)_clipCursorRect.GetY(), (int)_clipCursorRect.GetWidth(), (int)_clipCursorRect.GetHeight() };
|
|
SDL_SetWindowMouseRect(_window, &rect);
|
|
}
|
|
return;
|
|
}
|
|
case SDL_EVENT_WINDOW_FOCUS_LOST:
|
|
{
|
|
SDL_StopTextInput(_window);
|
|
const SDL_Rect* currentClippingRect = SDL_GetWindowMouseRect(_window);
|
|
if (currentClippingRect != nullptr)
|
|
SDL_SetWindowMouseRect(_window, nullptr);
|
|
OnLostFocus();
|
|
return;
|
|
}
|
|
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
|
|
{
|
|
float scale = SDL_GetWindowDisplayScale(_window);
|
|
if (scale > 0.0f && _dpiScale != scale)
|
|
{
|
|
float oldScale = _dpiScale;
|
|
_dpiScale = scale;
|
|
_dpi = static_cast<int>(_dpiScale * DefaultDPI);
|
|
int w = static_cast<int>(_cachedClientRectangle.GetWidth() * (scale / oldScale));
|
|
int h = static_cast<int>(_cachedClientRectangle.GetHeight() * (scale / oldScale));
|
|
_cachedClientRectangle.Size = Float2(static_cast<float>(w), static_cast<float>(h));
|
|
SDL_SetWindowSize(_window, w, h);
|
|
// TODO: Recalculate fonts
|
|
}
|
|
return;
|
|
}
|
|
#if false
|
|
case SDL_EVENT_DROP_BEGIN:
|
|
{
|
|
Focus();
|
|
Float2 mousePosition;
|
|
SDL_GetGlobalMouseState(&mousePosition.X, &mousePosition.Y);
|
|
mousePosition = ScreenToClient(mousePosition);
|
|
|
|
DragDropEffect effect;
|
|
SDLDropTextData dropData;
|
|
OnDragEnter(&dropData, mousePosition, effect);
|
|
OnDragOver(&dropData, mousePosition, effect);
|
|
return;
|
|
}
|
|
case SDL_EVENT_DROP_POSITION:
|
|
{
|
|
DragDropEffect effect = DragDropEffect::None;
|
|
|
|
SDLDropTextData dropData;
|
|
OnDragOver(&dropData, Float2(static_cast<float>(event.drop.x), static_cast<float>(event.drop.y)), effect);
|
|
return;
|
|
}
|
|
case SDL_EVENT_DROP_FILE:
|
|
{
|
|
SDLDropFilesData dropData;
|
|
dropData.Files.Add(StringAnsi(event.drop.data).ToString()); // TODO: collect multiple files at once?
|
|
|
|
Focus();
|
|
|
|
Float2 mousePosition;
|
|
SDL_GetGlobalMouseState(&mousePosition.X, &mousePosition.Y);
|
|
mousePosition = ScreenToClient(mousePosition);
|
|
DragDropEffect effect = DragDropEffect::None;
|
|
OnDragDrop(&dropData, mousePosition, effect);
|
|
return;
|
|
}
|
|
case SDL_EVENT_DROP_TEXT:
|
|
{
|
|
SDLDropTextData dropData;
|
|
String str = StringAnsi(event.drop.data).ToString();
|
|
dropData.Text = StringView(str);
|
|
|
|
Focus();
|
|
Float2 mousePosition;
|
|
SDL_GetGlobalMouseState(&mousePosition.X, &mousePosition.Y);
|
|
mousePosition = ScreenToClient(mousePosition);
|
|
DragDropEffect effect = DragDropEffect::None;
|
|
OnDragDrop(&dropData, mousePosition, effect);
|
|
return;
|
|
}
|
|
case SDL_EVENT_DROP_COMPLETE:
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
|
|
{
|
|
#if !PLATFORM_WINDOWS
|
|
OnDragLeave(); // Check for release of mouse button too?
|
|
#endif
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (_settings.AllowInput)
|
|
{
|
|
if (SDLInput::HandleEvent(this, event))
|
|
return;
|
|
}
|
|
}
|
|
|
|
void* SDLWindow::GetNativePtr() const
|
|
{
|
|
return _handle;
|
|
}
|
|
|
|
void SDLWindow::Show()
|
|
{
|
|
if (_visible)
|
|
return;
|
|
|
|
if (_showAfterFirstPaint)
|
|
{
|
|
if (RenderTask)
|
|
RenderTask->Enabled = true;
|
|
return;
|
|
}
|
|
|
|
SDL_ShowWindow(_window);
|
|
if (_settings.AllowInput && _settings.ActivateWhenFirstShown)
|
|
Focus();
|
|
else if (_settings.Parent == nullptr)
|
|
BringToFront();
|
|
|
|
// Reused top-most windows (DockHintWindow) doesn't stay on top for some reason
|
|
if (_settings.IsTopmost && _settings.Type != WindowType::Tooltip)
|
|
SDL_SetWindowAlwaysOnTop(_window, true);
|
|
|
|
if (_isTrackingMouse)
|
|
{
|
|
if (!SDL_CaptureMouse(true))
|
|
{
|
|
if (!SDLPlatform::UsesWayland()) // Suppress "That operation is not supported" errors
|
|
LOG(Warning, "SDL_CaptureMouse: {0}", String(SDL_GetError()));
|
|
}
|
|
}
|
|
|
|
WindowBase::Show();
|
|
}
|
|
|
|
void SDLWindow::Hide()
|
|
{
|
|
if (!_visible)
|
|
return;
|
|
|
|
SDL_HideWindow(_window);
|
|
|
|
WindowBase::Hide();
|
|
}
|
|
|
|
void SDLWindow::Minimize()
|
|
{
|
|
if (!_settings.AllowMinimize)
|
|
return;
|
|
|
|
SDL_MinimizeWindow(_window);
|
|
}
|
|
|
|
void SDLWindow::Maximize()
|
|
{
|
|
if (!_settings.AllowMaximize)
|
|
return;
|
|
|
|
#if PLATFORM_WINDOWS && BORDERLESS_MAXIMIZE_WORKAROUND == 1
|
|
// Workaround for "SDL_BORDERLESS_RESIZABLE_STYLE" hint not working as expected when maximizing windows
|
|
auto style = ::GetWindowLong((HWND)_handle, GWL_STYLE);
|
|
style &= ~STYLE_RESIZABLE;
|
|
::SetWindowLong((HWND)_handle, GWL_STYLE, style);
|
|
|
|
SDL_MaximizeWindow(_window);
|
|
|
|
style = ::GetWindowLong((HWND)_handle, GWL_STYLE) | STYLE_RESIZABLE;
|
|
::SetWindowLong((HWND)_handle, GWL_STYLE, style);
|
|
#else
|
|
SDL_MaximizeWindow(_window);
|
|
#endif
|
|
}
|
|
|
|
void SDLWindow::SetBorderless(bool isBorderless, bool maximized)
|
|
{
|
|
if (IsFullscreen())
|
|
SetIsFullscreen(false);
|
|
|
|
// Fixes issue of borderless window not going full screen
|
|
if (IsMaximized())
|
|
Restore();
|
|
|
|
_settings.HasBorder = !isBorderless;
|
|
|
|
BringToFront();
|
|
|
|
if (isBorderless)
|
|
{
|
|
SDL_SetWindowBordered(_window, !isBorderless ? true : false);
|
|
if (maximized)
|
|
{
|
|
Maximize();
|
|
}
|
|
else
|
|
Focus();
|
|
}
|
|
else
|
|
{
|
|
SDL_SetWindowBordered(_window, !isBorderless ? true : false);
|
|
if (maximized)
|
|
{
|
|
Maximize();
|
|
}
|
|
else
|
|
Focus();
|
|
}
|
|
|
|
CheckForWindowResize();
|
|
}
|
|
|
|
void SDLWindow::Restore()
|
|
{
|
|
#if PLATFORM_WINDOWS && BORDERLESS_MAXIMIZE_WORKAROUND == 1
|
|
// Workaround for "SDL_BORDERLESS_RESIZABLE_STYLE" hint not working as expected when maximizing windows
|
|
auto style = ::GetWindowLong((HWND)_handle, GWL_STYLE);
|
|
style &= ~STYLE_RESIZABLE;
|
|
::SetWindowLong((HWND)_handle, GWL_STYLE, style);
|
|
|
|
SDL_RestoreWindow(_window);
|
|
|
|
style = ::GetWindowLong((HWND)_handle, GWL_STYLE) | STYLE_RESIZABLE;
|
|
::SetWindowLong((HWND)_handle, GWL_STYLE, style);
|
|
#else
|
|
SDL_RestoreWindow(_window);
|
|
#endif
|
|
}
|
|
|
|
bool SDLWindow::IsClosed() const
|
|
{
|
|
return _handle == nullptr;
|
|
}
|
|
|
|
bool SDLWindow::IsForegroundWindow() const
|
|
{
|
|
SDL_WindowFlags flags = SDL_GetWindowFlags(_window);
|
|
return (flags & SDL_WINDOW_INPUT_FOCUS) != 0;
|
|
}
|
|
|
|
void SDLWindow::BringToFront(bool force)
|
|
{
|
|
auto activateWhenRaised = SDL_GetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED);
|
|
SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, "0");
|
|
SDL_RaiseWindow(_window);
|
|
SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, activateWhenRaised);
|
|
}
|
|
|
|
void SDLWindow::SetClientBounds(const Rectangle& clientArea)
|
|
{
|
|
int newX = static_cast<int>(clientArea.GetLeft());
|
|
int newY = static_cast<int>(clientArea.GetTop());
|
|
int newW = static_cast<int>(clientArea.GetWidth());
|
|
int newH = static_cast<int>(clientArea.GetHeight());
|
|
|
|
SetSDLWindowScreenPosition(this, newX, newY);
|
|
SDL_SetWindowSize(_window, newW, newH);
|
|
}
|
|
|
|
bool IsPopupWindow(WindowType type)
|
|
{
|
|
return type == WindowType::Popup || type == WindowType::Tooltip;
|
|
}
|
|
|
|
void GetRelativeWindowOffset(WindowType type, SDLWindow* parentWindow, Int2& positionOffset)
|
|
{
|
|
if (!IsPopupWindow(type))
|
|
return;
|
|
|
|
SDLWindow* window = parentWindow;
|
|
while (window != nullptr)
|
|
{
|
|
Int2 parentPosition;
|
|
SDL_GetWindowPosition(window->GetSDLWindow(), &parentPosition.X, &parentPosition.Y);
|
|
positionOffset -= parentPosition;
|
|
|
|
if (!IsPopupWindow(window->GetSettings().Type))
|
|
break;
|
|
|
|
window = window->GetSettings().Parent;
|
|
}
|
|
}
|
|
|
|
Int2 GetSDLWindowScreenPosition(const SDLWindow* window)
|
|
{
|
|
Int2 relativeOffset(0, 0);
|
|
GetRelativeWindowOffset(window->GetSettings().Type, window->GetSettings().Parent, relativeOffset);
|
|
|
|
Int2 position;
|
|
SDL_GetWindowPosition(window->GetSDLWindow(), &position.X, &position.Y);
|
|
|
|
return position - relativeOffset;
|
|
}
|
|
|
|
void SetSDLWindowScreenPosition(const SDLWindow* window, const int x, const int y)
|
|
{
|
|
Int2 relativePosition(x, y);
|
|
GetRelativeWindowOffset(window->GetSettings().Type, window->GetSettings().Parent, relativePosition);
|
|
SDL_SetWindowPosition(window->GetSDLWindow(), relativePosition.X, relativePosition.Y);
|
|
}
|
|
|
|
void SDLWindow::SetPosition(const Float2& position)
|
|
{
|
|
Int2 topLeftBorder;
|
|
SDL_GetWindowBordersSize(_window, &topLeftBorder.Y, &topLeftBorder.X, nullptr, nullptr);
|
|
|
|
Int2 screenPosition(static_cast<int>(position.X), static_cast<int>(position.Y));
|
|
screenPosition += topLeftBorder;
|
|
|
|
if (false && SDLPlatform::UsesX11())
|
|
{
|
|
// TODO: is this needed?
|
|
auto monitorBounds = Platform::GetMonitorBounds(Float2::Minimum);
|
|
screenPosition += Int2(monitorBounds.GetTopLeft());
|
|
}
|
|
|
|
SetSDLWindowScreenPosition(this, screenPosition.X, screenPosition.Y);
|
|
}
|
|
|
|
void SDLWindow::SetClientPosition(const Float2& position)
|
|
{
|
|
SetSDLWindowScreenPosition(this, static_cast<int>(position.X), static_cast<int>(position.Y));
|
|
}
|
|
|
|
void SDLWindow::SetIsFullscreen(bool isFullscreen)
|
|
{
|
|
SDL_SetWindowFullscreen(_window, isFullscreen ? true : false);
|
|
if (!isFullscreen)
|
|
{
|
|
// The window is set to always-on-top for some reason when leaving fullscreen
|
|
SDL_SetWindowAlwaysOnTop(_window, false);
|
|
}
|
|
|
|
WindowBase::SetIsFullscreen(isFullscreen);
|
|
}
|
|
|
|
bool SDLWindow::IsAlwaysOnTop() const
|
|
{
|
|
SDL_WindowFlags flags = SDL_GetWindowFlags(_window);
|
|
return (flags & SDL_WINDOW_ALWAYS_ON_TOP) != 0;
|
|
}
|
|
|
|
void SDLWindow::SetIsAlwaysOnTop(bool isAlwaysOnTop)
|
|
{
|
|
if (!SDL_SetWindowAlwaysOnTop(_window, isAlwaysOnTop))
|
|
LOG(Warning, "SDL_SetWindowAlwaysOnTop failed: {0}", String(SDL_GetError()));
|
|
// Not sure if this should change _settings.IsTopmost to reflect the new value?
|
|
}
|
|
|
|
Float2 SDLWindow::GetPosition() const
|
|
{
|
|
Int2 topLeftBorder;
|
|
SDL_GetWindowBordersSize(_window, &topLeftBorder.Y, &topLeftBorder.X, nullptr, nullptr);
|
|
|
|
Int2 position = GetSDLWindowScreenPosition(this);
|
|
position -= topLeftBorder;
|
|
|
|
return Float2(static_cast<float>(position.X), static_cast<float>(position.Y));
|
|
}
|
|
|
|
Float2 SDLWindow::GetSize() const
|
|
{
|
|
int top, left, bottom, right;
|
|
SDL_GetWindowBordersSize(_window, &top, &left, &bottom, &right);
|
|
|
|
int w, h;
|
|
SDL_GetWindowSizeInPixels(_window, &w, &h);
|
|
return Float2(static_cast<float>(w + left + right), static_cast<float>(h + top + bottom));
|
|
}
|
|
|
|
Float2 SDLWindow::GetClientSize() const
|
|
{
|
|
int w, h;
|
|
SDL_GetWindowSizeInPixels(_window, &w, &h);
|
|
|
|
return Float2(static_cast<float>(w), static_cast<float>(h));;
|
|
}
|
|
|
|
Float2 SDLWindow::ScreenToClient(const Float2& screenPos) const
|
|
{
|
|
Int2 position = GetSDLWindowScreenPosition(this);
|
|
return screenPos - Float2(static_cast<float>(position.X), static_cast<float>(position.Y));
|
|
}
|
|
|
|
Float2 SDLWindow::ClientToScreen(const Float2& clientPos) const
|
|
{
|
|
Int2 position = GetSDLWindowScreenPosition(this);
|
|
return clientPos + Float2(static_cast<float>(position.X), static_cast<float>(position.Y));
|
|
}
|
|
|
|
void SDLWindow::FlashWindow()
|
|
{
|
|
SDL_FlashWindow(_window, SDL_FLASH_UNTIL_FOCUSED);
|
|
}
|
|
|
|
float SDLWindow::GetOpacity() const
|
|
{
|
|
float opacity = SDL_GetWindowOpacity(_window);
|
|
if (opacity < 0.0f)
|
|
{
|
|
LOG(Warning, "SDL_GetWindowOpacity failed: {0}", String(SDL_GetError()));
|
|
opacity = 1.0f;
|
|
}
|
|
return opacity;
|
|
}
|
|
|
|
void SDLWindow::SetOpacity(const float opacity)
|
|
{
|
|
if (!SDL_SetWindowOpacity(_window, opacity))
|
|
LOG(Warning, "SDL_SetWindowOpacity failed: {0}", String(SDL_GetError()));
|
|
}
|
|
|
|
void SDLWindow::Focus()
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
auto activateWhenRaised = SDL_GetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED);
|
|
SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, "1");
|
|
|
|
// Forcing the window to focus causes issues with opening context menus while window is maximized
|
|
//auto forceRaiseWindow = SDL_GetHint(SDL_HINT_FORCE_RAISEWINDOW);
|
|
//SDL_SetHint(SDL_HINT_FORCE_RAISEWINDOW, "1");
|
|
|
|
SDL_RaiseWindow(_window);
|
|
|
|
SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, activateWhenRaised);
|
|
//SDL_SetHint(SDL_HINT_FORCE_RAISEWINDOW, forceRaiseWindow);
|
|
#else
|
|
SDL_RaiseWindow(_window);
|
|
#endif
|
|
}
|
|
|
|
String SDLWindow::GetTitle() const
|
|
{
|
|
return String(SDL_GetWindowTitle(_window));
|
|
}
|
|
|
|
void SDLWindow::SetTitle(const StringView& title)
|
|
{
|
|
SDL_SetWindowTitle(_window, title.ToStringAnsi().Get());
|
|
}
|
|
|
|
void SDLWindow::StartTrackingMouse(bool useMouseScreenOffset)
|
|
{
|
|
if (_isTrackingMouse)
|
|
return;
|
|
|
|
_isTrackingMouse = true;
|
|
_trackingMouseOffset = Float2::Zero;
|
|
_isUsingMouseOffset = useMouseScreenOffset;
|
|
|
|
if (_visible)
|
|
{
|
|
if (!SDL_CaptureMouse(true))
|
|
{
|
|
if (!SDLPlatform::UsesWayland()) // Suppress "That operation is not supported" errors
|
|
LOG(Warning, "SDL_CaptureMouse: {0}", String(SDL_GetError()));
|
|
}
|
|
|
|
// For viewport camera mouse tracking we want to use relative mode for best precision
|
|
if (_cursor == CursorType::Hidden)
|
|
Input::Mouse->SetRelativeMode(true, this);
|
|
}
|
|
}
|
|
|
|
void SDLWindow::EndTrackingMouse()
|
|
{
|
|
if (!_isTrackingMouse)
|
|
return;
|
|
|
|
_isTrackingMouse = false;
|
|
_isHorizontalFlippingMouse = false;
|
|
_isVerticalFlippingMouse = false;
|
|
|
|
if (!SDL_CaptureMouse(false))
|
|
{
|
|
if (!SDLPlatform::UsesWayland()) // Suppress "That operation is not supported" errors
|
|
LOG(Warning, "SDL_CaptureMouse: {0}", String(SDL_GetError()));
|
|
}
|
|
|
|
Input::Mouse->SetRelativeMode(false, this);
|
|
}
|
|
|
|
void SDLWindow::StartClippingCursor(const Rectangle& bounds)
|
|
{
|
|
if (!IsFocused())
|
|
return;
|
|
|
|
_isClippingCursor = true;
|
|
SDL_Rect rect{ (int)bounds.GetX(), (int)bounds.GetY(), (int)bounds.GetWidth(), (int)bounds.GetHeight() };
|
|
SDL_SetWindowMouseRect(_window, &rect);
|
|
}
|
|
|
|
void SDLWindow::EndClippingCursor()
|
|
{
|
|
if (!_isClippingCursor)
|
|
return;
|
|
|
|
_isClippingCursor = false;
|
|
SDL_SetWindowMouseRect(_window, nullptr);
|
|
}
|
|
|
|
void SDLWindow::SetCursor(CursorType type)
|
|
{
|
|
CursorType oldCursor = _cursor;
|
|
WindowBase::SetCursor(type);
|
|
if (oldCursor != type)
|
|
SDLWindow::UpdateCursor();
|
|
}
|
|
|
|
void SDLWindow::CheckForWindowResize()
|
|
{
|
|
return;
|
|
// Cache client size
|
|
_clientSize = GetClientSize();
|
|
int32 width = (int32)(_clientSize.X);
|
|
int32 height = (int32)(_clientSize.Y);
|
|
|
|
// Check for windows maximized size and see if it needs to adjust position if needed
|
|
if (_maximized)
|
|
{
|
|
// Pick the current monitor data for sizing
|
|
SDL_Rect rect;
|
|
auto displayId = SDL_GetDisplayForWindow(_window);
|
|
SDL_GetDisplayUsableBounds(displayId, &rect);
|
|
|
|
if (width > rect.w && height > rect.h)
|
|
{
|
|
width = rect.w;
|
|
height = rect.h;
|
|
SDL_SetWindowSize(_window, width, height);
|
|
}
|
|
}
|
|
|
|
// Check if window size has been changed
|
|
if (width > 0 && height > 0 && (_swapChain == nullptr || width != _swapChain->GetWidth() || height != _swapChain->GetHeight()))
|
|
OnResize(width, height);
|
|
}
|
|
|
|
void SDLWindow::UpdateCursor()
|
|
{
|
|
if (_cursor == CursorType::Hidden)
|
|
{
|
|
SDL_HideCursor();
|
|
|
|
if (_isTrackingMouse)
|
|
Input::Mouse->SetRelativeMode(true, this);
|
|
return;
|
|
}
|
|
SDL_ShowCursor();
|
|
//if (_isTrackingMouse)
|
|
// Input::Mouse->SetRelativeMode(false, this);
|
|
|
|
int32 index = SDL_SYSTEM_CURSOR_DEFAULT;
|
|
switch (_cursor)
|
|
{
|
|
case CursorType::Cross:
|
|
index = SDL_SYSTEM_CURSOR_CROSSHAIR;
|
|
break;
|
|
case CursorType::Hand:
|
|
index = SDL_SYSTEM_CURSOR_POINTER;
|
|
break;
|
|
case CursorType::Help:
|
|
//index = SDL_SYSTEM_CURSOR_DEFAULT;
|
|
break;
|
|
case CursorType::IBeam:
|
|
index = SDL_SYSTEM_CURSOR_TEXT;
|
|
break;
|
|
case CursorType::No:
|
|
index = SDL_SYSTEM_CURSOR_NOT_ALLOWED;
|
|
break;
|
|
case CursorType::Wait:
|
|
index = SDL_SYSTEM_CURSOR_WAIT;
|
|
break;
|
|
case CursorType::SizeAll:
|
|
index = SDL_SYSTEM_CURSOR_MOVE;
|
|
break;
|
|
case CursorType::SizeNESW:
|
|
index = SDL_SYSTEM_CURSOR_NESW_RESIZE;
|
|
break;
|
|
case CursorType::SizeNS:
|
|
index = SDL_SYSTEM_CURSOR_NS_RESIZE;
|
|
break;
|
|
case CursorType::SizeNWSE:
|
|
index = SDL_SYSTEM_CURSOR_NWSE_RESIZE;
|
|
break;
|
|
case CursorType::SizeWE:
|
|
index = SDL_SYSTEM_CURSOR_EW_RESIZE;
|
|
break;
|
|
case CursorType::Default:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (Cursors[index] == nullptr)
|
|
Cursors[index] = SDL_CreateSystemCursor(static_cast<SDL_SystemCursor>(index));
|
|
SDL_SetCursor(Cursors[index]);
|
|
}
|
|
|
|
#endif
|
|
|