Bladeren bron

emscripten: Another attempt at optionally having the canvas use whole window.

Fixes #11949.
Ryan C. Gordon 5 dagen geleden
bovenliggende
commit
24b47814f8

+ 22 - 0
include/SDL3/SDL_hints.h

@@ -753,6 +753,28 @@ extern "C" {
  */
 #define SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT "SDL_EMSCRIPTEN_KEYBOARD_ELEMENT"
 
+/**
+ * Dictate that newly-created windows will fill the whole browser window.
+ *
+ * The canvas element fills the entire document. Resize events will be
+ * generated as the browser window is resized, as that will adjust the canvas
+ * size as well. The canvas will cover anything else on the page, including
+ * any controls provided by Emscripten in its generated HTML file. Often times
+ * this is desirable for a browser-based game, but it means several things that
+ * we expect of an SDL window on other platforms might not work as expected,
+ * such as minimum window sizes and aspect ratios.
+ *
+ * This hint overrides SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_FILL_DOCUMENT_BOOLEAN
+ * properties when creating an SDL window.
+ *
+ * This hint only applies to the emscripten platform.
+ *
+ * This hint should be set before creating a window.
+ *
+ * \since This hint is available since SDL 3.4.0.
+ */
+#define SDL_HINT_EMSCRIPTEN_FILL_DOCUMENT "SDL_EMSCRIPTEN_FILL_DOCUMENT"
+
 /**
  * A variable that controls whether the on-screen keyboard should be shown
  * when text input is active.

+ 14 - 0
include/SDL3/SDL_video.h

@@ -1353,6 +1353,15 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren
  *
  * - `SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING`: the id given to the
  *   canvas element. This should start with a '#' sign
+ * - `SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_FILL_DOCUMENT_BOOLEAN`: true to make
+ *   the canvas element fill the entire document. Resize events will be
+ *   generated as the browser window is resized, as that will adjust the
+ *   canvas size as well. The canvas will cover anything else on the page,
+ *   including any controls provided by Emscripten in its generated HTML
+ *   file. Often times this is desirable for a browser-based game, but it
+ *   means several things that we expect of an SDL window on other platforms
+ *   might not work as expected, such as minimum window sizes and aspect
+ *   ratios. Default false.
  * - `SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING`: override the
  *   binding element for keyboard inputs for this canvas. The variable can be
  *   one of:
@@ -1426,6 +1435,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindowWithProperties(SDL_Prop
 #define SDL_PROP_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER     "SDL.window.create.win32.pixel_format_hwnd"
 #define SDL_PROP_WINDOW_CREATE_X11_WINDOW_NUMBER                   "SDL.window.create.x11.window"
 #define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING         "SDL.window.create.emscripten.canvas_id"
+#define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_FILL_DOCUMENT_BOOLEAN    "SDL.window.create.emscripten.fill_document"
 #define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING  "SDL.window.create.emscripten.keyboard_element"
 
 /**
@@ -1595,6 +1605,9 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowParent(SDL_Window *window)
  *
  * - `SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING`: the id the canvas element
  *   will have
+ * - `SDL_PROP_WINDOW_EMSCRIPTEN_FILL_DOCUMENT_BOOLEAN`: true if the
+ *   canvas is set to consume the entire browser window, bypassing some SDL
+ *   window functionality.
  * - `SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING`: the keyboard
  *   element that associates keyboard events to this window
  *
@@ -1644,6 +1657,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window
 #define SDL_PROP_WINDOW_X11_SCREEN_NUMBER                           "SDL.window.x11.screen"
 #define SDL_PROP_WINDOW_X11_WINDOW_NUMBER                           "SDL.window.x11.window"
 #define SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING                 "SDL.window.emscripten.canvas_id"
+#define SDL_PROP_WINDOW_EMSCRIPTEN_FILL_DOCUMENT_BOOLEAN            "SDL.window.emscripten.fill_document"
 #define SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING          "SDL.window.emscripten.keyboard_element"
 
 /**

+ 22 - 15
src/video/emscripten/SDL_emscriptenevents.c

@@ -505,24 +505,31 @@ static EM_BOOL Emscripten_HandleFullscreenChangeGlobal(int eventType, const Emsc
 static EM_BOOL Emscripten_HandleResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData)
 {
     SDL_WindowData *window_data = userData;
-    bool force = false;
-
-    // update pixel ratio
-    if (window_data->window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
-        if (window_data->pixel_ratio != emscripten_get_device_pixel_ratio()) {
-            window_data->pixel_ratio = emscripten_get_device_pixel_ratio();
-            force = true;
-        }
-    }
 
     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) {
-            double w = window_data->window->w;
-            double h = window_data->window->h;
+        bool force = false;
+
+        // update pixel ratio
+        if (window_data->window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
+            if (window_data->pixel_ratio != emscripten_get_device_pixel_ratio()) {
+                window_data->pixel_ratio = emscripten_get_device_pixel_ratio();
+                force = true;
+            }
+        }
 
-            if (window_data->external_size) {
-                emscripten_get_element_css_size(window_data->canvas_id, &w, &h);
+        if (window_data->fill_document || (window_data->window->flags & SDL_WINDOW_RESIZABLE)) {
+            double w, h;
+            if (window_data->fill_document) {
+                w = (double) uiEvent->windowInnerWidth;
+                h = (double) uiEvent->windowInnerHeight;
+            } else {
+                SDL_assert(window_data->window->flags & SDL_WINDOW_RESIZABLE);
+                w = window_data->window->w;
+                h = window_data->window->h;
+                // this will only work if the canvas size is set through css
+                if (window_data->external_size) {
+                    emscripten_get_element_css_size(window_data->canvas_id, &w, &h);
+                }
             }
 
             emscripten_set_canvas_element_size(window_data->canvas_id, SDL_lroundf(w * window_data->pixel_ratio), SDL_lroundf(h * window_data->pixel_ratio));

+ 84 - 16
src/video/emscripten/SDL_emscriptenvideo.c

@@ -478,6 +478,12 @@ static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window,
     }
     wdata->keyboard_element = SDL_strdup(selector);
 
+    if (SDL_GetHint(SDL_HINT_EMSCRIPTEN_FILL_DOCUMENT)) {
+        wdata->fill_document = SDL_GetHintBoolean(SDL_HINT_EMSCRIPTEN_FILL_DOCUMENT, false);
+    } else {
+        wdata->fill_document = SDL_GetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_FILL_DOCUMENT_BOOLEAN, false);
+    }
+
     if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
         wdata->pixel_ratio = emscripten_get_device_pixel_ratio();
     } else {
@@ -492,19 +498,56 @@ static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window,
     emscripten_get_element_css_size(wdata->canvas_id, &css_w, &css_h);
 
     wdata->external_size = SDL_floor(css_w) != 1 || SDL_floor(css_h) != 1;
+    if (wdata->external_size) {
+        wdata->fill_document = false;  // can't be resizable if something else is controlling it.
+    }
 
-    if ((window->flags & SDL_WINDOW_RESIZABLE) && wdata->external_size) {
-        // external css has resized us
-        scaled_w = css_w * wdata->pixel_ratio;
-        scaled_h = css_h * wdata->pixel_ratio;
+    // fill_document takes up the entire page and resizes as the browser window resizes.
+    if (wdata->fill_document) {
+        const int w = MAIN_THREAD_EM_ASM_INT({ return window.innerWidth; });
+        const int h = MAIN_THREAD_EM_ASM_INT({ return window.innerHeight; });
 
-        SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, SDL_lroundf(css_w), SDL_lroundf(css_h));
-    }
-    emscripten_set_canvas_element_size(wdata->canvas_id, SDL_lroundf(scaled_w), SDL_lroundf(scaled_h));
+        scaled_w = w * wdata->pixel_ratio;
+        scaled_h = h * wdata->pixel_ratio;
+
+        MAIN_THREAD_EM_ASM({
+            var canvas = document.querySelector(UTF8ToString($0));
+
+            // hide everything on the page that isn't the canvas.
+            var div = document.createElement('div');
+            div.id = 'SDL3_fill_document_background_elements';
 
-    // if the size is not being controlled by css, we need to scale down for hidpi
-    if (!wdata->external_size) {
+            div.SDL3_canvas = canvas;
+            div.SDL3_canvas_parent = canvas.parentNode;
+            div.SDL3_canvas_nextsib = canvas.nextSibling;
+
+            var children = Array.from(document.body.children);
+            for (var child of children) {
+                div.appendChild(child);
+            }
+            document.body.appendChild(div);
+            div.style.display = 'none';
+            document.body.appendChild(canvas);
+            canvas.style.position = 'fixed';
+            canvas.style.top = '0';
+            canvas.style.left = '0';
+        }, wdata->canvas_id);
+
+        emscripten_set_canvas_element_size(wdata->canvas_id, SDL_lroundf(scaled_w), SDL_lroundf(scaled_h));
+
+        // set_canvas_size unsets this
         if (wdata->pixel_ratio != 1.0f) {
+            emscripten_set_element_css_size(wdata->canvas_id, w, h);
+        }
+
+        // force the event to trigger, so pixel ratio changes can be handled
+        window->w = window->h = 0;
+        SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, SDL_lroundf(w), SDL_lroundf(h));
+    } else {
+        emscripten_set_canvas_element_size(wdata->canvas_id, SDL_lroundf(scaled_w), SDL_lroundf(scaled_h));
+
+        // if the size is not being controlled by css, we need to scale down for hidpi
+        if (!wdata->external_size && (wdata->pixel_ratio != 1.0f)) {
             // scale canvas down
             emscripten_set_element_css_size(wdata->canvas_id, window->w, window->h);
         }
@@ -521,16 +564,17 @@ static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window,
 
     Emscripten_RegisterEventHandlers(wdata);
 
-    // disable the emscripten "fullscreen" button.
+    // Make the emscripten "fullscreen" button go through SDL.
     MAIN_THREAD_EM_ASM({
         Module['requestFullscreen'] = function(lockPointer, resizeCanvas) {
             _requestFullscreenThroughSDL($0);
         };
     }, window);
 
-    // Ensure canvas_id and keyboard_element are added to the window's properties
+    // Ensure various things are added to the window's properties
     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);
+    SDL_SetBooleanProperty(window->props, SDL_PROP_WINDOW_EMSCRIPTEN_FILL_DOCUMENT_BOOLEAN, wdata->fill_document);
 
     // Window has been successfully created
     return true;
@@ -538,10 +582,12 @@ static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window,
 
 static void Emscripten_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window)
 {
-    SDL_WindowData *data;
-
     if (window->internal) {
-        data = window->internal;
+        SDL_WindowData *data = window->internal;
+        if (data->fill_document) {
+            return;  // canvas size is being dictated by the browser window size, refuse request.
+        }
+
         // update pixel ratio
         if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
             data->pixel_ratio = emscripten_get_device_pixel_ratio();
@@ -578,6 +624,9 @@ static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
 
         // We can't destroy the canvas, so resize it to zero instead
         emscripten_set_canvas_element_size(data->canvas_id, 0, 0);
+        if (data->pixel_ratio != 1.0f) {
+            emscripten_set_element_css_size(data->canvas_id, 1, 1);
+        }
         SDL_free(data->canvas_id);
 
         SDL_free(data->keyboard_element);
@@ -586,8 +635,27 @@ static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
         window->internal = NULL;
     }
 
-    // just ignore clicks on the fullscreen button while there's no SDL window.
-    MAIN_THREAD_EM_ASM({ Module['requestFullscreen'] = function(lockPointer, resizeCanvas) {}; });
+    MAIN_THREAD_EM_ASM({
+        // just ignore clicks on the fullscreen button while there's no SDL window.
+        Module['requestFullscreen'] = function(lockPointer, resizeCanvas) {};
+
+        // if we had previously hidden everything behind a fill_document window, put it back.
+        var div = document.getElementById('SDL3_fill_document_background_elements');
+        if (div) {
+            if (div.SDL3_canvas_nextsib) {
+                div.SDL3_canvas_parent.insertBefore(div.SDL3_canvas, div.SDL3_canvas_nextsib);
+            } else {
+                div.SDL3_canvas_parent.appendChild(div.SDL3_canvas);
+            }
+            while (div.firstChild) {
+                document.body.insertBefore(div.firstChild, div);
+            }
+            div.SDL3_canvas.style.position = undefined;
+            div.SDL3_canvas.style.top = undefined;
+            div.SDL3_canvas.style.left = undefined;
+            div.remove();
+        }
+    });
 }
 
 static SDL_FullscreenResult Emscripten_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen)

+ 2 - 0
src/video/emscripten/SDL_emscriptenvideo.h

@@ -38,6 +38,8 @@ struct SDL_WindowData
     char *canvas_id;
     char *keyboard_element;
 
+    bool fill_document;
+
     float pixel_ratio;
 
     bool external_size;