diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index 5f1ab6b920f62..83f86104ed77d 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -1395,13 +1395,21 @@ bool SDLTest_CommonInit(SDLTest_CommonState *state) } } + props = SDL_CreateProperties(); if (state->num_windows > 1) { (void)SDL_snprintf(title, SDL_arraysize(title), "%s %d", state->window_title, i + 1); +#ifdef SDL_PLATFORM_EMSCRIPTEN + if (i != 0) { + char id[64]; + SDL_snprintf(id, sizeof(id), "#canvas%d", i + 1); + SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING, id); + SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING, id); + } +#endif } else { SDL_strlcpy(title, state->window_title, SDL_arraysize(title)); } - props = SDL_CreateProperties(); SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, r.x); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, r.y); diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index a9d91a479c63b..a977950b176c2 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -2563,6 +2563,15 @@ SDL_Window *SDL_CreatePopupWindow(SDL_Window *parent, int offset_x, int offset_y SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, w); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, h); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, flags); +#ifdef SDL_PLATFORM_EMSCRIPTEN + // Must ensure that popup windows have a unique canvas_id + const char* canvas_id = SDL_GetStringProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING, ""); + if (!canvas_id || !*canvas_id) { + char new_canvas_id[64]; + SDL_snprintf(new_canvas_id, sizeof(new_canvas_id), "#popup%u", props); + SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING, new_canvas_id); + } +#endif window = SDL_CreateWindowWithProperties(props); SDL_DestroyProperties(props); return window; @@ -2578,6 +2587,14 @@ bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags) bool need_vulkan_load = false; SDL_WindowFlags graphics_flags; +#ifdef SDL_PLATFORM_EMSCRIPTEN + // Don't actually recreate the window in Emscripten because the SDL_WindowData's + // canvas_id and keyboard_element would be lost if destroyed + const bool recreate_window = false; +#else + const bool recreate_window = true; +#endif + // ensure no more than one of these flags is set graphics_flags = flags & (SDL_WINDOW_OPENGL | SDL_WINDOW_METAL | SDL_WINDOW_VULKAN); if (graphics_flags & (graphics_flags - 1)) { @@ -2646,7 +2663,7 @@ bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags) SDL_Vulkan_UnloadLibrary(); } - if (_this->DestroyWindow && !(flags & SDL_WINDOW_EXTERNAL)) { + if (recreate_window && _this->DestroyWindow && !(flags & SDL_WINDOW_EXTERNAL)) { _this->DestroyWindow(_this, window); } @@ -2676,7 +2693,7 @@ bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags) window->w = window->windowed.w = window->floating.w; window->h = window->windowed.h = window->floating.h; - if (!_this->CreateSDLWindow(_this, window, 0)) { + if (recreate_window && !_this->CreateSDLWindow(_this, window, 0)) { if (loaded_opengl) { SDL_GL_UnloadLibrary(); window->flags &= ~SDL_WINDOW_OPENGL; diff --git a/src/video/emscripten/SDL_emscriptenevents.c b/src/video/emscripten/SDL_emscriptenevents.c index cc999a74ddb02..ba21251d4beef 100644 --- a/src/video/emscripten/SDL_emscriptenevents.c +++ b/src/video/emscripten/SDL_emscriptenevents.c @@ -690,6 +690,9 @@ static EM_BOOL Emscripten_HandleResize(int eventType, const EmscriptenUiEvent *u } } + // Always re-sync the window when the browser window changes size + SDL_SyncWindow(window_data->window); + if (!(window_data->window->flags & SDL_WINDOW_FULLSCREEN)) { // this will only work if the canvas size is set through css if (window_data->window->flags & SDL_WINDOW_RESIZABLE) { @@ -874,14 +877,26 @@ EMSCRIPTEN_KEEPALIVE void Emscripten_HandlePointerGeneric(SDL_WindowData *window static void Emscripten_set_pointer_event_callbacks(SDL_WindowData *data) { MAIN_THREAD_EM_ASM({ - var target = document.querySelector(UTF8ToString($1)); + var id = UTF8ToString($1); + var target = document.querySelector(id); if (target) { var data = $0; - if (typeof(Module['SDL3']) === 'undefined') { + if (!Module['SDL3']) { Module['SDL3'] = {}; } + var SDL3 = Module['SDL3']; + if (!SDL3['window_data']) { + SDL3['window_data'] = {}; + } + + var window_datas = SDL3['window_data']; + if (!window_datas[id]) { + window_datas[id] = {}; + } + + var window_data = window_datas[id]; var makePointerEventCStruct = function(event) { var ptr = 0; @@ -907,23 +922,23 @@ static void Emscripten_set_pointer_event_callbacks(SDL_WindowData *data) return ptr; }; - SDL3.eventHandlerPointerEnter = function(event) { + window_data.eventHandlerPointerEnter = function(event) { var d = makePointerEventCStruct(event); if (d != 0) { _Emscripten_HandlePointerEnter(data, d); _SDL_free(d); } }; - target.addEventListener("pointerenter", SDL3.eventHandlerPointerEnter); + target.addEventListener("pointerenter", window_data.eventHandlerPointerEnter); - SDL3.eventHandlerPointerLeave = function(event) { + window_data.eventHandlerPointerLeave = function(event) { var d = makePointerEventCStruct(event); if (d != 0) { _Emscripten_HandlePointerLeave(data, d); _SDL_free(d); } }; - target.addEventListener("pointerleave", SDL3.eventHandlerPointerLeave); - target.addEventListener("pointercancel", SDL3.eventHandlerPointerLeave); // catch this, just in case. + target.addEventListener("pointerleave", window_data.eventHandlerPointerLeave); + target.addEventListener("pointercancel", window_data.eventHandlerPointerLeave); // catch this, just in case. - SDL3.eventHandlerPointerGeneric = function(event) { + window_data.eventHandlerPointerGeneric = function(event) { var d = makePointerEventCStruct(event); if (d != 0) { _Emscripten_HandlePointerGeneric(data, d); _SDL_free(d); } }; - target.addEventListener("pointerdown", SDL3.eventHandlerPointerGeneric); - target.addEventListener("pointerup", SDL3.eventHandlerPointerGeneric); - target.addEventListener("pointermove", SDL3.eventHandlerPointerGeneric); + target.addEventListener("pointerdown", window_data.eventHandlerPointerGeneric); + target.addEventListener("pointerup", window_data.eventHandlerPointerGeneric); + target.addEventListener("pointermove", window_data.eventHandlerPointerGeneric); } }, data, data->canvas_id, sizeof (Emscripten_PointerEvent)); } @@ -931,18 +946,21 @@ static void Emscripten_set_pointer_event_callbacks(SDL_WindowData *data) static void Emscripten_unset_pointer_event_callbacks(SDL_WindowData *data) { MAIN_THREAD_EM_ASM({ - var target = document.querySelector(UTF8ToString($0)); + var id = UTF8ToString($0); + var target = document.querySelector(id); if (target) { var SDL3 = Module['SDL3']; - target.removeEventListener("pointerenter", SDL3.eventHandlerPointerEnter); - target.removeEventListener("pointerleave", SDL3.eventHandlerPointerLeave); - target.removeEventListener("pointercancel", SDL3.eventHandlerPointerLeave); - target.removeEventListener("pointerdown", SDL3.eventHandlerPointerGeneric); - target.removeEventListener("pointerup", SDL3.eventHandlerPointerGeneric); - target.removeEventListener("pointermove", SDL3.eventHandlerPointerGeneric); - SDL3.eventHandlerPointerEnter = undefined; - SDL3.eventHandlerPointerLeave = undefined; - SDL3.eventHandlerPointerGeneric = undefined; + var window_datas = SDL3['window_data']; + var window_data = window_datas[id]; + target.removeEventListener("pointerenter", window_data.eventHandlerPointerEnter); + target.removeEventListener("pointerleave", window_data.eventHandlerPointerLeave); + target.removeEventListener("pointercancel", window_data.eventHandlerPointerLeave); + target.removeEventListener("pointerdown", window_data.eventHandlerPointerGeneric); + target.removeEventListener("pointerup", window_data.eventHandlerPointerGeneric); + target.removeEventListener("pointermove", window_data.eventHandlerPointerGeneric); + window_data.eventHandlerPointerEnter = undefined; + window_data.eventHandlerPointerLeave = undefined; + window_data.eventHandlerPointerGeneric = undefined; } }, data->canvas_id); } @@ -979,14 +997,26 @@ EM_JS_DEPS(dragndrop, "$writeArrayToMemory"); static void Emscripten_set_drag_event_callbacks(SDL_WindowData *data) { MAIN_THREAD_EM_ASM({ - var target = document.querySelector(UTF8ToString($1)); + var id = UTF8ToString($1); + var target = document.querySelector(id); if (target) { var data = $0; - if (typeof(Module['SDL3']) === 'undefined') { + if (!Module['SDL3']) { Module['SDL3'] = {}; } + var SDL3 = Module['SDL3']; + if (!SDL3['window_data']) { + SDL3['window_data'] = {}; + } + + var window_datas = SDL3['window_data']; + if (!window_datas[id]) { + window_datas[id] = {}; + } + + var window_data = window_datas[id]; var makeDropEventCStruct = function(event) { var ptr = 0; @@ -1000,15 +1030,27 @@ static void Emscripten_set_drag_event_callbacks(SDL_WindowData *data) return ptr; }; - SDL3.eventHandlerDropDragover = function(event) { + window_data.eventHandlerDropDragover = function(event) { event.preventDefault(); var d = makeDropEventCStruct(event); if (d != 0) { _Emscripten_SendDragEvent(data, d); _SDL_free(d); } }; - target.addEventListener("dragover", SDL3.eventHandlerDropDragover); + target.addEventListener("dragover", window_data.eventHandlerDropDragover); - SDL3.drop_count = 0; - FS.mkdir("/tmp/filedrop"); - SDL3.eventHandlerDropDrop = function(event) { + window_data.drop_count = 0; + function safeCreateDir(dir){ + try + { + FS.mkdir(dir) + } + catch(e) + { + // Throws if the directory already exists + } + } + safeCreateDir("/tmp/filedrop"); + safeCreateDir(`/tmp/filedrop/${id}/`); + + window_data.eventHandlerDropDrop = function(event) { event.preventDefault(); if (event.dataTransfer.types.includes("text/plain")) { let plain_text = stringToNewUTF8(event.dataTransfer.getData("text/plain")); @@ -1020,8 +1062,8 @@ static void Emscripten_set_drag_event_callbacks(SDL_WindowData *data) const file_reader = new FileReader(); file_reader.readAsArrayBuffer(file); file_reader.onload = function(event) { - const fs_dropdir = `/tmp/filedrop/${SDL3.drop_count}`; - SDL3.drop_count += 1; + const fs_dropdir = `/tmp/filedrop/${id}/${window_data.drop_count}`; + window_data.drop_count += 1; const fs_filepath = `${fs_dropdir}/${file.name}`; const c_fs_filepath = stringToNewUTF8(fs_filepath); @@ -1040,14 +1082,14 @@ static void Emscripten_set_drag_event_callbacks(SDL_WindowData *data) } _Emscripten_SendDragCompleteEvent(data); }; - target.addEventListener("drop", SDL3.eventHandlerDropDrop); + target.addEventListener("drop", window_data.eventHandlerDropDrop); - SDL3.eventHandlerDropDragend = function(event) { + window_data.eventHandlerDropDragend = function(event) { event.preventDefault(); _Emscripten_SendDragCompleteEvent(data); }; - target.addEventListener("dragend", SDL3.eventHandlerDropDragend); - target.addEventListener("dragleave", SDL3.eventHandlerDropDragend); + target.addEventListener("dragend", window_data.eventHandlerDropDragend); + target.addEventListener("dragleave", window_data.eventHandlerDropDragend); } }, data, data->canvas_id, sizeof (Emscripten_DropEvent)); } @@ -1055,14 +1097,29 @@ static void Emscripten_set_drag_event_callbacks(SDL_WindowData *data) static void Emscripten_unset_drag_event_callbacks(SDL_WindowData *data) { MAIN_THREAD_EM_ASM({ - var target = document.querySelector(UTF8ToString($0)); + var id = UTF8ToString($0); + var target = document.querySelector(id); if (target) { var SDL3 = Module['SDL3']; - target.removeEventListener("dragleave", SDL3.eventHandlerDropDragend); - target.removeEventListener("dragend", SDL3.eventHandlerDropDragend); - target.removeEventListener("drop", SDL3.eventHandlerDropDrop); - SDL3.drop_count = undefined; + var window_datas = SDL3['window_data']; + var window_data = window_datas[id]; + target.removeEventListener("dragleave", window_data.eventHandlerDropDragend); + target.removeEventListener("dragend", window_data.eventHandlerDropDragend); + target.removeEventListener("drop", window_data.eventHandlerDropDrop); + window_data.drop_count = undefined; + + function safeRemoveDir(path) { + try + { + FS.rmdir(path); + } + catch(e) + { + // Throws if directory doesn't exist + } + } + const path = `/tmp/filedrop/${id}/`; function recursive_remove(dirpath) { FS.readdir(dirpath).forEach((filename) => { const p = `${dirpath}/${filename}`; @@ -1073,14 +1130,14 @@ static void Emscripten_unset_drag_event_callbacks(SDL_WindowData *data) recursive_remove(p); } }); - FS.rmdir(dirpath); - }("/tmp/filedrop"); - - FS.rmdir("/tmp/filedrop"); - target.removeEventListener("dragover", SDL3.eventHandlerDropDragover); - SDL3.eventHandlerDropDragover = undefined; - SDL3.eventHandlerDropDrop = undefined; - SDL3.eventHandlerDropDragend = undefined; + safeRemoveDir(dirpath); + }(path); + + safeRemoveDir(path); + target.removeEventListener("dragover", window_data.eventHandlerDropDragover); + window_data.eventHandlerDropDragover = undefined; + window_data.eventHandlerDropDrop = undefined; + window_data.eventHandlerDropDragend = undefined; } }, data->canvas_id); } @@ -1132,7 +1189,6 @@ void Emscripten_RegisterEventHandlers(SDL_WindowData *data) { const char *keyElement; - // There is only one window and that window is the canvas emscripten_set_mousemove_callback(data->canvas_id, data, 0, Emscripten_HandleMouseMove); emscripten_set_mousedown_callback(data->canvas_id, data, 0, Emscripten_HandleMouseButton); diff --git a/src/video/emscripten/SDL_emscriptenframebuffer.c b/src/video/emscripten/SDL_emscriptenframebuffer.c index 503fac6804fcd..0fac680999e9a 100644 --- a/src/video/emscripten/SDL_emscriptenframebuffer.c +++ b/src/video/emscripten/SDL_emscriptenframebuffer.c @@ -35,6 +35,10 @@ bool Emscripten_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *wind // Free the old framebuffer surface SDL_WindowData *data = window->internal; + if (!data) { + // No framebuffer needed for this window + return true; + } surface = data->surface; SDL_DestroySurface(surface); @@ -74,30 +78,43 @@ bool Emscripten_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *wind var canvasId = UTF8ToString($3); var canvas = document.querySelector(canvasId); - //TODO: this should store a context per canvas - if (!Module['SDL3']) Module['SDL3'] = {}; + if (!Module['SDL3']) { + Module['SDL3'] = {}; + } + var SDL3 = Module['SDL3']; - if (SDL3.ctxCanvas !== canvas) { - SDL3.ctx = Module['createContext'](canvas, false, true); - SDL3.ctxCanvas = canvas; + if (!SDL3['window_data']) { + SDL3['window_data'] = {}; } - if (SDL3.w !== w || SDL3.h !== h || SDL3.imageCtx !== SDL3.ctx) { - SDL3.image = SDL3.ctx.createImageData(w, h); - SDL3.w = w; - SDL3.h = h; - SDL3.imageCtx = SDL3.ctx; + + var window_datas = SDL3['window_data']; + if (!window_datas[canvasId]) { + window_datas[canvasId] = {}; } - var data = SDL3.image.data; + + var window_data = window_datas[canvasId]; + + if (window_data.canvas !== canvas) { + window_data.ctx = Module['createContext'](canvas, false, true); + window_data.canvas = canvas; + } + if (window_data.w !== w || window_data.h !== h) { + window_data.image = window_data.ctx.createImageData(w, h); + window_data.w = w; + window_data.h = h; + } + + var data = window_data.image.data; var src = pixels / 4; var dst = 0; var num; - if (SDL3.data32Data !== data) { - SDL3.data32 = new Int32Array(data.buffer); - SDL3.data8 = new Uint8Array(data.buffer); - SDL3.data32Data = data; + if (window_data.data32Data !== data) { + window_data.data32 = new Int32Array(data.buffer); + window_data.data8 = new Uint8Array(data.buffer); + window_data.data32Data = data; } - var data32 = SDL3.data32; + var data32 = window_data.data32; num = data32.length; // logically we need to do // while (dst < num) { @@ -108,7 +125,7 @@ bool Emscripten_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *wind // native SDL_memcpy efficiencies, and the remaining loop // just stores, not load + store, so it is faster data32.set(HEAP32.subarray(src, src + num)); - var data8 = SDL3.data8; + var data8 = window_data.data8; var i = 3; var j = i + 4*num; if (num % 8 == 0) { @@ -138,7 +155,7 @@ bool Emscripten_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *wind } } - SDL3.ctx.putImageData(SDL3.image, 0, 0); + window_data.ctx.putImageData(window_data.image, 0, 0); }, surface->w, surface->h, surface->pixels, data->canvas_id); /* *INDENT-ON* */ // clang-format on @@ -153,9 +170,10 @@ bool Emscripten_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *wind void Emscripten_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *data = window->internal; - - SDL_DestroySurface(data->surface); - data->surface = NULL; + if (data) { + SDL_DestroySurface(data->surface); + data->surface = NULL; + } } #endif // SDL_VIDEO_DRIVER_EMSCRIPTEN diff --git a/src/video/emscripten/SDL_emscriptenopengles.c b/src/video/emscripten/SDL_emscriptenopengles.c index 227cdc55606ad..04152f80b3bee 100644 --- a/src/video/emscripten/SDL_emscriptenopengles.c +++ b/src/video/emscripten/SDL_emscriptenopengles.c @@ -151,6 +151,30 @@ bool Emscripten_GLES_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL if (context != window_data->gl_context) { return SDL_SetError("Cannot make context current to another window"); } + + // Emscripten only applies WebGL rendering to the canvas: Module['canvas'] + // So, the canvas in the Module object will be updated to the one associated to this window + const int canvas_set = MAIN_THREAD_EM_ASM_INT({ + try + { + var id = UTF8ToString($0); + var canvas = document.querySelector(id); + if (!canvas) { + return false; + } + Module['canvas'] = canvas; + return true; + } + catch(e) + { + // querySelector throws if id isn't a valid selector + } + return false; + }, window_data->canvas_id); + if (!canvas_set) { + SDL_SetError("Canvas '%s' is not present in DOM", window_data->canvas_id); + return false; + } } if (emscripten_webgl_make_context_current((EMSCRIPTEN_WEBGL_CONTEXT_HANDLE)context) != EMSCRIPTEN_RESULT_SUCCESS) { diff --git a/src/video/emscripten/SDL_emscriptenvideo.c b/src/video/emscripten/SDL_emscriptenvideo.c index 8ddcb95a5d86a..fffb616ccebcf 100644 --- a/src/video/emscripten/SDL_emscriptenvideo.c +++ b/src/video/emscripten/SDL_emscriptenvideo.c @@ -47,6 +47,12 @@ static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) static SDL_FullscreenResult Emscripten_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen); static void Emscripten_PumpEvents(SDL_VideoDevice *_this); static void Emscripten_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); +static bool Emscripten_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window); +static void Emscripten_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window); +static void Emscripten_HideWindow(SDL_VideoDevice *_this, SDL_Window *window); +static bool Emscripten_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window); + +static bool Emscripten_GetCanvasRect(const char *canvas_id, SDL_Rect *rect); static bool pumpevents_has_run = false; static int pending_swap_interval = -1; @@ -56,6 +62,8 @@ static int pending_swap_interval = -1; static void Emscripten_DeleteDevice(SDL_VideoDevice *device) { + SDL_VideoData *videodata = device->internal; + SDL_DestroyProperties(videodata->window_map); SDL_free(device); } @@ -133,6 +141,7 @@ EMSCRIPTEN_KEEPALIVE void Emscripten_SendSystemThemeChangedEvent(void) static SDL_VideoDevice *Emscripten_CreateDevice(void) { SDL_VideoDevice *device; + SDL_VideoData *videodata; // Initialize all variables that we clean on shutdown device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); @@ -140,6 +149,13 @@ static SDL_VideoDevice *Emscripten_CreateDevice(void) return NULL; } + videodata = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); + if (!videodata) { + return NULL; + } + videodata->window_map = SDL_CreateProperties(); + device->internal = videodata; + /* Firefox sends blur event which would otherwise prevent full screen * when the user clicks to allow full screen. * See https://bugzilla.mozilla.org/show_bug.cgi?id=1144964 @@ -156,12 +172,12 @@ static SDL_VideoDevice *Emscripten_CreateDevice(void) device->CreateSDLWindow = Emscripten_CreateWindow; device->SetWindowTitle = Emscripten_SetWindowTitle; - /*device->SetWindowIcon = Emscripten_SetWindowIcon; - device->SetWindowPosition = Emscripten_SetWindowPosition;*/ + /*device->SetWindowIcon = Emscripten_SetWindowIcon;*/ + device->SetWindowPosition = Emscripten_SetWindowPosition; device->SetWindowSize = Emscripten_SetWindowSize; - /*device->ShowWindow = Emscripten_ShowWindow; + device->ShowWindow = Emscripten_ShowWindow; device->HideWindow = Emscripten_HideWindow; - device->RaiseWindow = Emscripten_RaiseWindow; + /*device->RaiseWindow = Emscripten_RaiseWindow; device->MaximizeWindow = Emscripten_MaximizeWindow; device->MinimizeWindow = Emscripten_MinimizeWindow; device->RestoreWindow = Emscripten_RestoreWindow; @@ -169,6 +185,7 @@ static SDL_VideoDevice *Emscripten_CreateDevice(void) device->GetWindowSizeInPixels = Emscripten_GetWindowSizeInPixels; device->DestroyWindow = Emscripten_DestroyWindow; device->SetWindowFullscreen = Emscripten_SetWindowFullscreen; + device->SyncWindow = Emscripten_SyncWindow; device->CreateWindowFramebuffer = Emscripten_CreateWindowFramebuffer; device->UpdateWindowFramebuffer = Emscripten_UpdateWindowFramebuffer; @@ -186,6 +203,8 @@ static SDL_VideoDevice *Emscripten_CreateDevice(void) device->free = Emscripten_DeleteDevice; + device->device_caps = VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT; + Emscripten_ListenSystemTheme(); device->system_theme = Emscripten_GetSystemTheme(); @@ -456,9 +475,12 @@ EMSCRIPTEN_KEEPALIVE void requestFullscreenThroughSDL(SDL_Window *window) static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) { SDL_WindowData *wdata; + SDL_VideoData *videodata; double scaled_w, scaled_h; double css_w, css_h; const char *selector; + bool canvas_exists; + SDL_Rect rect; // Allocate window internal data wdata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData)); @@ -470,6 +492,124 @@ static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, if (!selector || !*selector) { selector = SDL_GetStringProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING, "#canvas"); } + + if (!*selector || selector[0] != '#') { + SDL_SetError("Canvas ID must begin with a '#' character."); + return false; + } + + // If an element with this id already exists, it should be verified that it is to an HTML5CanvasElement + canvas_exists = MAIN_THREAD_EM_ASM_INT({ + var id = UTF8ToString($0); + try + { + var element = document.querySelector(id); + if (element && element instanceof HTMLCanvasElement) { + return true; + } + } + catch(e) + { + // querySelector throws if the id isn't a valid selector + } + return false; + }, selector); + + rect.x = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_UNDEFINED); + if (SDL_WINDOWPOS_ISUNDEFINED(rect.x)) { + rect.x = 0; + } + + rect.y = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_UNDEFINED); + if (SDL_WINDOWPOS_ISUNDEFINED(rect.y)) { + rect.y = 0; + } + + // Offset position by parent's + if (window->parent) { + rect.x += window->parent->x; + rect.y += window->parent->y; + } + + rect.w = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, 0); + rect.h = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, 0); + + // Create the canvas if it doesn't exist. + // Assign a class to the element to signal that it was created by SDL3. + if (!canvas_exists) { + SDL_Window *parent = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, NULL); + const char *parent_id; + if (parent) { + SDL_WindowData *parent_window_data = parent->internal; + parent_id = parent_window_data->canvas_id; + } else { + parent_id = "body"; + } + if (SDL_WINDOWPOS_ISCENTERED(rect.x)) { + rect.x = MAIN_THREAD_EM_ASM_INT({ + var w = $0; + return window.innerWidth / 2 - w / 2; + }, rect.w); + } + if (SDL_WINDOWPOS_ISCENTERED(rect.y)) { + rect.y = MAIN_THREAD_EM_ASM_INT({ + var h = $0; + return window.innerHeight / 2 - h / 2; + }, rect.h); + } + canvas_exists = MAIN_THREAD_EM_ASM_INT({ + try + { + var id = UTF8ToString($0); + var rect = new Int32Array(Module.HEAP32.buffer, $1, 4); + var parent_id = UTF8ToString($2); + var canvas = document.querySelector(id); + if (canvas) { + // Element with this id is not a canvas + return false; + } + canvas = document.createElement("canvas"); + canvas.classList.add("SDL3_canvas"); + canvas.id = id.replace("#", ""); + canvas.oncontextmenu = (e) => { e.preventDefault(); }; + canvas.style.position = "absolute"; + canvas.style.left = `${rect[0]}px`; + canvas.style.top = `${rect[1]}px`; + canvas.width = rect[2]; + canvas.height = rect[3]; + var parent = document.querySelector(parent_id); + if (parent_id !== "body" && parent) { + parent.parentNode.append(canvas); + } else { + document.body.append(canvas); + } + return true; + } + catch(e) + { + // querySelector throws if id isn't a valid selector + } + return false; + }, selector, rect, parent_id); + } + + if (!canvas_exists) { + SDL_SetError("'%s' is not a valid selector or doesn't refer to an HTMLCanvasElement", selector); + return false; + } + + window->x = rect.x; + window->y = rect.y; + window->w = rect.w; + window->h = rect.h; + + // Ensure that there isn't another window that is using this canvas ID + videodata = _this->internal; + if (SDL_GetBooleanProperty(videodata->window_map, selector, false)) { + SDL_SetError("A window already exists that refers to canvas '%s'", selector); + return false; + } + wdata->canvas_id = SDL_strdup(selector); selector = SDL_GetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT); @@ -532,6 +672,14 @@ static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_SetStringProperty(window->props, SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING, wdata->canvas_id); SDL_SetStringProperty(window->props, SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING, wdata->keyboard_element); + if (!SDL_SetBooleanProperty(videodata->window_map, wdata->canvas_id, true)) { + return false; + } + + if (window->flags & SDL_WINDOW_HIDDEN) { + Emscripten_HideWindow(_this, window); + } + // Window has been successfully created return true; } @@ -570,14 +718,27 @@ static void Emscripten_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *data; + SDL_VideoData *videodata; if (window->internal) { data = window->internal; Emscripten_UnregisterEventHandlers(data); - // We can't destroy the canvas, so resize it to zero instead - emscripten_set_canvas_element_size(data->canvas_id, 0, 0); + MAIN_THREAD_EM_ASM({ + var id = UTF8ToString($0); + var canvas = document.querySelector(id); + if (canvas) { + if (canvas.classList.contains("SDL3_canvas")) { + canvas.remove(); + } else { + canvas.style.display = 'none'; + } + } + }, data->canvas_id); + + videodata = _this->internal; + SDL_SetBooleanProperty(videodata->window_map, data->canvas_id, false); SDL_free(data->canvas_id); SDL_free(data->keyboard_element); @@ -638,7 +799,124 @@ static SDL_FullscreenResult Emscripten_SetWindowFullscreen(SDL_VideoDevice *_thi static void Emscripten_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) { - emscripten_set_window_title(window->title); + SDL_VideoData *videodata = _this->internal; + if (!videodata->mainWindow) { + // Assume that the first window to have its title set is the main window + videodata->mainWindow = window; + } + if (videodata->mainWindow == window) { + emscripten_set_window_title(window->title); + } +} + +static bool Emscripten_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *window_data = window->internal; + int x, y; + if (window->parent) { + x = window->parent->x; + y = window->parent->y; + } else { + x = 0; + y = 0; + } + const bool success = MAIN_THREAD_EM_ASM_INT({ + try + { + var id = UTF8ToString($0); + var x = $1; + var y = $2; + var canvas = document.querySelector(id); + if (canvas) { + var style = window.getComputedStyle(canvas); + var position = style.getPropertyValue("position"); + if (position && style.position !== 'static') { + canvas.style.left = `${x}px`; + canvas.style.top = `${y}px`; + return true; + } + } + } + catch(e) + { + // querySelector throws if id is not a valid selector + } + return false; + }, window_data->canvas_id, window->pending.x + x, window->pending.y + y); + if (!success) { + SDL_SetError("Canvas is either not movable or doesn't exist"); + } + return success; } +static void Emscripten_SetWindowDisplay(SDL_Window *window, const char *display) +{ + SDL_WindowData *window_data = window->internal; + MAIN_THREAD_EM_ASM({ + var id = UTF8ToString($0); + var display = UTF8ToString($1); + try + { + let canvas = document.querySelector(id); + if (canvas) { + canvas.style.display = display; + } + } + catch(e) + { + // querySelector throws if id is not a valid selector + } + }, window_data->canvas_id, display); +} + +static void Emscripten_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + Emscripten_SetWindowDisplay(window, ""); +} + +static void Emscripten_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + Emscripten_SetWindowDisplay(window, "none"); +} + +static bool Emscripten_GetCanvasRect(const char *canvas_id, SDL_Rect *rect) +{ + return MAIN_THREAD_EM_ASM_INT({ + var id = UTF8ToString($0); + var array = new Uint32Array(Module.HEAP32.buffer, $1, 4); + try + { + let canvas = document.querySelector(id); + if (canvas) { + var rect = canvas.getBoundingClientRect(); + array[0] = rect.x; + array[1] = rect.y; + array[2] = rect.width; + array[3] = rect.height; + return true; + } + } + catch(e) + { + // querySelector throws if id is not a valid selector + } + return false; + }, canvas_id, rect); +} + +static bool Emscripten_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *window_data = window->internal; + SDL_Rect rect; + if (!Emscripten_GetCanvasRect(window_data->canvas_id, &rect)) { + SDL_SetError("Failed to find canvas element: %s", window_data->canvas_id); + return false; + } + + window->x = rect.x; + window->y = rect.y; + window->w = rect.w; + window->h = rect.h; + return true; +} #endif // SDL_VIDEO_DRIVER_EMSCRIPTEN diff --git a/src/video/emscripten/SDL_emscriptenvideo.h b/src/video/emscripten/SDL_emscriptenvideo.h index db4751ce88531..7d1367855f02b 100644 --- a/src/video/emscripten/SDL_emscriptenvideo.h +++ b/src/video/emscripten/SDL_emscriptenvideo.h @@ -50,6 +50,12 @@ struct SDL_WindowData bool mouse_focus_loss_pending; }; +struct SDL_VideoData +{ + SDL_PropertiesID window_map; + SDL_Window *mainWindow; +}; + bool Emscripten_ShouldSetSwapInterval(int interval); #endif // SDL_emscriptenvideo_h_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 164e2a142369a..96f21a466324b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -370,6 +370,7 @@ add_sdl_test_executable(testlocale NONINTERACTIVE SOURCES testlocale.c) add_sdl_test_executable(testlock NO_C90 SOURCES testlock.c) add_sdl_test_executable(testrwlock SOURCES testrwlock.c) add_sdl_test_executable(testmouse SOURCES testmouse.c) +add_sdl_test_executable(testmultiwindow SOURCES testmultiwindow.c) add_sdl_test_executable(testoverlay NEEDS_RESOURCES TESTUTILS SOURCES testoverlay.c) add_sdl_test_executable(testplatform NONINTERACTIVE SOURCES testplatform.c) diff --git a/test/testmultiwindow.c b/test/testmultiwindow.c new file mode 100644 index 0000000000000..e407028fead14 --- /dev/null +++ b/test/testmultiwindow.c @@ -0,0 +1,314 @@ +/* + 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. +*/ + +/* Program that creates a window at the global position of the mouse */ + +#define SDL_MAIN_USE_CALLBACKS 1 +#include +#include +#include + +#define VELOCITY 10 +#define MAX_BOUNCE 5 + +typedef struct { + SDL_Window *window; + SDL_Renderer *renderer; + SDL_Point velocity; + SDL_Color color; + Uint32 bounces; +} TestWindow; + +static bool UpdateTestWindow(TestWindow *testWindow, SDL_Rect bounds) { + SDL_Window *window = testWindow->window; + + int x, y; + SDL_GetWindowPosition(window, &x, &y); + int w, h; + SDL_GetWindowSize(window, &w, &h); + + x += testWindow->velocity.x; + y += testWindow->velocity.y; + + if (x < bounds.x) { + testWindow->velocity.x = VELOCITY; + ++testWindow->bounces; + } else if (x + w > bounds.x + bounds.w) { + testWindow->velocity.x = -VELOCITY; + ++testWindow->bounces; + } + x = SDL_clamp(x, bounds.x, bounds.x + bounds.w - w); + + if (y < bounds.y) { + testWindow->velocity.y = VELOCITY; + ++testWindow->bounces; + } else if (y + h > bounds.y + bounds.h) { + testWindow->velocity.y = -VELOCITY; + ++testWindow->bounces; + } + y = SDL_clamp(y, bounds.y, bounds.y + bounds.h - h); + + SDL_SetWindowPosition(window, x, y); + return testWindow->bounces < MAX_BOUNCE; +} + +static void DestroyTestWindow(TestWindow *testWindow) +{ + SDL_DestroyRenderer(testWindow->renderer); + SDL_DestroyWindow(testWindow->window); +} + +static void RenderTestWindow(TestWindow *testWindow) +{ + SDL_Renderer *renderer = testWindow->renderer; + SDL_Color color = testWindow->color; + + if (renderer) { + SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); + SDL_RenderClear(renderer); + SDL_RenderPresent(renderer); + } +} + +#define MAX_WINDOWS 8 + +typedef struct { + SDLTest_CommonState *state; + TestWindow *testWindows[MAX_WINDOWS]; + Uint64 lastCreate; + Uint64 lastUpdate; +} TestState; + +static void DestroyTestState(TestState *testState) +{ + int i; + SDLTest_CommonDestroyState(testState->state); + for (i = 0; i < MAX_WINDOWS; ++i) { + if (testState->testWindows[i]) { + DestroyTestWindow(testState->testWindows[i]); + } + } +} + +static TestWindow *createTestWindowAtMousePosition(SDLTest_CommonState *state) +{ + static int windowId = 0; + + TestWindow *testWindow; + + char title[1024]; + SDL_Rect rect; + SDL_Rect bounds; + SDL_PropertiesID props; + float x, y; + + SDL_GetDisplayUsableBounds(state->displayID, &bounds); + + SDL_snprintf(title, SDL_arraysize(title), "#window%d", ++windowId); + + SDL_GetGlobalMouseState(&x, &y); + rect.x = (int)SDL_ceilf(x); + rect.y = (int)SDL_ceilf(y); + + rect.w = SDL_rand(state->window_w); + rect.w = SDL_clamp(rect.w, 320, rect.w); + rect.h = SDL_rand(state->window_h); + rect.h = SDL_clamp(rect.w, 240, rect.h); + if (state->auto_scale_content) { + float scale = SDL_GetDisplayContentScale(state->displayID); + rect.w = (int)SDL_ceilf(rect.w * scale); + rect.h = (int)SDL_ceilf(rect.h * scale); + } + + /* Don't make a window if it wouldn't be visible in the display */ + if (!SDL_HasRectIntersection(&rect, &bounds)) { + return NULL; + } + + props = SDL_CreateProperties(); + SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title); + SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING, title); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, rect.x); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, rect.y); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, rect.w); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, rect.h); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, state->window_flags); + + testWindow = SDL_calloc(1, sizeof(TestWindow)); + if (!testWindow) { + return NULL; + } + testWindow->window = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); + if (!testWindow->window) { + SDL_Log("Couldn't create window: %s",SDL_GetError()); + return NULL; + } + if (state->window_minW || state->window_minH) { + SDL_SetWindowMinimumSize(testWindow->window, state->window_minW, state->window_minH); + } + if (state->window_maxW || state->window_maxH) { + SDL_SetWindowMaximumSize(testWindow->window, state->window_maxW, state->window_maxH); + } + if (state->window_min_aspect != 0.f || state->window_max_aspect != 0.f) { + SDL_SetWindowAspectRatio(testWindow->window, state->window_min_aspect, state->window_max_aspect); + } + if (!SDL_RectEmpty(&state->confine)) { + SDL_SetWindowMouseRect(testWindow->window, &state->confine); + } + + testWindow->renderer = SDL_CreateRenderer(testWindow->window, state->renderdriver); + if (!testWindow->renderer) { + SDL_Log("Couldn't create renderer: %s", SDL_GetError()); + return NULL; + } + if (state->logical_w == 0 || state->logical_h == 0) { + state->logical_w = state->window_w; + state->logical_h = state->window_h; + } + if (state->render_vsync) { + SDL_SetRenderVSync(testWindow->renderer, state->render_vsync); + } + if (!SDL_SetRenderLogicalPresentation(testWindow->renderer, state->logical_w, state->logical_h, state->logical_presentation)) { + SDL_Log("Couldn't set logical presentation: %s", SDL_GetError()); + return NULL; + } + if (state->scale != 0.0f) { + SDL_SetRenderScale(testWindow->renderer, state->scale, state->scale); + } + + testWindow->color.r = SDL_rand(256); + testWindow->color.g = SDL_rand(256); + testWindow->color.b = SDL_rand(256); + testWindow->color.a = 255; + + testWindow->velocity.x = VELOCITY * (SDL_rand(2) == 0 ? -1 : 1); + testWindow->velocity.y = VELOCITY * (SDL_rand(2) == 0 ? -1 : 1); + + SDL_ShowWindow(testWindow->window); + return testWindow; +} + +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ +#ifdef SDL_PLATFORM_EMSCRIPTEN + SDL_SetHint(SDL_HINT_VIDEO_SYNC_WINDOW_OPERATIONS, "1"); +#endif + + TestState *testState = SDL_calloc(1, sizeof(TestState)); + if (!testState) { + return SDL_APP_FAILURE; + } + + testState->state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); + if (!testState->state) { + return SDL_APP_FAILURE; + } + + testState->state->num_windows = 1; + + if (!SDLTest_CommonInit(testState->state)) { + return SDL_APP_FAILURE; + } + + testState->testWindows[0] = createTestWindowAtMousePosition(testState->state); + if (!testState->testWindows[0]) { + return SDL_APP_FAILURE; + } + testState->lastCreate = SDL_GetTicks(); + + *appstate = testState; + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + TestState *testState = appstate; + + switch (event->type) { + case SDL_EVENT_QUIT: + return SDL_APP_SUCCESS; + case SDL_EVENT_WINDOW_CLOSE_REQUESTED:{ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window == testState->state->windows[0]) { + return SDL_APP_SUCCESS; + } + }break; + default: + break; + } + + return SDLTest_CommonEventMainCallbacks(testState->state, event); +} + +SDL_AppResult SDL_AppIterate(void *appstate) +{ + TestState *testState = appstate; + SDL_Renderer *renderer = testState->state->renderers[0]; + const Uint64 now = SDL_GetTicks(); + int i; + float textY; + + for (i = 0; i < MAX_WINDOWS; ++i) { + if (testState->testWindows[i]) { + RenderTestWindow(testState->testWindows[i]); + } + } + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + textY = 0; + for (i = 0; i < MAX_WINDOWS; ++i) { + if (!testState->testWindows[i]) { + continue; + } + int x, y; + SDL_GetWindowPosition(testState->testWindows[i]->window, &x, &y); + int w, h; + SDL_GetWindowSize(testState->testWindows[i]->window, &w, &h); + SDL_RenderDebugTextFormat(renderer, 0, textY, "#%d Window's Position: %d %d; Size: %d %d", i+1, x, y, w, h); + textY += SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE; + } + SDL_RenderPresent(renderer); + + if (now - testState->lastUpdate > 16) { + SDL_Rect bounds; + SDL_GetDisplayUsableBounds(testState->state->displayID, &bounds); + for (i = 0; i < MAX_WINDOWS; ++i) { + if (testState->testWindows[i] && !UpdateTestWindow(testState->testWindows[i], bounds)) { + DestroyTestWindow(testState->testWindows[i]); + testState->testWindows[i] = NULL; + } + } + testState->lastUpdate = now; + } + + if (now - testState->lastCreate > 1000) { + for (i = 0; i < MAX_WINDOWS; ++i) { + if (!testState->testWindows[i]) { + testState->testWindows[i] = createTestWindowAtMousePosition(testState->state); + break; + } + } + testState->lastCreate = now; + } + return SDL_APP_CONTINUE; +} + +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + TestState *testState = appstate; + + if (testState) { + DestroyTestState(testState); + } +} \ No newline at end of file diff --git a/test/testpopup.c b/test/testpopup.c index 786f03c20642c..b151804a66d87 100644 --- a/test/testpopup.c +++ b/test/testpopup.c @@ -196,6 +196,7 @@ static void loop(void) if (SDL_GetTicks() > tooltip_timer) { if (!tooltip.win) { create_popup(&tooltip, false); + tooltip_timer = SDL_GetTicks(); } } @@ -211,6 +212,10 @@ static void loop(void) const SDL_Color *c = &colors[i % SDL_arraysize(colors)]; SDL_Renderer *renderer = menus[i].renderer; +#ifdef SDL_PLATFORM_EMSCRIPTEN + SDL_SyncWindow(menus[i].win); +#endif + SDL_SetRenderDrawColor(renderer, c->r, c->g, c->b, c->a); SDL_RenderClear(renderer); SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); @@ -242,6 +247,10 @@ int main(int argc, char *argv[]) { int i; +#ifdef SDL_PLATFORM_EMSCRIPTEN + SDL_SetHint(SDL_HINT_VIDEO_SYNC_WINDOW_OPERATIONS, "1"); +#endif + /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); if (!state) { diff --git a/test/testscale.c b/test/testscale.c index 766a2af2a0a74..5112ec15348e3 100644 --- a/test/testscale.c +++ b/test/testscale.c @@ -132,6 +132,11 @@ int main(int argc, char *argv[]) quit(2); } SDL_GetTextureSize(drawstate->sprite, &drawstate->sprite_rect.w, &drawstate->sprite_rect.h); + + /* Ensure each window will have a different scale to make each window have a unique display */ + drawstate->sprite_rect.w *= i * 2.f; + drawstate->sprite_rect.h *= i * 2.f; + SDL_SetTextureScaleMode(drawstate->sprite, SDL_SCALEMODE_PIXELART); drawstate->scale_direction = 1; }