Преглед на файлове

Emscripten: Support Custom Message Boxes (#12583)

* Allow custom message boxes with colors and multiple buttons to work if Asyncify is enabled
* Keep old functionality of using alert when Asyncify is not available
* Update testmessage to allow for setting random colors as the color scheme of the message box
Temdog007 преди 4 седмици
родител
ревизия
581b614291
променени са 3 файла, в които са добавени 224 реда и са изтрити 42 реда
  1. 1 17
      src/video/SDL_video.c
  2. 172 1
      src/video/emscripten/SDL_emscriptenvideo.c
  3. 51 24
      test/testmessage.c

+ 1 - 17
src/video/SDL_video.c

@@ -5724,23 +5724,7 @@ bool SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID)
 
 bool SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags flags, const char *title, const char *message, SDL_Window *window)
 {
-#ifdef SDL_PLATFORM_EMSCRIPTEN
-    // !!! FIXME: propose a browser API for this, get this #ifdef out of here?
-    /* Web browsers don't (currently) have an API for a custom message box
-       that can block, but for the most common case (SDL_ShowSimpleMessageBox),
-       we can use the standard Javascript alert() function. */
-    if (!title) {
-        title = "";
-    }
-    if (!message) {
-        message = "";
-    }
-    EM_ASM({
-        alert(UTF8ToString($0) + "\n\n" + UTF8ToString($1));
-    },
-            title, message);
-    return true;
-#elif defined(SDL_PLATFORM_3DS)
+#if defined(SDL_PLATFORM_3DS)
     errorConf errCnf;
     bool hasGpuRight;
 

+ 172 - 1
src/video/emscripten/SDL_emscriptenvideo.c

@@ -192,10 +192,181 @@ static SDL_VideoDevice *Emscripten_CreateDevice(void)
     return device;
 }
 
+static bool Emscripten_ShowMessagebox(const SDL_MessageBoxData *messageboxdata, int *buttonID) {
+    if (emscripten_has_asyncify() && SDL_GetHintBoolean(SDL_HINT_EMSCRIPTEN_ASYNCIFY, true)) {
+        char dialog_background[32];
+        char dialog_color[32];
+        char button_border[32];
+        char button_background[32];
+        char button_hovered[32];
+
+        if (messageboxdata->colorScheme) {
+            SDL_MessageBoxColor color = messageboxdata->colorScheme->colors[SDL_MESSAGEBOX_COLOR_BACKGROUND];
+            SDL_snprintf(dialog_background, sizeof(dialog_background), "rgb(%u, %u, %u)", color.r, color.g, color.b);
+
+            color = messageboxdata->colorScheme->colors[SDL_MESSAGEBOX_COLOR_TEXT];
+            SDL_snprintf(dialog_color, sizeof(dialog_color), "rgb(%u, %u, %u)", color.r, color.g, color.b);
+
+            color = messageboxdata->colorScheme->colors[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER];
+            SDL_snprintf(button_border, sizeof(button_border), "rgb(%u, %u, %u)", color.r, color.g, color.b);
+
+            color = messageboxdata->colorScheme->colors[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND];
+            SDL_snprintf(button_background, sizeof(button_background), "rgb(%u, %u, %u)", color.r, color.g, color.b);
+
+            color = messageboxdata->colorScheme->colors[SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED];
+            SDL_snprintf(button_hovered, sizeof(button_hovered), "rgb(%u, %u, %u)", color.r, color.g, color.b);
+        } else {
+            SDL_zero(dialog_background);
+            SDL_zero(dialog_color);
+            SDL_zero(button_border);
+            SDL_zero(button_background);
+            SDL_zero(button_hovered);
+        }
+
+        // TODO: Handle parent window when multiple windows can be added in Emscripten builds
+        char dialog_id[64];
+        SDL_snprintf(dialog_id, sizeof(dialog_id), "SDL3_messagebox_%u", SDL_rand_bits());
+        EM_ASM({
+            var title = UTF8ToString($0);
+            var message = UTF8ToString($1);
+            var background = UTF8ToString($2);
+            var color = UTF8ToString($3);
+            var id = UTF8ToString($4);
+
+            // Dialogs are always put in the front of the DOM
+            var dialog = document.createElement("dialog");
+            // Set class to allow for CSS selectors
+            dialog.classList.add("SDL3_messagebox");
+            dialog.id = id;
+            dialog.style.color = color;
+            dialog.style.backgroundColor = background;
+            document.body.append(dialog);
+
+            var h1 = document.createElement("h1");
+            h1.innerText = title;
+            dialog.append(h1);
+
+            var p = document.createElement("p");
+            p.innerText = message;
+            dialog.append(p);
+
+            dialog.showModal();
+        }, messageboxdata->title, messageboxdata->message, dialog_background, dialog_color, dialog_id);
+
+        int i;
+        for (i = 0; i < messageboxdata->numbuttons; ++i) {
+            SDL_MessageBoxButtonData button = messageboxdata->buttons[i];
+
+            const int created = EM_ASM_INT({
+                    var dialog_id = UTF8ToString($0);
+                    var text = UTF8ToString($1);
+                    var responseId = $2;
+                    var clickOnReturn = $3;
+                    var clickOnEscape = $4;
+                    var border = UTF8ToString($5);
+                    var background = UTF8ToString($6);
+                    var hovered = UTF8ToString($7);
+
+                    var dialog = document.getElementById(dialog_id);
+                    if (!dialog) {
+                        return false;
+                    }
+
+                    var button = document.createElement("button");
+                    button.innerText = text;
+                    button.style.borderColor = border;
+                    button.style.backgroundColor = background;
+
+                    dialog.addEventListener('keydown', function(e) {
+                        if (clickOnReturn && e.key === "Enter") {
+                            e.preventDefault();
+                            button.click();
+                        } else if (clickOnEscape && e.key === "Escape") {
+                            e.preventDefault();
+                            button.click();
+                        }
+                    });
+                    dialog.addEventListener('cancel', function(e){
+                        e.preventDefault();
+                    });
+
+                    button.onmouseenter = function(e){
+                        button.style.backgroundColor = hovered;
+                    };
+                    button.onmouseleave = function(e){
+                        button.style.backgroundColor = background;
+                    };
+                    button.onclick = function(e) {
+                        dialog.close(responseId);
+                    };
+
+                    dialog.append(button);
+                    return true;
+                },
+                dialog_id,
+                button.text,
+                button.buttonID,
+                button.flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+                button.flags & SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+                button_border,
+                button_background,
+                button_hovered
+            );
+
+            if (!created) {
+                return false;
+            }
+        }
+
+        while (true) {
+            // give back control to browser for screen refresh
+            emscripten_sleep(0);
+
+            const int dialog_open = EM_ASM_INT({
+                var dialog_id = UTF8ToString($0);
+
+                var dialog = document.getElementById(dialog_id);
+                if (!dialog) {
+                    return false;
+                }
+                return dialog.open;
+            }, dialog_id);
+
+            if (dialog_open) {
+                continue;
+            }
+
+            *buttonID = EM_ASM_INT({
+                var dialog_id = UTF8ToString($0);
+                var dialog = document.getElementById(dialog_id);
+                if (!dialog) {
+                    return 0;
+                }
+                try
+                {
+                    return parseInt(dialog.returnValue);
+                }
+                catch(e)
+                {
+                    return 0;
+                }
+            }, dialog_id);
+            break;
+        }
+
+    } else {
+        // Cannot add elements to DOM and block without Asyncify. So, fall back to the alert function.
+        EM_ASM({
+            alert(UTF8ToString($0) + "\n\n" + UTF8ToString($1));
+        }, messageboxdata->title, messageboxdata->message);
+    }
+    return true;
+}
+
 VideoBootStrap Emscripten_bootstrap = {
     EMSCRIPTENVID_DRIVER_NAME, "SDL emscripten video driver",
     Emscripten_CreateDevice,
-    NULL, // no ShowMessageBox implementation
+    Emscripten_ShowMessagebox,
     false
 };
 

