Skip to content

Commit 581b614

Browse files
authored
Emscripten: Support Custom Message Boxes (libsdl-org#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
1 parent 54f5b73 commit 581b614

File tree

3 files changed

+224
-42
lines changed

3 files changed

+224
-42
lines changed

src/video/SDL_video.c

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5724,23 +5724,7 @@ bool SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID)
57245724

57255725
bool SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags flags, const char *title, const char *message, SDL_Window *window)
57265726
{
5727-
#ifdef SDL_PLATFORM_EMSCRIPTEN
5728-
// !!! FIXME: propose a browser API for this, get this #ifdef out of here?
5729-
/* Web browsers don't (currently) have an API for a custom message box
5730-
that can block, but for the most common case (SDL_ShowSimpleMessageBox),
5731-
we can use the standard Javascript alert() function. */
5732-
if (!title) {
5733-
title = "";
5734-
}
5735-
if (!message) {
5736-
message = "";
5737-
}
5738-
EM_ASM({
5739-
alert(UTF8ToString($0) + "\n\n" + UTF8ToString($1));
5740-
},
5741-
title, message);
5742-
return true;
5743-
#elif defined(SDL_PLATFORM_3DS)
5727+
#if defined(SDL_PLATFORM_3DS)
57445728
errorConf errCnf;
57455729
bool hasGpuRight;
57465730

src/video/emscripten/SDL_emscriptenvideo.c

Lines changed: 172 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,181 @@ static SDL_VideoDevice *Emscripten_CreateDevice(void)
192192
return device;
193193
}
194194

