Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sokol_app.h html5: cleanup canvas lookup handling (see #1154) #1159

Merged
merged 8 commits into from
Nov 23, 2024
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
## Updates

### 23-Nov-2024

- sokol_app.h html5: Merged PR https://github.com/floooh/sokol/pull/1159 (related
issue https://github.com/floooh/sokol/issues/1154).

This cleans up code that is concerned about finding the WebGL/WebGPU HTML canvas by:

- removing any leftover hacks from the time when Emscripten moved
from `document.getElementById()` to `document.querySelector()` for
looking up the canvas object
- adding two options for canvas objects that can't be looked up via
`document.querySelector()`

If you don't provide a custom canvas name to sokol_app.h this change
is non-breaking. Otherwise:

- in sokol_main(): change `.html5_canvas_name` to `.html5_canvas_selector`
- change the canvas name string to a CSS selector string (e.g.
from `"my_canvas"` to `"#my_canvas"`)

For more options to communicate the HTML canvas object to sokol_app.h,
please read the new doc section `SETTING THE CANVAS OBJECT ON THE WEB PLATFORM` in sokol_app.h.

Additionally, please also note the simplified `shell.html` in the
sokol-samples repository (some outdated cruft has been removed):

https://github.com/floooh/sokol-samples/blob/master/webpage/shell.html

Many thanks to @konsumer for kicking off the feature and the following
discussion :)


### 19-Nov-2024

- Merged PR https://github.com/floooh/sokol/pull/1155, this allows to use
Expand Down
129 changes: 107 additions & 22 deletions sokol_app.h
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,87 @@
To prevent individual events from bubbling, call sapp_consume_event() from within
the sokol_app.h event callback when that specific event is reported.


SETTING THE CANVAS OBJECT ON THE WEB PLATFORM
=============================================
On the web, sokol_app.h and the Emscripten SDK functions need to find
the WebGL/WebGPU canvas intended for rendering and attaching event
handlers. This can happen in four ways:

1. do nothing and just set the id of the canvas object to 'canvas' (preferred)
2. via a CSS Selector string (preferred)
3. by setting the `Module.canvas` property to the canvas object
4. by adding the canvas object to the global variable `specialHTMLTargets[]`
(this is a special variable used by the Emscripten runtime to lookup
event target objects for which document.querySelector() cannot be used)

The easiest way is to just name your canvas object 'canvas':

<canvas id="canvas" ...></canvas>

This works because the default css selector string used by sokol_app.h
is '#canvas'.

If you name your canvas differently, you need to communicate that name to
sokol_app.h via `sapp_desc.html5_canvas_selector` as a regular css selector
string that's compatible with `document.querySelector()`. E.g. if your canvas
object looks like this:

<canvas id="bla" ...></canvas>

The `sapp_desc.html5_canvas_selector` string must be set to '#bla':

.html5_canvas_selector = "#bla"

If the canvas object cannot be looked up via `document.querySelector()` you
need to use one of the alternative methods, both involve the special
Emscripten runtime `Module` object which is usually setup in the index.html
like this before the WASM blob is loaded and instantiated:

<script type='text/javascript'>
var Module = {
// ...
};
</script>

The first option is to set the `Module.canvas` property to your canvas object:

<script type='text/javascript'>
var Module = {
canvas: my_canvas_object,
};
</script>

When sokol_app.h initializes, it will check the global Module object whether
a `Module.canvas` property exists and is an object. This method will add
a new entry to the `specialHTMLTargets[]` object

The other option is to add the canvas under a name chosen by you to the
special `specialHTMLTargets[]` map, which is used by the Emscripten runtime
to lookup 'event target objects' which are not visible to `document.querySelector()`.
Note that `specialHTMLTargets[]` must be updated after the Emscripten runtime
has started but before the WASM code is running. A good place for this is
the special `Module.preRun` array in index.html:

<script type='text/javascript'>
var Module = {
preRun: [
() => {
specialHTMLTargets['my_canvas'] = my_canvas_object;
}
],
};
</script>

In that case, pass the same string to sokol_app.h which is used as key
in the specialHTMLTargets[] map:

.html5_canvas_selector = "my_canvas"