+ 51 - 24
test/testmessage.c

@@ -36,6 +36,7 @@ quit(int rc)
 static int SDLCALL
 button_messagebox(void *eventNumber)
 {
+    int i;
     const SDL_MessageBoxButtonData buttons[] = {
         { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
           0,
@@ -43,52 +44,78 @@ button_messagebox(void *eventNumber)
         { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
           1,
           "Cancel" },
+        { 0,
+          2,
+          "Retry" }
     };
-
     SDL_MessageBoxData data = {
         SDL_MESSAGEBOX_INFORMATION,
         NULL, /* no parent window */
         "Custom MessageBox",
         "This is a custom messagebox",
-        2,
+        sizeof(buttons) / sizeof(SDL_MessageBoxButtonData),
         NULL, /* buttons */
         NULL  /* Default color scheme */
     };
 
-    int button = -1;
-    int success = 0;
-    data.buttons = buttons;
-    if (eventNumber) {
-        data.message = "This is a custom messagebox from a background thread.";
-    }
+    for (i = 0; ; ++i) {
+        SDL_MessageBoxColorScheme colorScheme;
+        if (i != 0) {
+            int j;
+            for (j = 0; j < SDL_MESSAGEBOX_COLOR_COUNT; ++j) {
+                colorScheme.colors[j].r = SDL_rand(256);
+                colorScheme.colors[j].g = SDL_rand(256);
+                colorScheme.colors[j].b = SDL_rand(256);
+            }
+            data.colorScheme = &colorScheme;
+        } else {
+            data.colorScheme = NULL;
+        }
+
+        int button = -1;
+        data.buttons = buttons;
+        if (eventNumber) {
+            data.message = "This is a custom messagebox from a background thread.";
+        }
+
+        if (!SDL_ShowMessageBox(&data, &button)) {
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error Presenting MessageBox: %s", SDL_GetError());
+            if (eventNumber) {
+                SDL_Event event;
+                event.type = (Uint32)(intptr_t)eventNumber;
+                SDL_PushEvent(&event);
+                return 1;
+            } else {
+                quit(2);
+            }
+        }
+
+        const char* text;
+        if (button == 1) {
+            text = "Cancel";
+        } else if (button == 2) {
+            text = "Retry";
+        } else {
+            text = "OK";
+        }
+        SDL_Log("Pressed button: %d, %s", button, button == -1 ? "[closed]" : text);
 
-    success = SDL_ShowMessageBox(&data, &button);
-    if (success == -1) {
-        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error Presenting MessageBox: %s", SDL_GetError());
         if (eventNumber) {
             SDL_Event event;
             event.type = (Uint32)(intptr_t)eventNumber;
             SDL_PushEvent(&event);
-            return 1;
-        } else {
-            quit(2);
         }
-    }
-    SDL_Log("Pressed button: %d, %s", button, button == -1 ? "[closed]" : button == 1 ? "Cancel"
-                                                                                        : "OK");
 
-    if (eventNumber) {
-        SDL_Event event;
-        event.type = (Uint32)(intptr_t)eventNumber;
-        SDL_PushEvent(&event);
+        if (button == 2) {
+            continue;
+        }
+        return 0;
     }
-
-    return 0;
 }
 
 int main(int argc, char *argv[])
 {
-    int success;
+    bool success;
     SDLTest_CommonState *state;
 
     /* Initialize test framework */