195+
static bool Emscripten_ShowMessagebox(const SDL_MessageBoxData *messageboxdata, int *buttonID) {
196+
if (emscripten_has_asyncify() && SDL_GetHintBoolean(SDL_HINT_EMSCRIPTEN_ASYNCIFY, true)) {
197+
char dialog_background[32];
198+
char dialog_color[32];
199+
char button_border[32];
200+
char button_background[32];
201+
char button_hovered[32];
202+
203+
if (messageboxdata->colorScheme) {
204+
SDL_MessageBoxColor color = messageboxdata->colorScheme->colors[SDL_MESSAGEBOX_COLOR_BACKGROUND];
205+
SDL_snprintf(dialog_background, sizeof(dialog_background), "rgb(%u, %u, %u)", color.r, color.g, color.b);
206+
207+
color = messageboxdata->colorScheme->colors[SDL_MESSAGEBOX_COLOR_TEXT];
208+
SDL_snprintf(dialog_color, sizeof(dialog_color), "rgb(%u, %u, %u)", color.r, color.g, color.b);
209+
210+
color = messageboxdata->colorScheme->colors[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER];
211+
SDL_snprintf(button_border, sizeof(button_border), "rgb(%u, %u, %u)", color.r, color.g, color.b);
212+
213+
color = messageboxdata->colorScheme->colors[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND];
214+
SDL_snprintf(button_background, sizeof(button_background), "rgb(%u, %u, %u)", color.r, color.g, color.b);
215+
216+
color = messageboxdata->colorScheme->colors[SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED];
217+
SDL_snprintf(button_hovered, sizeof(button_hovered), "rgb(%u, %u, %u)", color.r, color.g, color.b);
218+
} else {
219+
SDL_zero(dialog_background);
220+
SDL_zero(dialog_color);
221+
SDL_zero(button_border);
222+
SDL_zero(button_background);
223+
SDL_zero(button_hovered);
224+
}
225+
226+
// TODO: Handle parent window when multiple windows can be added in Emscripten builds
227+
char dialog_id[64];
228+
SDL_snprintf(dialog_id, sizeof(dialog_id), "SDL3_messagebox_%u", SDL_rand_bits());
229+
EM_ASM({
230+
var title = UTF8ToString($0);
231+
var message = UTF8ToString($1);
232+
var background = UTF8ToString($2);
233+
var color = UTF8ToString($3);
234+
var id = UTF8ToString($4);
235+
236+
// Dialogs are always put in the front of the DOM
237+
var dialog = document.createElement("dialog");
238+
// Set class to allow for CSS selectors
239+
dialog.classList.add("SDL3_messagebox");
240+
dialog.id = id;
241+
dialog.style.color = color;
242+
dialog.style.backgroundColor = background;
243+
document.body.append(dialog);
244+
245+
var h1 = document.createElement("h1");
246+
h1.innerText = title;
247+
dialog.append(h1);
248+
249+
var p = document.createElement("p");
250+
p.innerText = message;
251+
dialog.append(p);
252+
253+
dialog.showModal();
254+
}, messageboxdata->title, messageboxdata->message, dialog_background, dialog_color, dialog_id);
255+
256+
int i;
257+
for (i = 0; i < messageboxdata->numbuttons; ++i) {
258+
SDL_MessageBoxButtonData button = messageboxdata->buttons[i];
259+
260+
const int created = EM_ASM_INT({
261+
var dialog_id = UTF8ToString($0);
262+
var text = UTF8ToString($1);
263+
var responseId = $2;
264+
var clickOnReturn = $3;
265+
var clickOnEscape = $4;
266+
var border = UTF8ToString($5);
267+
var background = UTF8ToString($6);
268+
var hovered = UTF8ToString($7);
269+
270+
var dialog = document.getElementById(dialog_id);
271+
if (!dialog) {
272+
return false;
273+
}
274+
275+
var button = document.createElement("button");
276+
button.innerText = text;
277+
button.style.borderColor = border;
278+
button.style.backgroundColor = background;
279+
280+
dialog.addEventListener('keydown', function(e) {
281+
if (clickOnReturn && e.key === "Enter") {
282+
e.preventDefault();
283+
button.click();
284+
} else if (clickOnEscape && e.key === "Escape") {
285+
e.preventDefault();
286+
button.click();
287+
}
288+
});
289+
dialog.addEventListener('cancel', function(e){
290+
e.preventDefault();
291+
});
292+
293+
button.onmouseenter = function(e){
294+
button.style.backgroundColor = hovered;
295+
};
296+
button.onmouseleave = function(e){
297+
button.style.backgroundColor = background;
298+
};
299+
button.onclick = function(e) {
300+
dialog.close(responseId);
301+
};
302+
303+
dialog.append(button);
304+
return true;
305+
},
306+
dialog_id,
307+
button.text,
308+
button.buttonID,
309+
button.flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
310+
button.flags & SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
311+
button_border,
312+
button_background,
313+
button_hovered
314+
);
315+
316+
if (!created) {
317+
return false;
318+
}
319+
}
320+
321+
while (true) {
322+
// give back control to browser for screen refresh
323+
emscripten_sleep(0);
324+
325+
const int dialog_open = EM_ASM_INT({
326+
var dialog_id = UTF8ToString($0);
327+
328+
var dialog = document.getElementById(dialog_id);
329+
if (!dialog) {
330+
return false;
331+
}
332+
return dialog.open;
333+
}, dialog_id);
334+
335+
if (dialog_open) {
336+
continue;
337+
}
338+
339+
*buttonID = EM_ASM_INT({
340+
var dialog_id = UTF8ToString($0);
341+
var dialog = document.getElementById(dialog_id);
342+
if (!dialog) {
343+
return 0;
344+
}
345+
try
346+
{
347+
return parseInt(dialog.returnValue);
348+
}
349+
catch(e)
350+
{
351+
return 0;
352+
}
353+
}, dialog_id);
354+
break;
355+
}
356+
357+
} else {
358+
// Cannot add elements to DOM and block without Asyncify. So, fall back to the alert function.
359+
EM_ASM({
360+
alert(UTF8ToString($0) + "\n\n" + UTF8ToString($1));
361+
}, messageboxdata->title, messageboxdata->message);
362+
}
363+
return true;
364+
}
365+
195366
VideoBootStrap Emscripten_bootstrap = {
196367
EMSCRIPTENVID_DRIVER_NAME, "SDL emscripten video driver",
197368
Emscripten_CreateDevice,
198-
NULL, // no ShowMessageBox implementation
369+
Emscripten_ShowMessagebox,
199370
false
200371
};
201372