If sokol_app.h can't find your canvas for some reason check for warning
messages on the browser console.


OPTIONAL: DON'T HIJACK main() (#define SOKOL_NO_ENTRY)
======================================================
NOTE: SOKOL_NO_ENTRY and sapp_run() is currently not supported on Android.
Expand Down Expand Up @@ -1147,7 +1228,6 @@
the standard logger in sokol_log.h instead, otherwise you won't see any warnings or
errors.


TEMP NOTE DUMP
==============
- sapp_desc needs a bool whether to initialize depth-stencil surface
Expand Down Expand Up @@ -1717,7 +1797,7 @@ typedef struct sapp_desc {
bool win32_console_utf8; // if true, set the output console codepage to UTF-8
bool win32_console_create; // if true, attach stdout/stderr to a new console window
bool win32_console_attach; // if true, attach stdout/stderr to parent process
const char* html5_canvas_name; // the name (id) of the HTML5 canvas element, default is "canvas"
const char* html5_canvas_selector; // css selector of the HTML5 canvas element, default is "#canvas"
bool html5_canvas_resize; // if true, the HTML5 canvas size is set to sapp_desc.width/height, otherwise canvas size is tracked
bool html5_preserve_drawing_buffer; // HTML5 only: whether to preserve default framebuffer content between frames
bool html5_premultiplied_alpha; // HTML5 only: whether the rendered pixels use premultiplied alpha convention
Expand Down Expand Up @@ -3115,7 +3195,7 @@ _SOKOL_PRIVATE sapp_desc _sapp_desc_defaults(const sapp_desc* desc) {
res.gl_minor_version = 3;
#endif
}
res.html5_canvas_name = _sapp_def(res.html5_canvas_name, "canvas");
res.html5_canvas_selector = _sapp_def(res.html5_canvas_selector, "#canvas");
res.clipboard_size = _sapp_def(res.clipboard_size, 8192);
res.max_dropped_files = _sapp_def(res.max_dropped_files, 1);
res.max_dropped_file_path_length = _sapp_def(res.max_dropped_file_path_length, 2048);
Expand All @@ -3142,9 +3222,8 @@ _SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) {
_sapp.framebuffer_height = _sapp.window_height;
_sapp.sample_count = _sapp.desc.sample_count;
_sapp.swap_interval = _sapp.desc.swap_interval;
_sapp.html5_canvas_selector[0] = '#';
_sapp_strcpy(_sapp.desc.html5_canvas_name, &_sapp.html5_canvas_selector[1], sizeof(_sapp.html5_canvas_selector) - 1);
_sapp.desc.html5_canvas_name = &_sapp.html5_canvas_selector[1];
_sapp_strcpy(_sapp.desc.html5_canvas_selector, _sapp.html5_canvas_selector, sizeof(_sapp.html5_canvas_selector));
_sapp.desc.html5_canvas_selector = _sapp.html5_canvas_selector;
_sapp.html5_ask_leave_site = _sapp.desc.html5_ask_leave_site;
_sapp.clipboard.enabled = _sapp.desc.enable_clipboard;
if (_sapp.clipboard.enabled) {
Expand Down Expand Up @@ -4780,7 +4859,7 @@ _SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) {
#if defined(_SAPP_EMSCRIPTEN)

#if defined(EM_JS_DEPS)
EM_JS_DEPS(sokol_app, "$withStackSave,$stringToUTF8OnStack");
EM_JS_DEPS(sokol_app, "$withStackSave,$stringToUTF8OnStack,$findCanvasEventTarget");
#endif

#ifdef __cplusplus
Expand Down Expand Up @@ -4930,10 +5009,8 @@ _SOKOL_PRIVATE void _sapp_emsc_set_clipboard_string(const char* str) {
sapp_js_write_clipboard(str);
}

EM_JS(void, sapp_js_add_dragndrop_listeners, (const char* canvas_name_cstr), {
EM_JS(void, sapp_js_add_dragndrop_listeners, (void), {
Module.sokol_drop_files = [];
const canvas_name = UTF8ToString(canvas_name_cstr);
const canvas = document.getElementById(canvas_name);
Module.sokol_dragenter = (event) => {
event.stopPropagation();
event.preventDefault();
Expand Down Expand Up @@ -4966,6 +5043,8 @@ EM_JS(void, sapp_js_add_dragndrop_listeners, (const char* canvas_name_cstr), {
// FIXME? see computation of targetX/targetY in emscripten via getClientBoundingRect
__sapp_emsc_end_drop(event.clientX, event.clientY, mods);
};
\x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F
const canvas = Module.sapp_emsc_target;
canvas.addEventListener('dragenter', Module.sokol_dragenter, false);
canvas.addEventListener('dragleave', Module.sokol_dragleave, false);
canvas.addEventListener('dragover', Module.sokol_dragover, false);
Expand Down Expand Up @@ -5005,24 +5084,30 @@ EM_JS(void, sapp_js_fetch_dropped_file, (int index, _sapp_html5_fetch_callback c
reader.readAsArrayBuffer(files[index]);
});

EM_JS(void, sapp_js_remove_dragndrop_listeners, (const char* canvas_name_cstr), {
const canvas_name = UTF8ToString(canvas_name_cstr);
const canvas = document.getElementById(canvas_name);
EM_JS(void, sapp_js_remove_dragndrop_listeners, (void), {
\x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F
const canvas = Module.sapp_emsc_target;
canvas.removeEventListener('dragenter', Module.sokol_dragenter);
canvas.removeEventListener('dragleave', Module.sokol_dragleave);
canvas.removeEventListener('dragover', Module.sokol_dragover);
canvas.removeEventListener('drop', Module.sokol_drop);
});

EM_JS(void, sapp_js_init, (const char* c_str_target), {
// lookup and store canvas object by name
const target_str = UTF8ToString(c_str_target);
Module.sapp_emsc_target = document.getElementById(target_str);
EM_JS(void, sapp_js_init, (const char* c_str_target_selector), {
const target_selector_str = UTF8ToString(c_str_target_selector);
if (Module['canvas'] !== undefined) {
if (typeof Module['canvas'] === 'object') {
specialHTMLTargets[target_selector_str] = Module['canvas'];
} else {
console.warn("sokol_app.h: Module['canvas'] is set but is not an object");
}
}
Module.sapp_emsc_target = findCanvasEventTarget(target_selector_str);
if (!Module.sapp_emsc_target) {
console.log("sokol_app.h: invalid target:" + target_str);
console.warn("sokol_app.h: can't find html5_canvas_selector ", target_selector_str);
}
if (!Module.sapp_emsc_target.requestPointerLock) {
console.log("sokol_app.h: target doesn't support requestPointerLock:" + target_str);
console.warn("sokol_app.h: target doesn't support requestPointerLock: ", target_selector_str);
}
});

Expand Down Expand Up @@ -5853,7 +5938,7 @@ _SOKOL_PRIVATE void _sapp_emsc_register_eventhandlers(void) {
sapp_js_add_clipboard_listener();
}
if (_sapp.drop.enabled) {
sapp_js_add_dragndrop_listeners(&_sapp.html5_canvas_selector[1]);
sapp_js_add_dragndrop_listeners();
}
#if defined(SOKOL_GLES3)
emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb);
Expand Down Expand Up @@ -5887,7 +5972,7 @@ _SOKOL_PRIVATE void _sapp_emsc_unregister_eventhandlers(void) {
sapp_js_remove_clipboard_listener();
}
if (_sapp.drop.enabled) {
sapp_js_remove_dragndrop_listeners(&_sapp.html5_canvas_selector[1]);
sapp_js_remove_dragndrop_listeners();
}
#if defined(SOKOL_GLES3)
emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, 0);
Expand Down Expand Up @@ -5931,7 +6016,7 @@ _SOKOL_PRIVATE void _sapp_emsc_frame_main_loop(void) {

_SOKOL_PRIVATE void _sapp_emsc_run(const sapp_desc* desc) {
_sapp_init_state(desc);
sapp_js_init(&_sapp.html5_canvas_selector[1]);
sapp_js_init(_sapp.html5_canvas_selector);
double w, h;
if (_sapp.desc.html5_canvas_resize) {
w = (double) _sapp_def(_sapp.desc.width, _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH);
Expand Down