Implement input focus behavior in HTML5

- Key and mouse events are only consumed if canvas is focused
 - NOTIFICATION_WM_MOUSE_ENTER, _MOUSE_EXIT, _FOCUS_IN and _FOCUS_OUT are
   emitted
This commit is contained in:
L. Krause 2017-07-22 15:43:05 +02:00
parent e4dc52ba81
commit ca28c068c0
3 changed files with 94 additions and 31 deletions

View File

@ -19,7 +19,7 @@ javascript_objects = []
for x in javascript_files: for x in javascript_files:
javascript_objects.append(env_javascript.Object(x)) javascript_objects.append(env_javascript.Object(x))
env.Append(LINKFLAGS=["-s", "EXPORTED_FUNCTIONS=\"['_main','_audio_server_mix_function','_main_after_fs_sync']\""]) env.Append(LINKFLAGS=["-s", "EXPORTED_FUNCTIONS=\"['_main','_audio_server_mix_function','_main_after_fs_sync','_send_notification']\""])
env.Append(LINKFLAGS=["--shell-file", '"platform/javascript/godot_shell.html"']) env.Append(LINKFLAGS=["--shell-file", '"platform/javascript/godot_shell.html"'])
# output file name without file extension # output file name without file extension

View File

@ -83,6 +83,10 @@
color: white; color: white;
} }
#canvas:focus {
outline: none;
}
/* Status display /* Status display
* ============== */ * ============== */
@ -147,7 +151,7 @@ $GODOT_HEAD_INCLUDE
</head> </head>
<body> <body>
<div id="container"> <div id="container">
<canvas id="canvas" width="640" height="480" onclick="canvas.ownerDocument.defaultView.focus();" oncontextmenu="event.preventDefault();"> <canvas id="canvas" width="640" height="480" tabindex="0" oncontextmenu="event.preventDefault();">
HTML5 canvas appears to be unsupported in the current browser.<br /> HTML5 canvas appears to be unsupported in the current browser.<br />
Please try updating or use a different browser. Please try updating or use a different browser.
</canvas> </canvas>

View File

