diff options
Diffstat (limited to 'platform/javascript/display_server_javascript.cpp')
-rw-r--r-- | platform/javascript/display_server_javascript.cpp | 975 |
1 files changed, 380 insertions, 595 deletions
diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index 0312efb377..5d960ef80c 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-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -30,13 +30,17 @@ #include "platform/javascript/display_server_javascript.h" -#include "drivers/dummy/rasterizer_dummy.h" +#ifdef GLES3_ENABLED +#include "drivers/gles3/rasterizer_gles3.h" +#endif #include "platform/javascript/os_javascript.h" +#include "servers/rendering/rasterizer_dummy.h" #include <emscripten.h> #include <png.h> #include "dom_keys.inc" +#include "godot_js.h" #define DOM_BUTTON_LEFT 0 #define DOM_BUTTON_MIDDLE 1 @@ -49,74 +53,28 @@ 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; -} - -static void focus_canvas() { - /* clang-format off */ - EM_ASM( - Module['canvas'].focus(); - ); - /* clang-format on */ +bool DisplayServerJavaScript::check_size_force_redraw() { + return godot_js_display_size_update() != 0; } -static bool is_canvas_focused() { - /* clang-format off */ - return EM_ASM_INT_V( - return document.activeElement == Module['canvas']; - ); - /* clang-format on */ -} - -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))); -} - -static bool cursor_inside_canvas = true; - -EM_BOOL DisplayServerJavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) { +void DisplayServerJavaScript::fullscreen_change_callback(int p_fullscreen) { 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) { - // This event property is the only reliable data on - // browser fullscreen state. - if (p_event->isFullscreen) { - display->window_mode = WINDOW_MODE_FULLSCREEN; - } else { - display->window_mode = WINDOW_MODE_WINDOWED; - } + if (p_fullscreen) { + display->window_mode = WINDOW_MODE_FULLSCREEN; + } else { + display->window_mode = WINDOW_MODE_WINDOWED; } - 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,127 +86,111 @@ 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) { - 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); +void DisplayServerJavaScript::dom2godot_mod(Ref<InputEventWithModifiers> ev, int p_mod) { + ev->set_shift_pressed(p_mod & 1); + ev->set_alt_pressed(p_mod & 2); + ev->set_ctrl_pressed(p_mod & 4); + ev->set_meta_pressed(p_mod & 8); } -static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) { +void DisplayServerJavaScript::key_callback(int p_pressed, int p_repeat, int p_modifiers) { + DisplayServerJavaScript *ds = get_singleton(); + JSKeyEvent &key_event = ds->key_event; + // Resume audio context after input in case autoplay was denied. + OS_JavaScript::get_singleton()->resume_audio(); + Ref<InputEventKey> ev; - ev.instance(); - ev->set_echo(emscripten_event->repeat); - dom2godot_mod(emscripten_event, ev); - 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`). - if (unicode.length() != 1) { - // Might be empty as well, but better than nonsense. - unicode = String::utf8(emscripten_event->charValue); - } + ev.instantiate(); + ev->set_echo(p_repeat); + ev->set_keycode(dom_code2godot_scancode(key_event.code, key_event.key, false)); + ev->set_physical_keycode(dom_code2godot_scancode(key_event.code, key_event.key, true)); + ev->set_pressed(p_pressed); + dom2godot_mod(ev, p_modifiers); + + String unicode = String::utf8(key_event.key); if (unicode.length() == 1) { ev->set_unicode(unicode[0]); } - - return ev; -} - -EM_BOOL DisplayServerJavaScript::keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) { - DisplayServerJavaScript *display = get_singleton(); - Ref<InputEventKey> ev = setup_key_event(p_event); - ev->set_pressed(true); - if (ev->get_unicode() == 0 && keycode_has_unicode(ev->get_keycode())) { - // Defer to keypress event for legacy unicode retrieval. - display->deferred_key_event = ev; - // Do not suppress keypress event. - return false; - } Input::get_singleton()->parse_input_event(ev); - return true; -} - -EM_BOOL DisplayServerJavaScript::keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) { - DisplayServerJavaScript *display = get_singleton(); - display->deferred_key_event->set_unicode(p_event->charCode); - Input::get_singleton()->parse_input_event(display->deferred_key_event); - return true; -} -EM_BOOL DisplayServerJavaScript::keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) { - Ref<InputEventKey> ev = setup_key_event(p_event); - ev->set_pressed(false); - Input::get_singleton()->parse_input_event(ev); - return ev->get_keycode() != KEY_UNKNOWN && ev->get_keycode() != 0; + // Make sure to flush all events so we can call restricted APIs inside the event. + Input::get_singleton()->flush_buffered_events(); } // Mouse -EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) { - DisplayServerJavaScript *display = get_singleton(); +int DisplayServerJavaScript::mouse_button_callback(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers) { + DisplayServerJavaScript *ds = get_singleton(); + Point2 pos(p_x, p_y); + Input::get_singleton()->set_mouse_position(pos); Ref<InputEventMouseButton> ev; - ev.instance(); - ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_MOUSEDOWN); - ev->set_position(compute_position_in_canvas(p_event->clientX, p_event->clientY)); - ev->set_global_position(ev->get_position()); - dom2godot_mod(p_event, ev); + ev.instantiate(); + ev->set_position(pos); + ev->set_global_position(pos); + ev->set_pressed(p_pressed); + dom2godot_mod(ev, p_modifiers); - switch (p_event->button) { + switch (p_button) { case DOM_BUTTON_LEFT: - ev->set_button_index(BUTTON_LEFT); + ev->set_button_index(MouseButton::LEFT); break; case DOM_BUTTON_MIDDLE: - ev->set_button_index(BUTTON_MIDDLE); + ev->set_button_index(MouseButton::MIDDLE); break; case DOM_BUTTON_RIGHT: - ev->set_button_index(BUTTON_RIGHT); + ev->set_button_index(MouseButton::RIGHT); break; case DOM_BUTTON_XBUTTON1: - ev->set_button_index(BUTTON_XBUTTON1); + ev->set_button_index(MouseButton::MB_XBUTTON1); break; case DOM_BUTTON_XBUTTON2: - ev->set_button_index(BUTTON_XBUTTON2); + ev->set_button_index(MouseButton::MB_XBUTTON2); break; default: return false; } - if (ev->is_pressed()) { - double diff = emscripten_get_now() - display->last_click_ms; - - if (ev->get_button_index() == display->last_click_button_index) { - if (diff < 400 && Point2(display->last_click_pos).distance_to(ev->get_position()) < 5) { - display->last_click_ms = 0; - display->last_click_pos = Point2(-100, -100); - display->last_click_button_index = -1; - ev->set_doubleclick(true); + if (p_pressed) { + uint64_t diff = (OS::get_singleton()->get_ticks_usec() / 1000) - ds->last_click_ms; + + if (ev->get_button_index() == ds->last_click_button_index) { + if (diff < 400 && Point2(ds->last_click_pos).distance_to(ev->get_position()) < 5) { + ds->last_click_ms = 0; + ds->last_click_pos = Point2(-100, -100); + ds->last_click_button_index = MouseButton::NONE; + ev->set_double_click(true); } } else { - display->last_click_button_index = ev->get_button_index(); + ds->last_click_button_index = ev->get_button_index(); } - if (!ev->is_doubleclick()) { - display->last_click_ms += diff; - display->last_click_pos = ev->get_position(); + if (!ev->is_double_click()) { + ds->last_click_ms += diff; + ds->last_click_pos = ev->get_position(); } } - Input *input = Input::get_singleton(); - int mask = input->get_mouse_button_mask(); - int button_flag = 1 << (ev->get_button_index() - 1); + MouseButton mask = Input::get_singleton()->get_mouse_button_mask(); + MouseButton button_flag = mouse_button_to_mask(ev->get_button_index()); if (ev->is_pressed()) { - // Since the event is consumed, focus manually. The containing iframe, - // if exists, may not have focus yet, so focus even if already focused. - focus_canvas(); mask |= button_flag; - } else if (mask & button_flag) { + } else if ((mask & button_flag) != MouseButton::NONE) { mask &= ~button_flag; } else { // Received release event, but press was outside the canvas, so ignore. @@ -256,40 +198,45 @@ EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const E } ev->set_button_mask(mask); - input->parse_input_event(ev); + Input::get_singleton()->parse_input_event(ev); + // Resume audio context after input in case autoplay was denied. + OS_JavaScript::get_singleton()->resume_audio(); + + // Make sure to flush all events so we can call restricted APIs inside the event. + Input::get_singleton()->flush_buffered_events(); + // Prevent multi-click text selection and wheel-click scrolling anchor. // Context menu is prevented through contextmenu event. return true; } -EM_BOOL DisplayServerJavaScript::mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) { - 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); +void DisplayServerJavaScript::mouse_move_callback(double p_x, double p_y, double p_rel_x, double p_rel_y, int p_modifiers) { + MouseButton input_mask = Input::get_singleton()->get_mouse_button_mask(); // 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) - return false; + if (!get_singleton()->cursor_inside_canvas && input_mask == MouseButton::NONE) { + return; + } + Point2 pos(p_x, p_y); + Input::get_singleton()->set_mouse_position(pos); Ref<InputEventMouseMotion> ev; - ev.instance(); - dom2godot_mod(p_event, ev); + ev.instantiate(); + dom2godot_mod(ev, p_modifiers); ev->set_button_mask(input_mask); ev->set_position(pos); - ev->set_global_position(ev->get_position()); + ev->set_global_position(pos); - ev->set_relative(Vector2(p_event->movementX, p_event->movementY)); - input->set_mouse_position(ev->get_position()); - ev->set_speed(input->get_last_mouse_speed()); + ev->set_relative(Vector2(p_rel_x, p_rel_y)); + Input::get_singleton()->set_mouse_position(ev->get_position()); + ev->set_speed(Input::get_singleton()->get_last_mouse_speed()); - input->parse_input_event(ev); - // Don't suppress mouseover/-leave events. - return false; + Input::get_singleton()->parse_input_event(ev); } // 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 +277,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 +292,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; @@ -385,7 +299,7 @@ void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, Curso Rect2 atlas_rect; if (texture.is_valid()) { - image = texture->get_data(); + image = texture->get_image(); } if (!image.is_valid() && atlas_texture.is_valid()) { @@ -408,7 +322,7 @@ void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, Curso ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - image = texture->get_data(); + image = texture->get_image(); ERR_FAIL_COND(!image.is_valid()); @@ -440,53 +354,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] = ""; - } - - cursors[p_shape] = url; - - Vector<Variant> params; - params.push_back(p_cursor); - params.push_back(p_hotspot); - cursors_cache.insert(p_shape, params); + godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), png.ptr(), len, p_hotspot.x, p_hotspot.y); - } 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), nullptr, 0, 0, 0); } cursor_set_shape(cursor_shape); @@ -494,46 +365,45 @@ void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, Curso // Mouse mode void DisplayServerJavaScript::mouse_set_mode(MouseMode p_mode) { - ERR_FAIL_COND_MSG(p_mode == MOUSE_MODE_CONFINED, "MOUSE_MODE_CONFINED is not supported for the HTML5 platform."); - if (p_mode == mouse_get_mode()) + ERR_FAIL_COND_MSG(p_mode == MOUSE_MODE_CONFINED || p_mode == MOUSE_MODE_CONFINED_HIDDEN, "MOUSE_MODE_CONFINED is not supported for the HTML5 platform."); + if (p_mode == mouse_get_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); - emscripten_exit_pointerlock(); + godot_js_display_cursor_set_visible(1); + godot_js_display_cursor_lock_set(0); } else if (p_mode == MOUSE_MODE_HIDDEN) { - set_css_cursor("none"); - emscripten_exit_pointerlock(); + godot_js_display_cursor_set_visible(0); + godot_js_display_cursor_lock_set(0); } else if (p_mode == MOUSE_MODE_CAPTURED) { - EMSCRIPTEN_RESULT result = emscripten_request_pointerlock("canvas", 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); + godot_js_display_cursor_set_visible(1); + godot_js_display_cursor_lock_set(1); } } 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; + if (godot_js_display_cursor_is_locked()) { + return MOUSE_MODE_CAPTURED; + } + return MOUSE_MODE_VISIBLE; } -// Wheel +Point2i DisplayServerJavaScript::mouse_get_position() const { + return Input::get_singleton()->get_mouse_position(); +} -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); - if (!is_canvas_focused()) { - if (cursor_inside_canvas) { - focus_canvas(); +// Wheel +int DisplayServerJavaScript::mouse_wheel_callback(double p_delta_x, double p_delta_y) { + if (!godot_js_display_canvas_is_focused()) { + if (get_singleton()->cursor_inside_canvas) { + godot_js_display_canvas_focus(); } else { return false; } @@ -541,199 +411,208 @@ EM_BOOL DisplayServerJavaScript::wheel_callback(int p_event_type, const Emscript Input *input = Input::get_singleton(); Ref<InputEventMouseButton> ev; - ev.instance(); + ev.instantiate(); ev->set_position(input->get_mouse_position()); ev->set_global_position(ev->get_position()); - ev->set_shift(input->is_key_pressed(KEY_SHIFT)); - ev->set_alt(input->is_key_pressed(KEY_ALT)); - ev->set_control(input->is_key_pressed(KEY_CONTROL)); - ev->set_metakey(input->is_key_pressed(KEY_META)); - - if (p_event->deltaY < 0) - ev->set_button_index(BUTTON_WHEEL_UP); - else if (p_event->deltaY > 0) - ev->set_button_index(BUTTON_WHEEL_DOWN); - else if (p_event->deltaX > 0) - ev->set_button_index(BUTTON_WHEEL_LEFT); - else if (p_event->deltaX < 0) - ev->set_button_index(BUTTON_WHEEL_RIGHT); - else + ev->set_shift_pressed(input->is_key_pressed(Key::SHIFT)); + ev->set_alt_pressed(input->is_key_pressed(Key::ALT)); + ev->set_ctrl_pressed(input->is_key_pressed(Key::CTRL)); + ev->set_meta_pressed(input->is_key_pressed(Key::META)); + + if (p_delta_y < 0) { + ev->set_button_index(MouseButton::WHEEL_UP); + } else if (p_delta_y > 0) { + ev->set_button_index(MouseButton::WHEEL_DOWN); + } else if (p_delta_x > 0) { + ev->set_button_index(MouseButton::WHEEL_LEFT); + } else if (p_delta_x < 0) { + ev->set_button_index(MouseButton::WHEEL_RIGHT); + } else { return false; + } // Different browsers give wildly different delta values, and we can't // interpret deltaMode, so use default value for wheel events' factor. - int button_flag = 1 << (ev->get_button_index() - 1); + MouseButton button_flag = mouse_button_to_mask(ev->get_button_index()); ev->set_pressed(true); ev->set_button_mask(input->get_mouse_button_mask() | button_flag); input->parse_input_event(ev); - ev->set_pressed(false); - ev->set_button_mask(input->get_mouse_button_mask() & ~button_flag); - input->parse_input_event(ev); + Ref<InputEventMouseButton> release = ev->duplicate(); + release->set_pressed(false); + release->set_button_mask(MouseButton(input->get_mouse_button_mask() & ~button_flag)); + input->parse_input_event(release); return true; } // Touch -EM_BOOL DisplayServerJavaScript::touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) { - DisplayServerJavaScript *display = get_singleton(); - Ref<InputEventScreenTouch> ev; - ev.instance(); - int lowest_id_index = -1; - for (int i = 0; i < p_event->numTouches; ++i) { - const EmscriptenTouchPoint &touch = p_event->touches[i]; - if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier) - lowest_id_index = i; - if (!touch.isChanged) - continue; - ev->set_index(touch.identifier); - ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY)); - display->touches[i] = ev->get_position(); - ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_TOUCHSTART); +void DisplayServerJavaScript::touch_callback(int p_type, int p_count) { + DisplayServerJavaScript *ds = get_singleton(); + + const JSTouchEvent &touch_event = ds->touch_event; + for (int i = 0; i < p_count; i++) { + Point2 point(touch_event.coords[i * 2], touch_event.coords[i * 2 + 1]); + if (p_type == 2) { + // touchmove + Ref<InputEventScreenDrag> ev; + ev.instantiate(); + ev->set_index(touch_event.identifier[i]); + ev->set_position(point); + + Point2 &prev = ds->touches[i]; + ev->set_relative(ev->get_position() - prev); + prev = ev->get_position(); + + Input::get_singleton()->parse_input_event(ev); + } else { + // touchstart/touchend + Ref<InputEventScreenTouch> ev; + + // Resume audio context after input in case autoplay was denied. + OS_JavaScript::get_singleton()->resume_audio(); + + ev.instantiate(); + ev->set_index(touch_event.identifier[i]); + ev->set_position(point); + ev->set_pressed(p_type == 0); + ds->touches[i] = point; - Input::get_singleton()->parse_input_event(ev); + Input::get_singleton()->parse_input_event(ev); + + // Make sure to flush all events so we can call restricted APIs inside the event. + Input::get_singleton()->flush_buffered_events(); + } } - // Resume audio context after input in case autoplay was denied. - return true; } -EM_BOOL DisplayServerJavaScript::touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) { - DisplayServerJavaScript *display = get_singleton(); - Ref<InputEventScreenDrag> ev; - ev.instance(); - int lowest_id_index = -1; - for (int i = 0; i < p_event->numTouches; ++i) { - const EmscriptenTouchPoint &touch = p_event->touches[i]; - if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier) - lowest_id_index = i; - if (!touch.isChanged) - continue; - ev->set_index(touch.identifier); - ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY)); - Point2 &prev = display->touches[i]; - ev->set_relative(ev->get_position() - prev); - prev = ev->get_position(); +bool DisplayServerJavaScript::screen_is_touchscreen(int p_screen) const { + return godot_js_display_touchscreen_is_available(); +} - Input::get_singleton()->parse_input_event(ev); +// Virtual Keyboard +void DisplayServerJavaScript::vk_input_text_callback(const char *p_text, int p_cursor) { + DisplayServerJavaScript *ds = DisplayServerJavaScript::get_singleton(); + if (!ds || ds->input_text_callback.is_null()) { + return; + } + // Call input_text + Variant event = String::utf8(p_text); + Variant *eventp = &event; + Variant ret; + Callable::CallError ce; + ds->input_text_callback.call((const Variant **)&eventp, 1, ret, ce); + // Insert key right to reach position. + Input *input = Input::get_singleton(); + Ref<InputEventKey> k; + for (int i = 0; i < p_cursor; i++) { + k.instantiate(); + k->set_pressed(true); + k->set_echo(false); + k->set_keycode(Key::RIGHT); + input->parse_input_event(k); + k.instantiate(); + k->set_pressed(false); + k->set_echo(false); + k->set_keycode(Key::RIGHT); + input->parse_input_event(k); } - return true; } -bool DisplayServerJavaScript::screen_is_touchscreen(int p_screen) const { - return EM_ASM_INT({ return 'ontouchstart' in window; }); +void DisplayServerJavaScript::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) { + godot_js_display_vk_show(p_existing_text.utf8().get_data(), p_multiline, p_cursor_start, p_cursor_end); } -// Gamepad +void DisplayServerJavaScript::virtual_keyboard_hide() { + godot_js_display_vk_hide(); +} -EM_BOOL DisplayServerJavaScript::gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data) { +void DisplayServerJavaScript::window_blur_callback() { + Input::get_singleton()->release_pressed_events(); +} + +// Gamepad +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++) { - Input::JoyAxis joy_axis; - joy_axis.min = -1; - joy_axis.value = state.axis[axis]; - input->joy_axis(joypad, axis, joy_axis); + int32_t pads = godot_js_input_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_input_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 JoyAxis::TRIGGER by Godot. + if (s_standard && (b == 6 || b == 7)) { + Input::JoyAxisValue joy_axis; + joy_axis.min = 0; + joy_axis.value = value; + JoyAxis a = b == 6 ? JoyAxis::TRIGGER_LEFT : JoyAxis::TRIGGER_RIGHT; + input->joy_axis(idx, a, joy_axis); + } else { + input->joy_button(idx, (JoyButton)b, value); } } + for (int a = 0; a < s_axes_num; a++) { + Input::JoyAxisValue joy_axis; + joy_axis.min = -1; + joy_axis.value = s_axes[a]; + input->joy_axis(idx, (JoyAxis)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"); +#ifdef GLES3_ENABLED + drivers.push_back("opengl3"); +#endif return drivers; } // 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 = String::utf8(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; @@ -742,14 +621,6 @@ 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 */ -} - void DisplayServerJavaScript::set_icon(const Ref<Image> &p_icon) { ERR_FAIL_COND(p_icon.is_null()); Ref<Image> icon = p_icon; @@ -778,33 +649,10 @@ 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(); - Callable cb = get_singleton()->input_event_callback; if (!cb.is_null()) { Variant ev = p_event; @@ -815,150 +663,85 @@ void DisplayServerJavaScript::_dispatch_input_event(const Ref<InputEvent> &p_eve } } -DisplayServer *DisplayServerJavaScript::create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - return memnew(DisplayServerJavaScript(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); -} - -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 */ - - RasterizerDummy::make_current(); // TODO GLES2 in Godot 4.0... or webgpu? -#if 0 - EmscriptenWebGLContextAttributes attributes; - emscripten_webgl_init_context_attributes(&attributes); - attributes.alpha = GLOBAL_GET("display/window/per_pixel_transparency/allowed"); - attributes.antialias = false; - ERR_FAIL_INDEX_V(p_video_driver, VIDEO_DRIVER_MAX, ERR_INVALID_PARAMETER); - - if (p_desired.layered) { - set_window_per_pixel_transparency_enabled(true); - } +DisplayServer *DisplayServerJavaScript::create_func(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Size2i &p_resolution, Error &r_error) { + return memnew(DisplayServerJavaScript(p_rendering_driver, p_window_mode, p_vsync_mode, p_flags, p_resolution, r_error)); +} - bool gl_initialization_error = false; +DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Size2i &p_resolution, Error &r_error) { + r_error = OK; // Always succeeds for now. - if (RasterizerGLES2::is_viable() == OK) { - attributes.majorVersion = 1; - RasterizerGLES2::register_config(); - RasterizerGLES2::make_current(); - } else { - gl_initialization_error = true; - } + // Ensure the canvas ID. + godot_js_config_canvas_id_get(canvas_id, 256); - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(canvas_id.utf8().get_data(), &attributes); - if (emscripten_webgl_make_context_current(ctx) != EMSCRIPTEN_RESULT_SUCCESS) { - gl_initialization_error = true; - } + // Handle contextmenu, webglcontextlost + godot_js_display_setup_canvas(p_resolution.x, p_resolution.y, p_window_mode == WINDOW_MODE_FULLSCREEN, OS::get_singleton()->is_hidpi_allowed() ? 1 : 0); - if (gl_initialization_error) { - OS::get_singleton()->alert("Your browser does not seem to support WebGL. Please update your browser version.", - "Unable to initialize video driver"); - return ERR_UNAVAILABLE; - } + // Check if it's windows. + swap_cancel_ok = godot_js_display_is_swap_ok_cancel() == 1; - video_driver_index = p_video_driver; -#endif + // Expose method for requesting quit. + godot_js_os_request_quit_cb(request_quit_callback); + +#ifdef GLES3_ENABLED + // TODO "vulkan" defaults to webgl2 for now. + bool wants_webgl2 = p_rendering_driver == "opengl3" || p_rendering_driver == "vulkan"; + bool webgl2_init_failed = wants_webgl2 && !godot_js_display_has_webgl(2); + if (wants_webgl2 && !webgl2_init_failed) { + EmscriptenWebGLContextAttributes attributes; + emscripten_webgl_init_context_attributes(&attributes); + //attributes.alpha = GLOBAL_GET("display/window/per_pixel_transparency/allowed"); + attributes.alpha = true; + attributes.antialias = false; + attributes.majorVersion = 2; - /* clang-format off */ - window_set_mode(p_mode); - if (EM_ASM_INT_V({ return Module['resizeCanvasOnStart'] })) { - /* clang-format on */ - window_set_size(p_resolution); + webgl_ctx = emscripten_webgl_create_context(canvas_id, &attributes); + if (emscripten_webgl_make_context_current(webgl_ctx) != EMSCRIPTEN_RESULT_SUCCESS) { + webgl2_init_failed = true; + } else { + RasterizerGLES3::make_current(); + } } + if (webgl2_init_failed) { + OS::get_singleton()->alert("Your browser does not seem to support WebGL2. Please update your browser version.", + "Unable to initialize video driver"); + } + if (!wants_webgl2 || webgl2_init_failed) { + RasterizerDummy::make_current(); + } +#else + RasterizerDummy::make_current(); +#endif - 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); \ - 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) - 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 */ + // JS Input interface (js/libs/library_godot_input.js) + godot_js_input_mouse_button_cb(&DisplayServerJavaScript::mouse_button_callback); + godot_js_input_mouse_move_cb(&DisplayServerJavaScript::mouse_move_callback); + godot_js_input_mouse_wheel_cb(&DisplayServerJavaScript::mouse_wheel_callback); + godot_js_input_touch_cb(&DisplayServerJavaScript::touch_callback, touch_event.identifier, touch_event.coords); + godot_js_input_key_cb(&DisplayServerJavaScript::key_callback, key_event.code, key_event.key); + godot_js_input_paste_cb(update_clipboard_callback); + godot_js_input_drop_files_cb(drop_files_js_callback); + godot_js_input_gamepad_cb(&DisplayServerJavaScript::gamepad_callback); + + // JS Display interface (js/libs/library_godot_display.js) + godot_js_display_fullscreen_cb(&DisplayServerJavaScript::fullscreen_change_callback); + godot_js_display_window_blur_cb(&window_blur_callback); + 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_vk_cb(&vk_input_text_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); +#ifdef GLES3_ENABLED + if (webgl_ctx) { + emscripten_webgl_commit_frame(); + emscripten_webgl_destroy_context(webgl_ctx); + } +#endif } bool DisplayServerJavaScript::has_feature(Feature p_feature) const { @@ -977,11 +760,11 @@ bool DisplayServerJavaScript::has_feature(Feature p_feature) const { //case FEATURE_MOUSE_WARP: //case FEATURE_NATIVE_DIALOG: //case FEATURE_NATIVE_ICON: - //case FEATURE_NATIVE_VIDEO: //case FEATURE_WINDOW_TRANSPARENCY: //case FEATURE_KEEP_SCREEN_ON: //case FEATURE_ORIENTATION: - //case FEATURE_VIRTUAL_KEYBOARD: + case FEATURE_VIRTUAL_KEYBOARD: + return godot_js_display_vk_available() != 0; default: return false; } @@ -1004,20 +787,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 { @@ -1051,7 +837,7 @@ void DisplayServerJavaScript::window_set_input_event_callback(const Callable &p_ } void DisplayServerJavaScript::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { - input_text_callback = p_callable; // TODO unused... do I need this? + input_text_callback = p_callable; } void DisplayServerJavaScript::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { @@ -1059,11 +845,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 +885,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 +905,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 +955,24 @@ bool DisplayServerJavaScript::can_any_window_draw() const { } void DisplayServerJavaScript::process_events() { - if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) + Input::get_singleton()->flush_buffered_events(); + if (godot_js_input_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(); +#ifdef GLES3_ENABLED + if (webgl_ctx) { + emscripten_webgl_commit_frame(); + } +#endif } |