test/testmessage.c

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,59 +36,86 @@ quit(int rc)
3636
static int SDLCALL
3737
button_messagebox(void *eventNumber)
3838
{
39+
int i;
3940
const SDL_MessageBoxButtonData buttons[] = {
4041
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
4142
0,
4243
"OK" },
4344
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
4445
1,
4546
"Cancel" },
47+
{ 0,
48+
2,
49+
"Retry" }
4650
};
47-
4851
SDL_MessageBoxData data = {
4952
SDL_MESSAGEBOX_INFORMATION,
5053
NULL, /* no parent window */
5154
"Custom MessageBox",
5255
"This is a custom messagebox",
53-
2,
56+
sizeof(buttons) / sizeof(SDL_MessageBoxButtonData),
5457
NULL, /* buttons */
5558
NULL /* Default color scheme */
5659
};
5760

58-
int button = -1;
59-
int success = 0;
60-
data.buttons = buttons;
61-
if (eventNumber) {
62-
data.message = "This is a custom messagebox from a background thread.";
63-
}
61+
for (i = 0; ; ++i) {
62+
SDL_MessageBoxColorScheme colorScheme;
63+
if (i != 0) {
64+
int j;
65+
for (j = 0; j < SDL_MESSAGEBOX_COLOR_COUNT; ++j) {
66+
colorScheme.colors[j].r = SDL_rand(256);
67+
colorScheme.colors[j].g = SDL_rand(256);
68+
colorScheme.colors[j].b = SDL_rand(256);
69+
}
70+
data.colorScheme = &colorScheme;
71+
} else {
72+
data.colorScheme = NULL;
73+
}
74+
75+
int button = -1;
76+
data.buttons = buttons;
77+
if (eventNumber) {
78+
data.message = "This is a custom messagebox from a background thread.";
79+
}
80+
81+
if (!SDL_ShowMessageBox(&data, &button)) {
82+
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error Presenting MessageBox: %s", SDL_GetError());
83+
if (eventNumber) {
84+
SDL_Event event;
85+
event.type = (Uint32)(intptr_t)eventNumber;
86+
SDL_PushEvent(&event);
87+
return 1;
88+
} else {
89+
quit(2);
90+
}
91+
}
92+
93+
const char* text;
94+
if (button == 1) {
95+
text = "Cancel";
96+
} else if (button == 2) {
97+
text = "Retry";
98+
} else {
99+
text = "OK";
100+
}
101+
SDL_Log("Pressed button: %d, %s", button, button == -1 ? "[closed]" : text);
64102

65-
success = SDL_ShowMessageBox(&data, &button);
66-
if (success == -1) {
67-
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error Presenting MessageBox: %s", SDL_GetError());
68103
if (eventNumber) {
69104
SDL_Event event;
70105
event.type = (Uint32)(intptr_t)eventNumber;
71106
SDL_PushEvent(&event);
72-
return 1;
73-
} else {
74-
quit(2);
75107
}
76-
}
77-
SDL_Log("Pressed button: %d, %s", button, button == -1 ? "[closed]" : button == 1 ? "Cancel"
78-
: "OK");
79108

80-
if (eventNumber) {
81-
SDL_Event event;
82-
event.type = (Uint32)(intptr_t)eventNumber;
83-
SDL_PushEvent(&event);
109+
if (button == 2) {
110+
continue;
111+
}
112+
return 0;
84113
}
85-
86-
return 0;
87114
}
88115

89116
int main(int argc, char *argv[])
90117
{
91-
int success;
118+
bool success;
92119
SDLTest_CommonState *state;
93120

94121
/* Initialize test framework */

0 commit comments

Comments
 (0)