@ -145,6 +145,31 @@ static EM_BOOL _fullscreen_change_callback(int event_type, const EmscriptenFulls
static InputDefault *_input; static InputDefault *_input;
static bool is_canvas_focused() {
/* clang-format off */
return EM_ASM_INT_V(
return document.activeElement == Module.canvas;
);
/* clang-format on */
}
static void focus_canvas() {
/* clang-format off */
EM_ASM(
Module.canvas.focus();
);
/* clang-format on */
}
static bool _cursor_inside_canvas = true;
static bool is_cursor_inside_canvas() {
return _cursor_inside_canvas;
}
static EM_BOOL _mousebutton_callback(int event_type, const EmscriptenMouseEvent *mouse_event, void *user_data) { static EM_BOOL _mousebutton_callback(int event_type, const EmscriptenMouseEvent *mouse_event, void *user_data) {
ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_MOUSEDOWN && event_type != EMSCRIPTEN_EVENT_MOUSEUP, false); ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_MOUSEDOWN && event_type != EMSCRIPTEN_EVENT_MOUSEUP, false);
@ -164,26 +189,42 @@ static EM_BOOL _mousebutton_callback(int event_type, const EmscriptenMouseEvent
} }
int mask = _input->get_mouse_button_mask(); int mask = _input->get_mouse_button_mask();
if (ev->is_pressed()) if (ev->is_pressed()) {
// since the event is consumed, focus manually
if (!is_canvas_focused()) {
focus_canvas();
}
mask |= 1 << ev->get_button_index(); mask |= 1 << ev->get_button_index();
else } else if (mask & (1 << ev->get_button_index())) {
mask &= ~(1 << ev->get_button_index()); mask &= ~(1 << ev->get_button_index());
} else {
// release event, but press was outside the canvas, so ignore
return false;
}
ev->set_button_mask(mask >> 1); ev->set_button_mask(mask >> 1);
_input->parse_input_event(ev); _input->parse_input_event(ev);
// prevent selection dragging
return true; return true;
} }
static EM_BOOL _mousemove_callback(int event_type, const EmscriptenMouseEvent *mouse_event, void *user_data) { static EM_BOOL _mousemove_callback(int event_type, const EmscriptenMouseEvent *mouse_event, void *user_data) {
ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_MOUSEMOVE, false); ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_MOUSEMOVE, false);
OS_JavaScript *os = static_cast<OS_JavaScript *>(user_data);
int input_mask = _input->get_mouse_button_mask();
Point2 pos = Point2(mouse_event->canvasX, mouse_event->canvasY);
// outside the canvas, only read mouse movement if dragging started inside
// the canvas; imitating desktop app behaviour
if (!is_cursor_inside_canvas() && !input_mask)
return false;
Ref<InputEventMouseMotion> ev; Ref<InputEventMouseMotion> ev;
ev.instance(); ev.instance();
dom2godot_mod(mouse_event, ev); dom2godot_mod(mouse_event, ev);
ev->set_button_mask(_input->get_mouse_button_mask() >> 1); ev->set_button_mask(input_mask >> 1);
ev->set_position(Point2(mouse_event->canvasX, mouse_event->canvasY)); ev->set_position(pos);
ev->set_global_position(ev->get_position()); ev->set_global_position(ev->get_position());
ev->set_relative(_input->get_mouse_position() - ev->get_position()); ev->set_relative(_input->get_mouse_position() - ev->get_position());
@ -191,12 +232,20 @@ static EM_BOOL _mousemove_callback(int event_type, const EmscriptenMouseEvent *m
ev->set_speed(_input->get_last_mouse_speed()); ev->set_speed(_input->get_last_mouse_speed());
_input->parse_input_event(ev); _input->parse_input_event(ev);
return true; // don't suppress mouseover/leave events
return false;
} }
static EM_BOOL _wheel_callback(int event_type, const EmscriptenWheelEvent *wheel_event, void *user_data) { static EM_BOOL _wheel_callback(int event_type, const EmscriptenWheelEvent *wheel_event, void *user_data) {
ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_WHEEL, false); ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_WHEEL, false);
if (!is_canvas_focused()) {
if (is_cursor_inside_canvas()) {
focus_canvas();
} else {
return false;
}
}
Ref<InputEventMouseButton> ev; Ref<InputEventMouseButton> ev;
ev.instance(); ev.instance();
@ -387,6 +436,15 @@ static EM_BOOL joy_callback_func(int p_type, const EmscriptenGamepadEvent *p_eve
return false; return false;
} }
extern "C" {
void send_notification(int notif) {
if (notif == MainLoop::NOTIFICATION_WM_MOUSE_ENTER || notif == MainLoop::NOTIFICATION_WM_MOUSE_EXIT) {
_cursor_inside_canvas = notif == MainLoop::NOTIFICATION_WM_MOUSE_ENTER;
}
OS_JavaScript::get_singleton()->get_main_loop()->notification(notif);
}
}
void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
print_line("Init OS"); print_line("Init OS");
@ -465,17 +523,17 @@ void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, i
EM_CHECK(ev) EM_CHECK(ev)
EMSCRIPTEN_RESULT result; EMSCRIPTEN_RESULT result;
SET_EM_CALLBACK("#canvas", mousemove, _mousemove_callback) SET_EM_CALLBACK("#window", mousemove, _mousemove_callback)
SET_EM_CALLBACK("#canvas", mousedown, _mousebutton_callback) SET_EM_CALLBACK("#canvas", mousedown, _mousebutton_callback)
SET_EM_CALLBACK("#canvas", mouseup, _mousebutton_callback) SET_EM_CALLBACK("#window", mouseup, _mousebutton_callback)
SET_EM_CALLBACK("#canvas", wheel, _wheel_callback) SET_EM_CALLBACK("#window", wheel, _wheel_callback)
SET_EM_CALLBACK("#canvas", touchstart, _touchpress_callback) SET_EM_CALLBACK("#window", touchstart, _touchpress_callback)
SET_EM_CALLBACK("#canvas", touchmove, _touchmove_callback) SET_EM_CALLBACK("#window", touchmove, _touchmove_callback)
SET_EM_CALLBACK("#canvas", touchend, _touchpress_callback) SET_EM_CALLBACK("#window", touchend, _touchpress_callback)
SET_EM_CALLBACK("#canvas", touchcancel, _touchpress_callback) SET_EM_CALLBACK("#window", touchcancel, _touchpress_callback)
SET_EM_CALLBACK(NULL, keydown, _keydown_callback) SET_EM_CALLBACK("#canvas", keydown, _keydown_callback)
SET_EM_CALLBACK(NULL, keypress, _keypress_callback) SET_EM_CALLBACK("#canvas", keypress, _keypress_callback)
SET_EM_CALLBACK(NULL, keyup, _keyup_callback) SET_EM_CALLBACK("#canvas", keyup, _keyup_callback)
SET_EM_CALLBACK(NULL, resize, _browser_resize_callback) SET_EM_CALLBACK(NULL, resize, _browser_resize_callback)
SET_EM_CALLBACK(NULL, fullscreenchange, _fullscreen_change_callback) SET_EM_CALLBACK(NULL, fullscreenchange, _fullscreen_change_callback)
SET_EM_CALLBACK_NODATA(gamepadconnected, joy_callback_func) SET_EM_CALLBACK_NODATA(gamepadconnected, joy_callback_func)
@ -485,6 +543,21 @@ void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, i
#undef SET_EM_CALLBACK #undef SET_EM_CALLBACK
#undef EM_CHECK #undef EM_CHECK
/* clang-format off */
EM_ASM_ARGS({
const send_notification = Module.cwrap('send_notification', null, ['number']);
const notifs = arguments;
(['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, i) {
Module.canvas.addEventListener(event, send_notification.bind(this, notifs[i]));
});
},
MainLoop::NOTIFICATION_WM_MOUSE_ENTER,
MainLoop::NOTIFICATION_WM_MOUSE_EXIT,
MainLoop::NOTIFICATION_WM_FOCUS_IN,
MainLoop::NOTIFICATION_WM_FOCUS_OUT
);
/* clang-format on */
#ifdef JAVASCRIPT_EVAL_ENABLED #ifdef JAVASCRIPT_EVAL_ENABLED
javascript_eval = memnew(JavaScript); javascript_eval = memnew(JavaScript);
GlobalConfig::get_singleton()->add_singleton(GlobalConfig::Singleton("JavaScript", javascript_eval)); GlobalConfig::get_singleton()->add_singleton(GlobalConfig::Singleton("JavaScript", javascript_eval));
@ -777,20 +850,6 @@ void OS_JavaScript::main_loop_end() {
main_loop->finish(); main_loop->finish();
} }
void OS_JavaScript::main_loop_focusout() {
if (main_loop)
main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT);
//audio_driver_javascript.set_pause(true);
}
void OS_JavaScript::main_loop_focusin() {
if (main_loop)
main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN);
//audio_driver_javascript.set_pause(false);
}
void OS_JavaScript::process_accelerometer(const Vector3 &p_accelerometer) { void OS_JavaScript::process_accelerometer(const Vector3 &p_accelerometer) {
input->set_accelerometer(p_accelerometer); input->set_accelerometer(p_accelerometer);