diff --git a/.gitignore b/.gitignore index 3e4ab5a00ea5..be2dbce587e6 100644 --- a/.gitignore +++ b/.gitignore @@ -124,7 +124,7 @@ build-cache/ ###################### ## Enso-Development ## ###################### -/dist +dist/ distribution/lib/Standard/Examples/*/data/scratch_file distribution/lib/Standard/Examples/*/data/image.png distribution/editions diff --git a/.idea/runConfigurations/clippy__wasm__linux_.xml b/.idea/runConfigurations/clippy-all-wasm-linux.xml similarity index 76% rename from .idea/runConfigurations/clippy__wasm__linux_.xml rename to .idea/runConfigurations/clippy-all-wasm-linux.xml index e4f0f05f5e1f..c2ebbde49000 100644 --- a/.idea/runConfigurations/clippy__wasm__linux_.xml +++ b/.idea/runConfigurations/clippy-all-wasm-linux.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/clippy__wasm__macos_.xml b/.idea/runConfigurations/clippy-all-wasm-macos.xml similarity index 76% rename from .idea/runConfigurations/clippy__wasm__macos_.xml rename to .idea/runConfigurations/clippy-all-wasm-macos.xml index be831a72f001..8c77c2852253 100644 --- a/.idea/runConfigurations/clippy__wasm__macos_.xml +++ b/.idea/runConfigurations/clippy-all-wasm-macos.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/clippy__wasm__windows_.xml b/.idea/runConfigurations/clippy-all-wasm-windows.xml similarity index 76% rename from .idea/runConfigurations/clippy__wasm__windows_.xml rename to .idea/runConfigurations/clippy-all-wasm-windows.xml index c8496c2543fc..f10ca2f44beb 100644 --- a/.idea/runConfigurations/clippy__wasm__windows_.xml +++ b/.idea/runConfigurations/clippy-all-wasm-windows.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/clippy__wasm_.xml b/.idea/runConfigurations/clippy-all-wasm.xml similarity index 76% rename from .idea/runConfigurations/clippy__wasm_.xml rename to .idea/runConfigurations/clippy-all-wasm.xml index 18cb6a19561c..e6bd5b6b9a1c 100644 --- a/.idea/runConfigurations/clippy__wasm_.xml +++ b/.idea/runConfigurations/clippy-all-wasm.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/clippy-ensogl-example-focus-management-wasm-linux.xml b/.idea/runConfigurations/clippy-ensogl-example-focus-management-wasm-linux.xml new file mode 100644 index 000000000000..b3d9a029ce70 --- /dev/null +++ b/.idea/runConfigurations/clippy-ensogl-example-focus-management-wasm-linux.xml @@ -0,0 +1,27 @@ + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/clippy-ensogl-example-focus-management-wasm-macos.xml b/.idea/runConfigurations/clippy-ensogl-example-focus-management-wasm-macos.xml new file mode 100644 index 000000000000..96a3ec90c3f2 --- /dev/null +++ b/.idea/runConfigurations/clippy-ensogl-example-focus-management-wasm-macos.xml @@ -0,0 +1,27 @@ + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/clippy-ensogl-example-focus-management-wasm-windows.xml b/.idea/runConfigurations/clippy-ensogl-example-focus-management-wasm-windows.xml new file mode 100644 index 000000000000..9a0d398e592f --- /dev/null +++ b/.idea/runConfigurations/clippy-ensogl-example-focus-management-wasm-windows.xml @@ -0,0 +1,27 @@ + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/clippy-ensogl-example-focus-management-wasm.xml b/.idea/runConfigurations/clippy-ensogl-example-focus-management-wasm.xml new file mode 100644 index 000000000000..9a0b6a42b721 --- /dev/null +++ b/.idea/runConfigurations/clippy-ensogl-example-focus-management-wasm.xml @@ -0,0 +1,27 @@ + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test__native_.xml b/.idea/runConfigurations/doc-test-all-native.xml similarity index 74% rename from .idea/runConfigurations/doc_test__native_.xml rename to .idea/runConfigurations/doc-test-all-native.xml index 7df5b9c687ca..893818c20d23 100644 --- a/.idea/runConfigurations/doc_test__native_.xml +++ b/.idea/runConfigurations/doc-test-all-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_build_scripts__native_.xml b/.idea/runConfigurations/doc-test-build-scripts-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_build_scripts__native_.xml rename to .idea/runConfigurations/doc-test-build-scripts-native.xml index 013ba8c8c3b9..2e1613698f5a 100644 --- a/.idea/runConfigurations/doc_test_build_scripts__native_.xml +++ b/.idea/runConfigurations/doc-test-build-scripts-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_config_reader__native_.xml b/.idea/runConfigurations/doc-test-config-reader-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_config_reader__native_.xml rename to .idea/runConfigurations/doc-test-config-reader-native.xml index b91aff188709..b75157a58c79 100644 --- a/.idea/runConfigurations/doc_test_config_reader__native_.xml +++ b/.idea/runConfigurations/doc-test-config-reader-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_debug_scene_component_list_panel_view__native_.xml b/.idea/runConfigurations/doc-test-debug-scene-component-list-panel-view-native.xml similarity index 69% rename from .idea/runConfigurations/doc_test_debug_scene_component_list_panel_view__native_.xml rename to .idea/runConfigurations/doc-test-debug-scene-component-list-panel-view-native.xml index 10f83625ffad..c33f35e017a5 100644 --- a/.idea/runConfigurations/doc_test_debug_scene_component_list_panel_view__native_.xml +++ b/.idea/runConfigurations/doc-test-debug-scene-component-list-panel-view-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_debug_scene_icons__native_.xml b/.idea/runConfigurations/doc-test-debug-scene-icons-native.xml similarity index 71% rename from .idea/runConfigurations/doc_test_debug_scene_icons__native_.xml rename to .idea/runConfigurations/doc-test-debug-scene-icons-native.xml index bbcee8465935..62fe1d22efa8 100644 --- a/.idea/runConfigurations/doc_test_debug_scene_icons__native_.xml +++ b/.idea/runConfigurations/doc-test-debug-scene-icons-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_debug_scene_interface__native_.xml b/.idea/runConfigurations/doc-test-debug-scene-interface-native.xml similarity index 71% rename from .idea/runConfigurations/doc_test_debug_scene_interface__native_.xml rename to .idea/runConfigurations/doc-test-debug-scene-interface-native.xml index 3d976054e784..cfc19f8625a0 100644 --- a/.idea/runConfigurations/doc_test_debug_scene_interface__native_.xml +++ b/.idea/runConfigurations/doc-test-debug-scene-interface-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_double_representation__native_.xml b/.idea/runConfigurations/doc-test-double-representation-native.xml similarity index 71% rename from .idea/runConfigurations/doc_test_double_representation__native_.xml rename to .idea/runConfigurations/doc-test-double-representation-native.xml index d7c6bff94466..49d44b34ba68 100644 --- a/.idea/runConfigurations/doc_test_double_representation__native_.xml +++ b/.idea/runConfigurations/doc-test-double-representation-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_engine_model__native_.xml b/.idea/runConfigurations/doc-test-engine-model-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_engine_model__native_.xml rename to .idea/runConfigurations/doc-test-engine-model-native.xml index b588ef1486b0..1db727552579 100644 --- a/.idea/runConfigurations/doc_test_engine_model__native_.xml +++ b/.idea/runConfigurations/doc-test-engine-model-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_automata__native_.xml b/.idea/runConfigurations/doc-test-enso-automata-native.xml similarity index 73% rename from .idea/runConfigurations/doc_test_enso_automata__native_.xml rename to .idea/runConfigurations/doc-test-enso-automata-native.xml index 5a2f144dfb8e..e0b4462e1470 100644 --- a/.idea/runConfigurations/doc_test_enso_automata__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-automata-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_build_cli__native_.xml b/.idea/runConfigurations/doc-test-enso-build-cli-native.xml similarity index 73% rename from .idea/runConfigurations/doc_test_enso_build_cli__native_.xml rename to .idea/runConfigurations/doc-test-enso-build-cli-native.xml index a1ced1cc4aa8..e8416e9f9d1b 100644 --- a/.idea/runConfigurations/doc_test_enso_build_cli__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-build-cli-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_build_utilities__native_.xml b/.idea/runConfigurations/doc-test-enso-build-utilities-native.xml similarity index 71% rename from .idea/runConfigurations/doc_test_enso_build_utilities__native_.xml rename to .idea/runConfigurations/doc-test-enso-build-utilities-native.xml index e2eb8d11b9bc..130672538a04 100644 --- a/.idea/runConfigurations/doc_test_enso_build_utilities__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-build-utilities-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_data_structures__native_.xml b/.idea/runConfigurations/doc-test-enso-data-structures-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_enso_data_structures__native_.xml rename to .idea/runConfigurations/doc-test-enso-data-structures-native.xml index 900adceed586..09df8e26addc 100644 --- a/.idea/runConfigurations/doc_test_enso_data_structures__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-data-structures-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_formatter__native_.xml b/.idea/runConfigurations/doc-test-enso-formatter-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_enso_formatter__native_.xml rename to .idea/runConfigurations/doc-test-enso-formatter-native.xml index 0be7e926b55c..a50fbddbe5d5 100644 --- a/.idea/runConfigurations/doc_test_enso_formatter__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-formatter-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_frp__native_.xml b/.idea/runConfigurations/doc-test-enso-frp-native.xml similarity index 73% rename from .idea/runConfigurations/doc_test_enso_frp__native_.xml rename to .idea/runConfigurations/doc-test-enso-frp-native.xml index 2cb16f02b375..96c2545a692e 100644 --- a/.idea/runConfigurations/doc_test_enso_frp__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-frp-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_gui__native_.xml b/.idea/runConfigurations/doc-test-enso-gui-native.xml similarity index 74% rename from .idea/runConfigurations/doc_test_enso_gui__native_.xml rename to .idea/runConfigurations/doc-test-enso-gui-native.xml index 05ec9f1e9285..6e354d0950db 100644 --- a/.idea/runConfigurations/doc_test_enso_gui__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-gui-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_macro_utils__native_.xml b/.idea/runConfigurations/doc-test-enso-macro-utils-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_enso_macro_utils__native_.xml rename to .idea/runConfigurations/doc-test-enso-macro-utils-native.xml index 10d7945fa7d0..998bcec9b673 100644 --- a/.idea/runConfigurations/doc_test_enso_macro_utils__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-macro-utils-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_metamodel__native_.xml b/.idea/runConfigurations/doc-test-enso-metamodel-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_enso_metamodel__native_.xml rename to .idea/runConfigurations/doc-test-enso-metamodel-native.xml index 8ae907bf2236..6eae7f46869f 100644 --- a/.idea/runConfigurations/doc_test_enso_metamodel__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-metamodel-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_parser_generate_java__native_.xml b/.idea/runConfigurations/doc-test-enso-parser-generate-java-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_enso_parser_generate_java__native_.xml rename to .idea/runConfigurations/doc-test-enso-parser-generate-java-native.xml index 4a559c685a3c..80457690f142 100644 --- a/.idea/runConfigurations/doc_test_enso_parser_generate_java__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-parser-generate-java-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_prelude__native_.xml b/.idea/runConfigurations/doc-test-enso-prelude-native.xml similarity index 73% rename from .idea/runConfigurations/doc_test_enso_prelude__native_.xml rename to .idea/runConfigurations/doc-test-enso-prelude-native.xml index dbe1e13f9c87..1280860b29f2 100644 --- a/.idea/runConfigurations/doc_test_enso_prelude__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-prelude-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_profiler_data__native_.xml b/.idea/runConfigurations/doc-test-enso-profiler-data-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_enso_profiler_data__native_.xml rename to .idea/runConfigurations/doc-test-enso-profiler-data-native.xml index d2e1eb935c87..e9cec75edd69 100644 --- a/.idea/runConfigurations/doc_test_enso_profiler_data__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-profiler-data-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_profiler_demo_data__native_.xml b/.idea/runConfigurations/doc-test-enso-profiler-demo-data-native.xml similarity index 71% rename from .idea/runConfigurations/doc_test_enso_profiler_demo_data__native_.xml rename to .idea/runConfigurations/doc-test-enso-profiler-demo-data-native.xml index 3003753768e8..b07491b338f8 100644 --- a/.idea/runConfigurations/doc_test_enso_profiler_demo_data__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-profiler-demo-data-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_reflect_macros__native_.xml b/.idea/runConfigurations/doc-test-enso-reflect-macros-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_enso_reflect_macros__native_.xml rename to .idea/runConfigurations/doc-test-enso-reflect-macros-native.xml index 53e5f079a722..a4323b291b20 100644 --- a/.idea/runConfigurations/doc_test_enso_reflect_macros__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-reflect-macros-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_reflect__native_.xml b/.idea/runConfigurations/doc-test-enso-reflect-native.xml similarity index 73% rename from .idea/runConfigurations/doc_test_enso_reflect__native_.xml rename to .idea/runConfigurations/doc-test-enso-reflect-native.xml index 26c385f6db13..7a9a6804903d 100644 --- a/.idea/runConfigurations/doc_test_enso_reflect__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-reflect-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_shapely_macros__native_.xml b/.idea/runConfigurations/doc-test-enso-shapely-macros-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_enso_shapely_macros__native_.xml rename to .idea/runConfigurations/doc-test-enso-shapely-macros-native.xml index c7006c3dc768..d977e5f5972b 100644 --- a/.idea/runConfigurations/doc_test_enso_shapely_macros__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-shapely-macros-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_shortcuts__native_.xml b/.idea/runConfigurations/doc-test-enso-shortcuts-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_enso_shortcuts__native_.xml rename to .idea/runConfigurations/doc-test-enso-shortcuts-native.xml index 4d757c9d7aae..d2d037d1e6e8 100644 --- a/.idea/runConfigurations/doc_test_enso_shortcuts__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-shortcuts-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_types__native_.xml b/.idea/runConfigurations/doc-test-enso-types-native.xml similarity index 73% rename from .idea/runConfigurations/doc_test_enso_types__native_.xml rename to .idea/runConfigurations/doc-test-enso-types-native.xml index e0962151d56b..63ce2ab30aa3 100644 --- a/.idea/runConfigurations/doc_test_enso_types__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-types-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_enso_web__native_.xml b/.idea/runConfigurations/doc-test-enso-web-native.xml similarity index 73% rename from .idea/runConfigurations/doc_test_enso_web__native_.xml rename to .idea/runConfigurations/doc-test-enso-web-native.xml index 2fea8fb60e0e..b89cca7b31a1 100644 --- a/.idea/runConfigurations/doc_test_enso_web__native_.xml +++ b/.idea/runConfigurations/doc-test-enso-web-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_derive_theme__native_.xml b/.idea/runConfigurations/doc-test-ensogl-derive-theme-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_ensogl_derive_theme__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-derive-theme-native.xml index 380947072efd..07e9130844a5 100644 --- a/.idea/runConfigurations/doc_test_ensogl_derive_theme__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-derive-theme-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_drop_manager__native_.xml b/.idea/runConfigurations/doc-test-ensogl-drop-manager-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_ensogl_drop_manager__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-drop-manager-native.xml index bcc6c1353f40..aab2f2c9945b 100644 --- a/.idea/runConfigurations/doc_test_ensogl_drop_manager__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-drop-manager-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_example_animation__native_.xml b/.idea/runConfigurations/doc-test-ensogl-example-animation-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_ensogl_example_animation__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-example-animation-native.xml index 50e160f65c2c..17d3fbbbc967 100644 --- a/.idea/runConfigurations/doc_test_ensogl_example_animation__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-example-animation-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_example_complex_shape_system__native_.xml b/.idea/runConfigurations/doc-test-ensogl-example-complex-shape-system-native.xml similarity index 71% rename from .idea/runConfigurations/doc_test_ensogl_example_complex_shape_system__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-example-complex-shape-system-native.xml index 1380d2bbf658..e3f2413b3f6e 100644 --- a/.idea/runConfigurations/doc_test_ensogl_example_complex_shape_system__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-example-complex-shape-system-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_example_custom_shape_system__native_.xml b/.idea/runConfigurations/doc-test-ensogl-example-custom-shape-system-native.xml similarity index 71% rename from .idea/runConfigurations/doc_test_ensogl_example_custom_shape_system__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-example-custom-shape-system-native.xml index 533400ef6665..9777fa07c440 100644 --- a/.idea/runConfigurations/doc_test_ensogl_example_custom_shape_system__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-example-custom-shape-system-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_example_dom_symbols__native_.xml b/.idea/runConfigurations/doc-test-ensogl-example-dom-symbols-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_ensogl_example_dom_symbols__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-example-dom-symbols-native.xml index 69f6b7dcf77a..5059904702cf 100644 --- a/.idea/runConfigurations/doc_test_ensogl_example_dom_symbols__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-example-dom-symbols-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_example_easing_animator__native_.xml b/.idea/runConfigurations/doc-test-ensogl-example-easing-animator-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_ensogl_example_easing_animator__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-example-easing-animator-native.xml index 2dfd49b4cc85..654677987e94 100644 --- a/.idea/runConfigurations/doc_test_ensogl_example_easing_animator__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-example-easing-animator-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc-test-ensogl-example-focus-management-native.xml b/.idea/runConfigurations/doc-test-ensogl-example-focus-management-native.xml new file mode 100644 index 000000000000..1925188da464 --- /dev/null +++ b/.idea/runConfigurations/doc-test-ensogl-example-focus-management-native.xml @@ -0,0 +1,27 @@ + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_example_grid_view__native_.xml b/.idea/runConfigurations/doc-test-ensogl-example-grid-view-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_ensogl_example_grid_view__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-example-grid-view-native.xml index 840c8cb3adef..fb992621fe42 100644 --- a/.idea/runConfigurations/doc_test_ensogl_example_grid_view__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-example-grid-view-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_example_mouse_events__native_.xml b/.idea/runConfigurations/doc-test-ensogl-example-mouse-events-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_ensogl_example_mouse_events__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-example-mouse-events-native.xml index fd15245b9d76..60c432017374 100644 --- a/.idea/runConfigurations/doc_test_ensogl_example_mouse_events__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-example-mouse-events-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_example_render_profile_flamegraph__native_.xml b/.idea/runConfigurations/doc-test-ensogl-example-render-profile-flamegraph-native.xml similarity index 71% rename from .idea/runConfigurations/doc_test_ensogl_example_render_profile_flamegraph__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-example-render-profile-flamegraph-native.xml index 7989047670e2..58014feeec68 100644 --- a/.idea/runConfigurations/doc_test_ensogl_example_render_profile_flamegraph__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-example-render-profile-flamegraph-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_example_slider__native_.xml b/.idea/runConfigurations/doc-test-ensogl-example-slider-native.xml similarity index 73% rename from .idea/runConfigurations/doc_test_ensogl_example_slider__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-example-slider-native.xml index bc5098bf0203..84fbe5a85f16 100644 --- a/.idea/runConfigurations/doc_test_ensogl_example_slider__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-example-slider-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_example_sprite_system_benchmark__native_.xml b/.idea/runConfigurations/doc-test-ensogl-example-sprite-system-benchmark-native.xml similarity index 71% rename from .idea/runConfigurations/doc_test_ensogl_example_sprite_system_benchmark__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-example-sprite-system-benchmark-native.xml index a6aff066f606..8fb4fae91e1d 100644 --- a/.idea/runConfigurations/doc_test_ensogl_example_sprite_system_benchmark__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-example-sprite-system-benchmark-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_example_sprite_system__native_.xml b/.idea/runConfigurations/doc-test-ensogl-example-sprite-system-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_ensogl_example_sprite_system__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-example-sprite-system-native.xml index e1fd461db53a..d3323e436656 100644 --- a/.idea/runConfigurations/doc_test_ensogl_example_sprite_system__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-example-sprite-system-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_example_text_area__native_.xml b/.idea/runConfigurations/doc-test-ensogl-example-text-area-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_ensogl_example_text_area__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-example-text-area-native.xml index 1eb21a77ede5..4b95942e5bdb 100644 --- a/.idea/runConfigurations/doc_test_ensogl_example_text_area__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-example-text-area-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_file_browser__native_.xml b/.idea/runConfigurations/doc-test-ensogl-file-browser-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_ensogl_file_browser__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-file-browser-native.xml index c7c14efec934..6cb10bfcae24 100644 --- a/.idea/runConfigurations/doc_test_ensogl_file_browser__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-file-browser-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_gui_component__native_.xml b/.idea/runConfigurations/doc-test-ensogl-gui-component-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_ensogl_gui_component__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-gui-component-native.xml index 5996975fc665..e8e21230a2f2 100644 --- a/.idea/runConfigurations/doc_test_ensogl_gui_component__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-gui-component-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_hardcoded_theme__native_.xml b/.idea/runConfigurations/doc-test-ensogl-hardcoded-theme-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_ensogl_hardcoded_theme__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-hardcoded-theme-native.xml index c448bf426bd6..188d0badb555 100644 --- a/.idea/runConfigurations/doc_test_ensogl_hardcoded_theme__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-hardcoded-theme-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_label__native_.xml b/.idea/runConfigurations/doc-test-ensogl-label-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_ensogl_label__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-label-native.xml index 5cca70367535..11bb2734ed6b 100644 --- a/.idea/runConfigurations/doc_test_ensogl_label__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-label-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_scroll_area__native_.xml b/.idea/runConfigurations/doc-test-ensogl-scroll-area-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_ensogl_scroll_area__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-scroll-area-native.xml index cdd2d852dd28..1130a7fbd1f8 100644 --- a/.idea/runConfigurations/doc_test_ensogl_scroll_area__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-scroll-area-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_sequence_diagram__native_.xml b/.idea/runConfigurations/doc-test-ensogl-sequence-diagram-native.xml similarity index 71% rename from .idea/runConfigurations/doc_test_ensogl_sequence_diagram__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-sequence-diagram-native.xml index 4151fa73a37b..4f152097ab7d 100644 --- a/.idea/runConfigurations/doc_test_ensogl_sequence_diagram__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-sequence-diagram-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_text_embedded_fonts__native_.xml b/.idea/runConfigurations/doc-test-ensogl-text-embedded-fonts-native.xml similarity index 71% rename from .idea/runConfigurations/doc_test_ensogl_text_embedded_fonts__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-text-embedded-fonts-native.xml index 1faeedc20c72..a9435ca97155 100644 --- a/.idea/runConfigurations/doc_test_ensogl_text_embedded_fonts__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-text-embedded-fonts-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ensogl_text_font_family__native_.xml b/.idea/runConfigurations/doc-test-ensogl-text-font-family-native.xml similarity index 71% rename from .idea/runConfigurations/doc_test_ensogl_text_font_family__native_.xml rename to .idea/runConfigurations/doc-test-ensogl-text-font-family-native.xml index 66c168c24d30..51c3f0394fc4 100644 --- a/.idea/runConfigurations/doc_test_ensogl_text_font_family__native_.xml +++ b/.idea/runConfigurations/doc-test-ensogl-text-font-family-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_fuzzly__native_.xml b/.idea/runConfigurations/doc-test-fuzzly-native.xml similarity index 73% rename from .idea/runConfigurations/doc_test_fuzzly__native_.xml rename to .idea/runConfigurations/doc-test-fuzzly-native.xml index ed2831515d1e..cb99a5cd727b 100644 --- a/.idea/runConfigurations/doc_test_fuzzly__native_.xml +++ b/.idea/runConfigurations/doc-test-fuzzly-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ide_ci__native_.xml b/.idea/runConfigurations/doc-test-ide-ci-native.xml similarity index 73% rename from .idea/runConfigurations/doc_test_ide_ci__native_.xml rename to .idea/runConfigurations/doc-test-ide-ci-native.xml index d37c5e32b528..09482bd5d1cb 100644 --- a/.idea/runConfigurations/doc_test_ide_ci__native_.xml +++ b/.idea/runConfigurations/doc-test-ide-ci-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ide_view_component_browser__native_.xml b/.idea/runConfigurations/doc-test-ide-view-component-browser-native.xml similarity index 71% rename from .idea/runConfigurations/doc_test_ide_view_component_browser__native_.xml rename to .idea/runConfigurations/doc-test-ide-view-component-browser-native.xml index ab69ca5e9a2d..4dcf2115d8b9 100644 --- a/.idea/runConfigurations/doc_test_ide_view_component_browser__native_.xml +++ b/.idea/runConfigurations/doc-test-ide-view-component-browser-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ide_view_component_list_panel_breadcrumbs__native_.xml b/.idea/runConfigurations/doc-test-ide-view-component-list-panel-breadcrumbs-native.xml similarity index 68% rename from .idea/runConfigurations/doc_test_ide_view_component_list_panel_breadcrumbs__native_.xml rename to .idea/runConfigurations/doc-test-ide-view-component-list-panel-breadcrumbs-native.xml index 3b07bdf0eac5..45998d8bbcdd 100644 --- a/.idea/runConfigurations/doc_test_ide_view_component_list_panel_breadcrumbs__native_.xml +++ b/.idea/runConfigurations/doc-test-ide-view-component-list-panel-breadcrumbs-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ide_view_graph_editor__native_.xml b/.idea/runConfigurations/doc-test-ide-view-graph-editor-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_ide_view_graph_editor__native_.xml rename to .idea/runConfigurations/doc-test-ide-view-graph-editor-native.xml index 732d6108969c..aaf89b2d705b 100644 --- a/.idea/runConfigurations/doc_test_ide_view_graph_editor__native_.xml +++ b/.idea/runConfigurations/doc-test-ide-view-graph-editor-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_ide_view__native_.xml b/.idea/runConfigurations/doc-test-ide-view-native.xml similarity index 73% rename from .idea/runConfigurations/doc_test_ide_view__native_.xml rename to .idea/runConfigurations/doc-test-ide-view-native.xml index 3d78fe7b3a92..24154f824353 100644 --- a/.idea/runConfigurations/doc_test_ide_view__native_.xml +++ b/.idea/runConfigurations/doc-test-ide-view-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_intellij_run_config_gen__native_.xml b/.idea/runConfigurations/doc-test-intellij-run-config-gen-native.xml similarity index 71% rename from .idea/runConfigurations/doc_test_intellij_run_config_gen__native_.xml rename to .idea/runConfigurations/doc-test-intellij-run-config-gen-native.xml index 083d276eadd9..e45252ecaee1 100644 --- a/.idea/runConfigurations/doc_test_intellij_run_config_gen__native_.xml +++ b/.idea/runConfigurations/doc-test-intellij-run-config-gen-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_json_rpc__native_.xml b/.idea/runConfigurations/doc-test-json-rpc-native.xml similarity index 73% rename from .idea/runConfigurations/doc_test_json_rpc__native_.xml rename to .idea/runConfigurations/doc-test-json-rpc-native.xml index 0121e057699e..76ad4dff676d 100644 --- a/.idea/runConfigurations/doc_test_json_rpc__native_.xml +++ b/.idea/runConfigurations/doc-test-json-rpc-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_logstat__native_.xml b/.idea/runConfigurations/doc-test-logstat-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_logstat__native_.xml rename to .idea/runConfigurations/doc-test-logstat-native.xml index a61e570b0481..c098cbd7090c 100644 --- a/.idea/runConfigurations/doc_test_logstat__native_.xml +++ b/.idea/runConfigurations/doc-test-logstat-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/doc_test_span_tree__native_.xml b/.idea/runConfigurations/doc-test-span-tree-native.xml similarity index 72% rename from .idea/runConfigurations/doc_test_span_tree__native_.xml rename to .idea/runConfigurations/doc-test-span-tree-native.xml index db1eeadf84b0..86a98d5a764e 100644 --- a/.idea/runConfigurations/doc_test_span_tree__native_.xml +++ b/.idea/runConfigurations/doc-test-span-tree-native.xml @@ -1,5 +1,12 @@ - - + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/.idea/runConfigurations/test-ensogl-example-focus-management-native.xml b/.idea/runConfigurations/test-ensogl-example-focus-management-native.xml new file mode 100644 index 000000000000..d7e64825128b --- /dev/null +++ b/.idea/runConfigurations/test-ensogl-example-focus-management-native.xml @@ -0,0 +1,27 @@ + + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index dd39f830e73e..95625b25784c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,6 +92,10 @@ - [Text rendering quality improvements][3855]. Glyphs are now hinted in a better way. Also, additional fine-tuning is performed per font and per host operating system. +- [Display objects can now emit and receive events in the same style as + JavaScript DOM events][3863]. The events system implements very similar + behavior to the one described here: + https://javascript.info/bubbling-and-capturing. #### Enso Standard Library @@ -375,6 +379,7 @@ [3855]: https://github.com/enso-org/enso/pull/3855 [3836]: https://github.com/enso-org/enso/pull/3836 [3782]: https://github.com/enso-org/enso/pull/3782 +[3863]: https://github.com/enso-org/enso/pull/3863 #### Enso Compiler diff --git a/Cargo.lock b/Cargo.lock index 548c7a8c30bb..5d8d52c5a3b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2052,7 +2052,6 @@ dependencies = [ "nalgebra 0.26.2", "percent-encoding 2.1.0", "unicode-segmentation", - "unidecode", "wasm-bindgen", "web-sys", ] @@ -2623,6 +2622,14 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ensogl-example-focus-management" +version = "0.1.0" +dependencies = [ + "ensogl-core", + "wasm-bindgen", +] + [[package]] name = "ensogl-example-grid-view" version = "0.1.0" @@ -2770,6 +2777,7 @@ dependencies = [ "ensogl-example-dom-symbols", "ensogl-example-drop-manager", "ensogl-example-easing-animator", + "ensogl-example-focus-management", "ensogl-example-grid-view", "ensogl-example-list-view", "ensogl-example-mouse-events", @@ -7166,12 +7174,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" -[[package]] -name = "unidecode" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402bb19d8e03f1d1a7450e2bd613980869438e0666331be3e073089124aa1adc" - [[package]] name = "unreachable" version = "1.0.0" diff --git a/app/gui/view/component-browser/component-list-panel/grid/src/lib.rs b/app/gui/view/component-browser/component-list-panel/grid/src/lib.rs index a0c6c891ff42..0a62e66ae8f5 100644 --- a/app/gui/view/component-browser/component-list-panel/grid/src/lib.rs +++ b/app/gui/view/component-browser/component-list-panel/grid/src/lib.rs @@ -723,9 +723,9 @@ impl component::Frp for Frp { // === Focus propagation === - grid.focus <+ input.focus; - grid.defocus <+ input.defocus; - grid.set_focus <+ input.set_focus; + grid.deprecated_focus <+ input.deprecated_focus; + grid.deprecated_defocus <+ input.deprecated_defocus; + grid.deprecated_set_focus <+ input.deprecated_set_focus; } // Set the proper number of columns so we can set column widths. diff --git a/app/gui/view/component-browser/component-list-panel/src/lib.rs b/app/gui/view/component-browser/component-list-panel/src/lib.rs index 6e2b20c92b0d..447c97d794a2 100644 --- a/app/gui/view/component-browser/component-list-panel/src/lib.rs +++ b/app/gui/view/component-browser/component-list-panel/src/lib.rs @@ -382,7 +382,7 @@ impl component::Frp for Frp { // TODO[ib] Temporary solution for focus, we grab keyboard events if the // component browser is visible. The proper implementation is tracked in // https://www.pivotaltracker.com/story/show/180872763 - model.grid.set_focus <+ is_visible; + model.grid.deprecated_set_focus <+ is_visible; on_hover <- is_hovered.on_true(); on_hover_end <- is_hovered.on_false(); diff --git a/app/gui/view/graph-editor/src/component/breadcrumbs/project_name.rs b/app/gui/view/graph-editor/src/component/breadcrumbs/project_name.rs index 32d46550b434..12f7d5f2b663 100644 --- a/app/gui/view/graph-editor/src/component/breadcrumbs/project_name.rs +++ b/app/gui/view/graph-editor/src/component/breadcrumbs/project_name.rs @@ -278,7 +278,7 @@ impl ProjectName { edit_click <- mouse_down.gate(&frp.ide_text_edit_mode); start_editing <- any(edit_click,frp.input.start_editing); eval_ start_editing ({ - text.set_focus(true); + text.deprecated_set_focus(true); text.set_cursor_at_mouse_position() }); frp.source.edit_mode <+ start_editing.to_true(); @@ -317,7 +317,7 @@ impl ProjectName { set_inactive <- any(&frp.deselect,&on_commit); eval_ set_inactive ([text,animations]{ - text.set_focus(false); + text.deprecated_set_focus(false); text.remove_all_cursors(); animations.color.set_target_value(deselected_color); }); diff --git a/app/gui/view/graph-editor/src/component/node/input/area.rs b/app/gui/view/graph-editor/src/component/node/input/area.rs index 8faaf3453007..f4ce7d54381b 100644 --- a/app/gui/view/graph-editor/src/component/node/input/area.rs +++ b/app/gui/view/graph-editor/src/component/node/input/area.rs @@ -753,7 +753,7 @@ impl Area { // === Cursor setup === eval frp.input.set_edit_mode ([model](edit_mode) { - model.label.set_focus(edit_mode); + model.label.deprecated_set_focus(edit_mode); if *edit_mode { // Reset the code to hide non-connected port names. model.label.set_content(model.expression.borrow().code.clone()); diff --git a/app/gui/view/src/code_editor.rs b/app/gui/view/src/code_editor.rs index 31b6fa7581ba..eb6f16867db9 100644 --- a/app/gui/view/src/code_editor.rs +++ b/app/gui/view/src/code_editor.rs @@ -90,11 +90,11 @@ impl View { hide <- any(frp.input.hide,hide_after_toggle); eval_ show (height_fraction.set_target_value(HEIGHT_FRACTION)); - eval_ show (model.focus()); + eval_ show (model.deprecated_focus()); eval_ hide (height_fraction.set_target_value(0.0)); eval_ hide ([model] { model.remove_all_cursors(); - model.defocus(); + model.deprecated_defocus(); }); frp.source.is_visible <+ bool(&frp.input.hide,&frp.input.show); diff --git a/app/gui/view/src/searcher.rs b/app/gui/view/src/searcher.rs index 2717ee65e254..e7fd694970d8 100644 --- a/app/gui/view/src/searcher.rs +++ b/app/gui/view/src/searcher.rs @@ -122,7 +122,7 @@ impl Model { let logger = Logger::new("SearcherView"); let display_object = display::object::Instance::new(); let list = app.new_view::>(); - list.focus(); + list.deprecated_focus(); let documentation = documentation::View::new(scene); let doc_provider = default(); scene.layers.node_searcher.add(&list); diff --git a/lib/rust/callback/src/lib.rs b/lib/rust/callback/src/lib.rs index 19d337c83bf3..3d8110d5966a 100644 --- a/lib/rust/callback/src/lib.rs +++ b/lib/rust/callback/src/lib.rs @@ -22,7 +22,6 @@ use enso_prelude::*; -use std::any::TypeId; use std::marker::Unsize; @@ -371,61 +370,6 @@ pub mod traits { -// ========================== -// === DynEventDispatcher === -// ========================== - -/// A dynamic event wrapper. Dynamic events can be pattern matched by their types. See docs of -/// `DynEventDispatcher` to learn more. -#[derive(Debug, Clone)] -pub struct DynEvent { - any: Rc, -} - -impl DynEvent { - /// Constructor. - pub fn new(t: T) -> Self { - let any = Rc::new(t); - DynEvent { any } - } -} - -/// A dynamic event dispatcher. Allows dispatching an event of any type and registering listeners -/// for a particular type. -#[derive(Derivative, Default)] -#[derivative(Debug)] -pub struct DynEventDispatcher { - #[derivative(Debug = "ignore")] - #[allow(clippy::type_complexity)] - listener_map: HashMap>)>>, -} - -impl DynEventDispatcher { - /// Registers a new listener for a given type. - pub fn add_listener, T: 'static>(&mut self, mut f: F) -> Handle { - let callback = Box::new(move |event: &DynEvent| { - event.any.downcast_ref::().iter().for_each(|t| f(t)) - }); - let type_id = PhantomData::.type_id(); - let handle = Handle::default(); - let guard = handle.guard(); - let listeners = self.listener_map.entry(type_id).or_insert_with(default); - listeners.push((guard, callback)); - handle - } - - /// Dispatch an event to all listeners registered for that particular event type. - pub fn dispatch(&mut self, event: &DynEvent) { - let type_id = event.any.type_id(); - self.listener_map.get_mut(&type_id).iter_mut().for_each(|listeners| { - listeners.retain(|(guard, _)| !guard.is_expired()); - listeners.iter_mut().for_each(move |(_, callback)| callback(event)); - }); - } -} - - - // ================== // === Benchmarks === // ================== diff --git a/lib/rust/ensogl/core/src/application/frp.rs b/lib/rust/ensogl/core/src/application/frp.rs index 5b3b594ed6b9..64113e490679 100644 --- a/lib/rust/ensogl/core/src/application/frp.rs +++ b/lib/rust/ensogl/core/src/application/frp.rs @@ -219,13 +219,16 @@ macro_rules! define_endpoints { $(<$($param $(:($($constraints)*))?),*>)? Input { $($([$($input_opts)*])?)? + // FIXME[WD]: To be removed, see: https://www.pivotaltracker.com/story/show/183776234 /// Focus the element. Focused elements are meant to receive shortcut events. - focus(), + deprecated_focus(), + // FIXME[WD]: To be removed, see: https://www.pivotaltracker.com/story/show/183776234 /// Defocus the element. Non-focused elements are meant to be inactive and don't /// receive shortcut events. - defocus(), + deprecated_defocus(), + // FIXME[WD]: To be removed, see: https://www.pivotaltracker.com/story/show/183776234 /// Wrapper for `focus` and `defocus`. - set_focus(bool), + deprecated_set_focus(bool), $($($(#[doc=$($in_doc )*])* $in_field ($($in_field_type )*)),*)? } @@ -357,8 +360,8 @@ macro_rules! define_endpoints { let mut command_map : HashMap = default(); $crate::frp::extend! { $($($global_opts)*)? $($($output_opts)*)? network $($out_field <- source.$out_field.sampler();)* - focus_events <- bool(&input.defocus,&input.focus); - focused <- any(&input.set_focus,&focus_events); + focus_events <- bool(&input.deprecated_defocus, &input.deprecated_focus); + focused <- any(&input.deprecated_set_focus, &focus_events); source.focused <+ focused; } $($crate::build_status_map! @@ -845,13 +848,16 @@ macro_rules! define_endpoints_2 { [<$($($param $(:$($constraints)*)?),*)?>] [<$($($param),*)?>] [<($($($param),*)?)>] Input { [$($($global_opts)*)? $($($($input_opts)*)?)?] + // FIXME[WD]: To be removed, see: https://www.pivotaltracker.com/story/show/183776234 /// Focus the element. Focused elements are meant to receive shortcut events. - focus(), + deprecated_focus(), + // FIXME[WD]: To be removed, see: https://www.pivotaltracker.com/story/show/183776234 /// Defocus the element. Non-focused elements are meant to be inactive and don't /// receive shortcut events. - defocus(), + deprecated_defocus(), + // FIXME[WD]: To be removed, see: https://www.pivotaltracker.com/story/show/183776234 /// Wrapper for `focus` and `defocus`. - set_focus(bool) + deprecated_set_focus(bool) $($(, $(#$in_field_attr)* $in_field $in_field_type @@ -1113,8 +1119,8 @@ macro_rules! define_endpoints_2_normalized_public { $crate::frp::extend! { $output_opts network $( $out_field <- private_output.$out_field.profile().sampler(); )* - focus_events <- bool(&public_input.defocus,&public_input.focus); - focused <- any(&public_input.set_focus,&focus_events); + focus_events <- bool(&public_input.deprecated_defocus, &public_input.deprecated_focus); + focused <- any(&public_input.deprecated_set_focus, &focus_events); private_output.focused <+ focused; } let mut status_map : HashMap> = default(); diff --git a/lib/rust/ensogl/core/src/display.rs b/lib/rust/ensogl/core/src/display.rs index 2014ebf10b65..513c0980cc01 100644 --- a/lib/rust/ensogl/core/src/display.rs +++ b/lib/rust/ensogl/core/src/display.rs @@ -40,4 +40,6 @@ pub mod types { pub use scene::dom::DomScene; pub use scene::Scene; } + +pub use object::event; pub use types::*; diff --git a/lib/rust/ensogl/core/src/display/object.rs b/lib/rust/ensogl/core/src/display/object.rs index 89eabad6fa1c..fef19273dd0d 100644 --- a/lib/rust/ensogl/core/src/display/object.rs +++ b/lib/rust/ensogl/core/src/display/object.rs @@ -10,6 +10,7 @@ use crate::display::scene::layer::Layer; // ============== pub mod class; +pub mod event; pub mod transform; pub use class::Any; diff --git a/lib/rust/ensogl/core/src/display/object/class.rs b/lib/rust/ensogl/core/src/display/object/class.rs index 9b273a535174..803eb2f9f4d9 100644 --- a/lib/rust/ensogl/core/src/display/object/class.rs +++ b/lib/rust/ensogl/core/src/display/object/class.rs @@ -10,6 +10,7 @@ use crate::display::scene::layer::Layer; use crate::display::scene::layer::WeakLayer; use crate::display::scene::Scene; +use super::event; use super::transform; use data::opt_vec::OptVec; use nalgebra::Matrix4; @@ -28,7 +29,7 @@ use transform::CachedTransform; #[derive(Derivative)] #[derivative(Debug(bound = ""))] #[allow(missing_docs)] -pub struct ParentBind { +pub struct ParentBind { pub parent: WeakInstance, pub index: usize, } @@ -39,9 +40,11 @@ impl ParentBind { } } -impl Drop for ParentBind { +impl Drop for ParentBind { fn drop(&mut self) { - self.parent().for_each(|p| p.remove_child_by_index(self.index)); + if let Some(parent) = self.parent() { + parent.remove_child_by_index(self.index) + } } } @@ -60,7 +63,7 @@ impl Drop for ParentBind { #[derive(Derivative)] #[derivative(Default(bound = ""))] #[allow(clippy::type_complexity)] -pub struct Callbacks { +pub struct Callbacks { on_updated: RefCell)>>>, on_show: RefCell)>>>, on_hide: RefCell>>, @@ -129,7 +132,7 @@ type SceneLayerDirty = dirty::SharedBool; /// the hierarchy is updated after calling the `update` method. #[derive(Derivative)] #[derivative(Debug(bound = ""))] -pub struct DirtyFlags { +pub struct DirtyFlags { parent: NewParentDirty, children: ChildrenDirty, removed_children: RemovedChildren, @@ -187,25 +190,68 @@ fn on_dirty_callback(f: &Rc>>) -> OnDirtyCallback { /// /// See the documentation of [`Instance`] to learn more. #[derive(Derivative)] -#[derivative(Debug(bound = ""), Default(bound = ""))] -pub struct Model { - host: PhantomData, +#[derivative(Debug(bound = ""))] +pub struct Model { + network: frp::Network, + /// Source for events. See the documentation of [`event::Event`] to learn more about events. + pub event_source: frp::Source, + capturing_event_fan: frp::Fan, + bubbling_event_fan: frp::Fan, + host: PhantomData, + focused_descendant: RefCell>>, /// Layer the object was explicitly assigned to by the user, if any. - assigned_layer: RefCell>, + assigned_layer: RefCell>, /// Layer where the object is displayed. It may be set to by user or inherited from the parent. - layer: RefCell>, - dirty: DirtyFlags, - callbacks: Callbacks, - parent_bind: RefCell>>, - children: RefCell>>, - transform: RefCell, - visible: Cell, + layer: RefCell>, + dirty: DirtyFlags, + callbacks: Callbacks, + parent_bind: Rc>>>, + children: RefCell>>, + transform: RefCell, + visible: Cell, } -impl Model { +impl Default for Model { + fn default() -> Self { + Self::new() + } +} + +impl Model { /// Constructor. pub fn new() -> Self { - default() + let network = frp::Network::new("display_object"); + let host = default(); + let focused_descendant = default(); + let assigned_layer = default(); + let layer = default(); + let dirty = default(); + let callbacks = default(); + let parent_bind = default(); + let children = default(); + let transform = default(); + let visible = default(); + let capturing_event_fan = frp::Fan::new(&network); + let bubbling_event_fan = frp::Fan::new(&network); + frp::extend! { network + event_source <- source(); + } + Self { + network, + event_source, + capturing_event_fan, + bubbling_event_fan, + host, + focused_descendant, + assigned_layer, + layer, + dirty, + callbacks, + parent_bind, + children, + transform, + visible, + } } /// Checks whether the object is visible. @@ -267,6 +313,19 @@ impl Model { self.dirty.unset_on_dirty(); self.dirty.parent.set(); } + + /// Get event stream for bubbling events. See docs of [`event::Event`] to learn more. + pub fn on_event(&self) -> frp::Stream> + where T: frp::Data { + self.bubbling_event_fan.output::>() + } + + /// Get event stream for capturing events. You should rather not need this function. Use + /// [`on_event`] instead. See docs of [`event::Event`] to learn more. + pub fn on_event_capturing(&self) -> frp::Stream> + where T: frp::Data { + self.capturing_event_fan.output::>() + } } @@ -440,7 +499,7 @@ impl Model { // === Register / Unregister === -impl Model { +impl Model { fn register_child>(&self, child: &T) -> usize { let index = self.children.borrow_mut().insert(child.weak_display_object()); self.dirty.children.set(index); @@ -450,12 +509,25 @@ impl Model { /// Removes and returns the parent bind. Please note that the parent is not updated as long as /// the parent bind is not dropped. fn take_parent_bind(&self) -> Option> { - self.parent_bind.borrow_mut().take() + let parent_bind = self.parent_bind.borrow_mut().take(); + if let Some(parent) = parent_bind.as_ref().and_then(|t| t.parent.upgrade()) { + let is_focused = self.focused_descendant.borrow().is_some(); + if is_focused { + parent.propagate_up_no_focus_instance(); + } + } + parent_bind } /// Set parent of the object. If the object already has a parent, the parent would be replaced. fn set_parent_bind(&self, bind: ParentBind) { trace!("Adding new parent bind."); + if let Some(focus_instance) = &*self.focused_descendant.borrow() { + if let Some(parent) = bind.parent.upgrade() { + parent._blur_tree(); + parent.propagate_up_new_focus_instance(focus_instance); + } + } if let Some(parent) = bind.parent() { let index = bind.index; let dirty = parent.dirty.children.clone_ref(); @@ -619,29 +691,173 @@ pub struct Id(usize); /// to be assigned to a particular [`scene::Layer`], and thus allows for easy to use depth /// management. #[derive(Derivative)] -#[derive(CloneRef)] -#[derivative(Clone(bound = ""), Default(bound = ""))] -pub struct Instance { +#[derive(CloneRef, Deref)] +#[derivative(Clone(bound = ""))] +pub struct Instance { rc: Rc>, } -impl Deref for Instance { - type Target = Rc>; - fn deref(&self) -> &Self::Target { - &self.rc + +impl Instance { + /// Create a new weak pointer to this display object instance. + pub fn downgrade(&self) -> WeakInstance { + let weak = Rc::downgrade(&self.rc); + WeakInstance { weak } } } -impl Instance { +impl Instance { /// Constructor. pub fn new() -> Self { - default() + Self { rc: Rc::new(Model::new()) }.init() + } + + fn init(self) -> Self { + // This implementation is a bit complex because we do not want to clone network to the FRP + // closure in order to avoid a memory leak. + let network = &self.network; + let parent_bind = &self.parent_bind; + let capturing_event_fan = &self.capturing_event_fan; + let bubbling_event_fan = &self.bubbling_event_fan; + frp::extend! { network + eval self.event_source ([parent_bind, capturing_event_fan, bubbling_event_fan] (event) { + let parent = parent_bind.borrow().as_ref().and_then(|t| t.parent()); + Self::emit_event_impl(event, parent, &capturing_event_fan, &bubbling_event_fan); + }); + } + self } - /// Create a new weak pointer to this display object instance. - pub fn downgrade(&self) -> WeakInstance { - let weak = Rc::downgrade(&self.rc); - WeakInstance { weak } + fn emit_event_impl( + event: &event::SomeEvent, + parent: Option>, + capturing_event_fan: &frp::Fan, + bubbling_event_fan: &frp::Fan, + ) { + let rev_parent_chain = Self::rev_parent_chain(parent); + if event.captures.get() { + for object in &rev_parent_chain { + if !event.is_cancelled() { + object.capturing_event_fan.emit(&event.data); + } else { + break; + } + } + } + if !event.is_cancelled() { + capturing_event_fan.emit(&event.data); + } + if !event.is_cancelled() { + bubbling_event_fan.emit(&event.data); + } + if event.bubbles.get() { + for object in rev_parent_chain.iter().rev() { + if !event.is_cancelled() { + object.bubbling_event_fan.emit(&event.data); + } else { + break; + } + } + } + } + + /// Get reversed parent chain of this display object (`[root, child_of root, ..., + /// parent]`). The last item is the argument passed to this function. + fn rev_parent_chain(parent: Option>) -> Vec> { + let mut vec = default(); + Self::build_rev_parent_chain(&mut vec, parent); + vec + } + + fn build_rev_parent_chain(vec: &mut Vec>, parent: Option>) { + if let Some(parent) = parent { + Self::build_rev_parent_chain(vec, parent.parent()); + vec.push(parent); + } + } + + fn _new_event(&self, payload: T) -> event::SomeEvent + where T: 'static { + event::SomeEvent::new(Some(self), payload) + } + + fn _emit_event(&self, payload: T) + where T: 'static { + self.event_source.emit(event::SomeEvent::new(Some(self), payload)); + } + + fn focused_descendant(&self) -> Option> { + self.focused_descendant.borrow().as_ref().and_then(|t| t.upgrade()) + } + + fn _focused_instance(&self) -> Option> { + if let Some(child) = self.focused_descendant() { + Some(child) + } else { + self.parent().and_then(|parent| parent._focused_instance()) + } + } + + fn _is_focused(&self) -> bool { + self.focused_descendant().as_ref() == Some(self) + } + + fn _focus(&self) { + self._blur_tree(); + self.propagate_up_new_focus_instance(&self.downgrade()); + let focus_event = self._new_event(event::Focus); + let focus_in_event = self._new_event(event::FocusIn); + focus_event.bubbles.set(false); + self.event_source.emit(focus_event); + self.event_source.emit(focus_in_event); + } + + fn _blur(&self) { + if self._is_focused() { + self.blur_unchecked(); + } + } + + /// Blur the display object tree this object belongs to. If any tree node (any node directly or + /// indirectly connected with each other) was focused, it will be blurred. + fn _blur_tree(&self) { + if let Some(instance) = self._focused_instance() { + instance.blur_unchecked(); + } + } + + /// Blur this object and propagate the information to root. Does not check if this object was + /// focused. Calling this method on a non-focused object may cause inconsistent state, as parent + /// objects will erase information about the currently focused object. + fn blur_unchecked(&self) { + self.propagate_up_no_focus_instance(); + let blur_event = self._new_event(event::Blur); + let focus_out_event = self._new_event(event::FocusOut); + blur_event.bubbles.set(false); + self.event_source.emit(blur_event); + self.event_source.emit(focus_out_event); + } + + /// Clears the focus info in this instance and all parent instances. In order to work properly, + /// this should be called on the focused instance. Otherwise, it may clear the information + /// only partially. + fn propagate_up_no_focus_instance(&self) { + *self.focused_descendant.borrow_mut() = None; + self.parent().for_each(|parent| parent.propagate_up_no_focus_instance()); + } + + /// Set the focus instance to the provided one here and in all instances on the path to the + /// root. + fn propagate_up_new_focus_instance(&self, instance: &WeakInstance) { + debug_assert!(self.focused_descendant.borrow().is_none()); + *self.focused_descendant.borrow_mut() = Some(instance.clone()); + self.parent().for_each(|parent| parent.propagate_up_new_focus_instance(instance)); + } +} + +impl Default for Instance { + fn default() -> Self { + Self::new() } } @@ -653,7 +869,9 @@ impl Instance { pub fn _id(&self) -> Id { Id(Rc::downgrade(&self.rc).as_ptr() as *const () as usize) } +} +impl Instance { /// Get the layers where this object is displayed. May be equal to layers it was explicitly /// assigned, or layers inherited from the parent. pub fn _display_layers(&self) -> Option { @@ -684,12 +902,6 @@ impl Instance { /// Adds a new `Object` as a child to the current one. pub fn _add_child>(&self, child: &T) { - self.clone_ref().add_child_take(child); - } - - /// Adds a new `Object` as a child to the current one. This is the same as `add_child` but takes - /// the ownership of `self`. - pub fn add_child_take>(self, child: &T) { trace!("Adding new child."); let child = child.display_object(); child.unset_parent(); @@ -786,7 +998,7 @@ impl Debug for Instance { #[derive(Derivative)] #[derivative(Clone(bound = ""))] #[derivative(Debug(bound = ""))] -pub struct WeakInstance { +pub struct WeakInstance { weak: Weak>, } @@ -855,7 +1067,7 @@ impl> Object for &T { // === ObjectOps === // ================= -impl + ?Sized> ObjectOps for T {} +impl + ?Sized> ObjectOps for T {} /// Implementation of operations available for every struct which implements `display::Object`. /// To learn more about the design, please refer to the documentation of [`Instance`]. @@ -863,12 +1075,17 @@ impl + ?Sized> ObjectOps for T {} // HOTFIX[WD]: We are using names with underscores in order to fix this bug: // https://github.com/rust-lang/rust/issues/70727 . To be removed as soon as the bug is fixed. #[allow(missing_docs)] -pub trait ObjectOps: Object { +pub trait ObjectOps: Object { + // === Information === + /// Globally unique identifier of this display object. fn id(&self) -> Id { self.display_object()._id() } + + // === Hierarchy === + /// Get the layers where this object is displayed. May be equal to layers it was explicitly /// assigned, or layers inherited from the parent. fn display_layer(&self) -> Option { @@ -908,6 +1125,62 @@ pub trait ObjectOps: Object { } + // === Events === + + /// Emit a new event. See docs of [`event::Event`] to learn more. + fn emit_event(&self, event: T) + where T: 'static { + self.display_object()._emit_event(event) + } + + /// Get event stream for bubbling events. See docs of [`event::Event`] to learn more. + fn on_event(&self) -> frp::Stream> + where T: frp::Data { + self.display_object().rc.on_event() + } + + /// Get event stream for capturing events. You should rather not need this function. Use + /// [`on_event`] instead. See docs of [`event::Event`] to learn more. + fn on_event_capturing(&self) -> frp::Stream> + where T: frp::Data { + self.display_object().rc.on_event_capturing() + } + + /// Creates a new event with this object set to target. + fn new_event(&self, payload: T) -> event::SomeEvent { + self.display_object()._new_event(payload) + } + + + // === Focus === + + /// Check whether this object is focused. + fn is_focused(&self) -> bool { + self.display_object()._is_focused() + } + + /// Focus this object. See docs of [`Event::Focus`] to learn more. + fn focus(&self) { + self.display_object()._focus() + } + + /// Blur ("unfocus") this object. See docs of [`Event::Blur`] to learn more. + fn blur(&self) { + self.display_object()._blur() + } + + /// Blur the display object tree this object belongs to. If any tree node (any node directly or + /// indirectly connected with each other) was focused, it will be blurred. + fn blur_tree(&self) { + self.display_object()._blur_tree() + } + + /// Get the currently focused object if any. See docs of [`Event::Focus`] to learn more. + fn focused_instance(&self) -> Option> { + self.display_object()._focused_instance() + } + + // === Transform === fn transform_matrix(&self) -> Matrix4 { @@ -1617,4 +1890,221 @@ mod tests { assert_eq!(node2.display_layer(), Some(layer2.downgrade())); assert_eq!(node3.display_layer(), Some(layer1.downgrade())); } + + #[test] + fn focus_consistency_test() { + // obj_root + // / \ + // obj_left_1 obj_right_1 + // | | + // obj_left_2 obj_right_2 + let obj_root = Instance::<()>::new(); + let obj_left_1 = Instance::<()>::new(); + let obj_left_2 = Instance::<()>::new(); + let obj_right_1 = Instance::<()>::new(); + let obj_right_2 = Instance::<()>::new(); + obj_root.add_child(&obj_left_1); + obj_root.add_child(&obj_right_1); + obj_left_1.add_child(&obj_left_2); + obj_right_1.add_child(&obj_right_2); + + let check_focus_consistency = |focused: Option<&Instance<()>>| { + // Check that at most one object is focused and if so, that it is the correct one. + assert_eq!(obj_root.is_focused(), focused == Some(&obj_root)); + assert_eq!(obj_left_1.is_focused(), focused == Some(&obj_left_1)); + assert_eq!(obj_left_2.is_focused(), focused == Some(&obj_left_2)); + assert_eq!(obj_right_1.is_focused(), focused == Some(&obj_right_1)); + assert_eq!(obj_right_2.is_focused(), focused == Some(&obj_right_2)); + + // Check that all nodes contain the valid reference to the focused one. + assert_eq!(obj_root.focused_instance().as_ref(), focused); + assert_eq!(obj_left_1.focused_instance().as_ref(), focused); + assert_eq!(obj_left_2.focused_instance().as_ref(), focused); + assert_eq!(obj_right_1.focused_instance().as_ref(), focused); + assert_eq!(obj_right_2.focused_instance().as_ref(), focused); + + // Check that focus information is correctly distributed across the branches. + if focused == Some(&obj_root) { + assert_eq!(obj_root.focused_descendant().as_ref(), focused); + assert_eq!(obj_left_1.focused_descendant().as_ref(), None); + assert_eq!(obj_left_2.focused_descendant().as_ref(), None); + assert_eq!(obj_right_1.focused_descendant().as_ref(), None); + assert_eq!(obj_right_2.focused_descendant().as_ref(), None); + } else if focused == Some(&obj_left_1) { + assert_eq!(obj_root.focused_descendant().as_ref(), focused); + assert_eq!(obj_left_1.focused_descendant().as_ref(), focused); + assert_eq!(obj_left_2.focused_descendant().as_ref(), None); + assert_eq!(obj_right_1.focused_descendant().as_ref(), None); + assert_eq!(obj_right_2.focused_descendant().as_ref(), None); + } else if focused == Some(&obj_left_2) { + assert_eq!(obj_root.focused_descendant().as_ref(), focused); + assert_eq!(obj_left_1.focused_descendant().as_ref(), focused); + assert_eq!(obj_left_2.focused_descendant().as_ref(), focused); + assert_eq!(obj_right_1.focused_descendant().as_ref(), None); + assert_eq!(obj_right_2.focused_descendant().as_ref(), None); + } else if focused == Some(&obj_right_1) { + assert_eq!(obj_root.focused_descendant().as_ref(), focused); + assert_eq!(obj_left_1.focused_descendant().as_ref(), None); + assert_eq!(obj_left_2.focused_descendant().as_ref(), None); + assert_eq!(obj_right_1.focused_descendant().as_ref(), focused); + assert_eq!(obj_right_2.focused_descendant().as_ref(), None); + } else if focused == Some(&obj_right_2) { + assert_eq!(obj_root.focused_descendant().as_ref(), focused); + assert_eq!(obj_left_1.focused_descendant().as_ref(), None); + assert_eq!(obj_left_2.focused_descendant().as_ref(), None); + assert_eq!(obj_right_1.focused_descendant().as_ref(), focused); + assert_eq!(obj_right_2.focused_descendant().as_ref(), focused); + } + }; + + // === Checking the initial state === + + check_focus_consistency(None); + + + // === Checking if blurring works === + + obj_left_1.focus(); + check_focus_consistency(Some(&obj_left_1)); + + obj_left_2.blur(); + check_focus_consistency(Some(&obj_left_1)); + + obj_left_1.blur(); + check_focus_consistency(None); + + + // === Checking if focus stealing works === + + obj_left_1.focus(); + check_focus_consistency(Some(&obj_left_1)); + + obj_right_1.focus(); + check_focus_consistency(Some(&obj_right_1)); + + obj_left_2.focus(); + check_focus_consistency(Some(&obj_left_2)); + + obj_right_2.focus(); + check_focus_consistency(Some(&obj_right_2)); + + obj_root.blur_tree(); + check_focus_consistency(None); + + + // === Checking if detaching subtree removes focus from parent its parent === + + obj_left_2.focus(); + check_focus_consistency(Some(&obj_left_2)); + + obj_left_1.unset_parent(); + assert!(!obj_root.is_focused()); + assert!(!obj_left_1.is_focused()); + assert!(obj_left_2.is_focused()); + assert!(!obj_right_1.is_focused()); + assert!(!obj_right_2.is_focused()); + + assert_eq!(obj_root.focused_instance().as_ref(), None); + assert_eq!(obj_left_1.focused_instance().as_ref(), Some(&obj_left_2)); + assert_eq!(obj_left_2.focused_instance().as_ref(), Some(&obj_left_2)); + assert_eq!(obj_right_1.focused_instance().as_ref(), None); + assert_eq!(obj_right_2.focused_instance().as_ref(), None); + + + // === Checking if attaching subtree with a focus steals the existing one === + + obj_right_2.focus(); + obj_root.add_child(&obj_left_1); + check_focus_consistency(Some(&obj_left_2)); + } + + #[test] + fn focus_event_propagation_test() { + let obj_1 = Instance::<()>::new(); + let obj_2 = Instance::<()>::new(); + let obj_3 = Instance::<()>::new(); + obj_1.add_child(&obj_2); + obj_2.add_child(&obj_3); + + let capturing_1 = obj_1.on_event_capturing::(); + let capturing_2 = obj_2.on_event_capturing::(); + let capturing_3 = obj_3.on_event_capturing::(); + let bubbling_1 = obj_1.on_event::(); + let bubbling_2 = obj_2.on_event::(); + let bubbling_3 = obj_3.on_event::(); + + + // === Event phases test === + + let network = frp::Network::new("network"); + let out: Rc>> = default(); + frp::extend! { network + eval_ capturing_1 (out.borrow_mut().push("capturing_1")); + eval_ capturing_2 (out.borrow_mut().push("capturing_2")); + eval_ capturing_3 (out.borrow_mut().push("capturing_3")); + eval_ bubbling_1 (out.borrow_mut().push("bubbling_1")); + eval_ bubbling_2 (out.borrow_mut().push("bubbling_2")); + eval_ bubbling_3 (out.borrow_mut().push("bubbling_3")); + } + + obj_3.emit_event::(0.0); + assert_eq!(&*out.borrow(), &[ + "capturing_1", + "capturing_2", + "capturing_3", + "bubbling_3", + "bubbling_2", + "bubbling_1" + ]); + drop(network); + + + // === Cancelling the event === + + let network = frp::Network::new("network"); + let out: Rc>> = default(); + frp::extend! { network + eval_ capturing_1 (out.borrow_mut().push("capturing_1")); + eval capturing_2 ([out] (e) { + e.stop_propagation(); + out.borrow_mut().push("capturing_2") + }); + eval_ capturing_3 (out.borrow_mut().push("capturing_3")); + eval_ bubbling_1 (out.borrow_mut().push("bubbling_1")); + eval_ bubbling_2 (out.borrow_mut().push("bubbling_2")); + eval_ bubbling_3 (out.borrow_mut().push("bubbling_3")); + } + + obj_3.emit_event::(0.0); + assert_eq!(&*out.borrow(), &["capturing_1", "capturing_2",]); + drop(network); + + + // === Manual event creation === + + let network = frp::Network::new("network"); + let out: Rc>> = default(); + frp::extend! { network + eval_ capturing_1 (out.borrow_mut().push("capturing_1")); + eval_ capturing_2 (out.borrow_mut().push("capturing_2")); + eval_ capturing_3 (out.borrow_mut().push("capturing_3")); + eval_ bubbling_1 (out.borrow_mut().push("bubbling_1")); + eval bubbling_2 ([out] (e) { + e.stop_propagation(); + out.borrow_mut().push("bubbling_2") + }); + eval_ bubbling_3 (out.borrow_mut().push("bubbling_3")); + } + + let event = obj_3.new_event::(0.0); + obj_3.event_source.emit(&event); + assert_eq!(&*out.borrow(), &[ + "capturing_1", + "capturing_2", + "capturing_3", + "bubbling_3", + "bubbling_2" + ]); + drop(network); + } } diff --git a/lib/rust/ensogl/core/src/display/object/event.rs b/lib/rust/ensogl/core/src/display/object/event.rs new file mode 100644 index 000000000000..f6b3ee61097f --- /dev/null +++ b/lib/rust/ensogl/core/src/display/object/event.rs @@ -0,0 +1,183 @@ +//! Events implementation. Events behave in a similar way to JavaScript Events. When an event is +//! emitted, it is propagated in three stages: capturing, target, and bubbling. Each stage is +//! configurable and some events propagation can be cancelled. To learn more about the mechanics, +//! see: https://javascript.info/bubbling-and-capturing. + +use crate::prelude::*; + +use crate::display::object::class::Instance; +use crate::display::object::class::WeakInstance; + + + +// ============= +// === State === +// ============= + +/// Event state. It can be used to determine whether the event is being propagated, its propagation +/// is cancelled, or that the propagation cannot be cancelled. See docs of this module to learn +/// more. +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum State { + #[default] + Running, + RunningNonCancellable, + Cancelled, +} + + + +// ================= +// === SomeEvent === +// ================= + +/// Similar to [`Event`] but with a hidden payload type. It is used to construct, configure, and +/// emit new events. +#[allow(missing_docs)] +#[derive(Clone, CloneRef, Debug)] +pub struct SomeEvent { + pub data: frp::AnyData, + state: Rc>, + /// Indicates whether the event participates in the capturing phase. + pub captures: Rc>, + /// Indicates whether the event participates in the bubbling phase. + pub bubbles: Rc>, +} + +impl SomeEvent { + /// Constructor. + pub fn new(target: Option<&Instance>, payload: T) -> Self { + let event = Event::new(target.map(|t| t.downgrade()), payload); + let state = event.state.clone_ref(); + let captures = Rc::new(Cell::new(true)); + let bubbles = Rc::new(Cell::new(true)); + Self { data: frp::AnyData::new(event), state, captures, bubbles } + } + + /// The [`State]` of the event. + pub fn state(&self) -> State { + self.state.get() + } + + /// Check whether the event was cancelled. + pub fn is_cancelled(&self) -> bool { + self.state() == State::Cancelled + } +} + +impl Default for SomeEvent { + fn default() -> Self { + Self::new::<(), ()>(None, ()) + } +} + + + +// ============= +// === Event === +// ============= + +/// The [`Event`] interface represents an event which takes place in the EnsoGL display object +/// hierarchy. +/// +/// An event can be triggered by the user action e.g. clicking the mouse button or tapping keyboard, +/// or generated by APIs to represent the progress of an asynchronous task. It can also be triggered +/// programmatically, such as by calling the [`display::object::Instance::focus()`] method of an +/// element, or by defining the event, then sending it to a specified target using +/// [`display::object::Instance::event_source::emit(...)`]. +/// +/// See the JavaScript counterpart of this struct: +/// https://developer.mozilla.org/en-US/docs/Web/API/Event. +#[derive(Derivative, Deref)] +#[derivative(Clone(bound = ""))] +#[derivative(Debug(bound = "T: Debug"))] +#[derivative(Default(bound = "T: Default"))] +pub struct Event { + data: Rc>, +} + +/// Internal representation of [`Event`]. +#[derive(Deref, Derivative)] +#[derivative(Debug(bound = "T: Debug"))] +#[derivative(Default(bound = "T: Default"))] +pub struct EventData { + #[deref] + payload: T, + target: Option>, + state: Rc>, +} + +impl Event { + fn new(target: Option>, payload: T) -> Self { + let state = default(); + let data = Rc::new(EventData { payload, target, state }); + Self { data } + } + + /// Prevents further propagation of the current event in the capturing and bubbling phases. It + /// also does NOT prevent immediate propagation to other event-handlers. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation. + pub fn stop_propagation(&self) { + if self.state.get() == State::RunningNonCancellable { + warn!("Trying to cancel a non-cancellable event."); + } else { + self.state.set(State::Cancelled); + } + } + + /// A reference to the object onto which the event was dispatched. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/Event/target. + pub fn target(&self) -> Option> { + self.data.target.as_ref().and_then(|t| t.upgrade()) + } +} + + + +// ==================== +// === Basic Events === +// ==================== + +/// The [`Focus`] event fires when an element has received focus. The event does not bubble, but the +/// related [`FocusIn`] event that follows does bubble. +/// +/// The opposite of [`Focus`] is the [`Blur`] event, which fires when the element has lost focus. +/// +/// The [`Focus`] event is not cancelable. +/// +/// See: https://developer.mozilla.org/en-US/docs/Web/API/Element/focus_event. +#[derive(Clone, Copy, Debug, Default)] +pub struct Focus; + +/// The [`Blur`] event fires when an element has lost focus. The event does not bubble, but the +/// related [`FocusOut`] event that follows does bubble. +/// +/// The opposite of [`Blur`] is the [Focus] event, which fires when the element has received focus. +/// The [`Blur`] event is not cancelable. +/// +/// See: https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event. +#[derive(Clone, Copy, Debug, Default)] +pub struct Blur; + +/// The [`FocusIn`] event fires when an element has received focus, after the [`Focus`] event. The +/// two events differ in that [`FocusIn`] bubbles, while [`Focus`] does not. +/// +/// The opposite of [`FocusIn`] is the [`FocusOut`] event, which fires when the element has lost +/// focus. The [`FocusIn`] event is not cancelable. +/// +/// See: https://developer.mozilla.org/en-US/docs/Web/API/Element/focusin_event. +#[derive(Clone, Copy, Debug, Default)] +pub struct FocusIn; + +/// The [`FocusOut`] event fires when an element has lost focus, after the [`Blur`] event. The two +/// events differ in that [`FocusOut`] bubbles, while [`Blur`] does not. +/// +/// The opposite of [`FocusOut`] is the [`FocusIn`] event, which fires when the element has received +/// focus. The [`FocusOut`] event is not cancelable. +/// +/// See: https://developer.mozilla.org/en-US/docs/Web/API/Element/focusout_event. +#[derive(Clone, Copy, Debug, Default)] +pub struct FocusOut; diff --git a/lib/rust/ensogl/core/src/lib.rs b/lib/rust/ensogl/core/src/lib.rs index 26041d4865ee..5c0576a91cfd 100644 --- a/lib/rust/ensogl/core/src/lib.rs +++ b/lib/rust/ensogl/core/src/lib.rs @@ -21,6 +21,7 @@ #![feature(const_trait_impl)] #![feature(slice_as_chunks)] #![feature(local_key_cell_methods)] +#![feature(auto_traits)] // === Standard Linter Configuration === #![deny(non_ascii_idents)] #![warn(unsafe_code)] @@ -56,6 +57,7 @@ pub mod system; pub use animation::Animation; pub use animation::DEPRECATED_Animation; pub use animation::Easing; +pub use display::event; pub use enso_frp as frp; pub use enso_types as types; diff --git a/lib/rust/ensogl/example/Cargo.toml b/lib/rust/ensogl/example/Cargo.toml index 3b08f8fb1b6e..a7993bc4316c 100644 --- a/lib/rust/ensogl/example/Cargo.toml +++ b/lib/rust/ensogl/example/Cargo.toml @@ -13,6 +13,7 @@ ensogl-example-complex-shape-system = { path = "complex-shape-system" } ensogl-example-custom-shape-system = { path = "custom-shape-system" } ensogl-example-dom-symbols = { path = "dom-symbols" } ensogl-example-drop-manager = { path = "drop-manager" } +ensogl-example-focus-management = { path = "focus-management" } ensogl-example-easing-animator = { path = "easing-animator" } ensogl-example-list-view = { path = "list-view" } ensogl-example-grid-view = { path = "grid-view" } diff --git a/lib/rust/ensogl/example/focus-management/Cargo.toml b/lib/rust/ensogl/example/focus-management/Cargo.toml new file mode 100644 index 000000000000..f42941c0f828 --- /dev/null +++ b/lib/rust/ensogl/example/focus-management/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "ensogl-example-focus-management" +version = "0.1.0" +authors = ["Enso Team "] +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +ensogl-core = { path = "../../core" } +wasm-bindgen = { version = "0.2.78", features = ["nightly"] } + +# Stop wasm-pack from running wasm-opt, because we run it from our build scripts in order to customize options. +[package.metadata.wasm-pack.profile.release] +wasm-opt = false diff --git a/lib/rust/ensogl/example/focus-management/src/lib.rs b/lib/rust/ensogl/example/focus-management/src/lib.rs new file mode 100644 index 000000000000..bfeb5997f805 --- /dev/null +++ b/lib/rust/ensogl/example/focus-management/src/lib.rs @@ -0,0 +1,119 @@ +//! Example scene showing the usage of focusing objects in display object hierarchy. + +// === Standard Linter Configuration === +#![deny(non_ascii_idents)] +#![warn(unsafe_code)] +#![allow(clippy::bool_to_int_with_if)] +#![allow(clippy::let_and_return)] + +use ensogl_core::display::shape::*; +use ensogl_core::display::world::*; +use ensogl_core::prelude::*; +use wasm_bindgen::prelude::*; + +use ensogl_core::data::color; +use ensogl_core::display::navigation::navigator::Navigator; +use ensogl_core::display::object::ObjectOps; +use ensogl_core::event::FocusIn; +use ensogl_core::event::FocusOut; +use ensogl_core::Animation; + + + +// ============== +// === Shapes === +// ============== + +const BORDER_SIZE: f32 = 4.0; +const RECT_SIZE: f32 = 140.0; +const RECT_DIFF: f32 = 40.0; + +mod rectangle { + use super::*; + ensogl_core::shape! { + (style: Style, color: Vector4, border_size: f32, border_color: Vector4) { + let width = Var::::from("input_size.x"); + let height = Var::::from("input_size.y"); + let rect = Rect((&width, &height)).corners_radius(10.0.px()); + let inside = rect.shrink(BORDER_SIZE.px()); + let border = &inside.grow((border_size - 1.0).px()) - &inside; + let shape = inside.fill(color) + border.fill(border_color); + shape.into() + } + } +} + + + +// =================== +// === Entry Point === +// =================== + +fn define_rect(width: f32, height: f32, network: &frp::Network) -> rectangle::View { + let rect = rectangle::View::new(); + rect.size.set(Vector2::new(width, height)); + rect.color.set(color::Rgba::new(0.0, 0.0, 0.0, 0.3).into()); + + let border_size = Animation::::new(network); + let border_color = color::Animation::new(network); + + // Please note that this clones [`rect`] refs to closures, so [`network`] keeps them alive. + frp::extend! { network + eval_ rect.events.mouse_down (rect.focus()); + + eval border_size.value ((size) rect.border_size.set(*size)); + eval border_color.value ((color) rect.border_color.set(color::Rgba::from(color).into())); + + let rect_on_focus_in = rect.on_event::(); + let rect_on_focus_out = rect.on_event::(); + + border_size_on_focus_in <- rect_on_focus_in.constant(BORDER_SIZE); + border_size_on_focus_out <- rect_on_focus_out.constant(0.0); + new_border_size <- any(&border_size_on_focus_in, &border_size_on_focus_out); + border_size.target <+ new_border_size; + + new_color <- rect_on_focus_in.map (f!([rect] (e) { + let is_target = e.target().as_ref() == Some(rect.display_object()); + if is_target { + color::Lcha::from(color::Rgba::new(0.878, 0.25, 0.25, 1.0)) + } else { + color::Lcha::from(color::Rgba::new(0.247, 0.407, 0.808, 1.0)) + } + })); + border_color.target <+ new_color; + } + rect +} + +fn define_stack(network: &frp::Network) -> rectangle::View { + let h0 = define_rect(RECT_SIZE, RECT_SIZE, network); + let h1 = define_rect(RECT_SIZE - RECT_DIFF, RECT_SIZE - RECT_DIFF, network); + let h2 = define_rect(RECT_SIZE - 2.0 * RECT_DIFF, RECT_SIZE - 2.0 * RECT_DIFF, network); + h0.add_child(&h1); + h1.add_child(&h2); + h0 +} + +/// The example entry point. +#[entry_point] +#[allow(dead_code)] +pub fn main() { + let world = World::new().displayed_in("root"); + let scene = &world.default_scene; + let camera = scene.camera().clone_ref(); + let navigator = Navigator::new(scene, &camera); + let network = &scene.frp.network; + + let container_size = RECT_SIZE + RECT_DIFF; + let container = define_rect(container_size * 2.0, container_size, network); + let left_stack = define_stack(network); + let right_stack = define_stack(network); + left_stack.mod_position_x(|x| x - (container_size) / 2.0); + right_stack.mod_position_x(|x| x + (container_size) / 2.0); + + world.add_child(&container); + container.add_child(&left_stack); + container.add_child(&right_stack); + world.keep_alive_forever(); + mem::forget(navigator); +} diff --git a/lib/rust/ensogl/example/grid-view/src/lib.rs b/lib/rust/ensogl/example/grid-view/src/lib.rs index 95b4169d697d..8542f94657c2 100644 --- a/lib/rust/ensogl/example/grid-view/src/lib.rs +++ b/lib/rust/ensogl/example/grid-view/src/lib.rs @@ -102,7 +102,7 @@ fn configure_simple_grid_view(view: &grid_view::simple::SimpleGridView) -> frp:: }; view.set_entries_params(params); view.reset_entries(1000, 1000); - view.frp().focus(); + view.frp().deprecated_focus(); network } diff --git a/lib/rust/ensogl/example/list-view/src/lib.rs b/lib/rust/ensogl/example/list-view/src/lib.rs index f050cba29e0b..97dbf226a7de 100644 --- a/lib/rust/ensogl/example/list-view/src/lib.rs +++ b/lib/rust/ensogl/example/list-view/src/lib.rs @@ -97,7 +97,7 @@ fn init(app: &Application) { let provider = list_view::entry::AnyModelProvider::new(MockEntries::new(1000)); list_view.frp.resize(Vector2(100.0, 160.0)); list_view.frp.set_entries(provider); - list_view.focus(); + list_view.deprecated_focus(); app.display.add_child(&list_view); app.display.default_scene.layers.below_main.add(&list_view); diff --git a/lib/rust/ensogl/example/src/lib.rs b/lib/rust/ensogl/example/src/lib.rs index 020d9d2a1ef2..9912d581493f 100644 --- a/lib/rust/ensogl/example/src/lib.rs +++ b/lib/rust/ensogl/example/src/lib.rs @@ -35,6 +35,7 @@ pub use ensogl_example_complex_shape_system as complex_shape_system; pub use ensogl_example_dom_symbols as dom_symbols; pub use ensogl_example_drop_manager as drop_manager; pub use ensogl_example_easing_animator as easing_animator; +pub use ensogl_example_focus_management as focus_management; pub use ensogl_example_grid_view as grid_view; pub use ensogl_example_list_view as list_view; pub use ensogl_example_mouse_events as mouse_events; diff --git a/lib/rust/ensogl/example/text-area/src/lib.rs b/lib/rust/ensogl/example/text-area/src/lib.rs index bd8417c86ada..4c216f81114a 100644 --- a/lib/rust/ensogl/example/text-area/src/lib.rs +++ b/lib/rust/ensogl/example/text-area/src/lib.rs @@ -178,7 +178,7 @@ fn init(app: Application) { area.set_content(content); area.set_font("mplus1p"); area.set_property_default(color::Rgba::black()); - area.focus(); + area.deprecated_focus(); area.hover(); let borders = Borders::default(); diff --git a/lib/rust/frp/Cargo.toml b/lib/rust/frp/Cargo.toml index e26924864382..b4a08011939d 100644 --- a/lib/rust/frp/Cargo.toml +++ b/lib/rust/frp/Cargo.toml @@ -18,7 +18,6 @@ keyboard-types = { version = "0.5.0" } nalgebra = { version = "0.26.1" } percent-encoding = { version = "2.1.0" } unicode-segmentation = { version = "1.6.0" } -unidecode = { version = "0.3.0" } # We require exact version of wasm-bindgen because we do patching final js in our build process, # and this is vulnerable to any wasm-bindgen version change. wasm-bindgen = { version = "0.2.78", features = ["nightly"] } diff --git a/lib/rust/frp/src/any_data.rs b/lib/rust/frp/src/any_data.rs new file mode 100644 index 000000000000..7a071adf16eb --- /dev/null +++ b/lib/rust/frp/src/any_data.rs @@ -0,0 +1,31 @@ +//! Wrapper for any type of data that can be used as an FRP value. + +use crate::prelude::*; + + + +// =============== +// === AnyData === +// =============== + +/// Just like [`Rc`], but also with a [`Default`] implementation, which allows it to +/// be used as FRP stream value. +#[allow(missing_docs)] +#[derive(Debug, Clone, CloneRef, Deref)] +pub struct AnyData { + pub data: Rc, +} + +impl AnyData { + /// Constructor. + pub fn new(data: T) -> Self { + let data = Rc::new(data); + Self { data } + } +} + +impl Default for AnyData { + fn default() -> Self { + Self { data: Rc::new(()) } + } +} diff --git a/lib/rust/frp/src/fan.rs b/lib/rust/frp/src/fan.rs new file mode 100644 index 000000000000..10193e8f9a23 --- /dev/null +++ b/lib/rust/frp/src/fan.rs @@ -0,0 +1,148 @@ +//! A utility with a single input allowing emitting values of different types and multiple +//! outputs, one per type. + +use crate::prelude::*; + +use crate::AnyData; + +use std::any::TypeId; + + + +// =========== +// === Fan === +// =========== + +struct FanOutput { + stream: AnyData, + runner: Rc, +} + +impl Debug for FanOutput { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "FanOutput") + } +} + +/// A utility with a single input allowing emitting values of different types and multiple +/// outputs, one per type. See tests to learn how to use it. +#[allow(missing_docs)] +#[derive(Debug, Clone, CloneRef)] +pub struct Fan { + pub source: crate::Any, + network: crate::WeakNetwork, + map: Map, +} + +type Map = Rc>>; + +impl Fan { + /// Constructor. + pub fn new(network: &crate::Network) -> Self { + let map: Map = default(); + crate::extend! { network + source <- any_mut::(); + eval source([map] (event) { + if let Some(fan_output) = map.borrow_mut().get(&(*event.data).type_id()) { + (fan_output.runner)(&fan_output.stream, event); + } + }); + } + let network = network.downgrade(); + Self { source, network, map } + } + + /// Get the output stream of the fan for the given type. See docs of [`Fan`] for an example. + /// + /// # Safety + /// This implementation is safe because we remember the [`TypeId`] when erasing the type, and we + /// query the right function based on the [`TypeId`] when emitting the value. When changing this + /// implementation, please remember to test it rigorously. + #[allow(unsafe_code)] + pub fn output(&self) -> crate::Stream { + if let Some(network) = self.network.upgrade() { + let id = TypeId::of::(); + let mut map = self.map.borrow_mut(); + let fan_output = map.entry(id).or_insert_with(|| { + crate::extend! { network + output <- source::(); + } + let stream = AnyData::new(output); + let runner = Rc::new(|stream: &AnyData, event: &AnyData| { + let stream = if cfg!(debug_assertions) { + stream.downcast_ref::>().unwrap() + } else { + unsafe { stream.downcast_ref_unchecked::>() } + }; + let event = if cfg!(debug_assertions) { + event.downcast_ref::().unwrap() + } else { + unsafe { event.downcast_ref_unchecked::() } + }; + stream.emit(event.clone()); + }); + FanOutput { stream, runner } + }); + unsafe { + fan_output.stream.downcast_ref_unchecked::>().clone_ref().into() + } + } else { + crate::Source::new().into() + } + } + + /// Emit a new event to the fan. You should prefer using the [`Fan::source`] when emitting + /// events from an FRP network. See docs of [`Fan`] for an example. + pub fn emit(&self, event: &AnyData) { + self.source.emit(event.clone()); + } +} + + + +// ============= +// === Tests === +// ============= + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test1() { + let network = crate::Network::new("network"); + let fan = Fan::new(&network); + let out_usize = fan.output::(); + let out_string = fan.output::(); + let last_usize: Rc> = default(); + let last_string: Rc> = default(); + + crate::extend! { network + source_usize <- source::(); + source_string <- source::(); + fan.source <+ source_usize.any_data(); + fan.source <+ source_string.any_data(); + eval out_usize ([last_usize](t) *last_usize.borrow_mut() = *t); + eval out_string ([last_string](t) *last_string.borrow_mut() = t.clone()); + } + + assert_eq!(*last_usize.borrow(), 0); + assert_eq!(*last_string.borrow(), ""); + + source_usize.emit(1); + assert_eq!(*last_usize.borrow(), 1); + assert_eq!(*last_string.borrow(), ""); + + source_string.emit("foo".to_string()); + assert_eq!(*last_usize.borrow(), 1); + assert_eq!(*last_string.borrow(), "foo"); + + source_usize.emit(2); + assert_eq!(*last_usize.borrow(), 2); + assert_eq!(*last_string.borrow(), "foo"); + + source_string.emit("bar".to_string()); + assert_eq!(*last_usize.borrow(), 2); + assert_eq!(*last_string.borrow(), "bar"); + } +} diff --git a/lib/rust/frp/src/io/keyboard.rs b/lib/rust/frp/src/io/keyboard.rs index a5ac491beb9f..5c543d5af1ce 100644 --- a/lib/rust/frp/src/io/keyboard.rs +++ b/lib/rust/frp/src/io/keyboard.rs @@ -9,7 +9,6 @@ use crate::io::js::Listener; use enso_web::KeyboardEvent; use inflector::Inflector; use unicode_segmentation::UnicodeSegmentation; -use unidecode::unidecode; @@ -123,7 +122,7 @@ impl Key { if key == " " { Self::Space } else if key.graphemes(true).count() == 1 { - Self::Character(unidecode(&key).to_lowercase()) + Self::Character(key) } else { let key = KEY_NAME_MAP.get(key_ref).cloned().unwrap_or(Self::Other(key)); match (key, code) { diff --git a/lib/rust/frp/src/lib.rs b/lib/rust/frp/src/lib.rs index d1afa141bdd8..1e1ab58babed 100644 --- a/lib/rust/frp/src/lib.rs +++ b/lib/rust/frp/src/lib.rs @@ -163,10 +163,13 @@ #![feature(specialization)] #![feature(trait_alias)] #![feature(unboxed_closures)] +#![feature(downcast_unchecked)] #![recursion_limit = "512"] +pub mod any_data; pub mod data; pub mod debug; +pub mod fan; pub mod future; pub mod io; pub mod macros; @@ -179,7 +182,9 @@ pub use network::*; pub use node::*; pub use nodes::*; +pub use any_data::AnyData; pub use enso_web as web; +pub use fan::Fan; pub use stream::Stream; /// Set of often used types and functions. diff --git a/lib/rust/frp/src/macros.rs b/lib/rust/frp/src/macros.rs index e178dbfab37d..835b595c07eb 100644 --- a/lib/rust/frp/src/macros.rs +++ b/lib/rust/frp/src/macros.rs @@ -234,7 +234,7 @@ macro_rules! extend_line2 { ([] $net:ident eval_ $tgt1:ident . $tgt2:ident . $tgt3:ident . $tgt4:ident ($($args:tt)*) $($ts:tt)*) => { $crate::extend_line2! { [] $net def _eval = $tgt1 . $tgt2 . $tgt3 . $tgt4 . map (f_!($($args)*)) $($ts)* } }; ([] $net:ident eval_ $tgt1:ident . $tgt2:ident . $tgt3:ident . $tgt4:ident . $tgt5:ident ($($args:tt)*) $($ts:tt)*) => { $crate::extend_line2! { [] $net def _eval = $tgt1 . $tgt2 . $tgt3 . $tgt4 . $tgt5 . map (f_!($($args)*)) $($ts)* } }; - ([] $net:ident trace $($path:ident).*) => { $net.trace(stringify!($($path).*),&$($path).*); }; + ([] $net:ident trace $($ts:tt)*) => { $net.trace(stringify!($($ts)*),&$($ts)*); }; ([] $net:ident $($ts:tt)*) => { $($ts)*; } } diff --git a/lib/rust/frp/src/nodes.rs b/lib/rust/frp/src/nodes.rs index 1fe264c5d134..e24ff07ea7cf 100644 --- a/lib/rust/frp/src/nodes.rs +++ b/lib/rust/frp/src/nodes.rs @@ -116,6 +116,7 @@ impl Network { self.register(OwnedGateNot::new(label, event, behavior)) } + /// Unwraps the value of incoming events and emits the unwrapped values. pub fn unwrap(&self, label: Label, event: &T) -> Stream where T: EventOutput>, @@ -123,6 +124,7 @@ impl Network { self.register(OwnedUnwrap::new(label, event)) } + /// On every incoming event, iterate over its value and emit each element separately. pub fn iter(&self, label: Label, event: &T1) -> Stream where T1: EventOutput, @@ -131,6 +133,7 @@ impl Network { self.register(OwnedIter::new(label, event)) } + /// Fold the incoming value using [`Monoid`] implementation. pub fn fold(&self, label: Label, event: &T1) -> Stream where T1: EventOutput, @@ -139,6 +142,7 @@ impl Network { self.register(OwnedFold::new(label, event)) } + /// Get the 0-based index of the incoming event. pub fn _0(&self, label: Label, event: &T1) -> Stream>> where T1: EventOutput, @@ -147,6 +151,7 @@ impl Network { self.register(OwnedGet0::new(label, event)) } + /// Get the 1-based index of the incoming event. pub fn _1(&self, label: Label, event: &T1) -> Stream>> where T1: EventOutput, @@ -155,6 +160,7 @@ impl Network { self.register(OwnedGet1::new(label, event)) } + /// Get the 2-based index of the incoming event. pub fn _2(&self, label: Label, event: &T1) -> Stream>> where T1: EventOutput, @@ -174,6 +180,7 @@ impl Network { self.gate(label, t, &changed) } + /// Just like [`value.into()`] on the reference of the incoming value. pub fn ref_into(&self, label: Label, t: &T) -> Stream where T: EventOutput, @@ -182,6 +189,7 @@ impl Network { self.map(label, t, |v| v.into()) } + /// Just like [`value.clone().into()`] on the incoming value. pub fn cloned_into(&self, label: Label, t: &T) -> Stream where T: EventOutput, @@ -190,6 +198,7 @@ impl Network { self.map(label, t, |v| v.clone().into()) } + /// Just like [`Some(value.into())`] on the reference of the incoming value. pub fn ref_into_some(&self, label: Label, t: &T) -> Stream> where T: EventOutput, @@ -198,6 +207,7 @@ impl Network { self.map(label, t, |v| Some(v.into())) } + /// Just like [`Some(value.clone().into())`] on the incoming value. pub fn cloned_into_some(&self, label: Label, t: &T) -> Stream> where T: EventOutput, @@ -206,6 +216,15 @@ impl Network { self.map(label, t, |v| Some(v.clone().into())) } + /// Converts the incoming values to [`AnyData`] hiding their types. This can be used to create + /// FRP inputs accepting different types, not known at compile time. + pub fn any_data(&self, label: Label, t: &T) -> Stream + where + T: EventOutput, + T::Output: Clone + 'static, { + self.map(label, t, |v| crate::AnyData::new(v.clone())) + } + // === Bool Utils === diff --git a/lib/rust/frp/src/stream.rs b/lib/rust/frp/src/stream.rs index 5e23385ae09c..ae02ec593666 100644 --- a/lib/rust/frp/src/stream.rs +++ b/lib/rust/frp/src/stream.rs @@ -451,6 +451,20 @@ impl WeakNode { self.definition.upgrade().map(|definition| Node { stream, definition }) }) } + + /// Constructs a new [`WeakNode`] without allocating any memory. Calling [`upgrade`] on the + /// return value always gives [`None`]. + pub fn new() -> Self { + let stream = Stream { data: Weak::new() }; + let definition = Weak::new(); + Self { stream, definition } + } +} + +impl Default for WeakNode { + fn default() -> Self { + Self::new() + } } impl OwnedStream {