diff options
Diffstat (limited to 'platform/javascript')
-rw-r--r-- | platform/javascript/SCsub | 41 | ||||
-rw-r--r-- | platform/javascript/detect.py | 62 | ||||
-rw-r--r-- | platform/javascript/export/export.cpp | 12 | ||||
-rw-r--r-- | platform/javascript/godot_shell.html | 6 | ||||
-rw-r--r-- | platform/javascript/os_javascript.cpp | 128 | ||||
-rw-r--r-- | platform/javascript/os_javascript.h | 2 |
6 files changed, 174 insertions, 77 deletions
diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index 02ff2090f9..b804863ee1 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -19,29 +19,34 @@ javascript_objects = [] for x in javascript_files: 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"']) -html_file = env.Program('#bin/godot', javascript_objects, PROGSUFFIX=env["PROGSUFFIX"] + ".html")[0] +# output file name without file extension +basename = "godot" + env["PROGSUFFIX"] +target_dir = env.Dir("#bin") +js_file = target_dir.File(basename + ".js") +implicit_targets = [js_file] + +zip_dir = target_dir.Dir('.javascript_zip') +zip_files = env.InstallAs([zip_dir.File("godot.js"), zip_dir.File("godotfs.js")], [js_file, "#misc/dist/html_fs/godotfs.js"]) + +if env['wasm'] == 'yes': + wasm_file = target_dir.File(basename+'.wasm') + implicit_targets.append(wasm_file) + zip_files.append(InstallAs(zip_dir.File('godot.wasm'), wasm_file)) +else: + asmjs_files = [target_dir.File(basename+'.asm.js'), target_dir.File(basename+'.html.mem')] + zip_files.append(InstallAs([zip_dir.File('godot.asm.js'), zip_dir.File('godot.mem')], asmjs_files)) + implicit_targets.extend(asmjs_files) + +# HTML file must be the first target in the list +html_file = env.Program(["#bin/godot"] + implicit_targets, javascript_objects, PROGSUFFIX=env["PROGSUFFIX"]+".html")[0] Depends(html_file, "godot_shell.html") -basename = "godot" + env["PROGSUFFIX"] # output file name without file extension # Emscripten hardcodes file names, so replace common base name with # placeholder while leaving extension; also change `.html.mem` to just `.mem` fixup_html = env.Substfile(html_file, SUBST_DICT=[(basename, '$$GODOT_BASE'), ('.html.mem', '.mem')], SUBSTFILESUFFIX='.fixup.html') -zip_dir = env.Dir('#bin/.javascript_zip') -zip_files = [] -js_file = env.SideEffect(html_file.File(basename+'.js'), html_file) -zip_files.append(env.InstallAs( - [zip_dir.File('godot.html'), zip_dir.File('godot.js'), zip_dir.File('godotfs.js')], - [fixup_html, js_file, '#misc/dist/html_fs/godotfs.js'])) - -if env['wasm'] == 'yes': - wasm_file = env.SideEffect(html_file.File(basename+'.wasm'), html_file) - zip_files.append(env.InstallAs(zip_dir.File('godot.wasm'), wasm_file)) -else: - asmjs_files = env.SideEffect([html_file.File(basename+'.asm.js'), html_file.File(basename+'.html.mem')], html_file) - zip_files.append(env.InstallAs([zip_dir.File('godot.asm.js'), zip_dir.File('godot.mem')], asmjs_files)) - -Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX']+env['ZIPSUFFIX'], ZIPROOT=zip_dir) +zip_files.append(InstallAs(zip_dir.File('godot.html'), fixup_html)) +Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX']+env['ZIPSUFFIX'], ZIPROOT=zip_dir, ZIPCOMSTR="Archving $SOURCES as $TARGET") diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index 41fe3fb027..68c8d1eea5 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -1,6 +1,6 @@ import os -import sys import string +import sys def is_active(): @@ -12,7 +12,8 @@ def get_name(): def can_build(): - return os.environ.has_key("EMSCRIPTEN_ROOT") + + return (os.environ.has_key("EMSCRIPTEN_ROOT")) def get_opts(): @@ -27,12 +28,12 @@ def get_flags(): return [ ('tools', 'no'), - ('module_etc1_enabled', 'no'), ('module_theora_enabled', 'no'), ] def create(env): + # remove Windows' .exe suffix return env.Clone(tools=['textfile', 'zip'], PROGSUFFIX='') @@ -45,10 +46,26 @@ def escape_target_backslashes(target, source, env, for_signature): def configure(env): - env['ENV'] = os.environ - env.Append(CPPPATH=['#platform/javascript']) + ## Build type + + if (env["target"] == "release"): + env.Append(CCFLAGS=['-O3']) + env.Append(LINKFLAGS=['-O3']) + elif (env["target"] == "release_debug"): + env.Append(CCFLAGS=['-O2', '-DDEBUG_ENABLED']) + env.Append(LINKFLAGS=['-O2', '-s', 'ASSERTIONS=1']) + # retain function names at the cost of file size, for backtraces and profiling + env.Append(LINKFLAGS=['--profiling-funcs']) + + elif (env["target"] == "debug"): + env.Append(CCFLAGS=['-O1', '-D_DEBUG', '-g', '-DDEBUG_ENABLED']) + env.Append(LINKFLAGS=['-O1', '-g']) + + ## Compiler configuration + + env['ENV'] = os.environ env.PrependENVPath('PATH', os.environ['EMSCRIPTEN_ROOT']) env['CC'] = 'emcc' env['CXX'] = 'em++' @@ -57,6 +74,7 @@ def configure(env): # Emscripten's ar has issues with duplicate file names, so use cc env['AR'] = 'emcc' env['ARFLAGS'] = '-o' + if (os.name == 'nt'): # use TempFileMunge on Windows since some commands get too long for # cmd.exe even with spawn_fix @@ -68,26 +86,20 @@ def configure(env): env['OBJSUFFIX'] = '.bc' env['LIBSUFFIX'] = '.bc' - if (env["target"] == "release"): - env.Append(CCFLAGS=['-O3']) - env.Append(LINKFLAGS=['-O3']) - elif (env["target"] == "release_debug"): - env.Append(CCFLAGS=['-O2', '-DDEBUG_ENABLED']) - env.Append(LINKFLAGS=['-O2', '-s', 'ASSERTIONS=1']) - # retain function names at the cost of file size, for backtraces and profiling - env.Append(LINKFLAGS=['--profiling-funcs']) - elif (env["target"] == "debug"): - env.Append(CCFLAGS=['-O1', '-D_DEBUG', '-g', '-DDEBUG_ENABLED']) - env.Append(LINKFLAGS=['-O1', '-g']) + ## Compile flags - # TODO: Move that to opus module's config - if("module_opus_enabled" in env and env["module_opus_enabled"] != "no"): - env.opus_fixed_point = "yes" + env.Append(CPPPATH=['#platform/javascript']) + env.Append(CPPFLAGS=['-DJAVASCRIPT_ENABLED', '-DUNIX_ENABLED', '-DPTHREAD_NO_RENAME', '-DTYPED_METHOD_BIND', '-DNO_THREADS']) + env.Append(CPPFLAGS=['-DGLES3_ENABLED']) # These flags help keep the file size down env.Append(CPPFLAGS=["-fno-exceptions", '-DNO_SAFE_CAST', '-fno-rtti']) - env.Append(CPPFLAGS=['-DJAVASCRIPT_ENABLED', '-DUNIX_ENABLED', '-DPTHREAD_NO_RENAME', '-DTYPED_METHOD_BIND', '-DNO_THREADS']) - env.Append(CPPFLAGS=['-DGLES3_ENABLED']) + + if env['javascript_eval'] == 'yes': + env.Append(CPPFLAGS=['-DJAVASCRIPT_EVAL_ENABLED']) + + ## Link flags + env.Append(LINKFLAGS=['-s', 'USE_WEBGL2=1']) if (env['wasm'] == 'yes'): @@ -101,8 +113,6 @@ def configure(env): env.Append(LINKFLAGS=['-s', 'ASM_JS=1']) env.Append(LINKFLAGS=['--separate-asm']) - if env['javascript_eval'] == 'yes': - env.Append(CPPFLAGS=['-DJAVASCRIPT_EVAL_ENABLED']) - - - import methods + # TODO: Move that to opus module's config + if("module_opus_enabled" in env and env["module_opus_enabled"] != "no"): + env.opus_fixed_point = "yes" diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 4bdfdae39e..b436d52363 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -61,6 +61,7 @@ public: virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const; virtual String get_name() const; + virtual String get_os_name() const; virtual Ref<Texture> get_logo() const; virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const; @@ -74,6 +75,12 @@ public: virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags); virtual Ref<Texture> get_run_icon() const; + virtual void get_platform_features(List<String> *r_features) { + + r_features->push_back("web"); + r_features->push_back("JavaScript"); + } + EditorExportPlatformJavaScript(); }; @@ -167,6 +174,11 @@ String EditorExportPlatformJavaScript::get_name() const { return "HTML5"; } +String EditorExportPlatformJavaScript::get_os_name() const { + + return "JavaScript"; +} + Ref<Texture> EditorExportPlatformJavaScript::get_logo() const { return logo; diff --git a/platform/javascript/godot_shell.html b/platform/javascript/godot_shell.html index 6c7069a8f0..ee7399a129 100644 --- a/platform/javascript/godot_shell.html +++ b/platform/javascript/godot_shell.html @@ -83,6 +83,10 @@ color: white; } + #canvas:focus { + outline: none; + } + /* Status display * ============== */ @@ -147,7 +151,7 @@ $GODOT_HEAD_INCLUDE </head> <body> <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 /> Please try updating or use a different browser. </canvas> diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 9df26f1471..0708d46196 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -29,8 +29,8 @@ /*************************************************************************/ #include "os_javascript.h" -#include "core/global_config.h" #include "core/io/file_access_buffered_fa.h" +#include "core/project_settings.h" #include "dom_keys.h" #include "drivers/gles3/rasterizer_gles3.h" #include "drivers/unix/dir_access_unix.h" @@ -145,6 +145,31 @@ static EM_BOOL _fullscreen_change_callback(int event_type, const EmscriptenFulls 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) { 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(); - 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(); - else + } else if (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); _input->parse_input_event(ev); + // prevent selection dragging return true; } 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); + 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; ev.instance(); 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_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()); _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) { 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; ev.instance(); @@ -387,6 +436,15 @@ static EM_BOOL joy_callback_func(int p_type, const EmscriptenGamepadEvent *p_eve 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) { print_line("Init OS"); @@ -465,17 +523,17 @@ void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, i EM_CHECK(ev) 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", mouseup, _mousebutton_callback) - SET_EM_CALLBACK("#canvas", wheel, _wheel_callback) - SET_EM_CALLBACK("#canvas", touchstart, _touchpress_callback) - SET_EM_CALLBACK("#canvas", touchmove, _touchmove_callback) - SET_EM_CALLBACK("#canvas", touchend, _touchpress_callback) - SET_EM_CALLBACK("#canvas", touchcancel, _touchpress_callback) - SET_EM_CALLBACK(NULL, keydown, _keydown_callback) - SET_EM_CALLBACK(NULL, keypress, _keypress_callback) - SET_EM_CALLBACK(NULL, keyup, _keyup_callback) + SET_EM_CALLBACK("#window", mouseup, _mousebutton_callback) + SET_EM_CALLBACK("#window", wheel, _wheel_callback) + SET_EM_CALLBACK("#window", touchstart, _touchpress_callback) + SET_EM_CALLBACK("#window", touchmove, _touchmove_callback) + SET_EM_CALLBACK("#window", touchend, _touchpress_callback) + SET_EM_CALLBACK("#window", touchcancel, _touchpress_callback) + SET_EM_CALLBACK("#canvas", keydown, _keydown_callback) + SET_EM_CALLBACK("#canvas", keypress, _keypress_callback) + SET_EM_CALLBACK("#canvas", keyup, _keyup_callback) SET_EM_CALLBACK(NULL, resize, _browser_resize_callback) SET_EM_CALLBACK(NULL, fullscreenchange, _fullscreen_change_callback) SET_EM_CALLBACK_NODATA(gamepadconnected, joy_callback_func) @@ -485,9 +543,24 @@ void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, i #undef SET_EM_CALLBACK #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 javascript_eval = memnew(JavaScript); - GlobalConfig::get_singleton()->add_singleton(GlobalConfig::Singleton("JavaScript", javascript_eval)); + ProjectSettings::get_singleton()->add_singleton(ProjectSettings::Singleton("JavaScript", javascript_eval)); #endif visual_server->init(); @@ -777,20 +850,6 @@ void OS_JavaScript::main_loop_end() { 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) { input->set_accelerometer(p_accelerometer); @@ -828,7 +887,7 @@ String OS_JavaScript::get_data_dir() const { return get_data_dir_func(); */ return "/userfs"; - //return GlobalConfig::get_singleton()->get_singleton_object("GodotOS")->call("get_data_dir"); + //return ProjectSettings::get_singleton()->get_singleton_object("GodotOS")->call("get_data_dir"); }; String OS_JavaScript::get_executable_path() const { @@ -911,6 +970,11 @@ int OS_JavaScript::get_power_percent_left() { return power_manager->get_power_percent_left(); } +bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) { + + return p_feature == "web" || p_feature == "s3tc"; // TODO check for these features really being available +} + OS_JavaScript::OS_JavaScript(const char *p_execpath, GetDataDirFunc p_get_data_dir_func) { set_cmdline(p_execpath, get_cmdline_args()); main_loop = NULL; diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 65269148ec..24e96e20dd 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -164,6 +164,8 @@ public: virtual int get_power_seconds_left(); virtual int get_power_percent_left(); + virtual bool _check_internal_feature_support(const String &p_feature); + OS_JavaScript(const char *p_execpath, GetDataDirFunc p_get_data_dir_func); ~OS_JavaScript(); }; |