3#include <SDL2/SDL_syswm.h>
5#include <SDL2/SDL_vulkan.h>
18#include "common/config.h"
21#ifdef EVENGINE_ANDROID
22#include "android/android.h"
29#if defined(EVENGINE_WINDOWS)
34#include "macosx/macosx.h"
44Uint32 messageBoxFlag(
const std::string&
type) {
45 if (
type ==
"error")
return SDL_MESSAGEBOX_ERROR;
46 if (
type ==
"warning")
return SDL_MESSAGEBOX_WARNING;
47 return SDL_MESSAGEBOX_INFORMATION;
54#if defined(__EMSCRIPTEN__)
58 SDL_SetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT,
"canvas");
60 if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
61 throw Exception(
"Could not initialize SDL video subsystem (%s)", SDL_GetError());
66 SDL_QuitSubSystem(SDL_INIT_VIDEO);
71 f.
width =
static_cast<uint16_t
>(
w);
72 f.height =
static_cast<uint16_t
>(
h);
82 f.minwidth = std::max(
f.minwidth, (uint16_t)1);
83 f.minheight = std::max(
f.minheight, (uint16_t)1);
86 f.display =
static_cast<uint8_t
>(std::min(std::max(
int(
f.display), 0),
getDisplayCount() - 1));
87 if (
f.width == 0 ||
f.height == 0) {
88 SDL_DisplayMode mode = {};
89 SDL_GetDesktopDisplayMode(
f.display, &mode);
90 f.width =
static_cast<uint16_t
>(mode.w);
91 f.height =
static_cast<uint16_t
>(mode.h);
95#ifndef EVENGINE_WEBGPU
96 sdlflags = SDL_WINDOW_VULKAN;
105#ifdef EVENGINE_ANDROID
106 bool fullscreen =
f.fullscreen;
108 f.fullscreen =
false;
109 f.desktop_mode =
true;
114 sdlflags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
116 sdlflags |= SDL_WINDOW_FULLSCREEN;
117 SDL_DisplayMode mode = {0, width, height, 0,
nullptr};
120 if (SDL_GetClosestDisplayMode(
f.display, &mode, &mode) ==
nullptr) {
124 if (SDL_GetDisplayMode(
f.display, 0, &mode) < 0)
return false;
132 if (
f.resizable) sdlflags |= SDL_WINDOW_RESIZABLE;
133 if (
f.borderless) sdlflags |= SDL_WINDOW_BORDERLESS;
134 if (
f.high_dpi) sdlflags |= SDL_WINDOW_ALLOW_HIGHDPI;
135 if (
f.always_on_top) sdlflags |= SDL_WINDOW_ALWAYS_ON_TOP;
140 if (
f.use_position) {
142 SDL_Rect displaybounds = {};
143 SDL_GetDisplayBounds(
f.display, &displaybounds);
144 x += displaybounds.x;
145 y += displaybounds.y;
148 x =
y = SDL_WINDOWPOS_CENTERED_DISPLAY(
f.display);
150 x =
y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(
f.display);
157 if (!createWindowAndContext(
x,
y,
f.width,
f.height, sdlflags,
f.msaa,
f.stencil,
f.depth))
return false;
161 if (!iconRgba.empty())
setIconRGBA(iconRgba.data(), iconWidth, iconHeight);
167 SDL_SetWindowMinimumSize(window,
f.minwidth,
f.minheight);
169 if (
f.use_position ||
f.centered) SDL_SetWindowPosition(window,
x,
y);
171 SDL_RaiseWindow(window);
175#if defined(EVENGINE_ANDROID) || defined(EVENGINE_IOS)
180 SDL_GetWindowSize(window, &lw, &lh);
181 if (lw > 0 && lh > 0) {
182 f.width =
static_cast<uint16_t
>(lw);
183 f.height =
static_cast<uint16_t
>(lh);
190 if (
auto* surfaceHost = eve::cap::query<IWindowSurfaceHost>()) {
192#ifdef EVENGINE_WEBGPU
195 SDL_GetWindowSize(window, &pw, &ph);
197 SDL_Vulkan_GetDrawableSize(window, &pw, &ph);
199 if (pw <= 0 || ph <= 0) {
207 surfaceHost->initWithWindow(window);
209 surfaceHost->setViewportSize(
f.width,
f.height, pw, ph);
218 return setFullscreenInternal(fullscreen,
true);
222 return setFullscreenInternal(fullscreen,
false);
225bool Window::setFullscreenInternal(
bool fullscreen,
bool desktop_mode) {
226 if (!window)
return false;
239 sdlflags = SDL_WINDOW_FULLSCREEN_DESKTOP;
241 sdlflags = SDL_WINDOW_FULLSCREEN;
243 SDL_DisplayMode mode = {};
244 mode.w = windowWidth;
245 mode.h = windowHeight;
247 SDL_GetClosestDisplayMode(SDL_GetWindowDisplayIndex(window), &mode, &mode);
248 SDL_SetWindowDisplayMode(window, &mode);
252#ifdef EVENGINE_ANDROID
253 android::setImmersive(fullscreen);
258 SDL_ShowWindow(window);
259 SDL_RaiseWindow(window);
262 int rc = SDL_SetWindowFullscreen(window, sdlflags);
266 rc = SDL_SetWindowFullscreen(window, sdlflags);
274 if (!fullscreen) SDL_SetWindowMinimumSize(window, settings.
minwidth, settings.
minheight);
286 if (window) SDL_SetWindowTitle(window, title.c_str());
293 int count = SDL_GetNumVideoDisplays();
294 if (display < 0 || display >=
count) display = 0;
296 SDL_GetDisplayBounds(display, &
bounds);
300 settings.
display =
static_cast<uint8_t
>(display);
312 SDL_GetWindowPosition(window, &
wx, &
wy);
313 display = SDL_GetWindowDisplayIndex(window);
314 if (display < 0) display = 0;
316 SDL_GetDisplayBounds(display, &
bounds);
326 return window && (SDL_GetWindowFlags(window) & SDL_WINDOW_MAXIMIZED) != 0;
329 return window && (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) != 0;
332 return window && (SDL_GetWindowFlags(window) & SDL_WINDOW_INPUT_FOCUS) != 0;
335 return window && (SDL_GetWindowFlags(window) & SDL_WINDOW_MOUSE_FOCUS) != 0;
338 return window && (SDL_GetWindowFlags(window) & SDL_WINDOW_SHOWN) != 0
339 && (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) == 0;
343 settings.
vsync =
static_cast<uint8_t
>(vsync < 0 ? 0 : (vsync > 255 ? 255 : vsync));
353 if (!window || windowWidth <= 0)
return 1.0;
354 return double(pixelWidth) / double(windowWidth);
358 if (!window)
return 1.0;
364 if (!
x || !
y)
return;
371 if (!
x || !
y)
return;
373 if (
s == 0.0)
return;
379 if (!
x || !
y)
return;
381 if (
s == 0.0)
return;
387 if (!
x || !
y)
return;
397 return s == 0.0 ?
x :
x /
s;
411 int n = SDL_GetNumVideoDisplays();
412 return n < 0 ? 0 :
n;
417 const char*
n = SDL_GetDisplayName(display);
418 return n ? std::string(
n) : std::string();
423 switch (SDL_GetDisplayOrientation(display)) {
424 case SDL_ORIENTATION_LANDSCAPE:
return "landscape";
425 case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
return "landscapeFlipped";
426 case SDL_ORIENTATION_PORTRAIT:
return "portrait";
427 case SDL_ORIENTATION_PORTRAIT_FLIPPED:
return "portraitFlipped";
428 default:
return "unknown";
433 std::vector<WindowSize> out;
435 int modes = SDL_GetNumDisplayModes(display);
436 for (
int i = 0; i < modes; ++i) {
437 SDL_DisplayMode mode = {};
438 if (SDL_GetDisplayMode(display, i, &mode) == 0) {
442 if (std::find(out.begin(), out.end(),
s) == out.end()) out.push_back(
s);
449 outWidth = outHeight = 0;
451 SDL_DisplayMode mode = {};
452 if (SDL_GetDesktopDisplayMode(display, &mode) == 0) {
459 const std::string&
type,
bool attachToWindow) {
460 SDL_Window*
parent = (attachToWindow && window) ? window :
nullptr;
461 return SDL_ShowSimpleMessageBox(messageBoxFlag(
type), caption.c_str(),
message.c_str(),
parent) == 0;
465 if (data.buttons.empty())
return -1;
466 std::vector<SDL_MessageBoxButtonData> buttons;
467 buttons.reserve(data.buttons.size());
468 for (
size_t i = 0; i < data.buttons.size(); ++i) {
470 if (
int(i) == data.enterButtonIndex) flags |= SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
471 if (
int(i) == data.escapeButtonIndex) flags |= SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
472 buttons.push_back({flags, int(i), data.buttons[i].c_str()});
474 SDL_MessageBoxData sdl = {};
475 sdl.flags = messageBoxFlag(data.type);
476 sdl.window = (data.attachToWindow && window) ? window :
nullptr;
477 sdl.title = data.title.c_str();
478 sdl.message = data.message.c_str();
479 sdl.numbuttons = int(buttons.size());
480 sdl.buttons = buttons.data();
481 sdl.colorScheme =
nullptr;
483 if (SDL_ShowMessageBox(&sdl, &buttonid) < 0)
return -1;
488#ifdef EVENGINE_MACOSX
489 eve::macosx::requestAttention(continuous);
492#if SDL_VERSION_ATLEAST(2, 0, 16)
493 SDL_FlashWindow(window, continuous ? SDL_FLASH_UNTIL_FOCUSED : SDL_FLASH_BRIEFLY);
499 if (!rgba ||
w <= 0 ||
h <= 0)
return false;
501#if SDL_BYTEORDER == SDL_BIG_ENDIAN
502 const Uint32 rmask = 0xFF000000;
503 const Uint32 gmask = 0x00FF0000;
504 const Uint32 bmask = 0x0000FF00;
505 const Uint32 amask = 0x000000FF;
507 const Uint32 rmask = 0x000000FF;
508 const Uint32 gmask = 0x0000FF00;
509 const Uint32 bmask = 0x00FF0000;
510 const Uint32 amask = 0xFF000000;
513 SDL_Surface* surface =
514 SDL_CreateRGBSurfaceFrom(
const_cast<uint8_t*
>(rgba),
w,
h, 32,
w * 4, rmask, gmask, bmask, amask);
516 throw Exception(
"Could not create window icon surface: %s", SDL_GetError());
520 SDL_SetWindowIcon(window, surface);
522 SDL_FreeSurface(surface);
524 iconRgba.assign(rgba, rgba +
static_cast<size_t>(
w) *
h * 4);
528#ifdef EVENGINE_MACOSX
530 eve::macosx::setIconRGBA(rgba,
w,
h);
536bool Window::createWindowAndContext(
int x,
int y,
int w,
int h, Uint32 windowflags,
int msaa,
bool stencil,
int depth) {
537#ifdef EVENGINE_MACOSX
544 const std::string bundledVulkanDir = eve::macosx::bootstrapBundledVulkan();
545 if (!bundledVulkanDir.empty()) {
546 const std::string loader = bundledVulkanDir +
"/libvulkan.1.dylib";
547 if (SDL_Vulkan_LoadLibrary(loader.c_str()) < 0)
548 std::fprintf(stderr,
"eve: failed to load bundled Vulkan loader %s (%s)\n",
549 loader.c_str(), SDL_GetError());
553 window = SDL_CreateWindow(title.c_str(),
x,
y,
w,
h, windowflags);
556 throw Exception(
"Window Create Failed: %s", SDL_GetError());
568 if (
auto* surfaceHost = eve::cap::query<IWindowSurfaceHost>()) surfaceHost->onNativeWindowDestroyed();
570 SDL_DestroyWindow(window);
575 SDL_FlushEvent(SDL_WINDOWEVENT);
578 pixelWidth = pixelHeight = 0;
579 windowWidth = windowHeight = 0;
584 settings = newsettings;
586 SDL_GetWindowSize(window, &windowWidth, &windowHeight);
587#ifdef EVENGINE_WEBGPU
588 SDL_GetWindowSize(window, &pixelWidth, &pixelHeight);
590 SDL_Vulkan_GetDrawableSize(window, &pixelWidth, &pixelHeight);
593 if (updateGraphicsViewport && window) {
594 if (
auto* surfaceHost = eve::cap::query<IWindowSurfaceHost>())
595 surfaceHost->setViewportSize(windowWidth, windowHeight, pixelWidth, pixelHeight);
EVENGINE_API_FOUNDATION public API.
void getDesktopDimensions(int display, int &outWidth, int &outHeight) const override
Desktop resolution of a display.
bool hasMouseFocus() const override
True when the window has mouse focus.
bool setFullscreenExclusive(bool fullscreen) override
Switches between exclusive fullscreen and windowed mode.
bool isMaximized() const override
True when the window is maximized.
bool isVisible() const override
True when the window is visible (not hidden).
void maximize() override
Maximizes the window.
bool setIconRGBA(const uint8_t *rgba, int w, int h) override
Sets the window icon from tightly packed RGBA8 pixels (w*h*4 bytes).
void setVSync(int vsync) override
Enables/disables vertical sync (0 = off, 1 = on).
int getVSync() const override
Current vsync setting (0 = off, 1 = on).
int showMessageBoxData(const MessageBoxData &data) override
Shows a configurable message box; returns the pressed button index.
int getHeight() const override
Current logical window height.
void restore() override
Restores from minimized/maximized state.
double getDPIScale() const override
Current UI/logical DPI scale (1.0 on non-retina displays).
int getPixelHeight() const override
Framebuffer (pixel) height of the window.
std::string getDisplayOrientation(int display) const override
Display orientation string (e.g. "landscape").
bool hasFocus() const override
True when the window has keyboard focus.
double fromPixels(double x) const override
Scales a UI-pixel length back to logical units.
int getWidth() const override
Current logical window width.
bool isMinimized() const override
True when the window is minimized.
bool setWindowSettings(WindowSettings settings) override
Applies a full WindowSettings struct (recreates the window when needed).
void setWindowTitle(const std::string &title) override
Sets the window title shown in the OS title bar.
void fromPixelsXY(double px, double py, double &wx, double &wy) const override
Converts a framebuffer pixel XY pair to logical coordinates.
void requestAttention(bool continuous) override
Requests OS attention (flashing taskbar); continuous repeats until focused.
std::vector< WindowSize > getFullscreenSizes(int display) const override
Fullscreen sizes supported by a display.
void updateSettings(const WindowSettings &newsettings, bool updateGraphicsViewport)
Used by event backend on resize to refresh drawable size / viewport.
void windowToDPICoords(double *x, double *y) const override
Converts window logical coordinates to UI-DPI coordinates.
double getNativeDPIScale() const override
Native DPI scale reported by the OS for this window.
void pixelToWindowCoords(double *x, double *y) const override
Converts framebuffer pixel coordinates to window logical coordinates.
int getPixelWidth() const override
Framebuffer (pixel) width of the window.
bool isOpen() const override
True while the native window exists and has not been closed.
double toPixels(double x) const override
Scales a logical length to UI pixels.
int getDisplayCount() const override
Number of displays attached to the system.
void setPosition(int x, int y, int display) override
Moves the window to a position in logical units on the given display.
bool setFullscreenDesktop(bool fullscreen) override
Switches between desktop fullscreen and windowed mode.
void toPixelsXY(double wx, double wy, double &px, double &py) const override
Converts a logical XY pair to framebuffer pixels.
std::string getDisplayName(int display) const override
Human-readable display name for a display index.
void getPosition(int &x, int &y, int &display) override
Reads the current window position and display index.
void DPIToWindowCoords(double *x, double *y) const override
Converts UI-DPI coordinates to window logical coordinates.
void windowToPixelCoords(double *x, double *y) const override
Converts window logical coordinates to framebuffer pixel coordinates.
WindowSettings getWindowSettings() override
Returns the settings last applied to this window.
void close() override
Closes and destroys the underlying window.
void minimize() override
Minimizes the window.
Window()
Constructs an unopened window module; call setWindowSettings to create.
const std::string & getWindowTitle() const override
Current title string.
void setSize(int w, int h) override
Requests a new logical window size.
bool showMessageBox(const std::string &caption, const std::string &message, const std::string &type, bool attachToWindow) override
Shows a modal message box ("info", "warning", or "error").
~Window()
Destroys the SDL_Window if still open.
WidgetDesc window(std::string title, std::vector< WidgetDesc > children, std::string id)
Top-level window widget with a title bar.
void bindWindowEventSinkOwner(eve::window::Window *owner) noexcept
Bind the live Window module into the SDL event sink. @ownership Borrowed; cleared when the Window mod...
Build metadata (engine git commit, build time, third-party version).
Display settings for window creation/resize. width/height == 0 selects the desktop display mode size.
Parameters for a multi-button modal message box.
Logical width/height pair used by fullscreen size queries.