Merge pull request #46913 from Faless/js/4.x_vk

[HTML5] Experimental (opt-in) virtual keyboard support.
This commit is contained in:
Rémi Verschelde 2021-03-12 09:48:34 +01:00 committed by GitHub
commit 08767a16fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 208 additions and 10 deletions

View file

@ -536,6 +536,43 @@ bool DisplayServerJavaScript::screen_is_touchscreen(int p_screen) const {
return godot_js_display_touchscreen_is_available();
}
// Virtual Keybaord
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.instance();
k->set_pressed(true);
k->set_echo(false);
k->set_keycode(KEY_RIGHT);
input->parse_input_event(k);
k.instance();
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);
}
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();
@ -764,6 +801,7 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive
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);
}
@ -793,7 +831,8 @@ bool DisplayServerJavaScript::has_feature(Feature p_feature) const {
//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;
}
@ -866,7 +905,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) {

View file

@ -75,6 +75,8 @@ private:
static EM_BOOL keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
static EM_BOOL keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
static void vk_input_text_callback(const char *p_text, int p_cursor);
static EM_BOOL mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data);
static EM_BOOL mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data);
@ -135,6 +137,9 @@ public:
int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1) override;
void virtual_keyboard_hide() override;
// windows
Vector<DisplayServer::WindowID> get_window_list() const override;
WindowID get_window_at_screen_position(const Point2i &p_position) const override;

View file

