summaryrefslogtreecommitdiff
path: root/platform/javascript/display_server_javascript.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'platform/javascript/display_server_javascript.cpp')
-rw-r--r--platform/javascript/display_server_javascript.cpp645
1 files changed, 332 insertions, 313 deletions
diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp
index e2c0a3d763..a96c539a1f 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-2021 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2021 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 */
@@ -28,10 +28,13 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "platform/javascript/display_server_javascript.h"
+#include "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/dummy/rasterizer_dummy.h"
#include <emscripten.h>
#include <png.h>
@@ -50,38 +53,17 @@ DisplayServerJavaScript *DisplayServerJavaScript::get_singleton() {
}
// Window (canvas)
-void DisplayServerJavaScript::focus_canvas() {
- godot_js_display_canvas_focus();
-}
-
-bool DisplayServerJavaScript::is_canvas_focused() {
- return godot_js_display_canvas_is_focused() != 0;
-}
-
bool DisplayServerJavaScript::check_size_force_redraw() {
return godot_js_display_size_update() != 0;
}
-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]);
-}
-
-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.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) {
- 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.
@@ -118,125 +100,96 @@ void DisplayServerJavaScript::request_quit_callback() {
// 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);
+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);
}
-Ref<InputEventKey> DisplayServerJavaScript::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);
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.
@@ -244,37 +197,39 @@ 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) {
- 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);
+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 (!ds->cursor_inside_canvas && !input_mask)
- return false;
+ if (!get_singleton()->cursor_inside_canvas && input_mask == MouseButton::NONE) {
+ return;
+ }
+ Point2 pos(p_x, p_y);
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));
+ ev->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
- input->parse_input_event(ev);
- // Don't suppress mouseover/-leave events.
- return false;
+ Input::get_singleton()->parse_input_event(ev);
}
// Cursor
@@ -289,9 +244,9 @@ const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape
case DisplayServer::CURSOR_CROSS:
return "crosshair";
case DisplayServer::CURSOR_WAIT:
- return "progress";
- case DisplayServer::CURSOR_BUSY:
return "wait";
+ case DisplayServer::CURSOR_BUSY:
+ return "progress";
case DisplayServer::CURSOR_DRAG:
return "grab";
case DisplayServer::CURSOR_CAN_DROP:
@@ -319,6 +274,90 @@ const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape
}
}
+bool DisplayServerJavaScript::tts_is_speaking() const {
+ return godot_js_tts_is_speaking();
+}
+
+bool DisplayServerJavaScript::tts_is_paused() const {
+ return godot_js_tts_is_paused();
+}
+
+void DisplayServerJavaScript::update_voices_callback(int p_size, const char **p_voice) {
+ get_singleton()->voices.clear();
+ for (int i = 0; i < p_size; i++) {
+ Vector<String> tokens = String::utf8(p_voice[i]).split(";", true, 2);
+ if (tokens.size() == 2) {
+ Dictionary voice_d;
+ voice_d["name"] = tokens[1];
+ voice_d["id"] = tokens[1];
+ voice_d["language"] = tokens[0];
+ get_singleton()->voices.push_back(voice_d);
+ }
+ }
+}
+
+Array DisplayServerJavaScript::tts_get_voices() const {
+ godot_js_tts_get_voices(update_voices_callback);
+ return voices;
+}
+
+void DisplayServerJavaScript::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
+ if (p_interrupt) {
+ tts_stop();
+ }
+
+ if (p_text.is_empty()) {
+ tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, p_utterance_id);
+ return;
+ }
+
+ CharString string = p_text.utf8();
+ utterance_ids[p_utterance_id] = string;
+
+ godot_js_tts_speak(string.get_data(), p_voice.utf8().get_data(), CLAMP(p_volume, 0, 100), CLAMP(p_pitch, 0.f, 2.f), CLAMP(p_rate, 0.1f, 10.f), p_utterance_id, DisplayServerJavaScript::_js_utterance_callback);
+}
+
+void DisplayServerJavaScript::tts_pause() {
+ godot_js_tts_pause();
+}
+
+void DisplayServerJavaScript::tts_resume() {
+ godot_js_tts_resume();
+}
+
+void DisplayServerJavaScript::tts_stop() {
+ for (const KeyValue<int, CharString> &E : utterance_ids) {
+ tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, E.key);
+ }
+ utterance_ids.clear();
+ godot_js_tts_stop();
+}
+
+void DisplayServerJavaScript::_js_utterance_callback(int p_event, int p_id, int p_pos) {
+ DisplayServerJavaScript *ds = (DisplayServerJavaScript *)DisplayServer::get_singleton();
+ if (ds->utterance_ids.has(p_id)) {
+ int pos = 0;
+ if ((TTSUtteranceEvent)p_event == DisplayServer::TTS_UTTERANCE_BOUNDARY) {
+ // Convert position from UTF-8 to UTF-32.
+ const CharString &string = ds->utterance_ids[p_id];
+ for (int i = 0; i < MIN(p_pos, string.length()); i++) {
+ uint8_t c = string[i];
+ if ((c & 0xe0) == 0xc0) {
+ i += 1;
+ } else if ((c & 0xf0) == 0xe0) {
+ i += 2;
+ } else if ((c & 0xf8) == 0xf0) {
+ i += 3;
+ }
+ pos++;
+ }
+ } else if ((TTSUtteranceEvent)p_event != DisplayServer::TTS_UTTERANCE_STARTED) {
+ ds->utterance_ids.erase(p_id);
+ }
+ ds->tts_post_utterance_event((TTSUtteranceEvent)p_event, p_id, pos);
+ }
+}
+
void DisplayServerJavaScript::cursor_set_shape(CursorShape p_shape) {
ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
if (cursor_shape == p_shape) {
@@ -332,7 +371,7 @@ DisplayServer::CursorShape DisplayServerJavaScript::cursor_get_shape() const {
return cursor_shape;
}
-void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+void DisplayServerJavaScript::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
if (p_cursor.is_valid()) {
Ref<Texture2D> texture = p_cursor;
Ref<AtlasTexture> atlas_texture = p_cursor;
@@ -341,7 +380,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()) {
@@ -364,18 +403,19 @@ 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());
image = image->duplicate();
- if (atlas_texture.is_valid())
+ if (atlas_texture.is_valid()) {
image->crop_from_point(
atlas_rect.position.x,
atlas_rect.position.y,
texture_size.width,
texture_size.height);
+ }
if (image->get_format() != Image::FORMAT_RGBA8) {
image->convert(Image::FORMAT_RGBA8);
@@ -399,7 +439,7 @@ void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, Curso
godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), png.ptr(), len, p_hotspot.x, p_hotspot.y);
} else {
- godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), NULL, 0, 0, 0);
+ godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), nullptr, 0, 0, 0);
}
cursor_set_shape(cursor_shape);
@@ -407,23 +447,22 @@ 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) {
godot_js_display_cursor_set_visible(1);
- emscripten_exit_pointerlock();
+ godot_js_display_cursor_lock_set(0);
} else if (p_mode == MOUSE_MODE_HIDDEN) {
godot_js_display_cursor_set_visible(0);
- emscripten_exit_pointerlock();
+ godot_js_display_cursor_lock_set(0);
} else if (p_mode == MOUSE_MODE_CAPTURED) {
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.");
+ godot_js_display_cursor_lock_set(1);
}
}
@@ -432,18 +471,21 @@ DisplayServer::MouseMode DisplayServerJavaScript::mouse_get_mode() const {
return MOUSE_MODE_HIDDEN;
}
- EmscriptenPointerlockChangeEvent ev;
- emscripten_get_pointerlock_status(&ev);
- return (ev.isActive && String::utf8(ev.id) == String::utf8(&canvas_id[1])) ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE;
+ if (godot_js_display_cursor_is_locked()) {
+ return MOUSE_MODE_CAPTURED;
+ }
+ return MOUSE_MODE_VISIBLE;
+}
+
+Point2i DisplayServerJavaScript::mouse_get_position() const {
+ return Input::get_singleton()->get_mouse_position();
}
// 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 (ds->cursor_inside_canvas) {
- focus_canvas();
+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;
}
@@ -451,99 +493,96 @@ 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();
- Input::get_singleton()->parse_input_event(ev);
- }
- // Resume audio context after input in case autoplay was denied.
- return true;
-}
+ 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;
-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();
+ // 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();
+ }
}
- return true;
}
bool DisplayServerJavaScript::screen_is_touchscreen(int p_screen) const {
return godot_js_display_touchscreen_is_available();
}
-// Virtual Keybaord
+// 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 event = String::utf8(p_text);
Variant *eventp = &event;
Variant ret;
Callable::CallError ce;
@@ -552,15 +591,15 @@ void DisplayServerJavaScript::vk_input_text_callback(const char *p_text, int p_c
Input *input = Input::get_singleton();
Ref<InputEventKey> k;
for (int i = 0; i < p_cursor; i++) {
- k.instance();
+ k.instantiate();
k->set_pressed(true);
k->set_echo(false);
- k->set_keycode(KEY_RIGHT);
+ k->set_keycode(Key::RIGHT);
input->parse_input_event(k);
- k.instance();
+ k.instantiate();
k->set_pressed(false);
k->set_echo(false);
- k->set_keycode(KEY_RIGHT);
+ k->set_keycode(Key::RIGHT);
input->parse_input_event(k);
}
}
@@ -573,6 +612,10 @@ void DisplayServerJavaScript::virtual_keyboard_hide() {
godot_js_display_vk_hide();
}
+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();
@@ -585,49 +628,43 @@ void DisplayServerJavaScript::gamepad_callback(int p_index, int p_connected, con
void DisplayServerJavaScript::process_joypads() {
Input *input = Input::get_singleton();
- int32_t pads = godot_js_display_gamepad_sample_count();
+ 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_display_gamepad_sample_get(idx, s_btns, &s_btns_num, s_axes, &s_axes_num, &s_standard);
+ 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 JOY_AXIS_TRIGGER by Godot.
+ // axis to be handled as JoyAxis::TRIGGER by Godot.
if (s_standard && (b == 6 || b == 7)) {
- Input::JoyAxis 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);
+ input->joy_axis(idx, (JoyAxis)b, s_btns[b]);
} else {
- input->joy_button(idx, b, value);
+ input->joy_button(idx, (JoyButton)b, s_btns[b]);
}
}
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);
+ input->joy_axis(idx, (JoyAxis)a, s_axes[a]);
}
}
}
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
void DisplayServerJavaScript::update_clipboard_callback(const char *p_text) {
- get_singleton()->clipboard = p_text;
+ get_singleton()->clipboard = String::utf8(p_text);
}
void DisplayServerJavaScript::clipboard_set(const String &p_text) {
@@ -658,10 +695,6 @@ void DisplayServerJavaScript::send_window_event_callback(int p_notification) {
}
}
-void DisplayServerJavaScript::alert(const String &p_alert, const String &p_title) {
- godot_js_display_alert(p_alert.utf8().get_data());
-}
-
void DisplayServerJavaScript::set_icon(const Ref<Image> &p_icon) {
ERR_FAIL_COND(p_icon.is_null());
Ref<Image> icon = p_icon;
@@ -670,8 +703,9 @@ void DisplayServerJavaScript::set_icon(const Ref<Image> &p_icon) {
ERR_FAIL_COND(icon->decompress() != OK);
}
if (icon->get_format() != Image::FORMAT_RGBA8) {
- if (icon == p_icon)
+ if (icon == p_icon) {
icon = icon->duplicate();
+ }
icon->convert(Image::FORMAT_RGBA8);
}
@@ -694,11 +728,6 @@ void DisplayServerJavaScript::set_icon(const Ref<Image> &p_icon) {
}
void DisplayServerJavaScript::_dispatch_input_event(const Ref<InputEvent> &p_event) {
- OS_JavaScript *os = OS_JavaScript::get_singleton();
-
- // 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;
@@ -709,18 +738,18 @@ 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.
// 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, OS::get_singleton()->is_hidpi_allowed() ? 1 : 0);
+ godot_js_display_setup_canvas(p_resolution.x, p_resolution.y, (p_window_mode == WINDOW_MODE_FULLSCREEN || p_window_mode == WINDOW_MODE_EXCLUSIVE_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;
@@ -728,92 +757,70 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive
// 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
- 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);
- }
-
- bool gl_initialization_error = false;
-
- if (RasterizerGLES2::is_viable() == OK) {
- attributes.majorVersion = 1;
- RasterizerGLES2::register_config();
- RasterizerGLES2::make_current();
- } else {
- gl_initialization_error = true;
- }
-
- 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;
+#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;
+
+ 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 (gl_initialization_error) {
- OS::get_singleton()->alert("Your browser does not seem to support WebGL. Please update your browser version.",
+ 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");
- return ERR_UNAVAILABLE;
}
-
- video_driver_index = p_video_driver;
+ if (!wants_webgl2 || webgl2_init_failed) {
+ RasterizerDummy::make_current();
+ }
+#else
+ RasterizerDummy::make_current();
#endif
- EMSCRIPTEN_RESULT result;
-#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_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.
- 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)
-#undef SET_EM_CALLBACK
-#undef EM_CHECK
-
- // For APIs that are not (sufficiently) exposed, a
- // library is used below (implemented in library_godot_display.js).
+ // 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_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() {
- //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 {
switch (p_feature) {
- //case FEATURE_CONSOLE_WINDOW:
//case FEATURE_GLOBAL_MENU:
//case FEATURE_HIDPI:
//case FEATURE_IME:
@@ -827,12 +834,13 @@ 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:
return godot_js_display_vk_available() != 0;
+ case FEATURE_TEXT_TO_SPEECH:
+ return godot_js_display_tts_available() != 0;
default:
return false;
}
@@ -874,6 +882,10 @@ float DisplayServerJavaScript::screen_get_scale(int p_screen) const {
return godot_js_display_pixel_ratio_get();
}
+float DisplayServerJavaScript::screen_get_refresh_rate(int p_screen) const {
+ return SCREEN_REFRESH_RATE_FALLBACK; // Javascript doesn't have much of a need for the screen refresh rate, and there's no native way to do so.
+}
+
Vector<DisplayServer::WindowID> DisplayServerJavaScript::get_window_list() const {
Vector<WindowID> ret;
ret.push_back(MAIN_WINDOW_ID);
@@ -967,8 +979,9 @@ Size2i DisplayServerJavaScript::window_get_real_size(WindowID p_window) const {
}
void DisplayServerJavaScript::window_set_mode(WindowMode p_mode, WindowID p_window) {
- if (window_mode == p_mode)
+ if (window_mode == p_mode) {
return;
+ }
switch (p_mode) {
case WINDOW_MODE_WINDOWED: {
@@ -977,6 +990,7 @@ void DisplayServerJavaScript::window_set_mode(WindowMode p_mode, WindowID p_wind
}
window_mode = WINDOW_MODE_WINDOWED;
} break;
+ case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
case WINDOW_MODE_FULLSCREEN: {
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.");
@@ -1023,7 +1037,8 @@ bool DisplayServerJavaScript::can_any_window_draw() const {
}
void DisplayServerJavaScript::process_events() {
- if (godot_js_display_gamepad_sample() == OK) {
+ Input::get_singleton()->flush_buffered_events();
+ if (godot_js_input_gamepad_sample() == OK) {
process_joypads();
}
}
@@ -1037,5 +1052,9 @@ bool DisplayServerJavaScript::get_swap_cancel_ok() {
}
void DisplayServerJavaScript::swap_buffers() {
- //emscripten_webgl_commit_frame();
+#ifdef GLES3_ENABLED
+ if (webgl_ctx) {
+ emscripten_webgl_commit_frame();
+ }
+#endif
}