Merge pull request #46913 from Faless/js/4.x_vk
[HTML5] Experimental (opt-in) virtual keyboard support.
This commit is contained in:
commit
08767a16fd
9 changed files with 208 additions and 10 deletions
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue