diff options
Diffstat (limited to 'platform/javascript/display_server_javascript.cpp')
-rw-r--r-- | platform/javascript/display_server_javascript.cpp | 517 |
1 files changed, 163 insertions, 354 deletions
diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index b95674efc3..a605f22e16 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -37,6 +37,7 @@ #include <png.h> #include "dom_keys.inc" +#include "godot_js.h" #define DOM_BUTTON_LEFT 0 #define DOM_BUTTON_MIDDLE 1 @@ -49,55 +50,29 @@ DisplayServerJavaScript *DisplayServerJavaScript::get_singleton() { } // Window (canvas) -extern "C" EMSCRIPTEN_KEEPALIVE void _set_canvas_id(uint8_t *p_data, int p_data_size) { - DisplayServerJavaScript *display = DisplayServerJavaScript::get_singleton(); - display->canvas_id.parse_utf8((const char *)p_data, p_data_size); - display->canvas_id = "#" + display->canvas_id; +void DisplayServerJavaScript::focus_canvas() { + godot_js_display_canvas_focus(); } -static void focus_canvas() { - /* clang-format off */ - EM_ASM( - Module['canvas'].focus(); - ); - /* clang-format on */ +bool DisplayServerJavaScript::is_canvas_focused() { + return godot_js_display_canvas_is_focused() != 0; } -static bool is_canvas_focused() { - /* clang-format off */ - return EM_ASM_INT_V( - return document.activeElement == Module['canvas']; - ); - /* clang-format on */ +bool DisplayServerJavaScript::check_size_force_redraw() { + return godot_js_display_size_update() != 0; } -static Point2 compute_position_in_canvas(int x, int y) { - DisplayServerJavaScript *display = DisplayServerJavaScript::get_singleton(); - int canvas_x = EM_ASM_INT({ - return Module['canvas'].getBoundingClientRect().x; - }); - int canvas_y = EM_ASM_INT({ - return Module['canvas'].getBoundingClientRect().y; - }); - int canvas_width; - int canvas_height; - emscripten_get_canvas_element_size(display->canvas_id.utf8().get_data(), &canvas_width, &canvas_height); - - double element_width; - double element_height; - emscripten_get_element_css_size(display->canvas_id.utf8().get_data(), &element_width, &element_height); - - return Point2((int)(canvas_width / element_width * (x - canvas_x)), - (int)(canvas_height / element_height * (y - canvas_y))); +Point2 DisplayServerJavaScript::compute_position_in_canvas(int p_x, int p_y) { + int point[2]; + godot_js_display_compute_position(p_x, p_y, point, point + 1); + return Point2(point[0], point[1]); } -static bool cursor_inside_canvas = true; - EM_BOOL DisplayServerJavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) { DisplayServerJavaScript *display = get_singleton(); // Empty ID is canvas. String target_id = String::utf8(p_event->id); - if (target_id.empty() || "#" + target_id == display->canvas_id) { + if (target_id.is_empty() || target_id == String::utf8(&(display->canvas_id[1]))) { // This event property is the only reliable data on // browser fullscreen state. if (p_event->isFullscreen) { @@ -109,14 +84,15 @@ EM_BOOL DisplayServerJavaScript::fullscreen_change_callback(int p_event_type, co return false; } -// Drag and drop callback (see native/utils.js). -extern "C" EMSCRIPTEN_KEEPALIVE void _drop_files_callback(char *p_filev[], int p_filec) { - DisplayServerJavaScript *ds = DisplayServerJavaScript::get_singleton(); +// Drag and drop callback. +void DisplayServerJavaScript::drop_files_js_callback(char **p_filev, int p_filec) { + DisplayServerJavaScript *ds = get_singleton(); if (!ds) { ERR_FAIL_MSG("Unable to drop files because the DisplayServer is not active"); } - if (ds->drop_files_callback.is_null()) + if (ds->drop_files_callback.is_null()) { return; + } Vector<String> files; for (int i = 0; i < p_filec; i++) { files.push_back(String::utf8(p_filev[i])); @@ -128,23 +104,35 @@ extern "C" EMSCRIPTEN_KEEPALIVE void _drop_files_callback(char *p_filev[], int p ds->drop_files_callback.call((const Variant **)&vp, 1, ret, ce); } +// JavaScript quit request callback. +void DisplayServerJavaScript::request_quit_callback() { + DisplayServerJavaScript *ds = get_singleton(); + if (ds && !ds->window_event_callback.is_null()) { + Variant event = int(DisplayServer::WINDOW_EVENT_CLOSE_REQUEST); + Variant *eventp = &event; + Variant ret; + Callable::CallError ce; + ds->window_event_callback.call((const Variant **)&eventp, 1, ret, ce); + } +} + // Keys template <typename T> -static void dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) { +void DisplayServerJavaScript::dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) { godot_event->set_shift(emscripten_event_ptr->shiftKey); godot_event->set_alt(emscripten_event_ptr->altKey); godot_event->set_control(emscripten_event_ptr->ctrlKey); godot_event->set_metakey(emscripten_event_ptr->metaKey); } -static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) { +Ref<InputEventKey> DisplayServerJavaScript::setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) { Ref<InputEventKey> ev; ev.instance(); ev->set_echo(emscripten_event->repeat); dom2godot_mod(emscripten_event, ev); - ev->set_keycode(dom2godot_keycode(emscripten_event->keyCode)); - ev->set_physical_keycode(dom2godot_keycode(emscripten_event->keyCode)); + ev->set_keycode(dom_code2godot_scancode(emscripten_event->code, emscripten_event->key, false)); + ev->set_physical_keycode(dom_code2godot_scancode(emscripten_event->code, emscripten_event->key, true)); String unicode = String::utf8(emscripten_event->key); // Check if empty or multi-character (e.g. `CapsLock`). @@ -263,12 +251,13 @@ EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const E } EM_BOOL DisplayServerJavaScript::mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) { + DisplayServerJavaScript *ds = get_singleton(); Input *input = Input::get_singleton(); int input_mask = input->get_mouse_button_mask(); Point2 pos = compute_position_in_canvas(p_event->clientX, p_event->clientY); // For motion outside the canvas, only read mouse movement if dragging // started inside the canvas; imitating desktop app behaviour. - if (!cursor_inside_canvas && !input_mask) + if (!ds->cursor_inside_canvas && !input_mask) return false; Ref<InputEventMouseMotion> ev; @@ -289,7 +278,7 @@ EM_BOOL DisplayServerJavaScript::mousemove_callback(int p_event_type, const Emsc } // Cursor -static const char *godot2dom_cursor(DisplayServer::CursorShape p_shape) { +const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape p_shape) { switch (p_shape) { case DisplayServer::CURSOR_ARROW: return "auto"; @@ -330,35 +319,13 @@ static const char *godot2dom_cursor(DisplayServer::CursorShape p_shape) { } } -static void set_css_cursor(const char *p_cursor) { - /* clang-format off */ - EM_ASM_({ - Module['canvas'].style.cursor = UTF8ToString($0); - }, p_cursor); - /* clang-format on */ -} - -static bool is_css_cursor_hidden() { - /* clang-format off */ - return EM_ASM_INT({ - return Module['canvas'].style.cursor === 'none'; - }); - /* clang-format on */ -} - void DisplayServerJavaScript::cursor_set_shape(CursorShape p_shape) { ERR_FAIL_INDEX(p_shape, CURSOR_MAX); - - if (mouse_get_mode() == MOUSE_MODE_VISIBLE) { - if (cursors[p_shape] != "") { - Vector<String> url = cursors[p_shape].split("?"); - set_css_cursor(("url(\"" + url[0] + "\") " + url[1] + ", auto").utf8()); - } else { - set_css_cursor(godot2dom_cursor(p_shape)); - } + if (cursor_shape == p_shape) { + return; } - cursor_shape = p_shape; + godot_js_display_cursor_set_shape(godot2dom_cursor(cursor_shape)); } DisplayServer::CursorShape DisplayServerJavaScript::cursor_get_shape() const { @@ -367,17 +334,6 @@ DisplayServer::CursorShape DisplayServerJavaScript::cursor_get_shape() const { void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { if (p_cursor.is_valid()) { - Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape); - - if (cursor_c) { - if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) { - cursor_set_shape(p_shape); - return; - } - - cursors_cache.erase(p_shape); - } - Ref<Texture2D> texture = p_cursor; Ref<AtlasTexture> atlas_texture = p_cursor; Ref<Image> image; @@ -440,53 +396,10 @@ void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, Curso png.resize(len); ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr)); - char *object_url; - /* clang-format off */ - EM_ASM({ - var PNG_PTR = $0; - var PNG_LEN = $1; - var PTR = $2; - - var png = new Blob([HEAPU8.slice(PNG_PTR, PNG_PTR + PNG_LEN)], { type: 'image/png' }); - var url = URL.createObjectURL(png); - var length_bytes = lengthBytesUTF8(url) + 1; - var string_on_wasm_heap = _malloc(length_bytes); - setValue(PTR, string_on_wasm_heap, '*'); - stringToUTF8(url, string_on_wasm_heap, length_bytes); - }, png.ptr(), len, &object_url); - /* clang-format on */ - - String url = String::utf8(object_url) + "?" + itos(p_hotspot.x) + " " + itos(p_hotspot.y); - - /* clang-format off */ - EM_ASM({ _free($0); }, object_url); - /* clang-format on */ - - if (cursors[p_shape] != "") { - /* clang-format off */ - EM_ASM({ - URL.revokeObjectURL(UTF8ToString($0).split('?')[0]); - }, cursors[p_shape].utf8().get_data()); - /* clang-format on */ - cursors[p_shape] = ""; - } + godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), png.ptr(), len, p_hotspot.x, p_hotspot.y); - cursors[p_shape] = url; - - Vector<Variant> params; - params.push_back(p_cursor); - params.push_back(p_hotspot); - cursors_cache.insert(p_shape, params); - - } else if (cursors[p_shape] != "") { - /* clang-format off */ - EM_ASM({ - URL.revokeObjectURL(UTF8ToString($0).split('?')[0]); - }, cursors[p_shape].utf8().get_data()); - /* clang-format on */ - cursors[p_shape] = ""; - - cursors_cache.erase(p_shape); + } else { + godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), NULL, 0, 0, 0); } cursor_set_shape(cursor_shape); @@ -499,40 +412,37 @@ void DisplayServerJavaScript::mouse_set_mode(MouseMode p_mode) { return; if (p_mode == MOUSE_MODE_VISIBLE) { - // set_css_cursor must be called before set_cursor_shape to make the cursor visible - set_css_cursor(godot2dom_cursor(cursor_shape)); - cursor_set_shape(cursor_shape); + godot_js_display_cursor_set_visible(1); emscripten_exit_pointerlock(); } else if (p_mode == MOUSE_MODE_HIDDEN) { - set_css_cursor("none"); + godot_js_display_cursor_set_visible(0); emscripten_exit_pointerlock(); } else if (p_mode == MOUSE_MODE_CAPTURED) { - EMSCRIPTEN_RESULT result = emscripten_request_pointerlock("canvas", false); + godot_js_display_cursor_set_visible(1); + EMSCRIPTEN_RESULT result = emscripten_request_pointerlock(canvas_id, false); ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback."); ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback."); - // set_css_cursor must be called before cursor_set_shape to make the cursor visible - set_css_cursor(godot2dom_cursor(cursor_shape)); - cursor_set_shape(cursor_shape); } } DisplayServer::MouseMode DisplayServerJavaScript::mouse_get_mode() const { - if (is_css_cursor_hidden()) + if (godot_js_display_cursor_is_hidden()) { return MOUSE_MODE_HIDDEN; + } EmscriptenPointerlockChangeEvent ev; emscripten_get_pointerlock_status(&ev); - return (ev.isActive && String::utf8(ev.id) == "canvas") ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE; + return (ev.isActive && String::utf8(ev.id) == String::utf8(&canvas_id[1])) ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE; } // Wheel - EM_BOOL DisplayServerJavaScript::wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data) { ERR_FAIL_COND_V(p_event_type != EMSCRIPTEN_EVENT_WHEEL, false); + DisplayServerJavaScript *ds = get_singleton(); if (!is_canvas_focused()) { - if (cursor_inside_canvas) { + if (ds->cursor_inside_canvas) { focus_canvas(); } else { return false; @@ -623,63 +533,55 @@ EM_BOOL DisplayServerJavaScript::touchmove_callback(int p_event_type, const Emsc } bool DisplayServerJavaScript::screen_is_touchscreen(int p_screen) const { - return EM_ASM_INT({ return 'ontouchstart' in window; }); + return godot_js_display_touchscreen_is_available(); } // Gamepad - -EM_BOOL DisplayServerJavaScript::gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data) { +void DisplayServerJavaScript::gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid) { Input *input = Input::get_singleton(); - if (p_event_type == EMSCRIPTEN_EVENT_GAMEPADCONNECTED) { - String guid = ""; - if (String::utf8(p_event->mapping) == "standard") - guid = "Default HTML5 Gamepad"; - input->joy_connection_changed(p_event->index, true, String::utf8(p_event->id), guid); + if (p_connected) { + input->joy_connection_changed(p_index, true, String::utf8(p_id), String::utf8(p_guid)); } else { - input->joy_connection_changed(p_event->index, false, ""); + input->joy_connection_changed(p_index, false, ""); } - return true; } void DisplayServerJavaScript::process_joypads() { - int joypad_count = emscripten_get_num_gamepads(); Input *input = Input::get_singleton(); - for (int joypad = 0; joypad < joypad_count; joypad++) { - EmscriptenGamepadEvent state; - EMSCRIPTEN_RESULT query_result = emscripten_get_gamepad_status(joypad, &state); - // Chromium reserves gamepads slots, so NO_DATA is an expected result. - ERR_CONTINUE(query_result != EMSCRIPTEN_RESULT_SUCCESS && - query_result != EMSCRIPTEN_RESULT_NO_DATA); - if (query_result == EMSCRIPTEN_RESULT_SUCCESS && state.connected) { - int button_count = MIN(state.numButtons, 18); - int axis_count = MIN(state.numAxes, 8); - for (int button = 0; button < button_count; button++) { - float value = state.analogButton[button]; - input->joy_button(joypad, button, value); - } - for (int axis = 0; axis < axis_count; axis++) { + int32_t pads = godot_js_display_gamepad_sample_count(); + int32_t s_btns_num = 0; + int32_t s_axes_num = 0; + int32_t s_standard = 0; + float s_btns[16]; + float s_axes[10]; + for (int idx = 0; idx < pads; idx++) { + int err = godot_js_display_gamepad_sample_get(idx, s_btns, &s_btns_num, s_axes, &s_axes_num, &s_standard); + if (err) { + continue; + } + for (int b = 0; b < s_btns_num; b++) { + float value = s_btns[b]; + // Buttons 6 and 7 in the standard mapping need to be + // axis to be handled as JOY_AXIS_TRIGGER by Godot. + if (s_standard && (b == 6 || b == 7)) { Input::JoyAxis joy_axis; - joy_axis.min = -1; - joy_axis.value = state.axis[axis]; - input->joy_axis(joypad, axis, joy_axis); + joy_axis.min = 0; + joy_axis.value = value; + int a = b == 6 ? JOY_AXIS_TRIGGER_LEFT : JOY_AXIS_TRIGGER_RIGHT; + input->joy_axis(idx, a, joy_axis); + } else { + input->joy_button(idx, b, value); } } + for (int a = 0; a < s_axes_num; a++) { + Input::JoyAxis joy_axis; + joy_axis.min = -1; + joy_axis.value = s_axes[a]; + input->joy_axis(idx, a, joy_axis); + } } } -#if 0 -bool DisplayServerJavaScript::is_joy_known(int p_device) { - - return Input::get_singleton()->is_joy_mapped(p_device); -} - - -String DisplayServerJavaScript::get_joy_guid(int p_device) const { - - return Input::get_singleton()->get_joy_guid_remapped(p_device); -} -#endif - Vector<String> DisplayServerJavaScript::get_rendering_drivers_func() { Vector<String> drivers; drivers.push_back("dummy"); @@ -687,53 +589,30 @@ Vector<String> DisplayServerJavaScript::get_rendering_drivers_func() { } // Clipboard -extern "C" EMSCRIPTEN_KEEPALIVE void update_clipboard(const char *p_text) { - // Only call set_clipboard from OS (sets local clipboard) - DisplayServerJavaScript::get_singleton()->clipboard = p_text; +void DisplayServerJavaScript::update_clipboard_callback(const char *p_text) { + get_singleton()->clipboard = p_text; } void DisplayServerJavaScript::clipboard_set(const String &p_text) { - /* clang-format off */ - int err = EM_ASM_INT({ - var text = UTF8ToString($0); - if (!navigator.clipboard || !navigator.clipboard.writeText) - return 1; - navigator.clipboard.writeText(text).catch(function(e) { - // Setting OS clipboard is only possible from an input callback. - console.error("Setting OS clipboard is only possible from an input callback for the HTML5 plafrom. Exception:", e); - }); - return 0; - }, p_text.utf8().get_data()); - /* clang-format on */ + clipboard = p_text; + int err = godot_js_display_clipboard_set(p_text.utf8().get_data()); ERR_FAIL_COND_MSG(err, "Clipboard API is not supported."); } String DisplayServerJavaScript::clipboard_get() const { - /* clang-format off */ - EM_ASM({ - try { - navigator.clipboard.readText().then(function (result) { - ccall('update_clipboard', 'void', ['string'], [result]); - }).catch(function (e) { - // Fail graciously. - }); - } catch (e) { - // Fail graciously. - } - }); - /* clang-format on */ + godot_js_display_clipboard_get(update_clipboard_callback); return clipboard; } -extern "C" EMSCRIPTEN_KEEPALIVE void send_window_event(int p_notification) { +void DisplayServerJavaScript::send_window_event_callback(int p_notification) { + DisplayServerJavaScript *ds = get_singleton(); + if (!ds) { + return; + } if (p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER || p_notification == DisplayServer::WINDOW_EVENT_MOUSE_EXIT) { - cursor_inside_canvas = p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER; + ds->cursor_inside_canvas = p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER; } - OS_JavaScript *os = OS_JavaScript::get_singleton(); - if (os->is_finalizing()) - return; // We don't want events anymore. - DisplayServerJavaScript *ds = DisplayServerJavaScript::get_singleton(); - if (ds && !ds->window_event_callback.is_null()) { + if (!ds->window_event_callback.is_null()) { Variant event = int(p_notification); Variant *eventp = &event; Variant ret; @@ -743,11 +622,7 @@ extern "C" EMSCRIPTEN_KEEPALIVE void send_window_event(int p_notification) { } void DisplayServerJavaScript::alert(const String &p_alert, const String &p_title) { - /* clang-format off */ - EM_ASM_({ - window.alert(UTF8ToString($0)); - }, p_alert.utf8().get_data()); - /* clang-format on */ + godot_js_display_alert(p_alert.utf8().get_data()); } void DisplayServerJavaScript::set_icon(const Ref<Image> &p_icon) { @@ -778,29 +653,11 @@ void DisplayServerJavaScript::set_icon(const Ref<Image> &p_icon) { png.resize(len); ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr)); - /* clang-format off */ - EM_ASM({ - var PNG_PTR = $0; - var PNG_LEN = $1; - - var png = new Blob([HEAPU8.slice(PNG_PTR, PNG_PTR + PNG_LEN)], { type: "image/png" }); - var url = URL.createObjectURL(png); - var link = document.getElementById('-gd-engine-icon'); - if (link === null) { - link = document.createElement('link'); - link.rel = 'icon'; - link.id = '-gd-engine-icon'; - document.head.appendChild(link); - } - link.href = url; - }, png.ptr(), len); - /* clang-format on */ + godot_js_display_window_icon_set(png.ptr(), len); } void DisplayServerJavaScript::_dispatch_input_event(const Ref<InputEvent> &p_event) { OS_JavaScript *os = OS_JavaScript::get_singleton(); - if (os->is_finalizing()) - return; // We don't want events anymore. // Resume audio context after input in case autoplay was denied. os->resume_audio(); @@ -820,22 +677,19 @@ DisplayServer *DisplayServerJavaScript::create_func(const String &p_rendering_dr } DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - /* clang-format off */ - EM_ASM({ - const canvas = Module['canvas']; - var enc = new TextEncoder("utf-8"); - var buffer = new Uint8Array(enc.encode(canvas.id)); - var len = buffer.byteLength; - var out = _malloc(len); - HEAPU8.set(buffer, out); - ccall("_set_canvas_id", - "void", - ["number", "number"], - [out, len] - ); - _free(out); - }); - /* clang-format on */ + r_error = OK; // Always succeeds for now. + + // Ensure the canvas ID. + godot_js_config_canvas_id_get(canvas_id, 256); + + // Handle contextmenu, webglcontextlost + godot_js_display_setup_canvas(p_resolution.x, p_resolution.y, p_mode == WINDOW_MODE_FULLSCREEN); + + // Check if it's windows. + swap_cancel_ok = godot_js_display_is_swap_ok_cancel() == 1; + + // Expose method for requesting quit. + godot_js_os_request_quit_cb(request_quit_callback); RasterizerDummy::make_current(); // TODO GLES2 in Godot 4.0... or webgpu? #if 0 @@ -859,7 +713,7 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive gl_initialization_error = true; } - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(canvas_id.utf8().get_data(), &attributes); + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(canvas_id, &attributes); if (emscripten_webgl_make_context_current(ctx) != EMSCRIPTEN_RESULT_SUCCESS) { gl_initialization_error = true; } @@ -873,90 +727,48 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive video_driver_index = p_video_driver; #endif - /* clang-format off */ - window_set_mode(p_mode); - if (EM_ASM_INT_V({ return Module['resizeCanvasOnStart'] })) { - /* clang-format on */ - window_set_size(p_resolution); - } - EMSCRIPTEN_RESULT result; - CharString id = canvas_id.utf8(); #define EM_CHECK(ev) \ if (result != EMSCRIPTEN_RESULT_SUCCESS) \ ERR_PRINT("Error while setting " #ev " callback: Code " + itos(result)); #define SET_EM_CALLBACK(target, ev, cb) \ result = emscripten_set_##ev##_callback(target, nullptr, true, &cb); \ EM_CHECK(ev) -#define SET_EM_CALLBACK_NOTARGET(ev, cb) \ - result = emscripten_set_##ev##_callback(nullptr, true, &cb); \ +#define SET_EM_WINDOW_CALLBACK(ev, cb) \ + result = emscripten_set_##ev##_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, &cb); \ EM_CHECK(ev) // These callbacks from Emscripten's html5.h suffice to access most - // JavaScript APIs. For APIs that are not (sufficiently) exposed, EM_ASM - // is used below. - SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, mousemove, mousemove_callback) - SET_EM_CALLBACK(id.get_data(), mousedown, mouse_button_callback) - SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, mouseup, mouse_button_callback) - SET_EM_CALLBACK(id.get_data(), wheel, wheel_callback) - SET_EM_CALLBACK(id.get_data(), touchstart, touch_press_callback) - SET_EM_CALLBACK(id.get_data(), touchmove, touchmove_callback) - SET_EM_CALLBACK(id.get_data(), touchend, touch_press_callback) - SET_EM_CALLBACK(id.get_data(), touchcancel, touch_press_callback) - SET_EM_CALLBACK(id.get_data(), keydown, keydown_callback) - SET_EM_CALLBACK(id.get_data(), keypress, keypress_callback) - SET_EM_CALLBACK(id.get_data(), keyup, keyup_callback) + // JavaScript APIs. + SET_EM_CALLBACK(canvas_id, mousedown, mouse_button_callback) + SET_EM_WINDOW_CALLBACK(mousemove, mousemove_callback) + SET_EM_WINDOW_CALLBACK(mouseup, mouse_button_callback) + SET_EM_CALLBACK(canvas_id, wheel, wheel_callback) + SET_EM_CALLBACK(canvas_id, touchstart, touch_press_callback) + SET_EM_CALLBACK(canvas_id, touchmove, touchmove_callback) + SET_EM_CALLBACK(canvas_id, touchend, touch_press_callback) + SET_EM_CALLBACK(canvas_id, touchcancel, touch_press_callback) + SET_EM_CALLBACK(canvas_id, keydown, keydown_callback) + SET_EM_CALLBACK(canvas_id, keypress, keypress_callback) + SET_EM_CALLBACK(canvas_id, keyup, keyup_callback) SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, fullscreenchange, fullscreen_change_callback) - SET_EM_CALLBACK_NOTARGET(gamepadconnected, gamepad_change_callback) - SET_EM_CALLBACK_NOTARGET(gamepaddisconnected, gamepad_change_callback) -#undef SET_EM_CALLBACK_NOTARGET #undef SET_EM_CALLBACK #undef EM_CHECK - /* clang-format off */ - EM_ASM_ARGS({ - Module.listeners = {}; - const canvas = Module['canvas']; - const send_window_event = cwrap('send_window_event', null, ['number']); - const notifications = arguments; - (['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, index) { - Module.listeners[event] = send_window_event.bind(null, notifications[index]); - canvas.addEventListener(event, Module.listeners[event]); - }); - // Clipboard - const update_clipboard = cwrap('update_clipboard', null, ['string']); - Module.listeners['paste'] = function(evt) { - update_clipboard(evt.clipboardData.getData('text')); - }; - window.addEventListener('paste', Module.listeners['paste'], false); - Module.listeners['dragover'] = function(ev) { - // Prevent default behavior (which would try to open the file(s)) - ev.preventDefault(); - }; - Module.listeners['drop'] = Module.drop_handler; // Defined in native/utils.js - canvas.addEventListener('dragover', Module.listeners['dragover'], false); - canvas.addEventListener('drop', Module.listeners['drop'], false); - }, - WINDOW_EVENT_MOUSE_ENTER, - WINDOW_EVENT_MOUSE_EXIT, - WINDOW_EVENT_FOCUS_IN, - WINDOW_EVENT_FOCUS_OUT - ); - /* clang-format on */ + // For APIs that are not (sufficiently) exposed, a + // library is used below (implemented in library_godot_display.js). + godot_js_display_notification_cb(&send_window_event_callback, + WINDOW_EVENT_MOUSE_ENTER, + WINDOW_EVENT_MOUSE_EXIT, + WINDOW_EVENT_FOCUS_IN, + WINDOW_EVENT_FOCUS_OUT); + godot_js_display_paste_cb(update_clipboard_callback); + godot_js_display_drop_files_cb(drop_files_js_callback); + godot_js_display_gamepad_cb(&DisplayServerJavaScript::gamepad_callback); Input::get_singleton()->set_event_dispatch_function(_dispatch_input_event); } DisplayServerJavaScript::~DisplayServerJavaScript() { - EM_ASM({ - Object.entries(Module.listeners).forEach(function(kv) { - if (kv[0] == 'paste') { - window.removeEventListener(kv[0], kv[1], true); - } else { - Module['canvas'].removeEventListener(kv[0], kv[1]); - } - }); - Module.listeners = {}; - }); //emscripten_webgl_commit_frame(); //emscripten_webgl_destroy_context(webgl_ctx); } @@ -1004,20 +816,23 @@ Point2i DisplayServerJavaScript::screen_get_position(int p_screen) const { } Size2i DisplayServerJavaScript::screen_get_size(int p_screen) const { - EmscriptenFullscreenChangeEvent ev; - EMSCRIPTEN_RESULT result = emscripten_get_fullscreen_status(&ev); - ERR_FAIL_COND_V(result != EMSCRIPTEN_RESULT_SUCCESS, Size2i()); - return Size2i(ev.screenWidth, ev.screenHeight); + int size[2]; + godot_js_display_screen_size_get(size, size + 1); + return Size2(size[0], size[1]); } Rect2i DisplayServerJavaScript::screen_get_usable_rect(int p_screen) const { - int canvas[2]; - emscripten_get_canvas_element_size(canvas_id.utf8().get_data(), canvas, canvas + 1); - return Rect2i(0, 0, canvas[0], canvas[1]); + int size[2]; + godot_js_display_window_size_get(size, size + 1); + return Rect2i(0, 0, size[0], size[1]); } int DisplayServerJavaScript::screen_get_dpi(int p_screen) const { - return 96; // TODO maybe check pixel ratio via window.devicePixelRatio * 96? Inexact. + return godot_js_display_screen_dpi_get(); +} + +float DisplayServerJavaScript::screen_get_scale(int p_screen) const { + return godot_js_display_pixel_ratio_get(); } Vector<DisplayServer::WindowID> DisplayServerJavaScript::get_window_list() const { @@ -1059,11 +874,7 @@ void DisplayServerJavaScript::window_set_drop_files_callback(const Callable &p_c } void DisplayServerJavaScript::window_set_title(const String &p_title, WindowID p_window) { - /* clang-format off */ - EM_ASM_({ - document.title = UTF8ToString($0); - }, p_title.utf8().get_data()); - /* clang-format on */ + godot_js_display_window_title_set(p_title.utf8().get_data()); } int DisplayServerJavaScript::window_get_current_screen(WindowID p_window) const { @@ -1103,13 +914,13 @@ Size2i DisplayServerJavaScript::window_get_min_size(WindowID p_window) const { } void DisplayServerJavaScript::window_set_size(const Size2i p_size, WindowID p_window) { - emscripten_set_canvas_element_size(canvas_id.utf8().get_data(), p_size.x, p_size.y); + godot_js_display_desired_size_set(p_size.x, p_size.y); } Size2i DisplayServerJavaScript::window_get_size(WindowID p_window) const { - int canvas[2]; - emscripten_get_canvas_element_size(canvas_id.utf8().get_data(), canvas, canvas + 1); - return Size2(canvas[0], canvas[1]); + int size[2]; + godot_js_display_window_size_get(size, size + 1); + return Size2i(size[0], size[1]); } Size2i DisplayServerJavaScript::window_get_real_size(WindowID p_window) const { @@ -1123,20 +934,13 @@ void DisplayServerJavaScript::window_set_mode(WindowMode p_mode, WindowID p_wind switch (p_mode) { case WINDOW_MODE_WINDOWED: { if (window_mode == WINDOW_MODE_FULLSCREEN) { - emscripten_exit_fullscreen(); + godot_js_display_fullscreen_exit(); } window_mode = WINDOW_MODE_WINDOWED; - window_set_size(windowed_size); } break; case WINDOW_MODE_FULLSCREEN: { - EmscriptenFullscreenStrategy strategy; - strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; - strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF; - strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; - strategy.canvasResizedCallback = nullptr; - EMSCRIPTEN_RESULT result = emscripten_request_fullscreen_strategy(canvas_id.utf8().get_data(), false, &strategy); - ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "Enabling fullscreen is only possible from an input callback for the HTML5 platform."); - ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "Enabling fullscreen is only possible from an input callback for the HTML5 platform."); + int result = godot_js_display_fullscreen_request(); + ERR_FAIL_COND_MSG(result, "The request was denied. Remember that enabling fullscreen is only possible from an input callback for the HTML5 platform."); } break; case WINDOW_MODE_MAXIMIZED: case WINDOW_MODE_MINIMIZED: @@ -1180,14 +984,19 @@ bool DisplayServerJavaScript::can_any_window_draw() const { } void DisplayServerJavaScript::process_events() { - if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) + if (godot_js_display_gamepad_sample() == OK) { process_joypads(); + } } int DisplayServerJavaScript::get_current_video_driver() const { return 1; } +bool DisplayServerJavaScript::get_swap_cancel_ok() { + return swap_cancel_ok; +} + void DisplayServerJavaScript::swap_buffers() { //emscripten_webgl_commit_frame(); } |