@ -297,6 +297,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re
}
Dictionary config;
config["canvasResizePolicy"] = p_preset->get("html/canvas_resize_policy");
config["experimentalVK"] = p_preset->get("html/experimental_virtual_keyboard");
config["gdnativeLibs"] = libs;
config["executable"] = p_name;
config["args"] = args;
@ -352,6 +353,7 @@ void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_op
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_FILE, "*.html"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/head_include", PROPERTY_HINT_MULTILINE_TEXT), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "html/canvas_resize_policy", PROPERTY_HINT_ENUM, "None,Project,Adaptive"), 2));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/experimental_virtual_keyboard"), false));
}
String EditorExportPlatformJavaScript::get_name() const {

View file

@ -93,6 +93,13 @@ extern void godot_js_display_notification_cb(void (*p_callback)(int p_notificati
extern void godot_js_display_paste_cb(void (*p_callback)(const char *p_text));
extern void godot_js_display_drop_files_cb(void (*p_callback)(char **p_filev, int p_filec));
extern void godot_js_display_setup_canvas(int p_width, int p_height, int p_fullscreen, int p_hidpi);
// Display Virtual Keyboard
extern int godot_js_display_vk_available();
extern void godot_js_display_vk_cb(void (*p_input)(const char *p_text, int p_cursor));
extern void godot_js_display_vk_show(const char *p_text, int p_multiline, int p_start, int p_end);
extern void godot_js_display_vk_hide();
#ifdef __cplusplus
}
#endif

View file

@ -90,6 +90,14 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
* @default
*/
args: [],
/**
* When enabled, this will turn on experimental virtual keyboard support on mobile.
*
* @memberof EngineConfig
* @type {boolean}
* @default
*/
experimentalVK: false,
/**
* @ignore
* @type {Array.<string>}
@ -223,6 +231,7 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
this.locale = parse('locale', this.locale);
this.canvasResizePolicy = parse('canvasResizePolicy', this.canvasResizePolicy);
this.persistentPaths = parse('persistentPaths', this.persistentPaths);
this.experimentalVK = parse('experimentalVK', this.experimentalVK);
this.gdnativeLibs = parse('gdnativeLibs', this.gdnativeLibs);
this.fileSizes = parse('fileSizes', this.fileSizes);
this.args = parse('args', this.args);
@ -307,6 +316,7 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
'canvas': this.canvas,
'canvasResizePolicy': this.canvasResizePolicy,
'locale': locale,
'virtualKeyboard': this.experimentalVK,
'onExecute': this.onExecute,
'onExit': function (p_code) {
cleanup(); // We always need to call the cleanup callback to free memory.

View file

@ -231,6 +231,105 @@ const GodotDisplayDragDrop = {
};
mergeInto(LibraryManager.library, GodotDisplayDragDrop);
const GodotDisplayVK = {
$GodotDisplayVK__deps: ['$GodotRuntime', '$GodotConfig', '$GodotDisplayListeners'],
$GodotDisplayVK__postset: 'GodotOS.atexit(function(resolve, reject) { GodotDisplayVK.clear(); resolve(); });',
$GodotDisplayVK: {
textinput: null,
textarea: null,
available: function () {
return GodotConfig.virtual_keyboard && 'ontouchstart' in window;
},
init: function (input_cb) {
function create(what) {
const elem = document.createElement(what);
elem.style.display = 'none';
elem.style.position = 'absolute';
elem.style.zIndex = '-1';
elem.style.background = 'transparent';
elem.style.padding = '0px';
elem.style.margin = '0px';
elem.style.overflow = 'hidden';
elem.style.width = '0px';
elem.style.height = '0px';
elem.style.border = '0px';
elem.style.outline = 'none';
elem.readonly = true;
elem.disabled = true;
GodotDisplayListeners.add(elem, 'input', function (evt) {
const c_str = GodotRuntime.allocString(elem.value);
input_cb(c_str, elem.selectionEnd);
GodotRuntime.free(c_str);
}, false);
GodotDisplayListeners.add(elem, 'blur', function (evt) {
elem.style.display = 'none';
elem.readonly = true;
elem.disabled = true;
}, false);
GodotConfig.canvas.insertAdjacentElement('beforebegin', elem);
return elem;
}
GodotDisplayVK.textinput = create('input');
GodotDisplayVK.textarea = create('textarea');
GodotDisplayVK.updateSize();
},
show: function (text, multiline, start, end) {
if (!GodotDisplayVK.textinput || !GodotDisplayVK.textarea) {
return;
}
if (GodotDisplayVK.textinput.style.display !== '' || GodotDisplayVK.textarea.style.display !== '') {
GodotDisplayVK.hide();
}
GodotDisplayVK.updateSize();
const elem = multiline ? GodotDisplayVK.textarea : GodotDisplayVK.textinput;
elem.readonly = false;
elem.disabled = false;
elem.value = text;
elem.style.display = 'block';
elem.focus();
elem.setSelectionRange(start, end);
},
hide: function () {
if (!GodotDisplayVK.textinput || !GodotDisplayVK.textarea) {
return;
}
[GodotDisplayVK.textinput, GodotDisplayVK.textarea].forEach(function (elem) {
elem.blur();
elem.style.display = 'none';
elem.value = '';
});
},
updateSize: function () {
if (!GodotDisplayVK.textinput || !GodotDisplayVK.textarea) {
return;
}
const rect = GodotConfig.canvas.getBoundingClientRect();
function update(elem) {
elem.style.left = `${rect.left}px`;
elem.style.top = `${rect.top}px`;
elem.style.width = `${rect.width}px`;
elem.style.height = `${rect.height}px`;
}
update(GodotDisplayVK.textinput);
update(GodotDisplayVK.textarea);
},
clear: function () {
if (GodotDisplayVK.textinput) {
GodotDisplayVK.textinput.remove();
GodotDisplayVK.textinput = null;
}
if (GodotDisplayVK.textarea) {
GodotDisplayVK.textarea.remove();
GodotDisplayVK.textarea = null;
}
},
},
};
mergeInto(LibraryManager.library, GodotDisplayVK);
/*
* Display server cursor helper.
* Keeps track of cursor status and custom shapes.
@ -511,7 +610,7 @@ mergeInto(LibraryManager.library, GodotDisplayScreen);
* Exposes all the functions needed by DisplayServer implementation.
*/
const GodotDisplay = {
$GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotDisplayListeners', '$GodotDisplayDragDrop', '$GodotDisplayGamepads', '$GodotDisplayScreen'],
$GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotDisplayListeners', '$GodotDisplayDragDrop', '$GodotDisplayGamepads', '$GodotDisplayScreen', '$GodotDisplayVK'],
$GodotDisplay: {
window_icon: '',
findDPI: function () {
@ -580,7 +679,11 @@ const GodotDisplay = {
godot_js_display_size_update__sig: 'i',
godot_js_display_size_update: function () {
return GodotDisplayScreen.updateSize();
const updated = GodotDisplayScreen.updateSize();
if (updated) {
GodotDisplayVK.updateSize();
}
return updated;
},
godot_js_display_screen_size_get__sig: 'vii',
@ -811,6 +914,35 @@ const GodotDisplay = {
}
},
/*
* Virtual Keyboard
*/
godot_js_display_vk_show__sig: 'viiii',
godot_js_display_vk_show: function (p_text, p_multiline, p_start, p_end) {
const text = GodotRuntime.parseString(p_text);
const start = p_start > 0 ? p_start : 0;
const end = p_end > 0 ? p_end : start;
GodotDisplayVK.show(text, p_multiline, start, end);
},
godot_js_display_vk_hide__sig: 'v',
godot_js_display_vk_hide: function () {
GodotDisplayVK.hide();
},
godot_js_display_vk_available__sig: 'i',
godot_js_display_vk_available: function () {
return GodotDisplayVK.available();
},
godot_js_display_vk_cb__sig: 'vi',
godot_js_display_vk_cb: function (p_input_cb) {
const input_cb = GodotRuntime.get_func(p_input_cb);
if (GodotDisplayVK.available()) {
GodotDisplayVK.init(input_cb);
}
},
/*
* Gamepads
*/

View file

@ -59,6 +59,7 @@ const GodotConfig = {
canvas: null,
locale: 'en',
canvas_resize_policy: 2, // Adaptive
virtual_keyboard: false,
on_execute: null,
on_exit: null,
@ -66,6 +67,7 @@ const GodotConfig = {
GodotConfig.canvas_resize_policy = p_opts['canvasResizePolicy'];
GodotConfig.canvas = p_opts['canvas'];
GodotConfig.locale = p_opts['locale'] || GodotConfig.locale;
GodotConfig.virtual_keyboard = p_opts['virtualKeyboard'];
GodotConfig.on_execute = p_opts['onExecute'];
GodotConfig.on_exit = p_opts['onExit'];
},
@ -77,6 +79,7 @@ const GodotConfig = {
GodotConfig.canvas = null;
GodotConfig.locale = 'en';
GodotConfig.canvas_resize_policy = 2;
GodotConfig.virtual_keyboard = false;
GodotConfig.on_execute = null;
GodotConfig.on_exit = null;
},

View file

@ -848,7 +848,7 @@ void LineEdit::_notification(int p_what) {
}
if (has_focus()) {
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + Point2(using_placeholder ? 0 : x_ofs, y_ofs + TS->shaped_text_get_size(text_rid).y), get_viewport()->get_window_id());
}
@ -865,7 +865,7 @@ void LineEdit::_notification(int p_what) {
}
}
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
Point2 cursor_pos = Point2(get_cursor_position(), 1) * get_minimum_size().height;
DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos, get_viewport()->get_window_id());
@ -878,7 +878,7 @@ void LineEdit::_notification(int p_what) {
caret_blink_timer->stop();
}
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id());
DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id());
}

View file

@ -1702,7 +1702,7 @@ void TextEdit::_notification(int p_what) {
}
if (has_focus()) {
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos, get_viewport()->get_window_id());
}
@ -1715,7 +1715,7 @@ void TextEdit::_notification(int p_what) {
draw_caret = true;
}
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + _get_cursor_pixel_pos(false), get_viewport()->get_window_id());
}
@ -1744,7 +1744,7 @@ void TextEdit::_notification(int p_what) {
caret_blink_timer->stop();
}
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id());
DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id());
}