diff options
Diffstat (limited to 'platform/javascript/display_server_javascript.cpp')
-rw-r--r-- | platform/javascript/display_server_javascript.cpp | 594 |
1 files changed, 215 insertions, 379 deletions
diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index 8dc33bdf64..fda18a5c19 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 */ @@ -30,13 +30,14 @@ #include "platform/javascript/display_server_javascript.h" -#include "drivers/dummy/rasterizer_dummy.h" #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 @@ -44,69 +45,34 @@ #define DOM_BUTTON_XBUTTON1 3 #define DOM_BUTTON_XBUTTON2 4 -char DisplayServerJavaScript::canvas_id[256] = { 0 }; -static bool cursor_inside_canvas = true; - DisplayServerJavaScript *DisplayServerJavaScript::get_singleton() { return static_cast<DisplayServerJavaScript *>(DisplayServer::get_singleton()); } // Window (canvas) void DisplayServerJavaScript::focus_canvas() { - /* clang-format off */ - EM_ASM( - Module['canvas'].focus(); - ); - /* clang-format on */ + godot_js_display_canvas_focus(); } bool DisplayServerJavaScript::is_canvas_focused() { - /* clang-format off */ - return EM_ASM_INT_V( - return document.activeElement == Module['canvas']; - ); - /* clang-format on */ + return godot_js_display_canvas_is_focused() != 0; } bool DisplayServerJavaScript::check_size_force_redraw() { - int canvas_width; - int canvas_height; - emscripten_get_canvas_element_size(DisplayServerJavaScript::canvas_id, &canvas_width, &canvas_height); - if (last_width != canvas_width || last_height != canvas_height) { - last_width = canvas_width; - last_height = canvas_height; - // Update the framebuffer size for redraw. - emscripten_set_canvas_element_size(DisplayServerJavaScript::canvas_id, canvas_width, canvas_height); - return true; - } - return false; + return godot_js_display_size_update() != 0; } Point2 DisplayServerJavaScript::compute_position_in_canvas(int p_x, int p_y) { - 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(canvas_id, &canvas_width, &canvas_height); - - double element_width; - double element_height; - emscripten_get_element_css_size(canvas_id, &element_width, &element_height); - - return Point2((int)(canvas_width / element_width * (p_x - canvas_x)), - (int)(canvas_height / element_height * (p_y - canvas_y))); + int point[2]; + godot_js_display_compute_position(p_x, p_y, point, point + 1); + return Point2(point[0], point[1]); } 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); - String canvas_str_id = String::utf8(canvas_id); - if (target_id.empty() || target_id == canvas_str_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) { @@ -118,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])); @@ -137,19 +104,31 @@ 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> 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); + godot_event->set_shift_pressed(emscripten_event_ptr->shiftKey); + godot_event->set_alt_pressed(emscripten_event_ptr->altKey); + godot_event->set_ctrl_pressed(emscripten_event_ptr->ctrlKey); + godot_event->set_meta_pressed(emscripten_event_ptr->metaKey); } Ref<InputEventKey> DisplayServerJavaScript::setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) { Ref<InputEventKey> ev; - ev.instance(); + ev.instantiate(); ev->set_echo(emscripten_event->repeat); dom2godot_mod(emscripten_event, ev); ev->set_keycode(dom_code2godot_scancode(emscripten_event->code, emscripten_event->key, false)); @@ -193,7 +172,7 @@ EM_BOOL DisplayServerJavaScript::keyup_callback(int p_event_type, const Emscript 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; + return ev->get_keycode() != KEY_UNKNOWN && ev->get_keycode() != (Key)0; } // Mouse @@ -202,7 +181,7 @@ EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const E DisplayServerJavaScript *display = get_singleton(); Ref<InputEventMouseButton> ev; - ev.instance(); + ev.instantiate(); 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()); @@ -210,19 +189,19 @@ EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const E switch (p_event->button) { case DOM_BUTTON_LEFT: - ev->set_button_index(BUTTON_LEFT); + ev->set_button_index(MOUSE_BUTTON_LEFT); break; case DOM_BUTTON_MIDDLE: - ev->set_button_index(BUTTON_MIDDLE); + ev->set_button_index(MOUSE_BUTTON_MIDDLE); break; case DOM_BUTTON_RIGHT: - ev->set_button_index(BUTTON_RIGHT); + ev->set_button_index(MOUSE_BUTTON_RIGHT); break; case DOM_BUTTON_XBUTTON1: - ev->set_button_index(BUTTON_XBUTTON1); + ev->set_button_index(MOUSE_BUTTON_XBUTTON1); break; case DOM_BUTTON_XBUTTON2: - ev->set_button_index(BUTTON_XBUTTON2); + ev->set_button_index(MOUSE_BUTTON_XBUTTON2); break; default: return false; @@ -236,14 +215,14 @@ EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const E display->last_click_ms = 0; display->last_click_pos = Point2(-100, -100); display->last_click_button_index = -1; - ev->set_doubleclick(true); + ev->set_double_click(true); } } else { display->last_click_button_index = ev->get_button_index(); } - if (!ev->is_doubleclick()) { + if (!ev->is_double_click()) { display->last_click_ms += diff; display->last_click_pos = ev->get_position(); } @@ -251,7 +230,7 @@ EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const E Input *input = Input::get_singleton(); int mask = input->get_mouse_button_mask(); - int button_flag = 1 << (ev->get_button_index() - 1); + MouseButton button_flag = MouseButton(1 << (ev->get_button_index() - 1)); 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. @@ -272,16 +251,17 @@ 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; - ev.instance(); + ev.instantiate(); dom2godot_mod(p_event, ev); ev->set_button_mask(input_mask); @@ -339,35 +319,13 @@ const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape } } -void DisplayServerJavaScript::set_css_cursor(const char *p_cursor) { - /* clang-format off */ - EM_ASM_({ - Module['canvas'].style.cursor = UTF8ToString($0); - }, p_cursor); - /* clang-format on */ -} - -bool DisplayServerJavaScript::is_css_cursor_hidden() const { - /* 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 { @@ -376,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; @@ -394,7 +341,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()) { @@ -417,7 +364,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()); @@ -449,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] = ""; - } - - 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); @@ -503,45 +407,43 @@ 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); + 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; @@ -550,23 +452,23 @@ 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)); + 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_event->deltaY < 0) - ev->set_button_index(BUTTON_WHEEL_UP); + ev->set_button_index(MOUSE_BUTTON_WHEEL_UP); else if (p_event->deltaY > 0) - ev->set_button_index(BUTTON_WHEEL_DOWN); + ev->set_button_index(MOUSE_BUTTON_WHEEL_DOWN); else if (p_event->deltaX > 0) - ev->set_button_index(BUTTON_WHEEL_LEFT); + ev->set_button_index(MOUSE_BUTTON_WHEEL_LEFT); else if (p_event->deltaX < 0) - ev->set_button_index(BUTTON_WHEEL_RIGHT); + ev->set_button_index(MOUSE_BUTTON_WHEEL_RIGHT); else return false; @@ -576,11 +478,11 @@ EM_BOOL DisplayServerJavaScript::wheel_callback(int p_event_type, const Emscript int button_flag = 1 << (ev->get_button_index() - 1); ev->set_pressed(true); - ev->set_button_mask(input->get_mouse_button_mask() | button_flag); + ev->set_button_mask(MouseButton(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); + ev->set_button_mask(MouseButton(input->get_mouse_button_mask() & ~button_flag)); input->parse_input_event(ev); return true; @@ -590,7 +492,7 @@ EM_BOOL DisplayServerJavaScript::wheel_callback(int p_event_type, const Emscript 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(); + ev.instantiate(); int lowest_id_index = -1; for (int i = 0; i < p_event->numTouches; ++i) { const EmscriptenTouchPoint &touch = p_event->touches[i]; @@ -612,7 +514,7 @@ EM_BOOL DisplayServerJavaScript::touch_press_callback(int p_event_type, const Em 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(); + ev.instantiate(); int lowest_id_index = -1; for (int i = 0; i < p_event->numTouches; ++i) { const EmscriptenTouchPoint &touch = p_event->touches[i]; @@ -632,63 +534,92 @@ 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 +// 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(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); + } +} + +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); +} -EM_BOOL DisplayServerJavaScript::gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data) { +void DisplayServerJavaScript::virtual_keyboard_hide() { + godot_js_display_vk_hide(); +} + +// 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_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::JoyAxisValue joy_axis; + joy_axis.min = 0; + joy_axis.value = value; + JoyAxis a = b == 6 ? JOY_AXIS_TRIGGER_LEFT : JOY_AXIS_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"); @@ -696,53 +627,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; @@ -751,14 +659,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; @@ -787,29 +687,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(); @@ -824,23 +706,24 @@ 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)); +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)); } -DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { +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. - /* clang-format off */ - swap_cancel_ok = EM_ASM_INT({ - const win = (['Windows', 'Win64', 'Win32', 'WinCE']); - const plat = navigator.platform || ""; - if (win.indexOf(plat) !== -1) { - return 1; - } - return 0; - }) == 1; - /* clang-format on */ + // 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_window_mode == WINDOW_MODE_FULLSCREEN, OS::get_singleton()->is_hidpi_allowed() ? 1 : 0); + + // 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 @@ -878,13 +761,6 @@ 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; #define EM_CHECK(ev) \ if (result != EMSCRIPTEN_RESULT_SUCCESS) \ @@ -892,15 +768,11 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive #define SET_EM_CALLBACK(target, ev, cb) \ result = emscripten_set_##ev##_callback(target, nullptr, true, &cb); \ EM_CHECK(ev) -#define SET_EM_WINDOW_CALLBACK(ev, cb) \ - result = emscripten_set_##ev##_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, &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, nullptr, 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. + // 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) @@ -913,48 +785,25 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive 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({ - // Bind native event listeners. - // Module.listeners, and Module.drop_handler are defined in native/utils.js - 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.add(canvas, event, send_window_event.bind(null, notifications[index]), true); - }); - // Clipboard - const update_clipboard = cwrap('update_clipboard', null, ['string']); - Module.listeners.add(window, 'paste', function(evt) { - update_clipboard(evt.clipboardData.getData('text')); - }, false); - // Drag an drop - Module.listeners.add(canvas, 'dragover', function(ev) { - // Prevent default behavior (which would try to open the file(s)) - ev.preventDefault(); - }, false); - Module.listeners.add(canvas, 'drop', Module.drop_handler, 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); + godot_js_display_vk_cb(&vk_input_text_callback); Input::get_singleton()->set_event_dispatch_function(_dispatch_input_event); } DisplayServerJavaScript::~DisplayServerJavaScript() { - EM_ASM({ - Module.listeners.clear(); - }); //emscripten_webgl_commit_frame(); //emscripten_webgl_destroy_context(webgl_ctx); } @@ -975,11 +824,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; } @@ -1002,20 +851,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, 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 { @@ -1049,7 +901,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) { @@ -1057,11 +909,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 { @@ -1101,19 +949,13 @@ Size2i DisplayServerJavaScript::window_get_min_size(WindowID p_window) const { } void DisplayServerJavaScript::window_set_size(const Size2i p_size, WindowID p_window) { - last_width = p_size.x; - last_height = p_size.y; - double scale = EM_ASM_DOUBLE({ - return window.devicePixelRatio || 1; - }); - emscripten_set_canvas_element_size(canvas_id, p_size.x * scale, p_size.y * scale); - emscripten_set_element_css_size(canvas_id, 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, 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 { @@ -1127,20 +969,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, 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: @@ -1184,8 +1019,9 @@ 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 { |