From f4d236fcd4c95bbe3502286fda6589313a7d72a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Dani=C5=82o?= Date: Fri, 4 Mar 2022 15:13:23 +0100 Subject: [PATCH] EnsoGL context abstraction (#3293) --- Cargo.lock | 1 + Cargo.toml | 4 - app/gui/analytics/Cargo.toml | 2 +- app/gui/language/parser/Cargo.toml | 2 +- app/gui/language/span-tree/example/Cargo.toml | 2 +- app/gui/src/executor/web.rs | 16 +- app/gui/src/ide.rs | 2 +- app/gui/src/ide/initializer.rs | 9 +- app/gui/src/lib.rs | 4 - app/gui/src/presenter/graph.rs | 28 +- app/gui/src/transport/web.rs | 22 +- app/gui/view/Cargo.toml | 2 +- app/gui/view/debug_scene/interface/Cargo.toml | 2 +- app/gui/view/debug_scene/interface/src/lib.rs | 18 +- .../view/debug_scene/visualization/Cargo.toml | 2 +- .../view/debug_scene/visualization/src/lib.rs | 19 +- app/gui/view/graph-editor/Cargo.toml | 2 +- .../src/builtin/visualization/native/error.rs | 25 +- .../builtin/visualization/native/raw_text.rs | 25 +- .../src/component/add_node_button.rs | 2 +- .../graph-editor/src/component/breadcrumbs.rs | 4 +- .../src/component/breadcrumbs/breadcrumb.rs | 4 +- .../src/component/breadcrumbs/project_name.rs | 4 +- .../view/graph-editor/src/component/edge.rs | 4 +- .../view/graph-editor/src/component/node.rs | 8 +- .../src/component/node/action_bar.rs | 2 +- .../graph-editor/src/component/node/error.rs | 24 +- .../src/component/node/input/area.rs | 10 +- .../src/component/node/output/area.rs | 6 +- .../src/component/node/profiling.rs | 2 +- .../graph-editor/src/component/node/vcs.rs | 2 +- .../graph-editor/src/component/profiling.rs | 2 +- .../graph-editor/src/component/tooltip.rs | 2 +- .../src/component/visualization/container.rs | 42 +- .../visualization/container/action_bar.rs | 8 +- .../visualization/container/fullscreen.rs | 18 +- .../container/visualization_chooser.rs | 2 +- .../foreign/java_script/binding.rs | 46 +- .../foreign/java_script/definition.rs | 22 +- .../java_script/definition/function.rs | 32 - .../foreign/java_script/instance.rs | 67 +- app/gui/view/graph-editor/src/lib.rs | 11 +- app/gui/view/src/code_editor.rs | 6 +- app/gui/view/src/debug_mode_popup.rs | 6 +- app/gui/view/src/documentation.rs | 58 +- app/gui/view/src/open_dialog.rs | 4 +- app/gui/view/src/open_dialog/project_list.rs | 10 +- app/gui/view/src/project.rs | 7 +- app/gui/view/src/searcher.rs | 4 +- app/gui/view/src/searcher/icons.rs | 19 +- app/gui/view/src/status_bar.rs | 6 +- app/gui/view/src/window_control_buttons.rs | 4 +- app/gui/view/welcome-screen/Cargo.toml | 2 +- app/gui/view/welcome-screen/src/lib.rs | 54 +- app/gui/view/welcome-screen/src/side_menu.rs | 45 +- .../view/welcome-screen/src/template_cards.rs | 67 +- app/ide-desktop/lib/content/src/index.ts | 2 +- app/ide-desktop/lib/log-server/README.md | 7 - app/ide-desktop/lib/log-server/package.json | 31 - app/ide-desktop/lib/log-server/server.js | 119 -- app/ide-desktop/lib/log-server/test/test.js | 80 - {app/gui/docs => docs}/security/selfxss.md | 0 integration-test/src/lib.rs | 12 +- integration-test/tests/graph_editor.rs | 8 +- lib/rust/callback/src/lib.rs | 507 +++--- lib/rust/ensogl/component/button/src/lib.rs | 2 +- .../component/drop-down-menu/src/lib.rs | 4 +- .../ensogl/component/drop-manager/Cargo.toml | 2 +- .../ensogl/component/drop-manager/src/lib.rs | 53 +- .../ensogl/component/flame-graph/src/block.rs | 2 +- .../ensogl/component/gui/src/component.rs | 2 +- lib/rust/ensogl/component/label/src/lib.rs | 4 +- .../ensogl/component/list-view/src/entry.rs | 2 +- .../component/list-view/src/entry/list.rs | 8 +- .../ensogl/component/list-view/src/lib.rs | 6 +- .../ensogl/component/scroll-area/src/lib.rs | 2 +- .../ensogl/component/scrollbar/src/lib.rs | 4 +- .../component/selector/src/decimal_aligned.rs | 4 +- lib/rust/ensogl/component/selector/src/frp.rs | 24 +- lib/rust/ensogl/component/selector/src/lib.rs | 4 +- .../ensogl/component/selector/src/model.rs | 2 +- .../ensogl/component/selector/src/number.rs | 9 +- .../ensogl/component/selector/src/range.rs | 2 +- lib/rust/ensogl/component/shadow/src/lib.rs | 6 +- .../ensogl/component/text/msdf-sys/Cargo.toml | 2 +- .../component/text/src/component/area.rs | 8 +- .../component/text/src/typeface/glyph.rs | 4 +- lib/rust/ensogl/core/Cargo.toml | 2 +- lib/rust/ensogl/core/src/animation/loops.rs | 83 +- lib/rust/ensogl/core/src/application.rs | 33 +- lib/rust/ensogl/core/src/application/args.rs | 131 +- lib/rust/ensogl/core/src/control/io/mouse.rs | 101 +- .../ensogl/core/src/control/io/mouse/event.rs | 21 +- lib/rust/ensogl/core/src/debug/monitor.rs | 36 +- .../core/src/display/camera/camera2d.rs | 16 +- .../core/src/display/navigation/navigator.rs | 10 +- .../ensogl/core/src/display/object/class.rs | 62 +- lib/rust/ensogl/core/src/display/scene.rs | 360 ++-- lib/rust/ensogl/core/src/display/scene/dom.rs | 59 +- .../shape/primitive/shader/overload.rs | 37 +- .../core/src/display/style/javascript.rs | 18 +- .../ensogl/core/src/display/style/sheet.rs | 3 +- .../ensogl/core/src/display/style/theme.rs | 5 +- .../ensogl/core/src/display/symbol/dom.rs | 37 +- .../ensogl/core/src/display/symbol/gpu.rs | 65 +- .../symbol/gpu/geometry/primitive/mesh.rs | 6 +- .../core/src/display/symbol/gpu/registry.rs | 9 +- .../core/src/display/symbol/gpu/shader.rs | 26 +- lib/rust/ensogl/core/src/display/world.rs | 338 ++-- lib/rust/ensogl/core/src/lib.rs | 25 +- lib/rust/ensogl/core/src/system.rs | 10 + lib/rust/ensogl/core/src/system/context.rs | 118 ++ .../core/src/system/gpu/data/attribute.rs | 4 +- .../ensogl/core/src/system/gpu/data/buffer.rs | 15 +- .../core/src/system/gpu/data/buffer/usage.rs | 2 +- .../core/src/system/gpu/data/gl_enum.rs | 2 +- .../gpu/data/texture/storage/remote_image.rs | 26 +- .../core/src/system/gpu/data/uniform.rs | 2 +- lib/rust/ensogl/core/src/system/gpu/shader.rs | 167 +- lib/rust/ensogl/core/src/system/web.rs | 4 - .../ensogl/core/src/system/web/dom/shape.rs | 10 +- lib/rust/ensogl/doc/mouse-handling.md | 2 +- lib/rust/ensogl/example/animation/Cargo.toml | 2 +- lib/rust/ensogl/example/animation/src/lib.rs | 39 +- .../example/complex-shape-system/Cargo.toml | 2 +- .../example/complex-shape-system/src/lib.rs | 14 +- .../ensogl/example/dom-symbols/Cargo.toml | 2 +- .../ensogl/example/dom-symbols/src/lib.rs | 22 +- .../ensogl/example/drop-manager/Cargo.toml | 2 +- .../ensogl/example/drop-manager/src/lib.rs | 16 +- .../ensogl/example/easing-animator/Cargo.toml | 2 +- .../ensogl/example/easing-animator/src/lib.rs | 54 +- .../ensogl/example/glyph-system/Cargo.toml | 2 +- .../ensogl/example/glyph-system/src/lib.rs | 8 +- lib/rust/ensogl/example/list-view/Cargo.toml | 2 +- lib/rust/ensogl/example/list-view/src/lib.rs | 7 +- .../ensogl/example/mouse-events/Cargo.toml | 2 +- .../ensogl/example/mouse-events/src/lib.rs | 7 +- .../example/profiling-run-graph/src/lib.rs | 10 +- .../ensogl/example/scroll-area/Cargo.toml | 2 +- .../ensogl/example/scroll-area/src/lib.rs | 7 +- .../ensogl/example/shape-system/Cargo.toml | 2 +- .../ensogl/example/shape-system/src/lib.rs | 11 +- lib/rust/ensogl/example/slider/Cargo.toml | 2 +- lib/rust/ensogl/example/slider/src/lib.rs | 5 +- .../sprite-system-benchmark/Cargo.toml | 2 +- .../sprite-system-benchmark/src/lib.rs | 12 +- .../ensogl/example/sprite-system/Cargo.toml | 3 +- .../ensogl/example/sprite-system/src/lib.rs | 16 +- lib/rust/ensogl/example/text-area/Cargo.toml | 2 +- lib/rust/ensogl/example/text-area/src/lib.rs | 20 +- lib/rust/frp/Cargo.toml | 2 +- lib/rust/frp/src/debug.rs | 4 +- lib/rust/frp/src/io/js.rs | 80 +- lib/rust/frp/src/io/keyboard.rs | 35 +- lib/rust/logger/Cargo.toml | 2 +- lib/rust/not-used/eval-tt/Cargo.toml | 10 - lib/rust/not-used/eval-tt/src/lib.rs | 150 -- .../not-used/web-test-proc-macro/Cargo.toml | 14 - .../not-used/web-test-proc-macro/src/lib.rs | 111 -- lib/rust/not-used/web-test/Cargo.toml | 19 - .../not-used/web-test/src/bench_container.rs | 61 - lib/rust/not-used/web-test/src/bencher.rs | 174 -- lib/rust/not-used/web-test/src/container.rs | 55 - lib/rust/not-used/web-test/src/group.rs | 47 - lib/rust/not-used/web-test/src/lib.rs | 24 - lib/rust/prelude/Cargo.toml | 4 +- lib/rust/profiler/Cargo.toml | 2 +- lib/rust/profiler/src/internal.rs | 3 +- lib/rust/shortcuts/Cargo.toml | 2 +- lib/rust/shortcuts/example/Cargo.toml | 2 +- lib/rust/web/Cargo.toml | 2 +- lib/rust/web/js/rust_panic.js | 10 - lib/rust/web/src/binding.rs | 6 + lib/rust/web/src/binding/mock.rs | 681 ++++++++ lib/rust/web/src/binding/wasm.rs | 123 ++ lib/rust/web/src/clipboard.rs | 4 +- lib/rust/web/src/closure/storage.rs | 3 + lib/rust/web/src/event.rs | 6 +- lib/rust/web/src/event/listener.rs | 4 +- lib/rust/web/src/lib.rs | 1442 ++++++++++------- lib/rust/web/src/platform.rs | 6 +- lib/rust/web/src/resize_observer.rs | 31 +- lib/rust/web/src/stream.rs | 19 +- 184 files changed, 3702 insertions(+), 3283 deletions(-) delete mode 100644 app/gui/view/graph-editor/src/component/visualization/foreign/java_script/definition/function.rs delete mode 100644 app/ide-desktop/lib/log-server/README.md delete mode 100644 app/ide-desktop/lib/log-server/package.json delete mode 100644 app/ide-desktop/lib/log-server/server.js delete mode 100644 app/ide-desktop/lib/log-server/test/test.js rename {app/gui/docs => docs}/security/selfxss.md (100%) create mode 100644 lib/rust/ensogl/core/src/system/context.rs delete mode 100644 lib/rust/not-used/eval-tt/Cargo.toml delete mode 100644 lib/rust/not-used/eval-tt/src/lib.rs delete mode 100644 lib/rust/not-used/web-test-proc-macro/Cargo.toml delete mode 100644 lib/rust/not-used/web-test-proc-macro/src/lib.rs delete mode 100644 lib/rust/not-used/web-test/Cargo.toml delete mode 100644 lib/rust/not-used/web-test/src/bench_container.rs delete mode 100644 lib/rust/not-used/web-test/src/bencher.rs delete mode 100644 lib/rust/not-used/web-test/src/container.rs delete mode 100644 lib/rust/not-used/web-test/src/group.rs delete mode 100644 lib/rust/not-used/web-test/src/lib.rs delete mode 100644 lib/rust/web/js/rust_panic.js create mode 100644 lib/rust/web/src/binding.rs create mode 100644 lib/rust/web/src/binding/mock.rs create mode 100644 lib/rust/web/src/binding/wasm.rs diff --git a/Cargo.lock b/Cargo.lock index 1031fc8f1a..41b16ea315 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1461,6 +1461,7 @@ dependencies = [ name = "ensogl-example-sprite-system" version = "0.1.0" dependencies = [ + "enso-web", "ensogl-core", "wasm-bindgen", ] diff --git a/Cargo.toml b/Cargo.toml index 060dd63158..9c31e16abc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,13 +8,9 @@ members = [ "build/rust-scripts", "lib/rust/*", "lib/rust/profiler/data", - "lib/rust/not-used/*", "integration-test" ] -# This directory is not a crate -exclude = ["lib/rust/not-used"] - # The default memebers are those we want to check and test by default. default-members = ["app/gui", "lib/rust/*"] diff --git a/app/gui/analytics/Cargo.toml b/app/gui/analytics/Cargo.toml index 9ff9a04469..bd4da5d648 100644 --- a/app/gui/analytics/Cargo.toml +++ b/app/gui/analytics/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" [dependencies] js-sys = { version = "0.3.28" } -wasm-bindgen = { version = "0.2.58", features = ["nightly", "serde-serialize"] } +wasm-bindgen = { version = "0.2.78", features = ["nightly", "serde-serialize"] } diff --git a/app/gui/language/parser/Cargo.toml b/app/gui/language/parser/Cargo.toml index 2f7f7891ac..ab1d299481 100644 --- a/app/gui/language/parser/Cargo.toml +++ b/app/gui/language/parser/Cargo.toml @@ -21,7 +21,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["unbounded_depth"] } shrinkwraprs = { version = "0.2.1" } uuid = { version = "0.8", features = ["serde", "v5", "wasm-bindgen"] } -wasm-bindgen = { version = "0.2.58" } +wasm-bindgen = { version = "0.2.78" } [dev-dependencies] wasm-bindgen-test = { version = "0.3.8" } diff --git a/app/gui/language/span-tree/example/Cargo.toml b/app/gui/language/span-tree/example/Cargo.toml index fe16c804e1..585ebcd5dc 100644 --- a/app/gui/language/span-tree/example/Cargo.toml +++ b/app/gui/language/span-tree/example/Cargo.toml @@ -14,7 +14,7 @@ span-tree = { path = "../../span-tree" } enso-web = { path = "../../../../../lib/rust/web" } enso-prelude = { path = "../../../../../lib/rust/prelude"} enso-logger = { path = "../../../../../lib/rust/logger"} -wasm-bindgen = { version = "0.2.58", features = [ +wasm-bindgen = { version = "0.2.78", features = [ "nightly", "serde-serialize" ] } diff --git a/app/gui/src/executor/web.rs b/app/gui/src/executor/web.rs index fdd07123b0..f642f609d7 100644 --- a/app/gui/src/executor/web.rs +++ b/app/gui/src/executor/web.rs @@ -13,6 +13,9 @@ use futures::task::LocalFutureObj; use futures::task::LocalSpawn; use futures::task::SpawnError; +/// An alias for a main animation loop. +pub type MainLoop = animation::Loop>; + /// Executor. Uses a single-threaded `LocalPool` underneath, relying on ensogl's /// `animation::DynamicLoop` to do as much progress as possible on every animation frame. #[derive(Debug)] @@ -22,7 +25,7 @@ pub struct EventLoopExecutor { /// Executor's spawner handle. pub spawner: LocalSpawner, /// Event loop that calls us on each frame. - event_loop: Option, + event_loop: Option, /// Handle to the callback - if dropped, loop would have stopped calling us. /// Also owns a shared handle to the `executor`. cb_handle: Option, @@ -44,14 +47,14 @@ impl EventLoopExecutor { ///loop will live as long as this executor. pub fn new_running() -> EventLoopExecutor { let mut executor = EventLoopExecutor::new(); - executor.start_running(animation::DynamicLoop::new()); + executor.start_running(); executor } /// Returns a callback compatible with `animation::DynamicLoop` that once called shall /// attempt achieving as much progress on this executor's tasks as possible /// without stalling. - pub fn runner(&self) -> impl animation::DynamicLoopCallback { + pub fn runner(&self) -> impl FnMut(animation::TimeInfo) { let executor = self.executor.clone(); move |_| { // Safe, because this is the only place borrowing executor and loop @@ -67,11 +70,8 @@ impl EventLoopExecutor { /// /// The executor will keep copy of this loop handle, so caller is not /// required to keep it alive. - pub fn start_running(&mut self, event_loop: animation::DynamicLoop) { - let cb = self.runner(); - - self.cb_handle = Some(event_loop.on_frame(cb)); - self.event_loop = Some(event_loop); + fn start_running(&mut self) { + self.event_loop = Some(MainLoop::new(Box::new(self.runner()))); } /// Stops event loop (previously assigned by `run` method) from calling this diff --git a/app/gui/src/ide.rs b/app/gui/src/ide.rs index 4e0e913c95..53bab48b72 100644 --- a/app/gui/src/ide.rs +++ b/app/gui/src/ide.rs @@ -62,7 +62,7 @@ impl Ide { fn alive_log_sending_loop(&self) -> impl Future + 'static { let network = &self.network; - let scene = self.ensogl_app.display.scene(); + let scene = &self.ensogl_app.display.default_scene; let mouse = &scene.mouse.frp; let keyboard = &scene.keyboard.frp; diff --git a/app/gui/src/ide/initializer.rs b/app/gui/src/ide/initializer.rs index 1d701267b4..cd49674f7f 100644 --- a/app/gui/src/ide/initializer.rs +++ b/app/gui/src/ide/initializer.rs @@ -63,9 +63,9 @@ impl Initializer { let executor = setup_global_executor(); executor::global::spawn(async move { let ide = self.start().await; - web::get_element_by_id("loader") - .map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap())) - .ok(); + web::document + .get_element_by_id("loader") + .map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap())); std::mem::forget(ide); }); std::mem::forget(executor); @@ -75,8 +75,7 @@ impl Initializer { pub async fn start(self) -> Result { info!(self.logger, "Starting IDE with the following config: {self.config:?}"); - let root_element = web::get_html_element_by_id("root").unwrap(); - let ensogl_app = ensogl::application::Application::new(&root_element); + let ensogl_app = ensogl::application::Application::new("root"); Initializer::register_views(&ensogl_app); let view = ensogl_app.new_view::(); diff --git a/app/gui/src/lib.rs b/app/gui/src/lib.rs index 3379f75cbd..20a0aaa16a 100644 --- a/app/gui/src/lib.rs +++ b/app/gui/src/lib.rs @@ -67,7 +67,6 @@ pub mod transport; pub use crate::ide::*; pub use ide_view as view; -use ensogl::system::web; use wasm_bindgen::prelude::*; // Those imports are required to have all EnsoGL examples entry points visible in IDE. @@ -84,7 +83,6 @@ pub mod prelude { pub use ast::prelude::*; pub use enso_prelude::*; pub use ensogl::prelude::*; - pub use wasm_bindgen::prelude::*; pub use crate::constants; pub use crate::controller; @@ -116,8 +114,6 @@ pub mod prelude { #[wasm_bindgen] #[allow(dead_code)] pub fn entry_point_ide() { - web::forward_panic_hook_to_error(); - ensogl_text_msdf_sys::run_once_initialized(|| { // Logging of build information. #[cfg(debug_assertions)] diff --git a/app/gui/src/presenter/graph.rs b/app/gui/src/presenter/graph.rs index 7b27bab40a..68d26a2f91 100644 --- a/app/gui/src/presenter/graph.rs +++ b/app/gui/src/presenter/graph.rs @@ -15,6 +15,7 @@ use crate::executor::global::spawn_stream_handler; use crate::presenter::graph::state::State; use enso_frp as frp; +use enso_web::traits::*; use futures::future::LocalBoxFuture; use ide_view as view; use ide_view::graph_editor::component::node as node_view; @@ -22,6 +23,7 @@ use ide_view::graph_editor::component::visualization as visualization_view; use ide_view::graph_editor::EdgeEndpoint; + // =============== // === Aliases === // =============== @@ -609,6 +611,30 @@ impl Graph { impl controller::upload::DataProvider for ensogl_drop_manager::File { fn next_chunk(&mut self) -> LocalBoxFuture>>> { - self.read_chunk().map(|f| f.map_err(|e| e.into())).boxed_local() + self.read_chunk() + .map(|f| f.map_err(|e| StringError::new(e.print_to_string()).into())) + .boxed_local() + } +} + + + +// ============= +// === Error === +// ============= + +/// Generic error representation. This is used only once in the lines above. Probably should be +/// removed in the future. +#[derive(Debug, Fail)] +#[fail(display = "{}", message)] +pub struct StringError { + message: String, +} + +impl StringError { + /// Constructor. + pub fn new(message: impl Into) -> StringError { + let message = message.into(); + StringError { message } } } diff --git a/app/gui/src/transport/web.rs b/app/gui/src/transport/web.rs index 75b00d4e89..f3be73bffd 100644 --- a/app/gui/src/transport/web.rs +++ b/app/gui/src/transport/web.rs @@ -3,7 +3,7 @@ use crate::prelude::*; use enso_web::event::listener::Slot; -use enso_web::js_to_string; +use enso_web::traits::*; use failure::Error; use futures::channel::mpsc; use json_rpc::Transport; @@ -34,8 +34,8 @@ pub enum ConnectingError { impl ConnectingError { /// Create a `ConstructionError` value from a JS value describing an error. - pub fn construction_error(js_val: impl AsRef) -> Self { - let text = js_to_string(js_val); + pub fn construction_error(js_val: impl AsRef) -> Self { + let text = js_val.as_ref().print_to_string(); ConnectingError::ConstructionError(text) } } @@ -54,8 +54,8 @@ pub enum SendingError { impl SendingError { /// Constructs from the error yielded by one of the JS's WebSocket sending functions. - pub fn from_send_error(error: JsValue) -> SendingError { - SendingError::FailedToSend(js_to_string(&error)) + pub fn from_send_error(error: wasm_bindgen::JsValue) -> SendingError { + SendingError::FailedToSend(error.print_to_string()) } } @@ -192,7 +192,7 @@ impl Model { } /// Close the socket. - pub fn close(&mut self, reason: &str) -> Result<(), JsValue> { + pub fn close(&mut self, reason: &str) -> Result<(), wasm_bindgen::JsValue> { // If socket was manually requested to close, it should not try to reconnect then. self.auto_reconnect = false; let normal_closure = 1000; @@ -229,7 +229,7 @@ impl Model { /// Establish a new WS connection, using the same URL as the previous one. /// All callbacks will be transferred to the new connection. - pub fn reconnect(&mut self) -> Result<(), JsValue> { + pub fn reconnect(&mut self) -> Result<(), wasm_bindgen::JsValue> { if !self.auto_reconnect { return Err(js_sys::Error::new("Reconnecting has been disabled").into()); } @@ -256,7 +256,7 @@ impl Drop for Model { if let Err(e) = self.close("Rust Value has been dropped.") { error!( self.logger, - "Error when closing socket due to being dropped: {js_to_string(&e)}" + "Error when closing socket due to being dropped: {e.print_to_string()}" ) } } @@ -303,7 +303,7 @@ impl WebSocket { move |_| { if let Some(model) = model.upgrade() { if let Err(e) = model.borrow_mut().reconnect() { - error!(logger, "Failed to reconnect: {js_to_string(&e)}"); + error!(logger, "Failed to reconnect: {e.print_to_string()}"); } } } @@ -379,7 +379,7 @@ impl WebSocket { /// /// WARNING: `f` works under borrow_mut and must not give away control. fn send_with_open_socket(&mut self, f: F) -> Result - where F: FnOnce(&mut web_sys::WebSocket) -> Result { + where F: FnOnce(&mut web_sys::WebSocket) -> Result { // Sending through the closed WebSocket can return Ok() with error only // appearing in the log. We explicitly check for this to get failure as // early as possible. @@ -433,7 +433,7 @@ impl Transport for WebSocket { let event = TransportEvent::BinaryMessage(binary_data); channel::emit(&transmitter_copy, event); } else { - info!(logger_copy, "Received other kind of message: {js_to_string(e.data())}."); + info!(logger_copy, "Received other kind of message: {e.data().print_to_string()}."); } }); diff --git a/app/gui/view/Cargo.toml b/app/gui/view/Cargo.toml index 79f318035d..6a07e654c9 100644 --- a/app/gui/view/Cargo.toml +++ b/app/gui/view/Cargo.toml @@ -30,7 +30,7 @@ ordered-float = { version = "2.7.0" } serde_json = { version = "1.0" } serde = { version = "1.0", features = ["derive"] } uuid = { version = "0.8", features = ["serde", "v4", "wasm-bindgen"] } -wasm-bindgen = { version = "0.2.58", features = [ +wasm-bindgen = { version = "0.2.78", features = [ "nightly", "serde-serialize" ] } diff --git a/app/gui/view/debug_scene/interface/Cargo.toml b/app/gui/view/debug_scene/interface/Cargo.toml index 33cf4eff38..176e5c32ff 100644 --- a/app/gui/view/debug_scene/interface/Cargo.toml +++ b/app/gui/view/debug_scene/interface/Cargo.toml @@ -17,4 +17,4 @@ ide-view = { path = "../.." } parser = { path = "../../../language/parser" } span-tree = { path = "../../../language/span-tree" } uuid = { version = "0.8", features = ["v4", "wasm-bindgen"] } -wasm-bindgen = { version = "0.2.58", features = ["nightly"] } +wasm-bindgen = { version = "0.2.78", features = ["nightly"] } diff --git a/app/gui/view/debug_scene/interface/src/lib.rs b/app/gui/view/debug_scene/interface/src/lib.rs index 4dd2b84535..bfdb2ce03d 100644 --- a/app/gui/view/debug_scene/interface/src/lib.rs +++ b/app/gui/view/debug_scene/interface/src/lib.rs @@ -45,10 +45,8 @@ const STUB_MODULE: &str = "from Base import all\n\nmain = IO.println \"Hello\"\n #[wasm_bindgen] #[allow(dead_code)] pub fn entry_point_interface() { - web::forward_panic_hook_to_console(); - web::set_stack_trace_limit(); run_once_initialized(|| { - let app = Application::new(&web::get_html_element_by_id("root").unwrap()); + let app = Application::new("root"); init(&app); mem::forget(app); }); @@ -100,10 +98,10 @@ impl DummyTypeGenerator { // ======================== fn init(app: &Application) { - let _bg = app.display.scene().style_sheet.var(theme::application::background); + let _bg = app.display.default_scene.style_sheet.var(theme::application::background); let world = &app.display; - let scene = world.scene(); + let scene = &world.default_scene; let camera = scene.camera(); let navigator = Navigator::new(scene, &camera); @@ -252,7 +250,9 @@ fn init(app: &Application) { let mut to_theme_switch = 100; world - .on_frame(move |_| { + .on + .before_frame + .add(move |_| { let _keep_alive = &navigator; let _keep_alive = &root_view; @@ -292,9 +292,9 @@ fn init(app: &Application) { // Temporary code removing the web-loader instance. // To be changed in the future. if was_rendered && !loader_hidden { - web::get_element_by_id("loader") - .map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap())) - .ok(); + web::document + .get_element_by_id("loader") + .map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap())); loader_hidden = true; } was_rendered = true; diff --git a/app/gui/view/debug_scene/visualization/Cargo.toml b/app/gui/view/debug_scene/visualization/Cargo.toml index 238456b36c..d693c01def 100644 --- a/app/gui/view/debug_scene/visualization/Cargo.toml +++ b/app/gui/view/debug_scene/visualization/Cargo.toml @@ -16,4 +16,4 @@ ide-view = { path = "../.." } js-sys = { version = "0.3.28" } nalgebra = { version = "0.26.1" } serde_json = { version = "1.0" } -wasm-bindgen = { version = "0.2.58", features = ["nightly"] } +wasm-bindgen = { version = "0.2.78", features = ["nightly"] } diff --git a/app/gui/view/debug_scene/visualization/src/lib.rs b/app/gui/view/debug_scene/visualization/src/lib.rs index c9a959ddd9..db5581e8ea 100644 --- a/app/gui/view/debug_scene/visualization/src/lib.rs +++ b/app/gui/view/debug_scene/visualization/src/lib.rs @@ -11,6 +11,7 @@ use ensogl::prelude::*; +use ensogl::animation; use ensogl::application::Application; use ensogl::display::navigation::navigator::Navigator; use ensogl::system::web; @@ -22,6 +23,8 @@ use js_sys::Math::sin; use nalgebra::Vector2; use wasm_bindgen::prelude::*; + + fn generate_data(seconds: f64) -> Vec> { let mut data = Vec::new(); for x in 0..100 { @@ -89,10 +92,8 @@ fn constructor_graph() -> visualization::java_script::Definition { #[wasm_bindgen] #[allow(dead_code, missing_docs)] pub fn entry_point_visualization() { - web::forward_panic_hook_to_console(); - web::set_stack_trace_limit(); run_once_initialized(|| { - let app = Application::new(&web::get_html_element_by_id("root").unwrap()); + let app = Application::new("root"); init(&app); std::mem::forget(app); }); @@ -100,7 +101,7 @@ pub fn entry_point_visualization() { fn init(app: &Application) { let world = &app.display; - let scene = world.scene(); + let scene = &world.default_scene; let camera = scene.camera(); let navigator = Navigator::new(scene, &camera); let registry = Registry::new(); @@ -124,7 +125,9 @@ fn init(app: &Application) { let mut was_rendered = false; let mut loader_hidden = false; world - .on_frame(move |time_info| { + .on + .before_frame + .add(move |time_info: animation::TimeInfo| { let _keep_alive = &navigator; let data = generate_data((time_info.local / 1000.0).into()); @@ -137,9 +140,9 @@ fn init(app: &Application) { // Temporary code removing the web-loader instance. // To be changed in the future. if was_rendered && !loader_hidden { - web::get_element_by_id("loader") - .map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap())) - .ok(); + web::document + .get_element_by_id("loader") + .map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap())); loader_hidden = true; } was_rendered = true; diff --git a/app/gui/view/graph-editor/Cargo.toml b/app/gui/view/graph-editor/Cargo.toml index 3534edb425..760d8b24b9 100644 --- a/app/gui/view/graph-editor/Cargo.toml +++ b/app/gui/view/graph-editor/Cargo.toml @@ -33,7 +33,7 @@ serde_json = { version = "1.0" } serde = { version = "1.0", features = ["derive"] } sourcemap = "6.0" uuid = { version = "0.8", features = ["serde", "v4", "wasm-bindgen"] } -wasm-bindgen = { version = "0.2.58", features = ["nightly", "serde-serialize"] } +wasm-bindgen = { version = "0.2.78", features = ["nightly", "serde-serialize"] } [dependencies.web-sys] version = "0.3.4" diff --git a/app/gui/view/graph-editor/src/builtin/visualization/native/error.rs b/app/gui/view/graph-editor/src/builtin/visualization/native/error.rs index 750809d8fc..58030dbf13 100644 --- a/app/gui/view/graph-editor/src/builtin/visualization/native/error.rs +++ b/app/gui/view/graph-editor/src/builtin/visualization/native/error.rs @@ -14,8 +14,7 @@ use ensogl::display::scene::Scene; use ensogl::display::shape::primitive::StyleWatch; use ensogl::display::DomSymbol; use ensogl::system::web; -use ensogl::system::web::AttributeSetter; -use ensogl::system::web::StyleSetter; +use ensogl::system::web::traits::*; use ensogl_hardcoded_theme; use serde::Deserialize; use serde::Serialize; @@ -160,7 +159,7 @@ impl Model { /// Constructor. fn new(scene: Scene) -> Self { let logger = Logger::new("RawText"); - let div = web::create_div(); + let div = web::document.create_div_or_panic(); let dom = DomSymbol::new(&div); let size = Rc::new(Cell::new(Vector2(200.0, 200.0))); let displayed = Rc::new(CloneCell::new(Kind::Panic)); @@ -169,15 +168,15 @@ impl Model { let styles = StyleWatch::new(&scene.style_sheet); let padding_text = format!("{}px", PADDING_TEXT); - dom.dom().set_attribute_or_warn("class", "visualization scrollable", &logger); - dom.dom().set_style_or_warn("overflow-x", "hidden", &logger); - dom.dom().set_style_or_warn("overflow-y", "auto", &logger); - dom.dom().set_style_or_warn("font-family", "DejaVuSansMonoBook", &logger); - dom.dom().set_style_or_warn("font-size", "12px", &logger); - dom.dom().set_style_or_warn("border-radius", "14px", &logger); - dom.dom().set_style_or_warn("padding-left", &padding_text, &logger); - dom.dom().set_style_or_warn("padding-top", &padding_text, &logger); - dom.dom().set_style_or_warn("pointer-events", "auto", &logger); + dom.dom().set_attribute_or_warn("class", "visualization scrollable"); + dom.dom().set_style_or_warn("overflow-x", "hidden"); + dom.dom().set_style_or_warn("overflow-y", "auto"); + dom.dom().set_style_or_warn("font-family", "DejaVuSansMonoBook"); + dom.dom().set_style_or_warn("font-size", "12px"); + dom.dom().set_style_or_warn("border-radius", "14px"); + dom.dom().set_style_or_warn("padding-left", &padding_text); + dom.dom().set_style_or_warn("padding-top", &padding_text); + dom.dom().set_style_or_warn("pointer-events", "auto"); scene.dom.layers.back.manage(&dom); Model { logger, dom, size, styles, displayed, messages, scene }.init() @@ -243,7 +242,7 @@ impl Model { let green = text_color.green * 255.0; let blue = text_color.blue * 255.0; let text_color = format!("rgba({},{},{},{})", red, green, blue, text_color.alpha); - self.dom.dom().set_style_or_warn("color", text_color, &self.logger); + self.dom.dom().set_style_or_warn("color", text_color); } fn set_layer(&self, layer: Layer) { diff --git a/app/gui/view/graph-editor/src/builtin/visualization/native/raw_text.rs b/app/gui/view/graph-editor/src/builtin/visualization/native/raw_text.rs index 830dc9902a..ab31ec7d0a 100644 --- a/app/gui/view/graph-editor/src/builtin/visualization/native/raw_text.rs +++ b/app/gui/view/graph-editor/src/builtin/visualization/native/raw_text.rs @@ -11,8 +11,7 @@ use ensogl::display::scene::Scene; use ensogl::display::shape::primitive::StyleWatch; use ensogl::display::DomSymbol; use ensogl::system::web; -use ensogl::system::web::AttributeSetter; -use ensogl::system::web::StyleSetter; +use ensogl::system::web::traits::*; use ensogl_hardcoded_theme; @@ -85,7 +84,7 @@ impl RawTextModel { /// Constructor. fn new(scene: Scene) -> Self { let logger = Logger::new("RawText"); - let div = web::create_div(); + let div = web::document.create_div_or_panic(); let dom = DomSymbol::new(&div); let size = Rc::new(Cell::new(Vector2(200.0, 200.0))); @@ -100,16 +99,16 @@ impl RawTextModel { let text_color = format!("rgba({},{},{},{})", _red, _green, _blue, text_color.alpha); let padding_text = format!("{}px", PADDING_TEXT); - dom.dom().set_attribute_or_warn("class", "visualization scrollable", &logger); - dom.dom().set_style_or_warn("white-space", "pre", &logger); - dom.dom().set_style_or_warn("overflow-y", "auto", &logger); - dom.dom().set_style_or_warn("overflow-x", "auto", &logger); - dom.dom().set_style_or_warn("font-family", "DejaVuSansMonoBook", &logger); - dom.dom().set_style_or_warn("font-size", "12px", &logger); - dom.dom().set_style_or_warn("padding-left", &padding_text, &logger); - dom.dom().set_style_or_warn("padding-top", &padding_text, &logger); - dom.dom().set_style_or_warn("color", text_color, &logger); - dom.dom().set_style_or_warn("pointer-events", "auto", &logger); + dom.dom().set_attribute_or_warn("class", "visualization scrollable"); + dom.dom().set_style_or_warn("white-space", "pre"); + dom.dom().set_style_or_warn("overflow-y", "auto"); + dom.dom().set_style_or_warn("overflow-x", "auto"); + dom.dom().set_style_or_warn("font-family", "DejaVuSansMonoBook"); + dom.dom().set_style_or_warn("font-size", "12px"); + dom.dom().set_style_or_warn("padding-left", &padding_text); + dom.dom().set_style_or_warn("padding-top", &padding_text); + dom.dom().set_style_or_warn("color", text_color); + dom.dom().set_style_or_warn("pointer-events", "auto"); scene.dom.layers.back.manage(&dom); RawTextModel { logger, dom, size, scene }.init() diff --git a/app/gui/view/graph-editor/src/component/add_node_button.rs b/app/gui/view/graph-editor/src/component/add_node_button.rs index 590e6d7a02..5897721b8a 100644 --- a/app/gui/view/graph-editor/src/component/add_node_button.rs +++ b/app/gui/view/graph-editor/src/component/add_node_button.rs @@ -97,7 +97,7 @@ impl AddNodeButton { pub fn new(app: &Application) -> Self { let view = ensogl_component::button::View::new(app); let network = frp::Network::new("AddNodeButton"); - let scene = app.display.scene(); + let scene = &app.display.default_scene; let camera = scene.camera(); let style_watch = StyleWatchFrp::new(&scene.style_sheet); diff --git a/app/gui/view/graph-editor/src/component/breadcrumbs.rs b/app/gui/view/graph-editor/src/component/breadcrumbs.rs index 98e6c81d20..b25d06a330 100644 --- a/app/gui/view/graph-editor/src/component/breadcrumbs.rs +++ b/app/gui/view/graph-editor/src/component/breadcrumbs.rs @@ -185,7 +185,7 @@ impl BreadcrumbsModel { /// The `gap_width` describes an empty space on the left of all the content. This space will be /// covered by the background and is intended to make room for windows control buttons. pub fn new(app: Application, frp: &Frp) -> Self { - let scene = app.display.scene(); + let scene = &app.display.default_scene; let project_name = app.new_view(); let logger = Logger::new("Breadcrumbs"); let display_object = display::object::Instance::new(&logger); @@ -450,7 +450,7 @@ pub struct Breadcrumbs { impl Breadcrumbs { /// Constructor. pub fn new(app: Application) -> Self { - let scene = app.display.scene().clone_ref(); + let scene = app.display.default_scene.clone_ref(); let frp = Frp::new(); let model = Rc::new(BreadcrumbsModel::new(app, &frp)); let network = &frp.network; diff --git a/app/gui/view/graph-editor/src/component/breadcrumbs/breadcrumb.rs b/app/gui/view/graph-editor/src/component/breadcrumbs/breadcrumb.rs index 674bb4523c..0b7257158f 100644 --- a/app/gui/view/graph-editor/src/component/breadcrumbs/breadcrumb.rs +++ b/app/gui/view/graph-editor/src/component/breadcrumbs/breadcrumb.rs @@ -279,7 +279,7 @@ impl BreadcrumbModel { method_pointer: &MethodPointer, expression_id: &ast::Id, ) -> Self { - let scene = app.display.scene(); + let scene = &app.display.default_scene; let logger = Logger::new("Breadcrumbs"); let display_object = display::object::Instance::new(&logger); let view_logger = Logger::new_sub(&logger, "view_logger"); @@ -492,7 +492,7 @@ impl Breadcrumb { let frp = Frp::new(); let model = Rc::new(BreadcrumbModel::new(app, &frp, method_pointer, expression_id)); let network = &frp.network; - let scene = app.display.scene(); + let scene = &app.display.default_scene; // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape // system (#795) 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 eef2269c4c..840a351da1 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 @@ -134,7 +134,7 @@ impl ProjectNameModel { /// Constructor. fn new(app: &Application) -> Self { let app = app.clone_ref(); - let scene = app.display.scene(); + let scene = &app.display.default_scene; let logger = Logger::new("ProjectName"); let display_object = display::object::Instance::new(&logger); // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape @@ -251,7 +251,7 @@ impl ProjectName { let frp = Frp::new(); let model = Rc::new(ProjectNameModel::new(app)); let network = &frp.network; - let scene = app.display.scene(); + let scene = &app.display.default_scene; let text = &model.text_field.frp; // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape // system (#795) diff --git a/app/gui/view/graph-editor/src/component/edge.rs b/app/gui/view/graph-editor/src/component/edge.rs index b5d898e44e..225c84f2a9 100644 --- a/app/gui/view/graph-editor/src/component/edge.rs +++ b/app/gui/view/graph-editor/src/component/edge.rs @@ -1161,7 +1161,7 @@ impl Edge { /// Constructor. pub fn new(app: &Application) -> Self { let network = frp::Network::new("node_edge"); - let data = Rc::new(EdgeModelData::new(app.display.scene(), &network)); + let data = Rc::new(EdgeModelData::new(&app.display.default_scene, &network)); let model = Rc::new(EdgeModel { data }); Self { model, network }.init(app) } @@ -1182,7 +1182,7 @@ impl Edge { let shape_events = &self.frp.shape_events; let edge_color = color::Animation::new(network); let edge_focus_color = color::Animation::new(network); - let _style = StyleWatch::new(&app.display.scene().style_sheet); + let _style = StyleWatch::new(&app.display.default_scene.style_sheet); model.data.front.register_proxy_frp(network, &input.shape_events); model.data.back.register_proxy_frp(network, &input.shape_events); diff --git a/app/gui/view/graph-editor/src/component/node.rs b/app/gui/view/graph-editor/src/component/node.rs index 051a6a50bf..c10347f506 100644 --- a/app/gui/view/graph-editor/src/component/node.rs +++ b/app/gui/view/graph-editor/src/component/node.rs @@ -426,7 +426,7 @@ impl NodeModel { /// Constructor. pub fn new(app: &Application, registry: visualization::Registry) -> Self { ensogl::shapes_order_dependencies! { - app.display.scene() => { + app.display.default_scene => { //TODO[ao] The two lines below should not be needed - the ordering should be // transitive. But removing them causes a visual glitches described in // https://github.com/enso-org/ide/issues/1624 @@ -449,7 +449,7 @@ impl NodeModel { } } - let scene = app.display.scene(); + let scene = &app.display.default_scene; let logger = Logger::new("node"); let main_logger = Logger::new_sub(&logger, "main_area"); @@ -495,7 +495,7 @@ impl NodeModel { let output = output::Area::new(&logger, app); display_object.add_child(&output); - let style = StyleWatchFrp::new(&app.display.scene().style_sheet); + let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet); let comment = text::Area::new(app); display_object.add_child(&comment); @@ -624,7 +624,7 @@ impl Node { // in https://github.com/enso-org/ide/issues/1031 // let comment_color = color::Animation::new(network); let error_color_anim = color::Animation::new(network); - let style = StyleWatch::new(&app.display.scene().style_sheet); + let style = StyleWatch::new(&app.display.default_scene.style_sheet); let style_frp = &model.style; let action_bar = &model.action_bar.frp; // Hook up the display object position updates to the node's FRP. Required to calculate the diff --git a/app/gui/view/graph-editor/src/component/node/action_bar.rs b/app/gui/view/graph-editor/src/component/node/action_bar.rs index 6937190ce3..4ef0045f4b 100644 --- a/app/gui/view/graph-editor/src/component/node/action_bar.rs +++ b/app/gui/view/graph-editor/src/component/node/action_bar.rs @@ -134,7 +134,7 @@ struct Model { impl Model { fn new(logger: impl AnyLogger, app: &Application) -> Self { - let scene = app.display.scene(); + let scene = &app.display.default_scene; let logger = Logger::new_sub(logger, "ActionBar"); let display_object = display::object::Instance::new(&logger); let hover_area = hover_area::View::new(&logger); diff --git a/app/gui/view/graph-editor/src/component/node/error.rs b/app/gui/view/graph-editor/src/component/node/error.rs index 5af3498b4d..05e3cabe58 100644 --- a/app/gui/view/graph-editor/src/component/node/error.rs +++ b/app/gui/view/graph-editor/src/component/node/error.rs @@ -9,7 +9,7 @@ use ensogl::display::shape::StyleWatch; use ensogl::display::DomSymbol; use ensogl::display::Scene; use ensogl::system::web; -use ensogl::system::web::StyleSetter; +use ensogl::system::web::traits::*; use ensogl_component::shadow; use serde::Deserialize; use serde::Serialize; @@ -93,7 +93,7 @@ impl Container { let scene = scene.clone_ref(); let logger = Logger::new("error::Container"); let display_object = display::object::Instance::new(&logger); - let background_dom = Self::create_background_dom(&logger, &scene); + let background_dom = Self::create_background_dom(&scene); let visualization = error_visualization::Error::new(&scene); display_object.add_child(&background_dom); @@ -102,7 +102,7 @@ impl Container { Self { logger, visualization, scene, background_dom, display_object } } - fn create_background_dom(logger: &Logger, scene: &Scene) -> DomSymbol { + fn create_background_dom(scene: &Scene) -> DomSymbol { // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape // system (#795) let styles = StyleWatch::new(&scene.style_sheet); @@ -113,21 +113,21 @@ impl Container { let bg_blue = bg_color.blue * 255.0; let bg_hex = format!("rgba({},{},{},{})", bg_red, bg_green, bg_blue, bg_color.alpha); - let div = web::create_div(); + let div = web::document.create_div_or_panic(); let background_dom = DomSymbol::new(&div); let (width, height) = SIZE; let width = format!("{}.px", width); let height = format!("{}.px", height); let z_index = Z_INDEX.to_string(); let border_radius = format!("{}.px", BORDER_RADIUS); - background_dom.dom().set_style_or_warn("width", width, logger); - background_dom.dom().set_style_or_warn("height", height, logger); - background_dom.dom().set_style_or_warn("z-index", z_index, logger); - background_dom.dom().set_style_or_warn("overflow-y", "auto", logger); - background_dom.dom().set_style_or_warn("overflow-x", "auto", logger); - background_dom.dom().set_style_or_warn("background", bg_hex, logger); - background_dom.dom().set_style_or_warn("border-radius", border_radius, logger); - shadow::add_to_dom_element(&background_dom, &styles, logger); + background_dom.dom().set_style_or_warn("width", width); + background_dom.dom().set_style_or_warn("height", height); + background_dom.dom().set_style_or_warn("z-index", z_index); + background_dom.dom().set_style_or_warn("overflow-y", "auto"); + background_dom.dom().set_style_or_warn("overflow-x", "auto"); + background_dom.dom().set_style_or_warn("background", bg_hex); + background_dom.dom().set_style_or_warn("border-radius", border_radius); + shadow::add_to_dom_element(&background_dom, &styles); background_dom } 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 5336eb4d6b..26d469a493 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 @@ -245,8 +245,8 @@ impl Model { let label = app.new_view::(); let id_crumbs_map = default(); let expression = default(); - let styles = StyleWatch::new(&app.display.scene().style_sheet); - let styles_frp = StyleWatchFrp::new(&app.display.scene().style_sheet); + let styles = StyleWatch::new(&app.display.default_scene.style_sheet); + let styles_frp = StyleWatchFrp::new(&app.display.default_scene.style_sheet); display_object.add_child(&label); display_object.add_child(&ports); ports.add_child(&header); @@ -268,7 +268,7 @@ impl Model { fn init(self) -> Self { // FIXME[WD]: Depth sorting of labels to in front of the mouse pointer. Temporary solution. // It needs to be more flexible once we have proper depth management. - let scene = self.app.display.scene(); + let scene = &self.app.display.default_scene; self.label.remove_from_scene_layer(&scene.layers.main); self.label.add_to_scene_layer(&scene.layers.label); @@ -289,7 +289,7 @@ impl Model { } fn scene(&self) -> &Scene { - self.app.display.scene() + &self.app.display.default_scene } /// Run the provided function on the target port if exists. @@ -614,7 +614,7 @@ impl Area { // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for // shape system (#795) - let style_sheet = &self.model.app.display.scene().style_sheet; + let style_sheet = &self.model.app.display.default_scene.style_sheet; let styles = StyleWatch::new(style_sheet); let styles_frp = &self.model.styles_frp; let any_type_sel_color = styles_frp.get_color(theme::code::types::any::selection); diff --git a/app/gui/view/graph-editor/src/component/node/output/area.rs b/app/gui/view/graph-editor/src/component/node/output/area.rs index 7af07cf3f8..9df80efc74 100644 --- a/app/gui/view/graph-editor/src/component/node/output/area.rs +++ b/app/gui/view/graph-editor/src/component/node/output/area.rs @@ -175,8 +175,8 @@ impl Model { let id_crumbs_map = default(); let expression = default(); let port_count = default(); - let styles = StyleWatch::new(&app.display.scene().style_sheet); - let styles_frp = StyleWatchFrp::new(&app.display.scene().style_sheet); + let styles = StyleWatch::new(&app.display.default_scene.style_sheet); + let styles_frp = StyleWatchFrp::new(&app.display.default_scene.style_sheet); let frp = frp.output.clone_ref(); display_object.add_child(&label); display_object.add_child(&ports); @@ -199,7 +199,7 @@ impl Model { fn init(self) -> Self { // FIXME[WD]: Depth sorting of labels to in front of the mouse pointer. Temporary solution. // It needs to be more flexible once we have proper depth management. - let scene = self.app.display.scene(); + let scene = &self.app.display.default_scene; self.label.remove_from_scene_layer(&scene.layers.main); self.label.add_to_scene_layer(&scene.layers.label); diff --git a/app/gui/view/graph-editor/src/component/node/profiling.rs b/app/gui/view/graph-editor/src/component/node/profiling.rs index 4bfa96c1b3..6de31ad570 100644 --- a/app/gui/view/graph-editor/src/component/node/profiling.rs +++ b/app/gui/view/graph-editor/src/component/node/profiling.rs @@ -196,7 +196,7 @@ impl Deref for ProfilingLabel { impl ProfilingLabel { /// Constructs a `ProfilingLabel` for the given application. pub fn new(app: &Application) -> Self { - let scene = app.display.scene(); + let scene = &app.display.default_scene; let root = display::object::Instance::new(Logger::new("ProfilingIndicator")); let label = text::Area::new(app); diff --git a/app/gui/view/graph-editor/src/component/node/vcs.rs b/app/gui/view/graph-editor/src/component/node/vcs.rs index 4be6ad218e..be1869a393 100644 --- a/app/gui/view/graph-editor/src/component/node/vcs.rs +++ b/app/gui/view/graph-editor/src/component/node/vcs.rs @@ -158,7 +158,7 @@ impl StatusIndicator { // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape // system (#795) - let styles = StyleWatch::new(&app.display.scene().style_sheet); + let styles = StyleWatch::new(&app.display.default_scene.style_sheet); frp::extend! { network frp.source.status <+ frp.input.set_status; diff --git a/app/gui/view/graph-editor/src/component/profiling.rs b/app/gui/view/graph-editor/src/component/profiling.rs index 9771e90929..2a2eae46b1 100644 --- a/app/gui/view/graph-editor/src/component/profiling.rs +++ b/app/gui/view/graph-editor/src/component/profiling.rs @@ -143,7 +143,7 @@ impl Deref for Button { impl Button { /// Constructs a new button for toggling the editor's view mode. pub fn new(app: &Application) -> Button { - let scene = app.display.scene(); + let scene = &app.display.default_scene; let styles = StyleWatchFrp::new(&scene.style_sheet); let frp = Frp::new(); let network = &frp.network; diff --git a/app/gui/view/graph-editor/src/component/tooltip.rs b/app/gui/view/graph-editor/src/component/tooltip.rs index f2b9536d8f..471f2eb0a9 100644 --- a/app/gui/view/graph-editor/src/component/tooltip.rs +++ b/app/gui/view/graph-editor/src/component/tooltip.rs @@ -188,7 +188,7 @@ impl Tooltip { // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape // system (#795) - let styles = StyleWatch::new(&app.display.scene().style_sheet); + let styles = StyleWatch::new(&app.display.default_scene.style_sheet); let hide_delay_duration_ms = styles.get_number_or( ensogl_hardcoded_theme::application::tooltip::hide_delay_duration_ms, 0.0, diff --git a/app/gui/view/graph-editor/src/component/visualization/container.rs b/app/gui/view/graph-editor/src/component/visualization/container.rs index a0907e649b..93fcc86868 100644 --- a/app/gui/view/graph-editor/src/component/visualization/container.rs +++ b/app/gui/view/graph-editor/src/component/visualization/container.rs @@ -31,7 +31,7 @@ use ensogl::display::DomSymbol; use ensogl::Animation; use ensogl::system::web; -use ensogl::system::web::StyleSetter; +use ensogl::system::web::traits::*; use ensogl_component::shadow; @@ -192,19 +192,19 @@ impl View { bg_color.alpha ); - let div = web::create_div(); + let div = web::document.create_div_or_panic(); let background_dom = DomSymbol::new(&div); // TODO : We added a HTML background to the `View`, because "shape" background was - // overlapping the JS visualization. This should be further investigated - // while fixing rust visualization displaying. (#796) - background_dom.dom().set_style_or_warn("width", "0", &logger); - background_dom.dom().set_style_or_warn("height", "0", &logger); - background_dom.dom().set_style_or_warn("z-index", "1", &logger); - background_dom.dom().set_style_or_warn("overflow-y", "auto", &logger); - background_dom.dom().set_style_or_warn("overflow-x", "auto", &logger); - background_dom.dom().set_style_or_warn("background", bg_hex, &logger); - background_dom.dom().set_style_or_warn("border-radius", "14px", &logger); - shadow::add_to_dom_element(&background_dom, &styles, &logger); + // overlapping the JS visualization. This should be further investigated + // while fixing rust visualization displaying. (#796) + background_dom.dom().set_style_or_warn("width", "0"); + background_dom.dom().set_style_or_warn("height", "0"); + background_dom.dom().set_style_or_warn("z-index", "1"); + background_dom.dom().set_style_or_warn("overflow-y", "auto"); + background_dom.dom().set_style_or_warn("overflow-x", "auto"); + background_dom.dom().set_style_or_warn("background", bg_hex); + background_dom.dom().set_style_or_warn("border-radius", "14px"); + shadow::add_to_dom_element(&background_dom, &styles); display_object.add_child(&background_dom); Self { display_object, background, overlay, background_dom, scene }.init() @@ -259,7 +259,7 @@ pub struct ContainerModel { impl ContainerModel { /// Constructor. pub fn new(logger: &Logger, app: &Application, registry: visualization::Registry) -> Self { - let scene = app.display.scene(); + let scene = &app.display.default_scene; let logger = Logger::new_sub(logger, "visualization_container"); let display_object = display::object::Instance::new(&logger); let drag_root = display::object::Instance::new(&logger); @@ -391,10 +391,10 @@ impl ContainerModel { // self.fullscreen_view.background.shape.sprite.size.set(size); // self.view.background.shape.sprite.size.set(zero()); self.view.overlay.size.set(zero()); - dom.set_style_or_warn("width", "0", &self.logger); - dom.set_style_or_warn("height", "0", &self.logger); - bg_dom.set_style_or_warn("width", format!("{}px", size[0]), &self.logger); - bg_dom.set_style_or_warn("height", format!("{}px", size[1]), &self.logger); + dom.set_style_or_warn("width", "0"); + dom.set_style_or_warn("height", "0"); + bg_dom.set_style_or_warn("width", format!("{}px", size[0])); + bg_dom.set_style_or_warn("height", format!("{}px", size[1])); self.action_bar.frp.set_size.emit(Vector2::zero()); } else { // self.view.background.shape.radius.set(CORNER_RADIUS); @@ -402,10 +402,10 @@ impl ContainerModel { self.view.background.radius.set(CORNER_RADIUS); self.view.overlay.size.set(size); self.view.background.size.set(size + 2.0 * Vector2(PADDING, PADDING)); - dom.set_style_or_warn("width", format!("{}px", size[0]), &self.logger); - dom.set_style_or_warn("height", format!("{}px", size[1]), &self.logger); - bg_dom.set_style_or_warn("width", "0", &self.logger); - bg_dom.set_style_or_warn("height", "0", &self.logger); + dom.set_style_or_warn("width", format!("{}px", size[0])); + dom.set_style_or_warn("height", format!("{}px", size[1])); + bg_dom.set_style_or_warn("width", "0"); + bg_dom.set_style_or_warn("height", "0"); // self.fullscreen_view.background.shape.sprite.size.set(zero()); let action_bar_size = Vector2::new(size.x, ACTION_BAR_HEIGHT); diff --git a/app/gui/view/graph-editor/src/component/visualization/container/action_bar.rs b/app/gui/view/graph-editor/src/component/visualization/container/action_bar.rs index 525015ee70..a42c88e43c 100644 --- a/app/gui/view/graph-editor/src/component/visualization/container/action_bar.rs +++ b/app/gui/view/graph-editor/src/component/visualization/container/action_bar.rs @@ -271,9 +271,9 @@ impl Model { let icons = Icons::new(logger); let shapes = compound::events::MouseEvents::default(); - app.display.scene().layers.below_main.add_exclusive(&hover_area); - app.display.scene().layers.below_main.add_exclusive(&background); - app.display.scene().layers.above_nodes.add_exclusive(&icons); + app.display.default_scene.layers.below_main.add_exclusive(&hover_area); + app.display.default_scene.layers.below_main.add_exclusive(&background); + app.display.default_scene.layers.above_nodes.add_exclusive(&icons); shapes.add_sub_shape(&hover_area); shapes.add_sub_shape(&background); @@ -359,7 +359,7 @@ impl ActionBar { let network = &self.frp.network; let frp = &self.frp; let model = &self.model; - let mouse = &app.display.scene().mouse.frp; + let mouse = &app.display.default_scene.mouse.frp; let visualization_chooser = &model.visualization_chooser.frp; frp::extend! { network diff --git a/app/gui/view/graph-editor/src/component/visualization/container/fullscreen.rs b/app/gui/view/graph-editor/src/component/visualization/container/fullscreen.rs index 8565695367..102f69e823 100644 --- a/app/gui/view/graph-editor/src/component/visualization/container/fullscreen.rs +++ b/app/gui/view/graph-editor/src/component/visualization/container/fullscreen.rs @@ -8,7 +8,7 @@ use ensogl::display::shape::*; use ensogl::display::traits::*; use ensogl::display::DomSymbol; use ensogl::system::web; -use ensogl::system::web::StyleSetter; +use ensogl::system::web::traits::*; use ensogl_hardcoded_theme as theme; @@ -74,18 +74,18 @@ impl Panel { let blue = bg_color.blue * 255.0; let bg_hex = format!("rgba({},{},{},{})", red, green, blue, bg_color.alpha); - let div = web::create_div(); + let div = web::document.create_div_or_panic(); let background_dom = DomSymbol::new(&div); // TODO : We added a HTML background to the `View`, because "shape" background was // overlapping the JS visualization. This should be further investigated // while fixing rust visualization displaying. (#796) - background_dom.dom().set_style_or_warn("width", "0", &logger); - background_dom.dom().set_style_or_warn("height", "0", &logger); - background_dom.dom().set_style_or_warn("z-index", "1", &logger); - background_dom.dom().set_style_or_warn("overflow-y", "auto", &logger); - background_dom.dom().set_style_or_warn("overflow-x", "auto", &logger); - background_dom.dom().set_style_or_warn("background", bg_hex, &logger); - background_dom.dom().set_style_or_warn("border-radius", "0", &logger); + background_dom.dom().set_style_or_warn("width", "0"); + background_dom.dom().set_style_or_warn("height", "0"); + background_dom.dom().set_style_or_warn("z-index", "1"); + background_dom.dom().set_style_or_warn("overflow-y", "auto"); + background_dom.dom().set_style_or_warn("overflow-x", "auto"); + background_dom.dom().set_style_or_warn("background", bg_hex); + background_dom.dom().set_style_or_warn("border-radius", "0"); display_object.add_child(&background_dom); scene.dom.layers.fullscreen_vis.manage(&background_dom); diff --git a/app/gui/view/graph-editor/src/component/visualization/container/visualization_chooser.rs b/app/gui/view/graph-editor/src/component/visualization/container/visualization_chooser.rs index 6ede610203..b2e60e020d 100644 --- a/app/gui/view/graph-editor/src/component/visualization/container/visualization_chooser.rs +++ b/app/gui/view/graph-editor/src/component/visualization/container/visualization_chooser.rs @@ -60,7 +60,7 @@ struct Model { impl Model { pub fn new(app: &Application, registry: visualization::Registry) -> Self { let selection_menu = drop_down_menu::DropDownMenu::new(app); - app.display.scene().layers.below_main.add_exclusive(&selection_menu); + app.display.default_scene.layers.below_main.add_exclusive(&selection_menu); Self { selection_menu, registry } } diff --git a/app/gui/view/graph-editor/src/component/visualization/foreign/java_script/binding.rs b/app/gui/view/graph-editor/src/component/visualization/foreign/java_script/binding.rs index 2f76e4e97d..f64dda850f 100644 --- a/app/gui/view/graph-editor/src/component/visualization/foreign/java_script/binding.rs +++ b/app/gui/view/graph-editor/src/component/visualization/foreign/java_script/binding.rs @@ -4,17 +4,21 @@ use crate::prelude::*; use crate::component::type_coloring; use crate::component::visualization::foreign::java_script::PreprocessorCallback; -use crate::component::visualization::instance::PreprocessorConfiguration; use crate::Type; use ensogl::data::color; use ensogl::display::shape::StyleWatch; use ensogl::display::style::data::DataMatch; use ensogl::display::DomSymbol; +use ensogl::system::web::HtmlDivElement; +use ensogl::system::web::JsValue; use ensogl_hardcoded_theme; use fmt::Formatter; -use wasm_bindgen::prelude::*; -use web_sys::HtmlDivElement; +use wasm_bindgen::prelude::wasm_bindgen; + +#[cfg(target_arch = "wasm32")] +use crate::component::visualization::instance::PreprocessorConfiguration; + // ================= @@ -30,6 +34,10 @@ pub const JS_CLASS_NAME: &str = "Visualization"; // === JavaScript Bindings === // =========================== +// TODO: Add docs to the WASM32 bindings below - esp. what is the difference between +// __Visualization__ and Visualization. + +#[cfg(target_arch = "wasm32")] #[wasm_bindgen(module = "/src/component/visualization/foreign/java_script/visualization.js")] extern "C" { #[allow(unsafe_code)] @@ -48,8 +56,32 @@ extern "C" { pub fn emitPreprocessorChange(this: &Visualization) -> Result<(), JsValue>; } -/// Provides reference to the visualizations JavaScript base class. -pub fn js_class() -> JsValue { +#[allow(non_snake_case)] +#[cfg(not(target_arch = "wasm32"))] +fn __Visualization__() -> JsValue { + default() +} + +#[cfg(not(target_arch = "wasm32"))] +#[derive(Copy, Clone, Debug, Default)] +#[allow(missing_docs)] +pub struct Visualization {} + +#[cfg(not(target_arch = "wasm32"))] +#[allow(missing_docs)] +impl Visualization { + pub fn new() -> Self { + default() + } + + #[allow(non_snake_case)] + pub fn emitPreprocessorChange(&self) -> Result<(), JsValue> { + Ok(()) + } +} + +/// Provides reference to the [`Visualization`] JavaScript base class. +pub fn js_visualization_class() -> JsValue { __Visualization__() } @@ -59,7 +91,7 @@ pub fn js_class() -> JsValue { // === Theme === // ============= -/// The theming API that we expose to JS visualizations +/// The theming API that we expose to JS visualizations. #[wasm_bindgen] #[derive(Clone, Debug)] pub struct JsTheme { @@ -123,6 +155,7 @@ impl JsTheme { /// Data that is passed into the javascript Visualization baseclass. #[allow(missing_docs)] #[wasm_bindgen] +#[allow(dead_code)] // Some fields are used by wasm32 target only. pub struct JsConsArgs { #[wasm_bindgen(skip)] pub root: HtmlDivElement, @@ -151,6 +184,7 @@ impl JsConsArgs { } } +#[cfg(target_arch = "wasm32")] #[wasm_bindgen] impl JsConsArgs { /// Getter for the root element for the visualization. diff --git a/app/gui/view/graph-editor/src/component/visualization/foreign/java_script/definition.rs b/app/gui/view/graph-editor/src/component/visualization/foreign/java_script/definition.rs index 865cb3435e..1f682c8764 100644 --- a/app/gui/view/graph-editor/src/component/visualization/foreign/java_script/definition.rs +++ b/app/gui/view/graph-editor/src/component/visualization/foreign/java_script/definition.rs @@ -2,11 +2,7 @@ //! //! For details of the API please see: //! * docstrings in the `visualization.js` file; -//! * [visualization documentation](https://dev.enso.org/docs/ide/product/visualizations.html). -// FIXME: Can we simplify the above definition so its more minimal, yet functional? - -mod function; -use function::Function; +//! * [visualization documentation](https://enso.org/docs/developer/ide/product/visualizations.html). use crate::prelude::*; @@ -18,12 +14,13 @@ use crate::component::visualization::InstantiationResult; use crate::visualization::foreign::java_script::Sources; use ensogl::display::Scene; +use ensogl::system::web; +use ensogl::system::web::traits::*; +use ensogl::system::web::Function; +use ensogl::system::web::JsString; use ensogl::system::web::JsValue; use fmt::Formatter; -use js_sys; -use js_sys::JsString; use std::str::FromStr; -use wasm_bindgen::JsCast; @@ -63,16 +60,13 @@ impl Definition { pub fn new(project: visualization::path::Project, sources: Sources) -> Result { let source = sources.to_string(&project); let context = JsValue::NULL; - let function = Function::new_with_args(binding::JS_CLASS_NAME, &source) + let function = Function::new_with_args_fixed(binding::JS_CLASS_NAME, &source) .map_err(Error::InvalidFunction)?; - let js_class = binding::js_class(); + let js_class = binding::js_visualization_class(); let class = function.call1(&context, &js_class).map_err(Error::InvalidFunction)?; - let input_type = try_str_field(&class, field::INPUT_TYPE).unwrap_or_default(); - let input_format = try_str_field(&class, field::INPUT_FORMAT).unwrap_or_default(); let input_format = visualization::data::Format::from_str(&input_format).unwrap_or_default(); - let label = label(&class)?; let path = visualization::Path::new(project, label); let signature = visualization::Signature::new(path, input_type, input_format); @@ -102,7 +96,7 @@ impl From for visualization::Definition { // === Utils === fn try_str_field(obj: &JsValue, field: &str) -> Option { - let field = js_sys::Reflect::get(obj, &field.into()).ok()?; + let field = web::Reflect::get(obj, &field.into()).ok()?; let js_string = field.dyn_ref::()?; Some(js_string.into()) } diff --git a/app/gui/view/graph-editor/src/component/visualization/foreign/java_script/definition/function.rs b/app/gui/view/graph-editor/src/component/visualization/foreign/java_script/definition/function.rs deleted file mode 100644 index 8426158d9d..0000000000 --- a/app/gui/view/graph-editor/src/component/visualization/foreign/java_script/definition/function.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! The whole content of this file consists of parts that were copied from `js_sys` just to modify -//! `new_with_args` such that it catches syntax errors. This is supposed to be temporary solution. -//! I opened a GitHub issue suggesting that this will be included in wasm-bindgen -//! (https://github.com/rustwasm/wasm-bindgen/issues/2496). -//! A PR with a suggested change to wasm-bindgen can be found here: -//! https://github.com/rustwasm/wasm-bindgen/pull/2497 - -use js_sys::*; -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(extends = Object, is_type_of = JsValue::is_function)] - #[derive(Clone, Debug, PartialEq, Eq)] - pub type Function; - - /// The `Function` constructor creates a new `Function` object. Calling the - /// constructor directly can create functions dynamically, but suffers from - /// security and similar (but far less significant) performance issues - /// similar to `eval`. However, unlike `eval`, the `Function` constructor - /// allows executing code in the global scope, prompting better programming - /// habits and allowing for more efficient code minification. - #[allow(unsafe_code)] - #[wasm_bindgen(constructor, catch)] - pub fn new_with_args(args: &str, body: &str) -> Result; - - /// The `call()` method calls a function with a given this value and - /// arguments provided individually. - #[allow(unsafe_code)] - #[wasm_bindgen(method, catch, js_name = call)] - pub fn call1(this: &Function, context: &JsValue, arg1: &JsValue) -> Result; -} diff --git a/app/gui/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs b/app/gui/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs index 9bc9a7a491..09ea7967dd 100644 --- a/app/gui/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs +++ b/app/gui/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs @@ -11,6 +11,7 @@ use crate::prelude::*; use crate::component::visualization; use crate::component::visualization::instance::PreprocessorConfiguration; +use crate::component::visualization::java_script; use crate::component::visualization::java_script::binding::JsConsArgs; use crate::component::visualization::java_script::method; use crate::component::visualization::*; @@ -24,9 +25,8 @@ use ensogl::display::DomScene; use ensogl::display::DomSymbol; use ensogl::display::Scene; use ensogl::system::web; +use ensogl::system::web::traits::*; use ensogl::system::web::JsValue; -use ensogl::system::web::StyleSetter; -use js_sys; use std::fmt::Formatter; @@ -89,8 +89,8 @@ type PreprocessorCallbackCell = Rc> pub struct InstanceModel { pub root_node: DomSymbol, pub logger: Logger, - on_data_received: Rc>, - set_size: Rc>, + on_data_received: Rc>, + set_size: Rc>, #[derivative(Debug = "ignore")] object: Rc, #[derivative(Debug = "ignore")] @@ -104,8 +104,8 @@ impl InstanceModel { styles.get_color(ensogl_hardcoded_theme::graph_editor::visualization::background) } - fn create_root(scene: &Scene, logger: &Logger) -> result::Result { - let div = web::create_div(); + fn create_root(scene: &Scene) -> result::Result { + let div = web::document.create_div_or_panic(); let root_node = DomSymbol::new(&div); root_node .dom() @@ -117,7 +117,7 @@ impl InstanceModel { let bg_green = bg_color.green * 255.0; let bg_blue = bg_color.blue * 255.0; let bg_hex = format!("rgba({},{},{},{})", bg_red, bg_green, bg_blue, bg_color.alpha); - root_node.dom().set_style_or_warn("background", bg_hex, logger); + root_node.dom().set_style_or_warn("background", bg_hex); Ok(root_node) } @@ -140,11 +140,12 @@ impl InstanceModel { (closure_cell, closure) } + #[cfg(target_arch = "wasm32")] fn instantiate_class_with_args( class: &JsValue, args: JsConsArgs, ) -> result::Result { - let js_new = js_sys::Function::new_with_args("cls,arg", "return new cls(arg)"); + let js_new = web::Function::new_with_args_fixed("cls,arg", "return new cls(arg)").unwrap(); let context = JsValue::NULL; let object = js_new .call2(&context, class, &args.into()) @@ -156,17 +157,25 @@ impl InstanceModel { Ok(object) } + #[cfg(not(target_arch = "wasm32"))] + fn instantiate_class_with_args( + _class: &JsValue, + _args: JsConsArgs, + ) -> result::Result { + Ok(java_script::binding::Visualization::new()) + } + /// Tries to create a InstanceModel from the given visualisation class. pub fn from_class(class: &JsValue, scene: &Scene) -> result::Result { let logger = Logger::new("Instance"); - let root_node = Self::create_root(scene, &logger)?; + let root_node = Self::create_root(scene)?; let (preprocessor_change, closure) = Self::preprocessor_change_callback(); let styles = StyleWatch::new(&scene.style_sheet); let init_data = JsConsArgs::new(root_node.clone_ref(), styles, closure); let object = Self::instantiate_class_with_args(class, init_data)?; - let on_data_received = get_method(object.as_ref(), method::ON_DATA_RECEIVED).ok(); + let on_data_received = get_method(&object, method::ON_DATA_RECEIVED).ok(); let on_data_received = Rc::new(on_data_received); - let set_size = get_method(object.as_ref(), method::SET_SIZE).ok(); + let set_size = get_method(&object, method::SET_SIZE).ok(); let set_size = Rc::new(set_size); let object = Rc::new(object); let scene = scene.clone_ref(); @@ -188,12 +197,17 @@ impl InstanceModel { scene.manage(&self.root_node); } + #[cfg(target_arch = "wasm32")] fn set_size(&self, size: Vector2) { let data_json = JsValue::from_serde(&size).unwrap(); let _ = self.try_call1(&self.set_size, &data_json); self.root_node.set_size(size); } + #[cfg(not(target_arch = "wasm32"))] + fn set_size(&self, _size: Vector2) {} + + #[cfg(target_arch = "wasm32")] fn receive_data(&self, data: &Data) -> result::Result<(), DataError> { let data_json = match data { Data::Json { content } => content, @@ -209,15 +223,21 @@ impl InstanceModel { Ok(()) } + #[cfg(not(target_arch = "wasm32"))] + fn receive_data(&self, _data: &Data) -> result::Result<(), DataError> { + Ok(()) + } + /// Prompt visualization JS object to emit preprocessor change with its currently desired state. pub fn update_preprocessor(&self) -> result::Result<(), JsValue> { self.object.emitPreprocessorChange() } + #[cfg(target_arch = "wasm32")] /// Helper method to call methods on the wrapped javascript object. fn try_call1( &self, - method: &Option, + method: &Option, arg: &JsValue, ) -> result::Result<(), JsValue> { if let Some(method) = method { @@ -283,13 +303,11 @@ impl Instance { let callback = move |preprocessor_config| change.emit(preprocessor_config); let callback = Box::new(callback); self.model.preprocessor_change.borrow_mut().replace(callback); - if let Err(js_error) = self.model.update_preprocessor() { - use enso_frp::web::js_to_string; + if let Err(err) = self.model.update_preprocessor() { let logger = self.model.logger.clone(); error!( logger, - "Failed to trigger initial preprocessor update from JS: \ - {js_to_string(&js_error)}" + "Failed to trigger initial preprocessor update from JS: {err.print_to_string()}" ); } self @@ -311,10 +329,11 @@ impl display::Object for Instance { // === Utils === +#[cfg(target_arch = "wasm32")] /// Try to return the method specified by the given name on the given object as a -/// `js_sys::Function`. -fn get_method(object: &js_sys::Object, property: &str) -> Result { - let method_value = js_sys::Reflect::get(object, &property.into()); +/// `web::Function`. +fn get_method(object: &web::Object, property: &str) -> Result { + let method_value = web::Reflect::get(object, &property.into()); let method_value = method_value.map_err(|object| Error::PropertyNotFoundOnObject { object, property: property.to_string(), @@ -323,6 +342,14 @@ fn get_method(object: &js_sys::Object, property: &str) -> Result Result { + Ok(default()) +} diff --git a/app/gui/view/graph-editor/src/lib.rs b/app/gui/view/graph-editor/src/lib.rs index 0f5b312070..faed3cdd49 100644 --- a/app/gui/view/graph-editor/src/lib.rs +++ b/app/gui/view/graph-editor/src/lib.rs @@ -63,6 +63,7 @@ use ensogl::display::Scene; use ensogl::gui::cursor; use ensogl::prelude::*; use ensogl::system::web; +use ensogl::system::web::traits::*; use ensogl::Animation; use ensogl::DEPRECATED_Animation; use ensogl::DEPRECATED_Tween; @@ -1623,7 +1624,7 @@ impl GraphEditorModel { #[allow(missing_docs)] // FIXME[everyone] All pub functions should have docs, always. pub fn new(app: &Application, cursor: cursor::Cursor, frp: &Frp) -> Self { let network = &frp.network; - let scene = app.display.scene(); + let scene = &app.display.default_scene; let logger = Logger::new("GraphEditor"); let display_object = display::object::Instance::new(&logger); let nodes = Nodes::new(&logger); @@ -1687,7 +1688,7 @@ impl GraphEditorModel { } fn scene(&self) -> &Scene { - self.app.display.scene() + &self.app.display.default_scene } } @@ -2479,7 +2480,7 @@ pub fn enable_disable_toggle( #[allow(unused_parens)] fn new_graph_editor(app: &Application) -> GraphEditor { let world = &app.display; - let scene = world.scene(); + let scene = &world.default_scene; let cursor = &app.cursor; let frp = Frp::new(); let model = GraphEditorModelWithNetwork::new(app, cursor.clone_ref(), &frp); @@ -3263,8 +3264,8 @@ fn new_graph_editor(app: &Application) -> GraphEditor { viz_was_pressed <- viz_pressed.previous(); viz_press <- viz_press_ev.gate_not(&viz_was_pressed); viz_release <- viz_release_ev.gate(&viz_was_pressed); - viz_press_time <- viz_press . map(|_| web::performance().now() as f32); - viz_release_time <- viz_release . map(|_| web::performance().now() as f32); + viz_press_time <- viz_press . map(|_| web::window.performance_or_panic().now() as f32); + viz_release_time <- viz_release . map(|_| web::window.performance_or_panic().now() as f32); viz_press_time_diff <- viz_release_time.map2(&viz_press_time,|t1,t0| t1-t0); viz_preview_mode <- viz_press_time_diff.map(|t| *t > VIZ_PREVIEW_MODE_TOGGLE_TIME_MS); viz_preview_mode_end <- viz_release.gate(&viz_preview_mode).gate_not(&out.is_fs_visualization_displayed); diff --git a/app/gui/view/src/code_editor.rs b/app/gui/view/src/code_editor.rs index 06eb079dfd..4b5046d625 100644 --- a/app/gui/view/src/code_editor.rs +++ b/app/gui/view/src/code_editor.rs @@ -67,8 +67,8 @@ impl Deref for View { impl View { /// Create Code Editor component. pub fn new(app: &Application) -> Self { - let scene = app.display.scene(); - let styles = StyleWatchFrp::new(&app.display.scene().style_sheet); + let scene = &app.display.default_scene; + let styles = StyleWatchFrp::new(&app.display.default_scene.style_sheet); let frp = Frp::new(); let network = &frp.network; let model = app.new_view::(); @@ -101,7 +101,7 @@ impl View { frp.source.is_visible <+ frp.toggle.map2(&is_visible, |(),b| !b); def init = source_(); - let shape = app.display.scene().shape(); + let shape = app.display.default_scene.shape(); position <- all_with3(&height_fraction.value,shape,&init, |height_f,scene_size,_init| { let height = height_f * scene_size.height; let x = -scene_size.width / 2.0 + PADDING_LEFT; diff --git a/app/gui/view/src/debug_mode_popup.rs b/app/gui/view/src/debug_mode_popup.rs index 5c42d8537c..a1166a32fc 100644 --- a/app/gui/view/src/debug_mode_popup.rs +++ b/app/gui/view/src/debug_mode_popup.rs @@ -61,8 +61,8 @@ impl PopupLabel { let network = frp::Network::new("PopupLabel"); let label = Label::new(app); label.set_opacity(0.0); - let background_layer = &app.display.scene().layers.panel; - let text_layer = &app.display.scene().layers.panel_text; + let background_layer = &app.display.default_scene.layers.panel; + let text_layer = &app.display.default_scene.layers.panel_text; label.set_layers(background_layer, text_layer); let opacity_animation = Animation::new(&network); @@ -175,7 +175,7 @@ impl View { frp::extend! { network init <- source_(); - let shape = app.display.scene().shape(); + let shape = app.display.default_scene.shape(); _eval <- all_with(shape, &init, f!([model](scene_size, _init) { let half_height = scene_size.height / 2.0; let label_height = model.label_height(); diff --git a/app/gui/view/src/documentation.rs b/app/gui/view/src/documentation.rs index 352ac711cf..4b084df71b 100644 --- a/app/gui/view/src/documentation.rs +++ b/app/gui/view/src/documentation.rs @@ -15,13 +15,12 @@ use ensogl::display::shape::primitive::StyleWatch; use ensogl::display::DomSymbol; use ensogl::system::web; use ensogl::system::web::clipboard; -use ensogl::system::web::AttributeSetter; -use ensogl::system::web::StyleSetter; +use ensogl::system::web::traits::*; use ensogl_component::shadow; -use wasm_bindgen::closure::Closure; -use wasm_bindgen::JsCast; -use web_sys::HtmlElement; -use web_sys::MouseEvent; +use web::Closure; +use web::HtmlElement; +use web::JsCast; +use web::MouseEvent; @@ -70,9 +69,9 @@ impl Model { fn new(scene: &Scene) -> Self { let logger = Logger::new("DocumentationView"); let display_object = display::object::Instance::new(&logger); - let outer_div = web::create_div(); + let outer_div = web::document.create_div_or_panic(); let outer_dom = DomSymbol::new(&outer_div); - let inner_div = web::create_div(); + let inner_div = web::document.create_div_or_panic(); let inner_dom = DomSymbol::new(&inner_div); let size = Rc::new(Cell::new(Vector2(VIEW_WIDTH - PADDING, VIEW_HEIGHT - PADDING - PADDING_TOP))); @@ -85,22 +84,22 @@ impl Model { let bg_color = styles.get_color(style_path); let bg_color = bg_color.to_javascript_string(); - outer_dom.dom().set_style_or_warn("white-space", "normal", &logger); - outer_dom.dom().set_style_or_warn("overflow-y", "auto", &logger); - outer_dom.dom().set_style_or_warn("overflow-x", "auto", &logger); - outer_dom.dom().set_style_or_warn("background-color", bg_color, &logger); - outer_dom.dom().set_style_or_warn("pointer-events", "auto", &logger); - outer_dom.dom().set_style_or_warn("border-radius", format!("{}px", CORNER_RADIUS), &logger); - shadow::add_to_dom_element(&outer_dom, &styles, &logger); + outer_dom.dom().set_style_or_warn("white-space", "normal"); + outer_dom.dom().set_style_or_warn("overflow-y", "auto"); + outer_dom.dom().set_style_or_warn("overflow-x", "auto"); + outer_dom.dom().set_style_or_warn("background-color", bg_color); + outer_dom.dom().set_style_or_warn("pointer-events", "auto"); + outer_dom.dom().set_style_or_warn("border-radius", format!("{}px", CORNER_RADIUS)); + shadow::add_to_dom_element(&outer_dom, &styles); - inner_dom.dom().set_attribute_or_warn("class", "scrollable", &logger); - inner_dom.dom().set_style_or_warn("white-space", "normal", &logger); - inner_dom.dom().set_style_or_warn("overflow-y", "auto", &logger); - inner_dom.dom().set_style_or_warn("overflow-x", "auto", &logger); - inner_dom.dom().set_style_or_warn("padding", format!("{}px", PADDING), &logger); - inner_dom.dom().set_style_or_warn("padding-top", "5px", &logger); - inner_dom.dom().set_style_or_warn("pointer-events", "auto", &logger); - inner_dom.dom().set_style_or_warn("border-radius", format!("{}px", CORNER_RADIUS), &logger); + inner_dom.dom().set_attribute_or_warn("class", "scrollable"); + inner_dom.dom().set_style_or_warn("white-space", "normal"); + inner_dom.dom().set_style_or_warn("overflow-y", "auto"); + inner_dom.dom().set_style_or_warn("overflow-x", "auto"); + inner_dom.dom().set_style_or_warn("padding", format!("{}px", PADDING)); + inner_dom.dom().set_style_or_warn("padding-top", "5px"); + inner_dom.dom().set_style_or_warn("pointer-events", "auto"); + inner_dom.dom().set_style_or_warn("border-radius", format!("{}px", CORNER_RADIUS)); overlay.roundness.set(1.0); overlay.radius.set(CORNER_RADIUS); @@ -151,12 +150,11 @@ impl Model { let create_closures = || -> Option { let copy_button = copy_buttons.get_with_index(i)?.dyn_into::().ok()?; let code_block = code_blocks.get_with_index(i)?.dyn_into::().ok()?; - let closure = Box::new(move |_event: MouseEvent| { + let closure: Closure = Closure::new(move |_: MouseEvent| { let inner_code = code_block.inner_text(); clipboard::write_text(inner_code); }); - let closure: Closure = Closure::wrap(closure); - let callback = closure.as_ref().unchecked_ref(); + let callback = closure.as_js_function(); match copy_button.add_event_listener_with_callback("click", callback) { Ok(_) => Some(closure), Err(e) => { @@ -219,12 +217,8 @@ impl Model { let padding = (size.x.min(size.y) / 2.0).min(PADDING); self.outer_dom.set_size(Vector2(size.x, size.y)); self.inner_dom.set_size(Vector2(size.x - padding, size.y - padding - PADDING_TOP)); - self.inner_dom.dom().set_style_or_warn("padding", format!("{}px", padding), &self.logger); - self.inner_dom.dom().set_style_or_warn( - "padding-top", - format!("{}px", PADDING_TOP), - &self.logger, - ); + self.inner_dom.dom().set_style_or_warn("padding", format!("{}px", padding)); + self.inner_dom.dom().set_style_or_warn("padding-top", format!("{}px", PADDING_TOP)); } } diff --git a/app/gui/view/src/open_dialog.rs b/app/gui/view/src/open_dialog.rs index 558929a548..0734628595 100644 --- a/app/gui/view/src/open_dialog.rs +++ b/app/gui/view/src/open_dialog.rs @@ -37,7 +37,7 @@ impl OpenDialog { pub fn new(app: &Application) -> Self { let logger = Logger::new("OpenDialog"); let network = frp::Network::new("OpenDialog"); - let style_watch = StyleWatchFrp::new(&app.display.scene().style_sheet); + let style_watch = StyleWatchFrp::new(&app.display.default_scene.style_sheet); let project_list = project_list::ProjectList::new(app); let file_browser = FileBrowser::new(); // Once FileBrowser will be implemented as component, it should be instantiated this way: @@ -47,7 +47,7 @@ impl OpenDialog { display_object.add_child(&project_list); display_object.add_child(&file_browser); - app.display.scene().layers.panel.add_exclusive(&display_object); + app.display.default_scene.layers.panel.add_exclusive(&display_object); use theme::application as theme_app; let project_list_width = style_watch.get_number(theme_app::project_list::width); diff --git a/app/gui/view/src/open_dialog/project_list.rs b/app/gui/view/src/open_dialog/project_list.rs index 32e5f6a571..57d29f47a7 100644 --- a/app/gui/view/src/open_dialog/project_list.rs +++ b/app/gui/view/src/open_dialog/project_list.rs @@ -94,19 +94,19 @@ impl ProjectList { display_object.add_child(&background); display_object.add_child(&caption); display_object.add_child(&list); - app.display.scene().layers.panel.add_exclusive(&display_object); + app.display.default_scene.layers.panel.add_exclusive(&display_object); caption.set_content("Open Project"); - caption.add_to_scene_layer(&app.display.scene().layers.panel_text); - list.set_label_layer(app.display.scene().layers.panel_text.id()); + caption.add_to_scene_layer(&app.display.default_scene.layers.panel_text); + list.set_label_layer(app.display.default_scene.layers.panel_text.id()); ensogl::shapes_order_dependencies! { - app.display.scene() => { + app.display.default_scene => { background -> list_view::selection; list_view::background -> background; } } - let style_watch = StyleWatchFrp::new(&app.display.scene().style_sheet); + let style_watch = StyleWatchFrp::new(&app.display.default_scene.style_sheet); let width = style_watch.get_number(theme::width); let height = style_watch.get_number(theme::height); let bar_height = style_watch.get_number(theme::bar::height); diff --git a/app/gui/view/src/project.rs b/app/gui/view/src/project.rs index 8d82255e97..70d6cbebf6 100644 --- a/app/gui/view/src/project.rs +++ b/app/gui/view/src/project.rs @@ -22,6 +22,7 @@ use ensogl::display; use ensogl::display::shape::*; use ensogl::system::web; use ensogl::system::web::dom; +use ensogl::system::web::traits::*; use ensogl::Animation; use ensogl::DEPRECATED_Animation; use ensogl_hardcoded_theme::Theme; @@ -145,7 +146,7 @@ struct Model { impl Model { fn new(app: &Application) -> Self { let logger = Logger::new("project::View"); - let scene = app.display.scene(); + let scene = &app.display.default_scene; let display_object = display::object::Instance::new(&logger); let searcher = app.new_view::(); let graph_editor = app.new_view::(); @@ -213,7 +214,7 @@ impl Model { } fn set_html_style(&self, style: &'static str) { - web::with_element_by_id_or_warn(&self.logger, "root", |root| root.set_class_name(style)); + web::document.with_element_by_id_or_warn("root", |root| root.set_class_name(style)); } fn searcher_left_top_position_when_under_node_at(position: Vector2) -> Vector2 { @@ -369,7 +370,7 @@ impl View { display::style::javascript::expose_to_window(&app.themes); - let scene = app.display.scene().clone_ref(); + let scene = app.display.default_scene.clone_ref(); let model = Model::new(app); let frp = Frp::new(); let searcher = &model.searcher.frp; diff --git a/app/gui/view/src/searcher.rs b/app/gui/view/src/searcher.rs index 7b21246ff5..f1098ac2ab 100644 --- a/app/gui/view/src/searcher.rs +++ b/app/gui/view/src/searcher.rs @@ -113,7 +113,7 @@ struct Model { impl Model { fn new(app: &Application) -> Self { - let scene = app.display.scene(); + let scene = &app.display.default_scene; let app = app.clone_ref(); let logger = Logger::new("SearcherView"); let display_object = display::object::Instance::new(&logger); @@ -126,7 +126,7 @@ impl Model { // FIXME: StyleWatch is unsuitable here, as it was designed as an internal tool for shape // system (#795) - let style = StyleWatch::new(&app.display.scene().style_sheet); + let style = StyleWatch::new(&app.display.default_scene.style_sheet); let action_list_gap_path = ensogl_hardcoded_theme::application::searcher::action_list_gap; let action_list_gap = style.get_number_or(action_list_gap_path, 0.0); list.set_label_layer(scene.layers.above_nodes_text.id()); diff --git a/app/gui/view/src/searcher/icons.rs b/app/gui/view/src/searcher/icons.rs index b60d2a22ab..d16ea0757b 100644 --- a/app/gui/view/src/searcher/icons.rs +++ b/app/gui/view/src/searcher/icons.rs @@ -10,7 +10,7 @@ use ensogl::display::shape::compound::path::path; use ensogl::display::shape::*; use ensogl::display::DomSymbol; use ensogl::system::web; -use ensogl::system::web::StyleSetter; +use ensogl::system::web::traits::*; use ensogl_hardcoded_theme::application::searcher::icons as theme; use std::f32::consts::PI; use wasm_bindgen::prelude::*; @@ -998,27 +998,24 @@ mod frame { #[wasm_bindgen] #[allow(dead_code)] pub fn entry_point_searcher_icons() { - web::forward_panic_hook_to_console(); - web::set_stack_trace_limit(); - let logger = Logger::new("Icons example"); - let app = Application::new(&web::get_html_element_by_id("root").unwrap()); + let app = Application::new("root"); ensogl_hardcoded_theme::builtin::dark::register(&app); ensogl_hardcoded_theme::builtin::light::register(&app); ensogl_hardcoded_theme::builtin::light::enable(&app); let world = app.display.clone(); mem::forget(app); - let scene = world.scene(); + let scene = &world.default_scene; mem::forget(Navigator::new(scene, &scene.camera())); // === Grid === - let grid_div = web::create_div(); - grid_div.set_style_or_panic("width", "1000px"); - grid_div.set_style_or_panic("height", "16px"); - grid_div.set_style_or_panic("background-size", "1.0px 1.0px"); - grid_div.set_style_or_panic( + let grid_div = web::document.create_div_or_panic(); + grid_div.set_style_or_warn("width", "1000px"); + grid_div.set_style_or_warn("height", "16px"); + grid_div.set_style_or_warn("background-size", "1.0px 1.0px"); + grid_div.set_style_or_warn( "background-image", "linear-gradient(to right, grey 0.05px, transparent 0.05px), linear-gradient(to bottom, grey 0.05px, transparent 0.05px)", diff --git a/app/gui/view/src/status_bar.rs b/app/gui/view/src/status_bar.rs index 1458160a9b..6400a077ef 100644 --- a/app/gui/view/src/status_bar.rs +++ b/app/gui/view/src/status_bar.rs @@ -158,7 +158,7 @@ struct Model { impl Model { fn new(app: &Application) -> Self { - let scene = app.display.scene(); + let scene = &app.display.default_scene; let logger = Logger::new("StatusBar"); let display_object = display::object::Instance::new(&logger); let root = display::object::Instance::new(&logger); @@ -174,7 +174,7 @@ impl Model { label.add_to_scene_layer(&scene.layers.panel_text); let text_color_path = theme::application::status_bar::text; - let style = StyleWatch::new(&app.display.scene().style_sheet); + let style = StyleWatch::new(&app.display.default_scene.style_sheet); let text_color = style.get_color(text_color_path); label.frp.set_color_all.emit(text_color); label.frp.set_default_color.emit(text_color); @@ -279,7 +279,7 @@ impl View { let frp = Frp::new(); let model = Model::new(app); let network = &frp.network; - let scene = app.display.scene(); + let scene = &app.display.default_scene; enso_frp::extend! { network event_added <- frp.add_event.map(f!((label) model.add_event(label))); diff --git a/app/gui/view/src/window_control_buttons.rs b/app/gui/view/src/window_control_buttons.rs index e84e37b4ee..aa4e83e46a 100644 --- a/app/gui/view/src/window_control_buttons.rs +++ b/app/gui/view/src/window_control_buttons.rs @@ -150,7 +150,7 @@ impl Model { let display_object = display::object::Instance::new(&logger); ensogl::shapes_order_dependencies! { - app.display.scene() => { + app.display.default_scene => { shape -> close::shape; shape -> fullscreen::shape; } @@ -233,7 +233,7 @@ impl View { let model = Model::new(app); let network = &frp.network; - let style = StyleWatchFrp::new(&app.display.scene().style_sheet); + let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet); let style_frp = LayoutParams::from_theme(&style); let layout_style = style_frp.flatten(network); let radius = style.get_number(theme::radius); diff --git a/app/gui/view/welcome-screen/Cargo.toml b/app/gui/view/welcome-screen/Cargo.toml index 96e5d04c0e..f5f3aa5d5b 100644 --- a/app/gui/view/welcome-screen/Cargo.toml +++ b/app/gui/view/welcome-screen/Cargo.toml @@ -10,7 +10,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] ensogl = { path = "../../../../lib/rust/ensogl" } enso-frp = { path = "../../../../lib/rust/frp" } -wasm-bindgen = { version = "0.2.58", features = [ +wasm-bindgen = { version = "0.2.78", features = [ "nightly", "serde-serialize" ] } diff --git a/app/gui/view/welcome-screen/src/lib.rs b/app/gui/view/welcome-screen/src/lib.rs index 346300e407..54f6e10ddd 100644 --- a/app/gui/view/welcome-screen/src/lib.rs +++ b/app/gui/view/welcome-screen/src/lib.rs @@ -19,14 +19,12 @@ use ensogl::application::Application; use ensogl::display; use ensogl::display::DomSymbol; use ensogl::system::web; -use ensogl::system::web::NodeInserter; -use ensogl::system::web::StyleSetter; +use ensogl::system::web::traits::*; use std::rc::Rc; -use wasm_bindgen::closure::Closure; -use wasm_bindgen::JsCast; -use web_sys::Element; -use web_sys::HtmlDivElement; -use web_sys::MouseEvent; +use web::Closure; +use web::Element; +use web::HtmlDivElement; +use web::MouseEvent; @@ -83,26 +81,23 @@ impl Deref for ClickableElement { } impl ClickableElement { - pub fn new(element: Element, logger: &Logger) -> Self { + pub fn new(element: Element) -> Self { frp::new_network! { network click <- source_(); } - let closure: ClickClosure = Closure::wrap(Box::new(f_!(click.emit(())))); - let callback = closure.as_ref().unchecked_ref(); - if element.add_event_listener_with_callback("click", callback).is_err() { - error!(logger, "Could not add event listener for ClickableElement."); - } - network.store(&Rc::new(closure)); + let closure: ClickClosure = Closure::new(f_!(click.emit(()))); + let handle = web::add_event_listener(&element, "click", closure); + network.store(&handle); Self { element, network, click } } } + // ============= // === Model === // ============= - // === CSS Styles === static STYLESHEET: &str = include_str!("../style.css"); @@ -130,42 +125,37 @@ impl Model { let side_menu = SideMenu::new(&logger); let template_cards = TemplateCards::new(&logger); - let dom = Self::create_dom(&logger, &side_menu, &template_cards); + let dom = Self::create_dom(&side_menu, &template_cards); display_object.add_child(&dom); // Use `welcome_screen` layer to lock position when panning. - app.display.scene().dom.layers.welcome_screen.manage(&dom); + app.display.default_scene.dom.layers.welcome_screen.manage(&dom); - let style = web::create_element("style"); + let style = web::document.create_element_or_panic("style"); style.set_inner_html(STYLESHEET); - dom.append_or_warn(&style, &logger); + dom.append_or_warn(&style); Self { application, logger, dom, display_object, side_menu, template_cards } } - fn create_dom( - logger: &Logger, - side_menu: &SideMenu, - template_cards: &TemplateCards, - ) -> DomSymbol { - let root = web::create_div(); + fn create_dom(side_menu: &SideMenu, template_cards: &TemplateCards) -> DomSymbol { + let root = web::document.create_div_or_panic(); root.set_class_name(css_class::TEMPLATES_VIEW_ROOT); // We explicitly enable pointer events for Welcome Screen elements. Pointer events are // disabled for all DOM layers by default. See [`DomLayers`] documentation. - root.set_style_or_warn("pointer-events", "auto", logger); + root.set_style_or_warn("pointer-events", "auto"); let container = Self::create_content_container(); - container.append_or_warn(&side_menu.model.root_dom, logger); - container.append_or_warn(&template_cards.model.root_dom, logger); - root.append_or_warn(&container, logger); + container.append_or_warn(&side_menu.model.root_dom); + container.append_or_warn(&template_cards.model.root_dom); + root.append_or_warn(&container); DomSymbol::new(&root) } fn create_content_container() -> HtmlDivElement { - let container = web::create_div(); + let container = web::document.create_div_or_panic(); container.set_class_name(css_class::CONTAINER); - container } } @@ -220,7 +210,7 @@ impl View { frp::extend! { network // === Update DOM's size so CSS styles work correctly. === - let scene_size = app.display.scene().shape().clone_ref(); + let scene_size = app.display.default_scene.shape().clone_ref(); eval scene_size ((size) model.dom.set_size(Vector2::from(*size))); } frp::extend! { network diff --git a/app/gui/view/welcome-screen/src/side_menu.rs b/app/gui/view/welcome-screen/src/side_menu.rs index 18fb67d74b..3393e02093 100644 --- a/app/gui/view/welcome-screen/src/side_menu.rs +++ b/app/gui/view/welcome-screen/src/side_menu.rs @@ -8,34 +8,33 @@ use crate::ClickableElement; use enso_frp as frp; use ensogl::system::web; -use ensogl::system::web::NodeInserter; -use web_sys::Element; +use ensogl::system::web::traits::*; -// ================ +// ============= // === Model === -// ================ +// ============= #[derive(Clone, CloneRef, Debug)] pub struct Model { logger: Logger, - pub root_dom: Element, + pub root_dom: web::Element, new_project_button: ClickableElement, - projects_list_dom: Element, + projects_list_dom: web::Element, projects: Rc>>, } impl Model { /// Constructor. pub fn new(logger: Logger) -> Self { - let root_dom = web::create_element("aside"); + let root_dom = web::document.create_element_or_panic("aside"); root_dom.set_class_name(crate::css_class::SIDE_MENU); let header = Self::create_header("Your projects"); - root_dom.append_or_warn(&header, &logger); + root_dom.append_or_warn(&header); let projects_list_dom = Self::create_projects_list(); - root_dom.append_or_warn(&projects_list_dom, &logger); - let new_project_button = Self::create_new_project_button(&logger, &projects_list_dom); + root_dom.append_or_warn(&projects_list_dom); + let new_project_button = Self::create_new_project_button(&projects_list_dom); let projects = default(); Self { logger, root_dom, projects_list_dom, projects, new_project_button } @@ -56,38 +55,38 @@ impl Model { } fn add_projects_list_entry(&self, name: &str, open_project: &frp::Any) { - let entry = Self::create_project_list_entry(name, &self.logger); + let entry = Self::create_project_list_entry(name); let network = &entry.network; frp::extend! { network open_project <+ entry.click.constant(name.to_owned()); } let new_project_button = &self.new_project_button; - self.projects_list_dom.insert_before_or_warn(&entry, new_project_button, &self.logger); + self.projects_list_dom.insert_before_or_warn(&entry, new_project_button); self.projects.borrow_mut().push(entry); } - fn create_new_project_button(logger: &Logger, projects_list: &Element) -> ClickableElement { - let element = web::create_element("li"); + fn create_new_project_button(projects_list: &web::Element) -> ClickableElement { + let element = web::document.create_element_or_panic("li"); element.set_id(crate::css_id::NEW_PROJECT); element.set_inner_html(r#"Create a new project"#); - projects_list.append_or_warn(&element, logger); - ClickableElement::new(element, logger) + projects_list.append_or_warn(&element); + ClickableElement::new(element) } - fn create_header(text: &str) -> Element { - let header = web::create_element("h2"); + fn create_header(text: &str) -> web::Element { + let header = web::document.create_element_or_panic("h2"); header.set_text_content(Some(text)); header } - fn create_projects_list() -> Element { - web::create_element("ul") + fn create_projects_list() -> web::Element { + web::document.create_element_or_panic("ul") } - fn create_project_list_entry(project_name: &str, logger: &Logger) -> ClickableElement { - let element = web::create_element("li"); + fn create_project_list_entry(project_name: &str) -> ClickableElement { + let element = web::document.create_element_or_panic("li"); element.set_inner_html(&format!(r#" {}"#, project_name)); - ClickableElement::new(element, logger) + ClickableElement::new(element) } } diff --git a/app/gui/view/welcome-screen/src/template_cards.rs b/app/gui/view/welcome-screen/src/template_cards.rs index 6f558c43c1..6a1083b741 100644 --- a/app/gui/view/welcome-screen/src/template_cards.rs +++ b/app/gui/view/welcome-screen/src/template_cards.rs @@ -9,11 +9,10 @@ use crate::ClickableElement; use enso_frp as frp; use ensogl::system::web; -use ensogl::system::web::AttributeSetter; -use ensogl::system::web::NodeInserter; -use wasm_bindgen::JsCast; -use web_sys::Element; -use web_sys::HtmlDivElement; +use ensogl::system::web::traits::*; +use web::Element; +use web::HtmlDivElement; +use web::JsCast; @@ -88,16 +87,16 @@ pub struct Model { impl Model { /// Constructor. pub fn new(logger: Logger, open_template: &frp::Any) -> Self { - let root_dom = web::create_element("main"); + let root_dom = web::document.create_element_or_panic("main"); root_dom.set_class_name(crate::css_class::CONTENT); - let templates = web::create_div(); + let templates = web::document.create_div_or_panic(); let header = Self::create_header("Templates"); - templates.append_or_warn(&header, &logger); + templates.append_or_warn(&header); - let (cards_dom, cards) = Self::create_cards(&logger); - templates.append_or_warn(&cards_dom, &logger); - root_dom.append_or_warn(&templates, &logger); + let (cards_dom, cards) = Self::create_cards(); + templates.append_or_warn(&cards_dom); + root_dom.append_or_warn(&templates); let model = Self { logger, root_dom, cards: Rc::new(cards) }; model.setup_event_listeners(open_template); @@ -116,58 +115,54 @@ impl Model { } fn create_header(content: &str) -> Element { - let header = web::create_element("h2"); + let header = web::document.create_element_or_panic("h2"); header.set_text_content(Some(content)); header } /// Create main content, a set of cards. - fn create_cards(logger: &Logger) -> (HtmlDivElement, Vec) { + fn create_cards() -> (HtmlDivElement, Vec) { let mut cards = Vec::new(); - let dom = web::create_div(); + let dom = web::document.create_div_or_panic(); dom.set_class_name(crate::css_class::CARDS); - let row1 = Self::create_row(&[CARD_SPREADSHEETS, CARD_GEO], &mut cards, logger); - dom.append_or_warn(&row1, logger); + let row1 = Self::create_row(&[CARD_SPREADSHEETS, CARD_GEO], &mut cards); + dom.append_or_warn(&row1); - let row2 = Self::create_row(&[CARD_VISUALIZE], &mut cards, logger); - dom.append_or_warn(&row2, logger); + let row2 = Self::create_row(&[CARD_VISUALIZE], &mut cards); + dom.append_or_warn(&row2); (dom, cards) } - fn create_row( - definitions: &[CardDefinition], - cards: &mut Vec, - logger: &Logger, - ) -> HtmlDivElement { - let row = web::create_div(); + fn create_row(definitions: &[CardDefinition], cards: &mut Vec) -> HtmlDivElement { + let row = web::document.create_div_or_panic(); row.set_class_name(crate::css_class::ROW); for definition in definitions { - let card = Self::create_card(definition, logger); - row.append_or_warn(&card.element, logger); + let card = Self::create_card(definition); + row.append_or_warn(&card.element); cards.push(card.clone()); } row } /// Helper to create a single card DOM from provided definition. - fn create_card(definition: &CardDefinition, logger: &Logger) -> Card { - let card = web::create_div(); + fn create_card(definition: &CardDefinition) -> Card { + let card = web::document.create_div_or_panic(); card.set_class_name(&format!("{} {}", crate::css_class::CARD, definition.class)); if let Some(src) = definition.background_image_url { - let img = web::create_element("img"); - img.set_attribute_or_warn("src", src, logger); - card.append_or_warn(&img, logger); + let img = web::document.create_element_or_panic("img"); + img.set_attribute_or_warn("src", src); + card.append_or_warn(&img); } - let card_header = web::create_element("h3"); + let card_header = web::document.create_element_or_panic("h3"); card_header.set_text_content(Some(definition.header)); - card.append_or_warn(&card_header, logger); - let text_content = web::create_element("p"); + card.append_or_warn(&card_header); + let text_content = web::document.create_element_or_panic("p"); text_content.set_text_content(Some(definition.content)); - card.append_or_warn(&text_content, logger); + card.append_or_warn(&text_content); - let clickable_element = ClickableElement::new(card.unchecked_into(), logger); + let clickable_element = ClickableElement::new(card.unchecked_into()); Card { clickable_element, template_name: definition.template } } } diff --git a/app/ide-desktop/lib/content/src/index.ts b/app/ide-desktop/lib/content/src/index.ts index da16957578..39e734a9d3 100644 --- a/app/ide-desktop/lib/content/src/index.ts +++ b/app/ide-desktop/lib/content/src/index.ts @@ -188,7 +188,7 @@ function printScamWarning() { 'copy-paste something here, it is a scam and will give them access to your ' + 'account and data.' let msg2 = - 'See https://github.com/enso-org/enso/tree/develop/gui/docs/security/selfxss.md for more ' + + 'See https://github.com/enso-org/enso/blob/develop/docs/security/selfxss.md for more ' + 'information.' console.log('%cStop!', headerCSS1) console.log('%cYou may be victim of a scam!', headerCSS2) diff --git a/app/ide-desktop/lib/log-server/README.md b/app/ide-desktop/lib/log-server/README.md deleted file mode 100644 index 835056c9a7..0000000000 --- a/app/ide-desktop/lib/log-server/README.md +++ /dev/null @@ -1,7 +0,0 @@ -A simple http server that takes POST requests for path "/" on localhost and -writes their body to a new file in the "log" subdirectory of the current working -directory - -To start the server, make sure that all necessary packages are installed -(through Lerna) and call `npm run start`. To learn more about the options and -their default values, use the `--help` option. diff --git a/app/ide-desktop/lib/log-server/package.json b/app/ide-desktop/lib/log-server/package.json deleted file mode 100644 index 5a842edecf..0000000000 --- a/app/ide-desktop/lib/log-server/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "author": { - "name": "Enso Team", - "email": "contact@luna-lang.org" - }, - "homepage": "https://github.com/luna/ide", - "repository": { - "type": "git", - "url": "git@github.com:luna/ide.git" - }, - "bugs": { - "url": "https://github.com/luna/ide/issues" - }, - "name": "enso-studio-logging-service", - "description": "A logging service that can receive crash reports from the Enso IDE.", - "main": "server.js", - "dependencies": { - "express": "^4.17.1", - "uuid": "^8.3.1", - "yargs": "^16.1.1" - }, - "scripts": { - "start": "node server.js", - "test": "mocha" - }, - "devDependencies": { - "axios": "^0.21.0", - "mocha": "^8.2.1", - "mock-fs": "^4.13.0" - } -} diff --git a/app/ide-desktop/lib/log-server/server.js b/app/ide-desktop/lib/log-server/server.js deleted file mode 100644 index b3b0c9c9f8..0000000000 --- a/app/ide-desktop/lib/log-server/server.js +++ /dev/null @@ -1,119 +0,0 @@ -// This module implements a simple http server that listens for crash reports as POST requests and -// writes them to disk. To start the server, call `startServer(port)` or execute this file through -// Node.js. More information can be foun in the README.md. - -const express = require('express') -const fs = require('fs') -const uuid = require('uuid') -const yargs = require('yargs') - -const defaultPort = require('../../config.js').defaultLogServerPort - -module.exports = { - startServer, -} - -function main(argv) { - startServer(parse_args(argv).port) -} - -// ================= -// === Constants === -// ================= - -const httpStatusCodes = { - noContent: 204, - forbidden: 403, - badRequest: 400, - internalServerError: 500, -} - -// ======================== -// === Argument Parsing === -// ======================== - -function parse_args(argv) { - return yargs(argv) - .option('port', { - alias: 'p', - description: - 'The number of the port that this server will listen on. ' + - 'If the the number is 0 then an arbitrary free port will be chosen.', - type: 'number', - default: defaultPort, - }) - .help() - .alias('help', 'h').argv -} - -// ============== -// === Server === -// ============== - -function startServer(port) { - const app = express() - app.use(express.text()) - - app.post('/', async (req, res) => { - if ( - typeof req.headers.origin === 'undefined' || - new URL(req.headers.origin).hostname !== 'localhost' - ) { - res.sendStatus(httpStatusCodes.forbidden) - } else if (typeof req.body !== 'string') { - res.sendStatus(httpStatusCodes.badRequest) - } else { - try { - await writeLog(req.body) - console.log(`Saved log from origin ${req.headers.origin}`) - res.sendStatus(httpStatusCodes.noContent) - } catch (e) { - console.error('Could not write log file:\n' + e.message) - res.sendStatus(httpStatusCodes.internalServerError) - } - } - }) - - const server = app.listen(port, 'localhost') - server.on('listening', function () { - console.log(`Logging service listening at port ${server.address().port}`) - }) - return server -} - -// ================== -// === File Utils === -// ================== - -/** - * Writes message to a new file in the log sub directory. - * The file name is composed of the UTC time and date and a V4 UUID to guarantee uniqueness. - */ -async function writeLog(message) { - const dir = 'log' - const file = `${timestamp()}__${uuid.v4()}` - await fs.promises.mkdir(dir, { recursive: true }) - await fs.promises.writeFile(`${dir}/${file}`, message) -} - -/** - * Returns the current UTC date and time in the format "yyy-MM-dd_HH:mm:ss.". - */ -function timestamp() { - const d = new Date() - - const year = d.getUTCFullYear().toString() - const month = d.getUTCMonth().toString().padStart(2, '0') - const day = d.getUTCDate().toString().padStart(2, '0') - - const hour = d.getUTCHours().toString().padStart(2, '0') - const minute = d.getUTCMinutes().toString().padStart(2, '0') - const second = d.getUTCSeconds().toString().padStart(2, '0') - - return `${year}-${month}-${day}_${hour}:${minute}:${second}` -} - -if (require.main === module) { - const command_line_args = process.argv.slice(2) - main(command_line_args) -} diff --git a/app/ide-desktop/lib/log-server/test/test.js b/app/ide-desktop/lib/log-server/test/test.js deleted file mode 100644 index 45e0cda16e..0000000000 --- a/app/ide-desktop/lib/log-server/test/test.js +++ /dev/null @@ -1,80 +0,0 @@ -const assert = require('assert') -const axios = require('axios').default -const fs = require('fs') -const mockFs = require('mock-fs') -const path = require('path') - -const { startServer } = require('../server') - -describe('Logging Server', function () { - let server - let serverUrl - - const dummyMessage = '' - const goodConfig = { - headers: { - 'Content-Type': 'text/plain', - Origin: 'http://localhost/', - }, - } - const wrongOriginConfig = { - headers: { - 'Content-Type': 'text/plain', - Origin: 'http://attacker/', - }, - } - const wrongContentTypeConfig = { - headers: { - 'Content-Type': 'image/jpeg', - Origin: 'http://localhost/', - }, - } - - beforeEach(function (done) { - // The server relies on some package that imports from `raw-body` at runtime. - // Therefore, we need to include this directory in our mocked fs. - const rawBodyDir = path.dirname(require.resolve('raw-body')) - - // We mock the file system so the server does not actually write logs to disk. - mockFs({ - [rawBodyDir]: mockFs.load(rawBodyDir), - }) - - const port = 0 // Choose an arbitrary available port - server = startServer(port) - server.on('listening', function () { - serverUrl = `http://localhost:${server.address().port}/` - done() - }) - }) - - afterEach(function () { - server.close() - mockFs.restore() - }) - - it('should write the body of valid requests to a file', async function () { - await axios.post(serverUrl, dummyMessage, goodConfig) - const log_files = fs.readdirSync('log/') - assert.strictEqual(log_files.length, 1) - assert.strictEqual(fs.readFileSync(`log/${log_files[0]}`).toString(), dummyMessage) - }) - - it('should reject requests from origins other than localhost', async function () { - let req = axios.post(serverUrl, '', wrongOriginConfig) - await assert.rejects(req, 'Error: Request failed with status code 403') - assert(!fs.existsSync('log/')) - }) - - it('should keep running after requests', async function () { - await Promise.allSettled([ - axios.post(serverUrl, '', goodConfig), - axios.post(serverUrl, '', wrongOriginConfig), - axios.post(serverUrl, '', wrongContentTypeConfig), - ]) - - await axios.post(serverUrl, dummyMessage, goodConfig) - const log_files = fs.readdirSync('log/') - assert(log_files.some(file => fs.readFileSync(`log/${file}`).toString() === dummyMessage)) - }) -}) diff --git a/app/gui/docs/security/selfxss.md b/docs/security/selfxss.md similarity index 100% rename from app/gui/docs/security/selfxss.md rename to docs/security/selfxss.md diff --git a/integration-test/src/lib.rs b/integration-test/src/lib.rs index cd2828d2e3..06997e248d 100644 --- a/integration-test/src/lib.rs +++ b/integration-test/src/lib.rs @@ -21,9 +21,8 @@ use enso_frp::future::EventOutputExt; use enso_gui::executor::web::EventLoopExecutor; use enso_gui::initializer::setup_global_executor; use enso_gui::Ide; +use enso_web::traits::*; use enso_web::HtmlDivElement; -use enso_web::NodeInserter; -use enso_web::StyleSetter; use ensogl::application::Application; /// Reexports of commonly-used structures, methods and traits. @@ -57,12 +56,11 @@ impl IntegrationTest { /// Initializes the executor and `Ide` structure and returns new Fixture. pub async fn setup() -> Self { - enso_web::forward_panic_hook_to_error(); let executor = setup_global_executor(); - let root_div = enso_web::create_div(); + let root_div = enso_web::document.create_div_or_panic(); root_div.set_id("root"); - root_div.set_style_or_panic("display", "none"); - enso_web::body().append_or_panic(&root_div); + root_div.set_style_or_warn("display", "none"); + enso_web::document.body_or_panic().append_or_warn(&root_div); let initializer = enso_gui::ide::Initializer::new(default()); let ide = initializer.start().await.expect("Failed to initialize the application."); @@ -72,7 +70,7 @@ impl IntegrationTest { fn set_screen_size(app: &Application) { let (screen_width, screen_height) = Self::SCREEN_SIZE; - app.display.scene().layers.iter_sublayers_and_masks_nested(|layer| { + app.display.default_scene.layers.iter_sublayers_and_masks_nested(|layer| { layer.camera().set_screen(screen_width, screen_height) }); } diff --git a/integration-test/tests/graph_editor.rs b/integration-test/tests/graph_editor.rs index a931b39cc3..8b9d9ab5f8 100644 --- a/integration-test/tests/graph_editor.rs +++ b/integration-test/tests/graph_editor.rs @@ -75,7 +75,7 @@ async fn zooming() { let test = IntegrationTestOnNewProject::setup().await; let project = test.project_view(); let graph_editor = test.graph_editor(); - let camera = test.ide.ensogl_app.display.scene().layers.main.camera(); + let camera = test.ide.ensogl_app.display.default_scene.layers.main.camera(); let navigator = &graph_editor.model.navigator; let zoom_on_center = |amount: f32| ZoomEvent { focus: Vector2(0.0, 0.0), amount }; @@ -136,7 +136,7 @@ async fn adding_node_with_add_node_button() { assert_eq!(graph_editor.model.nodes.all.len(), INITIAL_NODE_COUNT + 2); // If there is a free space, the new node is created in the center of screen. - let camera = test.ide.ensogl_app.display.scene().layers.main.camera(); + let camera = test.ide.ensogl_app.display.default_scene.layers.main.camera(); camera.mod_position_xy(|pos| pos + Vector2(1000.0, 1000.0)); let wait_for_update = Duration::from_millis(500); sleep(wait_for_update).await; @@ -145,8 +145,8 @@ async fn adding_node_with_add_node_button() { assert!(node_source.is_none()); assert_eq!(graph_editor.model.nodes.all.len(), INITIAL_NODE_COUNT + 3); let node_position = graph_editor.model.get_node_position(node_id).expect("Node was not added"); - let center_of_screen = - test.ide.ensogl_app.display.scene().screen_to_scene_coordinates(Vector3(0.0, 0.0, 0.0)); + let scene = &test.ide.ensogl_app.display.default_scene; + let center_of_screen = scene.screen_to_scene_coordinates(Vector3(0.0, 0.0, 0.0)); assert_abs_diff_eq!(node_position.x, center_of_screen.x, epsilon = 10.0); assert_abs_diff_eq!(node_position.y, center_of_screen.y, epsilon = 10.0); } diff --git a/lib/rust/callback/src/lib.rs b/lib/rust/callback/src/lib.rs index 42ec9f160a..4b01db741b 100644 --- a/lib/rust/callback/src/lib.rs +++ b/lib/rust/callback/src/lib.rs @@ -1,47 +1,66 @@ -#![feature(trait_alias)] +//! Definitions of a callback registry – utility allowing attaching and running attached functions. -//! Definitions of callback handling utilities. +// === Linter configuration === +#![warn(missing_copy_implementations)] +#![warn(missing_debug_implementations)] +#![warn(missing_docs)] +#![warn(trivial_casts)] +#![warn(trivial_numeric_casts)] +#![warn(unsafe_code)] +#![warn(unused_import_braces)] +#![warn(unused_qualifications)] +// === Features === +#![feature(trait_alias)] +#![feature(unboxed_closures)] +#![feature(fn_traits)] +#![feature(unsize)] use enso_prelude::*; use std::any::TypeId; +use std::marker::Unsize; -// ================ -// === Callback === -// ================ +// ============================== +// === Popular Callback Types === +// ============================== -/// Immutable callback type. -pub trait CallbackFn = Fn() + 'static; +pub use callback_types::*; -/// Immutable callback object. -pub type Callback = Box; +/// Popular callback types. These are aliases for static [`Fn`] and [`FnMut`] with different amount +/// of arguments. The names directly correspond to the [`::registry`] namespace. For example, +/// the [`::registry::CopyMut3`] is a callback registry for [`CopyMut3`] callbacks. +#[allow(missing_docs)] +mod callback_types { + pub trait NoArgs = 'static + Fn(); + pub trait MutNoArgs = 'static + FnMut(); -/// Callback object smart constructor. -#[allow(non_snake_case)] -pub fn Callback(f: F) -> Callback { - Box::new(f) + pub trait Copy1 = 'static + Fn(T1); + pub trait Copy2 = 'static + Fn(T1, T2); + pub trait Copy3 = 'static + Fn(T1, T2, T3); + pub trait Copy4 = 'static + Fn(T1, T2, T3, T4); + pub trait Copy5 = 'static + Fn(T1, T2, T3, T4, T5); + + pub trait Ref1 = 'static + Fn(&T1); + pub trait Ref2 = 'static + Fn(&T1, &T2); + pub trait Ref3 = 'static + Fn(&T1, &T2, &T3); + pub trait Ref4 = 'static + Fn(&T1, &T2, &T3, &T4); + pub trait Ref5 = 'static + Fn(&T1, &T2, &T3, &T4, &T5); + + pub trait CopyMut1 = 'static + FnMut(T1); + pub trait CopyMut2 = 'static + FnMut(T1, T2); + pub trait CopyMut3 = 'static + FnMut(T1, T2, T3); + pub trait CopyMut4 = 'static + FnMut(T1, T2, T3, T4); + pub trait CopyMut5 = 'static + FnMut(T1, T2, T3, T4, T5); + + pub trait RefMut1 = 'static + FnMut(&T1); + pub trait RefMut2 = 'static + FnMut(&T1, &T2); + pub trait RefMut3 = 'static + FnMut(&T1, &T2, &T3); + pub trait RefMut4 = 'static + FnMut(&T1, &T2, &T3, &T4); + pub trait RefMut5 = 'static + FnMut(&T1, &T2, &T3, &T4, &T5); } -/// Mutable callback type. -pub trait CallbackMutFn = FnMut() + 'static; - -/// Mutable callback object. -pub type CallbackMut = Box; - -/// Mutable callback type with one parameter. -pub trait CallbackMut1Fn = FnMut(&T) + 'static; - -/// Mutable callback object with one parameter. -pub type CallbackMut1 = Box>; - -/// Mutable callback type with one parameter. -pub trait CopyCallbackMut1Fn = FnMut(T) + 'static; - -/// Mutable callback object with one parameter. -pub type CopyCallbackMut1 = Box>; - // ============== @@ -49,27 +68,21 @@ pub type CopyCallbackMut1 = Box>; // ============== /// Handle to a callback. When the handle is dropped, the callback is removed. -#[derive(Clone, CloneRef, Debug)] +#[derive(Clone, CloneRef, Debug, Default)] pub struct Handle { - rc: Rc>, + is_invalidated: Rc>, } impl Handle { - /// Constructor. - pub fn new() -> Self { - let rc = Rc::new(Cell::new(true)); - Self { rc } - } - /// Create guard for this handle. pub fn guard(&self) -> Guard { - Guard { weak: Rc::downgrade(&self.rc) } + Guard { weak: Rc::downgrade(&self.is_invalidated) } } /// Invalidates all handles. Even if there exist some active handles, the callback will not be /// run anymore after performing this operation. pub fn invalidate_all_handles(&self) { - self.rc.set(false) + self.is_invalidated.set(true) } /// Forget the handle. Warning! You would not be able to stop the callback after performing this @@ -79,12 +92,6 @@ impl Handle { } } -impl Default for Handle { - fn default() -> Self { - Self::new() - } -} - // ============= @@ -100,10 +107,75 @@ pub struct Guard { impl Guard { /// Checks if the handle is still valid. pub fn exists(&self) -> bool { - match self.weak.upgrade() { - None => false, - Some(t) => t.get(), - } + self.weak.upgrade().map_or(false, |t| !t.get()) + } +} + + + +// ================== +// === RegistryFn === +// ================== + +/// An abstraction for a [`Fn`] functions kept in the [`Registry`]. It is used to unify the handling +/// of [`Fn`] and [`FnMut`] functions. They are kept either in this structure or in the +/// [`RegistryFnMut`] one, both exposing the same API. +#[derive(Derivative)] +#[derivative(Clone(bound = ""))] +#[derivative(Debug(bound = ""))] +pub struct RegistryFn { + #[derivative(Debug = "ignore")] + function: Rc, +} + +/// An abstraction for a [`FnMut`] functions kept in the [`Registry`]. See the documentation of +/// [`RegistryFn`] to learn more. +#[derive(Derivative)] +#[derivative(Clone(bound = ""))] +#[derivative(Debug(bound = ""))] +pub struct RegistryFnMut { + #[derivative(Debug = "ignore")] + function: Rc>, +} + +/// Constructor abstraction for [`RegistryFn`] and [`RegistryFnMut`]. +#[allow(missing_docs)] +pub trait RegistryFnNew { + type InternalF: ?Sized; + fn new + 'static>(f: C) -> Self; +} + +/// Call abstraction for [`RegistryFn`] and [`RegistryFnMut`]. +#[allow(missing_docs)] +pub trait RegistryFnCall { + fn call(&self, args: Args); +} + +impl RegistryFnNew for RegistryFn { + type InternalF = F; + fn new + 'static>(f: C) -> Self { + let function = Rc::new(f); + Self { function } + } +} + +impl RegistryFnNew for RegistryFnMut { + type InternalF = F; + fn new + 'static>(f: C) -> Self { + let function = Rc::new(RefCell::new(f)); + Self { function } + } +} + +impl> RegistryFnCall for RegistryFn { + fn call(&self, args: Args) { + (&*self.function).call(args); + } +} + +impl> RegistryFnCall for RegistryFnMut { + fn call(&self, args: Args) { + (&mut *self.function.borrow_mut()).call_mut(args); } } @@ -113,117 +185,32 @@ impl Guard { // === Registry === // ================ -/// Registry gathering callbacks. Each registered callback is assigned with a handle. Callback and -/// handle lifetimes are strictly connected. As soon a handle is dropped, the callback is removed -/// as well. -#[derive(Default, Derivative)] -#[derivative(Debug)] -pub struct Registry { - #[derivative(Debug = "ignore")] - callback_list: Vec<(Guard, CallbackMut)>, -} - -impl Registry { - /// Adds new callback and returns a new handle for it. - pub fn add(&mut self, callback: F) -> Handle { - let callback = Box::new(callback); - let handle = Handle::new(); - let guard = handle.guard(); - self.callback_list.push((guard, callback)); - handle - } - - ///Checks whether there are any callbacks registered. - pub fn is_empty(&self) -> bool { - self.callback_list.is_empty() - } - - /// Fires all registered callbacks and removes the ones which got dropped. - pub fn run_all(&mut self) { - self.clear_unused_callbacks(); - self.callback_list.iter_mut().for_each(move |(_, callback)| callback()); - } - - /// Checks all registered callbacks and removes the ones which got dropped. - fn clear_unused_callbacks(&mut self) { - self.callback_list.retain(|(guard, _)| guard.exists()); - } -} - - - -// ========================= -// === SharedRegistryMut === -// ========================= - -/// Registry gathering callbacks implemented with internal mutability pattern. Each registered -/// callback is assigned with a handle. Callback and handle lifetimes are strictly connected. As -/// soon a handle is dropped, the callback is removed as well. -#[derive(Clone, CloneRef, Default, Derivative)] -#[derivative(Debug)] -#[allow(clippy::type_complexity)] -pub struct SharedRegistryMut { - #[derivative(Debug = "ignore")] - callback_list: Rc>)>>>, -} - -impl SharedRegistryMut { - /// Adds new callback and returns a new handle for it. - pub fn add(&self, callback: F) -> Handle { - let callback = Rc::new(RefCell::new(callback)); - let handle = Handle::new(); - let guard = handle.guard(); - self.callback_list.borrow_mut().push((guard, callback)); - handle - } - - ///Checks whether there are any callbacks registered. - pub fn is_empty(&self) -> bool { - self.callback_list.borrow().is_empty() - } - - /// Fires all registered callbacks and removes the ones which got dropped. The implementation is - /// safe - you are allowed to change the registry while a callback is running. - pub fn run_all(&self) { - self.clear_unused_callbacks(); - let callbacks = self.callback_list.borrow().clone(); - callbacks.iter().for_each(|(_, callback)| (&mut *callback.borrow_mut())()); - } - - /// Checks all registered callbacks and removes the ones which got dropped. - fn clear_unused_callbacks(&self) { - self.callback_list.borrow_mut().retain(|(guard, _)| guard.exists()); - } -} - - -// ========================== -// === SharedRegistryMut1 === -// ========================== - -/// Registry gathering callbacks implemented with internal mutability pattern. Each registered -/// callback is assigned with a handle. Callback and handle lifetimes are strictly connected. As -/// soon a handle is dropped, the callback is removed as well. +/// The main callback registry structure. The [`F`] parameter is either instantiated to +/// [`RegistryFn>`] or to [`RegistryFnMut>`]. See the generated aliases +/// for common types below, in the [`registry`] module. #[derive(CloneRef, Derivative)] #[derivative(Clone(bound = ""))] #[derivative(Debug(bound = ""))] #[derivative(Default(bound = ""))] -#[allow(clippy::type_complexity)] -pub struct SharedRegistryMut1 { +#[allow(missing_docs)] +pub struct Registry { #[derivative(Debug = "ignore")] - callback_list: Rc>>)>>>, + callback_list: Rc>>, } -impl SharedRegistryMut1 { +impl Registry { /// Constructor. pub fn new() -> Self { Self::default() } - /// Adds new callback and returns a new handle for it. - pub fn add>(&self, callback: F) -> Handle { - let callback = Rc::new(RefCell::new(callback)); - let handle = Handle::new(); + /// Add a new callback. Returns a new [`Handle`], which dropped, will unregister the callback. + pub fn add(&self, callback: C) -> Handle + where + F: RegistryFnNew, + C: Unsize<::InternalF> + 'static, { + let callback = F::new(callback); + let handle = Handle::default(); let guard = handle.guard(); self.callback_list.borrow_mut().push((guard, callback)); handle @@ -234,103 +221,164 @@ impl SharedRegistryMut1 { self.callback_list.borrow().is_empty() } - /// Fires all registered callbacks and removes the ones which got dropped. The implementation is - /// safe - you are allowed to change the registry while a callback is running. - pub fn run_all(&self, t: &T) { - self.clear_unused_callbacks(); - let callbacks = self.callback_list.borrow().clone(); - callbacks.iter().for_each(|(_, callback)| (&mut *callback.borrow_mut())(t)); - } - /// Checks all registered callbacks and removes the ones which got dropped. fn clear_unused_callbacks(&self) { self.callback_list.borrow_mut().retain(|(guard, _)| guard.exists()); } -} - - -// ================= -// === Registry1 === -// ================= - -/// Registry gathering callbacks. Each registered callback is assigned with a handle. Callback and -/// handle lifetimes are strictly connected. As soon a handle is dropped, the callback is removed -/// as well. -#[derive(Derivative)] -#[derivative(Debug, Default(bound = ""))] -pub struct Registry1 { - #[derivative(Debug = "ignore")] - callback_list: Vec<(Guard, CallbackMut1)>, -} - -impl Registry1 { - /// Adds new callback and returns a new handle for it. - pub fn add>(&mut self, callback: F) -> Handle { - let callback = Box::new(callback); - let handle = Handle::new(); - let guard = handle.guard(); - self.callback_list.push((guard, callback)); - handle - } - - ///Checks whether there are any callbacks registered. - pub fn is_empty(&self) -> bool { - self.callback_list.is_empty() - } - - /// Fires all registered callbacks and removes the ones which got dropped. - pub fn run_all(&mut self, t: &T) { + /// Fires all registered callbacks and removes the ones which got dropped. The implementation + /// is safe - you are allowed to change the registry while a callback is running. + pub fn run_all_with_args(&self, args: Args) + where F: Clone + RegistryFnCall { self.clear_unused_callbacks(); - self.callback_list.iter_mut().for_each(move |(_, callback)| callback(t)); + // The clone is performed in order for the callbacks to be able to register new ones. + let callbacks = self.callback_list.borrow().clone(); + callbacks.iter().for_each(move |(_, callback)| { + callback.call(args); + }); } +} - /// Checks all registered callbacks and removes the ones which got dropped. - fn clear_unused_callbacks(&mut self) { - self.callback_list.retain(|(guard, _)| guard.exists()); - } +/// Aliases for common [`Registry`] instantiations. The names directly correspond to the +/// [`::callback_types`] namespace. For example, the [`::registry::CopyMut3`] is a callback registry +/// for [`CopyMut3`] callbacks. +/// +/// The used naming convention is `("Copy" | "Ref") ("Mut" | "") ("NoArgs" | )`: +/// - The "Copy" prefix means that arguments are passed as copies. You should use this registry type +/// when working with callbacks consuming primitive types, such as [`usize`]. +/// - The "Ref" prefix means that the arguments are passed as references. You should use this +/// registry type when working with callbacks consuming complex types, not implementing [`Copy`]. +/// - The registry types which contain the "Mut" part accept `FnMut` functions. The rest +/// accepts the `Fn` ones. +/// - The arg number is a number from 1 to 5 indicating the number of arguments callbacks accept. +/// +/// For example: +/// - The [`CopyMut2`] registry accepts callbacks in a form of [`FnMut(T1,T2)`], where both [`T1`] +/// and [`T2`] will be passed as copies. +/// - The [`Ref1`] registry accepts callbacks in a form of [`Fn(&T1)`]. +/// - The [`NoArgs`] registry is a registry for [`Fn()`] functions. +/// - The [`MutNoArgs`] registry is a registry for [`FnMut()`] functions. +/// +/// It is possible to define a registry which uses callbacks whose arguments are a mix of references +/// and copy-able values. However, such types need to be defined manually and are not provided by +/// the alias set below. +#[allow(missing_docs)] +pub mod registry { + use super::*; + + pub type NoArgs = Registry>; + pub type MutNoArgs = Registry>; + + pub type Copy1 = Registry>; + pub type Copy2 = Registry>; + pub type Copy3 = Registry>; + pub type Copy4 = Registry>; + pub type Copy5 = Registry>; + + pub type Ref1 = Registry>; + pub type Ref2 = Registry>; + pub type Ref3 = Registry>; + pub type Ref4 = Registry>; + pub type Ref5 = Registry>; + + pub type CopyMut1 = Registry>; + pub type CopyMut2 = Registry>; + pub type CopyMut3 = Registry>; + pub type CopyMut4 = Registry>; + pub type CopyMut5 = Registry>; + + pub type RefMut1 = Registry>; + pub type RefMut2 = Registry>; + pub type RefMut3 = Registry>; + pub type RefMut4 = Registry>; + pub type RefMut5 = + Registry>; } -// ==================== -// === CopyRegistry === -// ==================== +// ====================== +// === RegistryRunner === +// ====================== -/// Specialized version of `Registry` for arguments implementing `Copy`. Passing copy-able elements -/// as values is more performant than by reference. -#[derive(Derivative)] -#[derivative(Debug(bound = ""), Default(bound = ""))] -pub struct CopyRegistry1 { - #[derivative(Debug = "ignore")] - callback_list: Vec<(Guard, CopyCallbackMut1)>, +/// Generator of traits allowing the usage of a [`run_all`] function. It is an alias for the +/// [`Registry::run_all_with_args`] where arguments are passed in a convenient way, instead than in +/// a tuple. +macro_rules! gen_runner_traits { + ($name:ident, $ref_name:ident, ($($arg:ident),*)) => { + #[allow(non_snake_case)] + pub trait $name { + $( type $arg; )* + fn run_all(&self, $($arg : Self::$arg),*); + } + + #[allow(non_snake_case)] + pub trait $ref_name { + $( type $arg; )* + fn run_all(&self, $($arg : &Self::$arg),*); + } + }; } -impl CopyRegistry1 { - /// Adds new callback and returns a new handle for it. - pub fn add>(&mut self, callback: F) -> Handle { - let callback = Box::new(callback); - let handle = Handle::new(); - let guard = handle.guard(); - self.callback_list.push((guard, callback)); - handle +macro_rules! gen_runner { + ($name:ident, $ref_name:ident, $data:ident, $data_ref:ident, <$($arg:ident),*>) => { + #[allow(non_snake_case)] + impl<$($arg: Copy),*> $name for registry::$data<$($arg),*> { + $(type $arg = $arg;)* + fn run_all(&self, $($arg : Self::$arg),*) { + self.run_all_with_args(($($arg),*,)) + } + } + + #[allow(non_snake_case)] + impl<$($arg),*> $ref_name for registry::$data_ref<$($arg),*> { + $(type $arg = $arg;)* + fn run_all(&self, $($arg : &Self::$arg),*) { + self.run_all_with_args(($($arg),*,)) + } + } + }; +} + + + +// ============== +// === Traits === +// ============== + +/// All trait types that should be imported to the scope when using callback registry. +#[allow(missing_docs)] +#[allow(non_camel_case_types)] +pub mod traits { + use super::*; + + pub trait RegistryRunner0 { + fn run_all(&self); } - ///Checks whether there are any callbacks registered. - pub fn is_empty(&self) -> bool { - self.callback_list.is_empty() + impl RegistryRunner0 for registry::MutNoArgs { + fn run_all(&self) { + self.run_all_with_args(()) + } } - /// Fires all registered callbacks and removes the ones which got dropped. - pub fn run_all(&mut self, t: T) { - self.clear_unused_callbacks(); - self.callback_list.iter_mut().for_each(move |(_, callback)| callback(t)); + impl RegistryRunner0 for registry::NoArgs { + fn run_all(&self) { + self.run_all_with_args(()) + } } - /// Checks all registered callbacks and removes the ones which got dropped. - fn clear_unused_callbacks(&mut self) { - self.callback_list.retain(|(guard, _)| guard.exists()); - } + gen_runner_traits!(RegistryRunner1, RegistryRunnerRef1, (T1)); + gen_runner_traits!(RegistryRunner2, RegistryRunnerRef2, (T1, T2)); + gen_runner_traits!(RegistryRunner3, RegistryRunnerRef3, (T1, T2, T3)); + gen_runner_traits!(RegistryRunner4, RegistryRunnerRef4, (T1, T2, T3, T4)); + gen_runner_traits!(RegistryRunner5, RegistryRunnerRef5, (T1, T2, T3, T4, T5)); + + gen_runner!(RegistryRunner1, RegistryRunnerRef1, CopyMut1, RefMut1, ); + gen_runner!(RegistryRunner2, RegistryRunnerRef2, CopyMut2, RefMut2, ); + gen_runner!(RegistryRunner3, RegistryRunnerRef3, CopyMut3, RefMut3, ); + gen_runner!(RegistryRunner4, RegistryRunnerRef4, CopyMut4, RefMut4, ); + gen_runner!(RegistryRunner5, RegistryRunnerRef5, CopyMut5, RefMut5, ); } @@ -360,17 +408,18 @@ impl DynEvent { #[derivative(Debug)] pub struct DynEventDispatcher { #[derivative(Debug = "ignore")] - listener_map: HashMap)>>, + #[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 { + 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::new(); + let handle = Handle::default(); let guard = handle.guard(); let listeners = self.listener_map.entry(type_id).or_insert_with(default); listeners.push((guard, callback)); diff --git a/lib/rust/ensogl/component/button/src/lib.rs b/lib/rust/ensogl/component/button/src/lib.rs index ea71f42cbc..65a6a0cb31 100644 --- a/lib/rust/ensogl/component/button/src/lib.rs +++ b/lib/rust/ensogl/component/button/src/lib.rs @@ -274,7 +274,7 @@ impl View { let frp = Frp::new(); let model = Model::::new(app); let network = &frp.network; - let scene = app.display.scene(); + let scene = &app.display.default_scene; let style = StyleWatchFrp::new(&scene.style_sheet); let mouse = &scene.mouse.frp; diff --git a/lib/rust/ensogl/component/drop-down-menu/src/lib.rs b/lib/rust/ensogl/component/drop-down-menu/src/lib.rs index 579ec24b66..8d58946f03 100644 --- a/lib/rust/ensogl/component/drop-down-menu/src/lib.rs +++ b/lib/rust/ensogl/component/drop-down-menu/src/lib.rs @@ -220,7 +220,7 @@ impl DropDownMenu { let frp = &self.frp; let model = &self.model; - let scene = app.display.scene(); + let scene = &app.display.default_scene; let mouse = &scene.mouse.frp; frp::extend! { network @@ -368,7 +368,7 @@ impl DropDownMenu { // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for // shape system (#795) - let styles = StyleWatch::new(&app.display.scene().style_sheet); + let styles = StyleWatch::new(&app.display.default_scene.style_sheet); let text_color = styles.get_color(theme::widget::list_view::text); model.label.set_default_color(text_color); diff --git a/lib/rust/ensogl/component/drop-manager/Cargo.toml b/lib/rust/ensogl/component/drop-manager/Cargo.toml index 7c1e07d0b5..80ef9c3095 100644 --- a/lib/rust/ensogl/component/drop-manager/Cargo.toml +++ b/lib/rust/ensogl/component/drop-manager/Cargo.toml @@ -10,7 +10,7 @@ enso-frp = { path = "../../../frp" } enso-logger = { path = "../../../logger"} enso-prelude = { path = "../../../prelude"} js-sys = { version = "0.3.28" } -wasm-bindgen = { version = "0.2.58", features = ["nightly"] } +wasm-bindgen = { version = "0.2.78", features = ["nightly"] } wasm-bindgen-futures = { version = "0.4.8" } [dependencies.web-sys] diff --git a/lib/rust/ensogl/component/drop-manager/src/lib.rs b/lib/rust/ensogl/component/drop-manager/src/lib.rs index e741108c43..7394aae59f 100644 --- a/lib/rust/ensogl/component/drop-manager/src/lib.rs +++ b/lib/rust/ensogl/component/drop-manager/src/lib.rs @@ -21,12 +21,16 @@ pub mod prelude { use crate::prelude::*; use enso_frp as frp; +use enso_web as web; use enso_web::stream::BlobExt; use enso_web::stream::ReadableStreamDefaultReader; -use enso_web::Error; +use enso_web::Closure; + +#[cfg(target_arch = "wasm32")] +use enso_web::JsCast; +#[cfg(target_arch = "wasm32")] use js_sys::Uint8Array; -use wasm_bindgen::prelude::*; -use wasm_bindgen::JsCast; +#[cfg(target_arch = "wasm32")] use wasm_bindgen_futures::JsFuture; @@ -54,7 +58,7 @@ pub struct File { impl File { /// Constructor from the [`web_sys::File`]. - pub fn from_js_file(file: &web_sys::File) -> Result { + pub fn from_js_file(file: &web_sys::File) -> Result { let name = ImString::new(file.name()); let size = file.size() as u64; let mime_type = ImString::new(file.type_()); @@ -64,6 +68,7 @@ impl File { Ok(File { name, mime_type, size, reader }) } + #[cfg(target_arch = "wasm32")] /// Read the next chunk of file content. /// /// If there is no more data, it returns [`None`]. @@ -71,7 +76,7 @@ impl File { /// The chunk size depend on the browser implementation, but it is assumed to be reasonable. /// See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/read and /// https://github.com/w3c/FileAPI/issues/144#issuecomment-570982732. - pub async fn read_chunk(&self) -> Result>, Error> { + pub async fn read_chunk(&self) -> Result>, web::JsValue> { if let Some(reader) = &*self.reader { let js_result = JsFuture::from(reader.read()).await?; let is_done = js_sys::Reflect::get(&js_result, &"done".into())?.as_bool().unwrap(); @@ -86,6 +91,12 @@ impl File { Ok(None) } } + + #[cfg(not(target_arch = "wasm32"))] + /// Read the next chunk of file content. + pub async fn read_chunk(&self) -> Result>, web::JsValue> { + Ok(None) + } } @@ -105,44 +116,40 @@ type DragOverClosure = Closure bool>; #[derive(Clone, CloneRef, Debug)] pub struct Manager { #[allow(dead_code)] - network: frp::Network, - files_received: frp::Source>, + network: frp::Network, + files_received: frp::Source>, #[allow(dead_code)] - drop_callback: Rc, + drop_handle: web::EventListenerHandle, #[allow(dead_code)] - drag_over_callback: Rc, + drag_over_handle: web::EventListenerHandle, } impl Manager { /// Constructor, adding listener to the given target. - pub fn new(target: &web_sys::EventTarget) -> Self { + pub fn new(target: &enso_web::EventTarget) -> Self { let logger = Logger::new("DropFileManager"); - debug!(logger, "Creating DropFileManager"); + debug!(logger, "Creating"); let network = frp::Network::new("DropFileManager"); frp::extend! { network files_received <- source(); } let drop: DropClosure = - Closure::wrap(Box::new(f!([logger,files_received](event:web_sys::DragEvent) { + Closure::new(f!([logger,files_received](event:web_sys::DragEvent) { debug!(logger, "Dropped files."); event.prevent_default(); Self::handle_drop_event(&logger,event,&files_received) - }))); + })); // To mark element as a valid drop target, the `dragover` event handler should return // `false`. See // https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop#define_the_drop_zone - let drag_over: DragOverClosure = Closure::wrap(Box::new(|event: web_sys::DragEvent| { + let drag_over: DragOverClosure = Closure::new(|event: web_sys::DragEvent| { event.prevent_default(); false - })); - let drop_js = drop.as_ref().unchecked_ref(); - let drag_over_js = drag_over.as_ref().unchecked_ref(); - target.add_event_listener_with_callback("drop", drop_js).unwrap(); - target.add_event_listener_with_callback("dragover", drag_over_js).unwrap(); - let drop_callback = Rc::new(drop); - let drag_over_callback = Rc::new(drag_over); - Self { network, files_received, drop_callback, drag_over_callback } + }); + let drop_handle = web::add_event_listener(target, "drop", drop); + let drag_over_handle = web::add_event_listener(target, "dragover", drag_over); + Self { network, files_received, drop_handle, drag_over_handle } } /// The frp endpoint emitting signal when a file is dropped. @@ -161,7 +168,7 @@ impl Manager { let files_iter = js_files_iter.filter_map(|f| match File::from_js_file(&f) { Ok(file) => Some(file), Err(err) => { - error!(logger, "Error when processing dropped file: {err:?}"); + error!(logger, "Error when processing dropped file: {err:?}."); None } }); diff --git a/lib/rust/ensogl/component/flame-graph/src/block.rs b/lib/rust/ensogl/component/flame-graph/src/block.rs index 5eb2feff65..54fddde848 100644 --- a/lib/rust/ensogl/component/flame-graph/src/block.rs +++ b/lib/rust/ensogl/component/flame-graph/src/block.rs @@ -112,7 +112,7 @@ impl component::Model for Model { } fn new(app: &Application, logger: &Logger) -> Self { - let scene = app.display.scene(); + let scene = &app.display.default_scene; let display_object = display::object::Instance::new(&logger); let label = app.new_view::(); let background = background::View::new(&logger); diff --git a/lib/rust/ensogl/component/gui/src/component.rs b/lib/rust/ensogl/component/gui/src/component.rs index fc177130a4..83921b1f40 100644 --- a/lib/rust/ensogl/component/gui/src/component.rs +++ b/lib/rust/ensogl/component/gui/src/component.rs @@ -83,7 +83,7 @@ impl> Component { let logger = Logger::new(M::label()); let model = Rc::new(M::new(&app, &logger)); let frp = F::default(); - let style = StyleWatchFrp::new(&app.display.scene().style_sheet); + let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet); frp.init(&app, &model, &style); let frp = Rc::new(frp); Self { frp, model, app, logger } diff --git a/lib/rust/ensogl/component/label/src/lib.rs b/lib/rust/ensogl/component/label/src/lib.rs index 9341e2eb8d..83a399b6b2 100644 --- a/lib/rust/ensogl/component/label/src/lib.rs +++ b/lib/rust/ensogl/component/label/src/lib.rs @@ -86,7 +86,7 @@ struct Model { impl Model { fn new(app: Application) -> Self { let app = app.clone_ref(); - let scene = app.display.scene(); + let scene = &app.display.default_scene; let logger = Logger::new("TextLabel"); let display_object = display::object::Instance::new(&logger); let label = app.new_view::(); @@ -95,7 +95,7 @@ impl Model { display_object.add_child(&background); display_object.add_child(&label); - let style = StyleWatch::new(&app.display.scene().style_sheet); + let style = StyleWatch::new(&app.display.default_scene.style_sheet); let model = Model { background, label, display_object, style }; model.set_layers(&scene.layers.tooltip, &scene.layers.tooltip_text); diff --git a/lib/rust/ensogl/component/list-view/src/entry.rs b/lib/rust/ensogl/component/list-view/src/entry.rs index 19872cefc8..ccbcc3d985 100644 --- a/lib/rust/ensogl/component/list-view/src/entry.rs +++ b/lib/rust/ensogl/component/list-view/src/entry.rs @@ -90,7 +90,7 @@ impl Entry for Label { let display_object = display::object::Instance::new(logger); let label = app.new_view::(); let network = frp::Network::new("list_view::entry::Label"); - let style_watch = StyleWatchFrp::new(&app.display.scene().style_sheet); + let style_watch = StyleWatchFrp::new(&app.display.default_scene.style_sheet); let color = style_watch.get_color(theme::widget::list_view::text); let size = style_watch.get_number(theme::widget::list_view::text::size); diff --git a/lib/rust/ensogl/component/list-view/src/entry/list.rs b/lib/rust/ensogl/component/list-view/src/entry/list.rs index f5fb3df9b7..c0d86ed2f6 100644 --- a/lib/rust/ensogl/component/list-view/src/entry/list.rs +++ b/lib/rust/ensogl/component/list-view/src/entry/list.rs @@ -78,7 +78,7 @@ where E::Model: Default let entries_range = Rc::new(CloneCell::new(default()..default())); let display_object = display::object::Instance::new(&logger); let provider = default(); - let label_layer = Rc::new(Cell::new(app.display.scene().layers.label.id())); + let label_layer = Rc::new(Cell::new(app.display.default_scene.layers.label.id())); List { logger, app, display_object, entries, entries_range, provider, label_layer } } @@ -183,7 +183,9 @@ where E::Model: Default /// Sets the scene layer where the labels will be placed. pub fn set_label_layer(&self, label_layer: LayerId) { - if let Some(layer) = self.app.display.scene().layers.get_sublayer(self.label_layer.get()) { + if let Some(layer) = + self.app.display.default_scene.layers.get_sublayer(self.label_layer.get()) + { for entry in &*self.entries.borrow() { entry.entry.set_label_layer(&layer); } @@ -198,7 +200,7 @@ where E::Model: Default } fn create_new_entry(&self) -> DisplayedEntry { - let layers = &self.app.display.scene().layers; + let layers = &self.app.display.default_scene.layers; let layer = layers.get_sublayer(self.label_layer.get()).unwrap_or_else(|| { error!( self.logger, diff --git a/lib/rust/ensogl/component/list-view/src/lib.rs b/lib/rust/ensogl/component/list-view/src/lib.rs index de5e1f4c48..c39feb2595 100644 --- a/lib/rust/ensogl/component/list-view/src/lib.rs +++ b/lib/rust/ensogl/component/list-view/src/lib.rs @@ -146,7 +146,7 @@ impl Model { fn padding(&self) -> f32 { // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape // system (#795) - let styles = StyleWatch::new(&self.app.display.scene().style_sheet); + let styles = StyleWatch::new(&self.app.display.default_scene.style_sheet); styles.get_number(ensogl_hardcoded_theme::application::searcher::padding) } @@ -188,7 +188,7 @@ impl Model { /// Check if the `point` is inside component assuming that it have given `size`. fn is_inside(&self, point: Vector2, size: Vector2) -> bool { let pos_obj_space = - self.app.display.scene().screen_to_object_space(&self.background, point); + self.app.display.default_scene.screen_to_object_space(&self.background, point); let x_range = (-size.x / 2.0)..=(size.x / 2.0); let y_range = (-size.y / 2.0)..=(size.y / 2.0); x_range.contains(&pos_obj_space.x) && y_range.contains(&pos_obj_space.y) @@ -295,7 +295,7 @@ where E::Model: Default let frp = &self.frp; let network = &frp.network; let model = &self.model; - let scene = app.display.scene(); + let scene = &app.display.default_scene; let mouse = &scene.mouse.frp; let view_y = DEPRECATED_Animation::::new(network); let selection_y = DEPRECATED_Animation::::new(network); diff --git a/lib/rust/ensogl/component/scroll-area/src/lib.rs b/lib/rust/ensogl/component/scroll-area/src/lib.rs index c1b71a839f..95402a237e 100644 --- a/lib/rust/ensogl/component/scroll-area/src/lib.rs +++ b/lib/rust/ensogl/component/scroll-area/src/lib.rs @@ -95,7 +95,7 @@ impl display::Object for ScrollArea { impl ScrollArea { /// Create a new scroll area for use in the given application. pub fn new(app: &Application) -> ScrollArea { - let scene = app.display.scene(); + let scene = &app.display.default_scene; let logger = Logger::new("ScrollArea"); let display_object = display::object::Instance::new(&logger); diff --git a/lib/rust/ensogl/component/scrollbar/src/lib.rs b/lib/rust/ensogl/component/scrollbar/src/lib.rs index 394c83d62d..356865a165 100644 --- a/lib/rust/ensogl/component/scrollbar/src/lib.rs +++ b/lib/rust/ensogl/component/scrollbar/src/lib.rs @@ -85,7 +85,7 @@ impl Frp { pub fn init(&self, app: &Application, model: &Model, style: &StyleWatchFrp) { let frp = &self; let network = &frp.network; - let scene = app.display.scene(); + let scene = &app.display.default_scene; let mouse = &scene.mouse.frp; let thumb_position = Animation::new(network); let thumb_color = color::Animation::new(network); @@ -304,7 +304,7 @@ impl Scrollbar { let app = app.clone_ref(); let model = Rc::new(Model::new(&app)); let frp = Frp::default(); - let style = StyleWatchFrp::new(&app.display.scene().style_sheet); + let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet); frp.init(&app, &model, &style); let frp = Rc::new(frp); Self { frp, model, app } diff --git a/lib/rust/ensogl/component/selector/src/decimal_aligned.rs b/lib/rust/ensogl/component/selector/src/decimal_aligned.rs index ddba1cd517..3bf2bc36a6 100644 --- a/lib/rust/ensogl/component/selector/src/decimal_aligned.rs +++ b/lib/rust/ensogl/component/selector/src/decimal_aligned.rs @@ -58,8 +58,8 @@ impl Model { let label_full = app.new_view::(); let label_left = app.new_view::(); - label_full.remove_from_scene_layer(&app.display.scene().layers.main); - label_full.add_to_scene_layer(&app.display.scene().layers.label); + label_full.remove_from_scene_layer(&app.display.default_scene.layers.main); + label_full.add_to_scene_layer(&app.display.default_scene.layers.label); root.add_child(&label_full); root.add_child(&label_left); diff --git a/lib/rust/ensogl/component/selector/src/frp.rs b/lib/rust/ensogl/component/selector/src/frp.rs index 95756802ee..49be503eea 100644 --- a/lib/rust/ensogl/component/selector/src/frp.rs +++ b/lib/rust/ensogl/component/selector/src/frp.rs @@ -75,24 +75,20 @@ impl Frp { size: frp::Stream, mouse: &Mouse, ) -> Frp { + let net = &network; + let scene = &model.app.display.default_scene; let shadow = shadow::frp_from_style(style, theme::shadow); let text_size = style.get_number(theme::text::size); - let is_dragging_left_overflow = - shape_is_dragged(network, &model.left_overflow.events, mouse); - let is_dragging_right_overflow = - shape_is_dragged(network, &model.right_overflow.events, mouse); - let is_dragging_track = shape_is_dragged(network, &model.track.events, mouse); - let is_dragging_background = shape_is_dragged(network, &model.background.events, mouse); - let is_dragging_left_handle = - shape_is_dragged(network, &model.track_handle_left.events, mouse); + let is_dragging_left_overflow = shape_is_dragged(net, &model.left_overflow.events, mouse); + let is_dragging_right_overflow = shape_is_dragged(net, &model.right_overflow.events, mouse); + let is_dragging_track = shape_is_dragged(net, &model.track.events, mouse); + let is_dragging_background = shape_is_dragged(net, &model.background.events, mouse); + let is_dragging_left_handle = shape_is_dragged(net, &model.track_handle_left.events, mouse); let is_dragging_right_handle = - shape_is_dragged(network, &model.track_handle_right.events, mouse); - - let background_click = - relative_shape_down_position(network, model.app.display.scene(), &model.background); - let track_click = - relative_shape_down_position(network, model.app.display.scene(), &model.track); + shape_is_dragged(net, &model.track_handle_right.events, mouse); + let background_click = relative_shape_down_position(net, scene, &model.background); + let track_click = relative_shape_down_position(net, scene, &model.track); // Initialisation of components. Required for correct layout on startup. model.label_right.set_position_y(text_size.value() / 2.0); diff --git a/lib/rust/ensogl/component/selector/src/lib.rs b/lib/rust/ensogl/component/selector/src/lib.rs index 013d75f366..c779281e7f 100644 --- a/lib/rust/ensogl/component/selector/src/lib.rs +++ b/lib/rust/ensogl/component/selector/src/lib.rs @@ -76,7 +76,7 @@ impl NumberPicker { let app = app.clone_ref(); let model = Rc::new(Model::new(&app)); let frp = number::Frp::default(); - let style = StyleWatchFrp::new(&app.display.scene().style_sheet); + let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet); frp.init(&app, &model, &style); let frp = Rc::new(frp); Self { frp, model, app } @@ -146,7 +146,7 @@ impl NumberRangePicker { let app = app.clone_ref(); let model = Rc::new(Model::new(&app)); let frp = range::Frp::default(); - let style = StyleWatchFrp::new(&app.display.scene().style_sheet); + let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet); frp.init(&app, &model, &style); let frp = Rc::new(frp); Self { frp, model, app } diff --git a/lib/rust/ensogl/component/selector/src/model.rs b/lib/rust/ensogl/component/selector/src/model.rs index d2197bf3a0..328dad0806 100644 --- a/lib/rust/ensogl/component/selector/src/model.rs +++ b/lib/rust/ensogl/component/selector/src/model.rs @@ -97,7 +97,7 @@ impl Model { let padding = default(); let app = app.clone_ref(); - let scene = app.display.scene(); + let scene = &app.display.default_scene; scene.layers.add_global_shapes_order_dependency::(); scene.layers.add_global_shapes_order_dependency::(); scene.layers.add_global_shapes_order_dependency::(); diff --git a/lib/rust/ensogl/component/selector/src/number.rs b/lib/rust/ensogl/component/selector/src/number.rs index 2f6e34b955..73bb1767d0 100644 --- a/lib/rust/ensogl/component/selector/src/number.rs +++ b/lib/rust/ensogl/component/selector/src/number.rs @@ -45,7 +45,7 @@ impl Frp { pub fn init(&self, app: &Application, model: &Model, style: &StyleWatchFrp) { let frp = &self; let network = &frp.network; - let scene = app.display.scene(); + let scene = &app.display.default_scene; let mouse = &scene.mouse.frp; model.show_background(true); @@ -55,11 +55,8 @@ impl Frp { let track_shape_system = scene.shapes.shape_system(PhantomData::); track_shape_system.shape_system.set_pointer_events(false); - let background_click = - relative_shape_down_position(network, model.app.display.scene(), &model.background); - let track_click = - relative_shape_down_position(network, model.app.display.scene(), &model.track); - + let background_click = relative_shape_down_position(network, scene, &model.background); + let track_click = relative_shape_down_position(network, scene, &model.track); let style_track_color = style.get_color(theme::component::slider::track::color); frp::extend! { network diff --git a/lib/rust/ensogl/component/selector/src/range.rs b/lib/rust/ensogl/component/selector/src/range.rs index 7686f0b6d3..203756dd43 100644 --- a/lib/rust/ensogl/component/selector/src/range.rs +++ b/lib/rust/ensogl/component/selector/src/range.rs @@ -41,7 +41,7 @@ impl Frp { pub fn init(&self, app: &Application, model: &Model, style: &StyleWatchFrp) { let frp = &self; let network = &frp.network; - let scene = app.display.scene(); + let scene = &app.display.default_scene; let mouse = &scene.mouse.frp; let base_frp = super::Frp::new(model, style, network, frp.resize.clone().into(), mouse); diff --git a/lib/rust/ensogl/component/shadow/src/lib.rs b/lib/rust/ensogl/component/shadow/src/lib.rs index e4a1858be2..bc1ec0a62b 100644 --- a/lib/rust/ensogl/component/shadow/src/lib.rs +++ b/lib/rust/ensogl/component/shadow/src/lib.rs @@ -20,7 +20,7 @@ use ensogl_core::display::shape::*; use ensogl_core::display::style; use ensogl_core::display::DomSymbol; use ensogl_core::frp; -use ensogl_core::system::web::StyleSetter; +use ensogl_core::system::web::traits::*; use ensogl_hardcoded_theme as theme; @@ -112,14 +112,14 @@ pub fn from_shape_with_parameters_and_alpha( } /// Add a theme defined box shadow to the given `DomSymbol`. -pub fn add_to_dom_element(element: &DomSymbol, style: &StyleWatch, logger: &Logger) { +pub fn add_to_dom_element(element: &DomSymbol, style: &StyleWatch) { let off_x = style.get_number(theme::shadow::offset_x); let off_y = -style.get_number(theme::shadow::offset_y); let alpha = style.get_number(ensogl_hardcoded_theme::shadow::html::alpha); let blur = style.get_number(ensogl_hardcoded_theme::shadow::html::blur); let spread = style.get_number(ensogl_hardcoded_theme::shadow::html::spread); let shadow = format!("{}px {}px {}px {}px rgba(0,0,0,{})", off_x, off_y, blur, spread, alpha); - element.dom().set_style_or_warn("box-shadow", shadow, logger); + element.dom().set_style_or_warn("box-shadow", shadow); } diff --git a/lib/rust/ensogl/component/text/msdf-sys/Cargo.toml b/lib/rust/ensogl/component/text/msdf-sys/Cargo.toml index f41a5c1755..06374c42b4 100644 --- a/lib/rust/ensogl/component/text/msdf-sys/Cargo.toml +++ b/lib/rust/ensogl/component/text/msdf-sys/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"] enso-prelude = { path = "../../../../prelude"} js-sys = { version = "0.3" } nalgebra = { version = "0.26.1" } -wasm-bindgen = { version = "0.2.58" } +wasm-bindgen = { version = "0.2.78" } [dev-dependencies] wasm-bindgen-test = { version = "0.3.8" } diff --git a/lib/rust/ensogl/component/text/src/component/area.rs b/lib/rust/ensogl/component/text/src/component/area.rs index da1744f2c2..dfd31989e4 100644 --- a/lib/rust/ensogl/component/text/src/component/area.rs +++ b/lib/rust/ensogl/component/text/src/component/area.rs @@ -304,7 +304,7 @@ impl Area { fn init(self) -> Self { let network = &self.frp.network; let model = &self.data; - let scene = model.app.display.scene(); + let scene = &model.app.display.default_scene; let mouse = &scene.mouse.frp; let input = &self.frp.input; let out = &self.frp.output; @@ -532,7 +532,7 @@ impl Area { fn symbols(&self) -> SmallVec<[display::Symbol; 1]> { let text_symbol = self.data.glyph_system.sprite_system().symbol.clone_ref(); - let shapes = &self.data.app.display.scene().shapes; + let shapes = &self.data.app.display.default_scene.shapes; let selection_system = shapes.shape_system(PhantomData::); let _selection_symbol = selection_system.shape_system.symbol.clone_ref(); //TODO[ao] we cannot move selection symbol, as it is global for all the text areas. @@ -568,7 +568,7 @@ impl AreaModel { /// Constructor. pub fn new(app: &Application, frp_endpoints: &FrpEndpoints) -> Self { let app = app.clone_ref(); - let scene = app.display.scene(); + let scene = &app.display.default_scene; let logger = Logger::new("text_area"); let selection_map = default(); let fonts = scene.extension::(); @@ -703,7 +703,7 @@ impl AreaModel { let origin_clip_space = camera.view_projection_matrix() * origin_world_space; let inv_object_matrix = self.transform_matrix().try_inverse().unwrap(); - let shape = self.app.display.scene().frp.shape.value(); + let shape = self.app.display.default_scene.frp.shape.value(); let clip_space_z = origin_clip_space.z; let clip_space_x = origin_clip_space.w * 2.0 * screen_pos.x / shape.width; let clip_space_y = origin_clip_space.w * 2.0 * screen_pos.y / shape.height; diff --git a/lib/rust/ensogl/component/text/src/typeface/glyph.rs b/lib/rust/ensogl/component/text/src/typeface/glyph.rs index 6db4f241af..94b4a2af3e 100644 --- a/lib/rust/ensogl/component/text/src/typeface/glyph.rs +++ b/lib/rust/ensogl/component/text/src/typeface/glyph.rs @@ -102,7 +102,9 @@ impl System { let logger = Logger::new("glyph_system"); let size = font::msdf::Texture::size(); let scene = scene.as_ref(); - let context = scene.context.clone_ref(); + // FIXME: The following line is unsafe. It can fail if the context was lost before calling + // this function. Also, the texture will not be restored after context restoration. + let context = scene.context.borrow().as_ref().unwrap().clone_ref(); let sprite_system = SpriteSystem::new(scene); let symbol = sprite_system.symbol(); let texture = Texture::new(&context, (0, 0)); diff --git a/lib/rust/ensogl/core/Cargo.toml b/lib/rust/ensogl/core/Cargo.toml index 7d6c04ebd5..4320d398dc 100644 --- a/lib/rust/ensogl/core/Cargo.toml +++ b/lib/rust/ensogl/core/Cargo.toml @@ -45,7 +45,7 @@ typenum = { version = "1.11.2" } # 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.58", features = ["nightly"] } +wasm-bindgen = { version = "0.2.78", features = ["nightly"] } [dependencies.web-sys] version = "0.3.4" diff --git a/lib/rust/ensogl/core/src/animation/loops.rs b/lib/rust/ensogl/core/src/animation/loops.rs index c24b4e5232..5da40d5f3c 100644 --- a/lib/rust/ensogl/core/src/animation/loops.rs +++ b/lib/rust/ensogl/core/src/animation/loops.rs @@ -1,12 +1,11 @@ -//! This module contains implementation of `DynamicLoop`, a loop manager which runs a -//! `DynamicLoopCallback` once per frame. +//! This module contains implementation of loops mainly used for per-frame callbacks firing. use crate::prelude::*; -use crate::control::callback; use crate::system::web; +use crate::system::web::traits::*; -use wasm_bindgen::prelude::Closure; +use web::Closure; @@ -70,7 +69,9 @@ where Callback: RawLoopCallback let weak_data = Rc::downgrade(&data); let on_frame = move |time| weak_data.upgrade().for_each(|t| t.borrow_mut().run(time)); data.borrow_mut().on_frame = Some(Closure::new(on_frame)); - let handle_id = web::request_animation_frame(data.borrow_mut().on_frame.as_ref().unwrap()); + let handle_id = web::window.request_animation_frame_with_closure_or_panic( + data.borrow_mut().on_frame.as_ref().unwrap(), + ); data.borrow_mut().handle_id = handle_id; Self { data } } @@ -100,14 +101,14 @@ impl RawLoopData { let callback = &mut self.callback; self.handle_id = self.on_frame.as_ref().map_or(default(), |on_frame| { callback(current_time_ms as f32); - web::request_animation_frame(on_frame) + web::window.request_animation_frame_with_closure_or_panic(on_frame) }) } } impl Drop for RawLoopData { fn drop(&mut self) { - web::cancel_animation_frame(self.handle_id); + web::window.cancel_animation_frame_or_panic(self.handle_id); } } @@ -238,71 +239,3 @@ where Callback: LoopCallback Self::new(FixedFrameRateSampler::new(frame_rate, callback)) } } - - - -// =================== -// === DynamicLoop === -// =================== - -/// A callback to register in DynamicLoop, taking time_ms:f32 as its input. -pub trait DynamicLoopCallback = callback::CopyCallbackMut1Fn; - -/// Animation loop which allows registering and unregistering callbacks dynamically. After a -/// callback is registered, a `callback::Handle` is returned. The callback is automatically removed -/// as soon as its handle is dropped. You can also use the `forget` method on the handle to make the -/// callback registered forever, but beware that it can easily lead to memory leaks. -/// -/// Please refer to `Loop` if you don't need the ability to add / remove callbacks dynamically and -/// you want better performance. -#[derive(Clone, CloneRef, Debug)] -pub struct DynamicLoop { - raw_loop: Loop>, - data: Rc>, -} - -/// Internal representation for `DynamicLoop`. -#[derive(Debug, Default)] -pub struct DynamicLoopData { - on_frame: callback::CopyRegistry1, - on_before_frame: callback::CopyRegistry1, - on_after_frame: callback::CopyRegistry1, -} - -impl Default for DynamicLoop { - fn default() -> Self { - let data = Rc::new(RefCell::new(DynamicLoopData::default())); - let weak = Rc::downgrade(&data); - let raw_loop: Loop> = Loop::new(Box::new(move |time| { - weak.upgrade().for_each(|data| { - let mut data_mut = data.borrow_mut(); - data_mut.on_before_frame.run_all(time); - data_mut.on_frame.run_all(time); - data_mut.on_after_frame.run_all(time); - }) - })); - Self { raw_loop, data } - } -} - -impl DynamicLoop { - /// Constructor. - pub fn new() -> Self { - default() - } - - /// Add new callback which will be run on every animation frame. - pub fn on_frame(&self, callback: F) -> callback::Handle { - self.data.borrow_mut().on_frame.add(Box::new(callback)) - } - - /// Add new callback which will be run on before all callbacks registered with `on_frame`. - pub fn on_before_frame(&self, callback: F) -> callback::Handle { - self.data.borrow_mut().on_before_frame.add(Box::new(callback)) - } - - /// Add new callback which will be run on after all callbacks registered with `on_frame`. - pub fn on_after_frame(&self, callback: F) -> callback::Handle { - self.data.borrow_mut().on_after_frame.add(Box::new(callback)) - } -} diff --git a/lib/rust/ensogl/core/src/application.rs b/lib/rust/ensogl/core/src/application.rs index 7d98aea135..c5fc9f4318 100644 --- a/lib/rust/ensogl/core/src/application.rs +++ b/lib/rust/ensogl/core/src/application.rs @@ -12,11 +12,12 @@ use crate::prelude::*; use crate::control::callback; use crate::display; +use crate::display::scene::DomPath; use crate::display::style::theme; use crate::display::world::World; use crate::gui::cursor::Cursor; use crate::system::web; -use enso_web::StyleSetter; +use enso_web::traits::*; @@ -41,19 +42,20 @@ pub struct Application { impl Application { /// Constructor. - pub fn new(dom: &web_sys::HtmlElement) -> Self { + pub fn new(dom: impl DomPath) -> Self { let logger = Logger::new("Application"); - let display = World::new(dom); - let scene = display.scene(); + let display = World::new(); + let scene = &display.default_scene; + scene.display_in(dom); let commands = command::Registry::create(&logger); let shortcuts = shortcut::Registry::new(&logger, &scene.mouse.frp, &scene.keyboard.frp, &commands); let views = view::Registry::create(&logger, &display, &commands, &shortcuts); - let themes = theme::Manager::from(&display.scene().style_sheet); - let cursor = Cursor::new(display.scene()); + let themes = theme::Manager::from(&display.default_scene.style_sheet); + let cursor = Cursor::new(&display.default_scene); display.add_child(&cursor); - web::body().set_style_or_panic("cursor", "none"); - let update_themes_handle = display.on_before_frame(f_!(themes.update())); + web::document.body_or_panic().set_style_or_warn("cursor", "none"); + let update_themes_handle = display.on.before_frame.add(f_!(themes.update())); Self { logger, cursor, display, commands, shortcuts, views, themes, update_themes_handle } } @@ -74,3 +76,18 @@ impl AsRef for Application { &self.themes } } + + +// ============= +// === Tests === +// ============= + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn native_compilation_in_test_mode() { + let _app = Application::new("root"); + } +} diff --git a/lib/rust/ensogl/core/src/application/args.rs b/lib/rust/ensogl/core/src/application/args.rs index ca60ed1838..cbd2591134 100644 --- a/lib/rust/ensogl/core/src/application/args.rs +++ b/lib/rust/ensogl/core/src/application/args.rs @@ -116,73 +116,82 @@ impl ArgReader for bool { #[macro_export] macro_rules! read_args { ([$($($path:tt)*).*] { $($field:ident : $field_type:ty),* $(,)? }) => { + mod _READ_ARGS { + use super::*; + use $crate::prelude::*; + use $crate::system::web::traits::*; - /// Reflection mechanism containing string representation of option names. - #[derive(Clone,Copy,Debug,Default)] - pub struct ArgNames; - impl ArgNames { - $( - /// Name of the field. - pub fn $field(&self) -> &'static str { - stringify!{$field} - } - )* - } - - /// The structure containing application configs. - #[derive(Clone,Debug,Default)] - #[allow(missing_docs)] - pub struct Args { - $(pub $field : Option<$field_type>),* - } - - impl Args { - /// Constructor. - fn new() -> Self { - let logger = Logger::new(stringify!{Args}); - let window = web::window(); - let path = vec![$($($path)*),*]; - match web::reflect_get_nested_object(&window,&path).ok() { - None => { - let path = path.join("."); - error!(&logger,"The config path '{path}' is invalid."); - default() + /// Reflection mechanism containing string representation of option names. + #[derive(Clone,Copy,Debug,Default)] + pub struct ArgNames; + impl ArgNames { + $( + /// Name of the field. + pub fn $field(&self) -> &'static str { + stringify!{$field} } - Some(cfg) => { - let keys = web::object_keys(&cfg); - let mut keys = keys.into_iter().collect::>(); - $( - let name = stringify!{$field}; - let tp = stringify!{$field_type}; - let $field = web::reflect_get_nested_string(&cfg,&[name]).ok(); - let $field = $field.map($crate::application::args::ArgReader::read_arg); - if $field == Some(None) { - warning!(&logger,"Failed to convert the argument '{name}' value \ - to the '{tp}' type."); - } - let $field = $field.flatten(); - keys.remove(name); - )* - for key in keys { - warning!(&logger,"Unknown config option provided '{key}'."); - } - Self {$($field),*} - } - } + )* } - /// This is a dummy function which initializes the arg reading process. This function - /// does nothing, however, in order to call it, the user would need to access a field in - /// the lazy static variable `ARGS`, which would trigger argument parsing process. - pub fn init(&self) {} + /// The structure containing application configs. + #[derive(Clone,Debug,Default)] + #[allow(missing_docs)] + pub struct Args { + $(pub $field : Option<$field_type>),* + } - /// Reflection mechanism to get string representation of argument names. - pub fn names(&self) -> ArgNames { ArgNames } - } + impl Args { + /// Constructor. + fn new() -> Self { + let logger = Logger::new(stringify!{Args}); + let path = vec![$($($path)*),*]; + match ensogl::system::web::Reflect::get_nested_object + (&ensogl::system::web::window,&path).ok() { + None => { + let path = path.join("."); + error!(&logger,"The config path '{path}' is invalid."); + default() + } + Some(cfg) => { + let keys = ensogl::system::web::Object::keys_vec(&cfg); + let mut keys = keys.into_iter().collect::>(); + $( + let name = stringify!{$field}; + let tp = stringify!{$field_type}; + let $field = ensogl::system::web::Reflect:: + get_nested_object_printed_as_string(&cfg,&[name]).ok(); + let $field = $field.map + ($crate::application::args::ArgReader::read_arg); + if $field == Some(None) { + warning!(&logger,"Failed to convert the argument '{name}' \ + value to the '{tp}' type."); + } + let $field = $field.flatten(); + keys.remove(name); + )* + for key in keys { + warning!(&logger,"Unknown config option provided '{key}'."); + } + Self {$($field),*} + } + } + } - lazy_static! { - /// Application arguments initialized in a lazy way (on first read). - pub static ref ARGS : Args = Args::new(); + /// This is a dummy function which initializes the arg reading process. This + /// function does nothing, however, in order to call it, the user would need to + /// access a field in the lazy static variable `ARGS`, which would trigger argument + /// parsing process. + pub fn init(&self) {} + + /// Reflection mechanism to get string representation of argument names. + pub fn names(&self) -> ArgNames { ArgNames } + } + + lazy_static! { + /// Application arguments initialized in a lazy way (on first read). + pub static ref ARGS : Args = Args::new(); + } } + pub use _READ_ARGS::*; }; } diff --git a/lib/rust/ensogl/core/src/control/io/mouse.rs b/lib/rust/ensogl/core/src/control/io/mouse.rs index 8b2db9037d..2193f8ac09 100644 --- a/lib/rust/ensogl/core/src/control/io/mouse.rs +++ b/lib/rust/ensogl/core/src/control/io/mouse.rs @@ -5,92 +5,50 @@ use crate::prelude::*; pub mod event; use crate::control::callback; +use crate::control::callback::traits::*; use crate::system::web; -use std::cell::RefCell; use std::rc::Rc; -use wasm_bindgen::prelude::Closure; -use wasm_bindgen::JsCast; -use wasm_bindgen::JsValue; +use web::Closure; +use web::JsCast; +use web::JsValue; pub use crate::frp::io::mouse::*; pub use event::*; -// ======================= -// === EventDispatcher === -// ======================= - -// TODO: Consider merging this implementation with crate::control::callback::* ones. - -/// Shared event dispatcher. -#[derive(Debug, CloneRef, Derivative)] -#[derivative(Clone(bound = ""))] -#[derivative(Default(bound = ""))] -pub struct EventDispatcher { - rc: Rc>>, -} - -impl EventDispatcher { - /// Adds a new callback. - pub fn add(&self, f: F) -> callback::Handle { - self.rc.borrow_mut().add(f) - } - - /// Dispatches event to all callbacks. - pub fn dispatch(&self, t: &T) { - self.rc.borrow_mut().run_all(t); - } -} - - - // ==================== // === MouseManager === // ==================== -/// An utility which registers JavaScript handlers for mouse events and translates them to Rust +/// A utility which registers JavaScript handlers for mouse events and translates them to Rust /// handlers. It is a top level mouse registry hub. #[derive(Clone, CloneRef, Debug, Shrinkwrap)] pub struct MouseManager { #[shrinkwrap(main_field)] dispatchers: MouseManagerDispatchers, - closures: Rc, + handles: Rc, dom: web::dom::WithKnownShape, } /// A JavaScript callback closure for any mouse event. -pub type MouseEventJsClosure = Closure; +pub type MouseEventJsClosure = Closure; macro_rules! define_bindings { ( $( $js_event:ident :: $js_name:ident => $name:ident ($target:ident) ),* $(,)? ) => { /// Keeps references to JavaScript closures in order to keep them alive. #[derive(Debug)] - pub struct MouseManagerClosures { - target : web::EventTarget, - $($name : MouseEventJsClosure),* - } - - impl Drop for MouseManagerClosures { - fn drop(&mut self) { - $( - let target = &self.target; - let js_closure = self.$name.as_ref().unchecked_ref(); - let js_name = stringify!($js_name); - let result = target.remove_event_listener_with_callback(js_name,js_closure); - if let Err(e) = result { panic!("Cannot add event listener. {:?}",e) } - )* - - } + pub struct MouseManagerEventListenerHandles { + $($name : web::EventListenerHandle),* } /// Set of dispatchers for various mouse events. #[derive(Clone,CloneRef,Debug,Default)] #[allow(missing_docs)] pub struct MouseManagerDispatchers { - $(pub $name : EventDispatcher<$target>),* + $(pub $name : callback::registry::RefMut1<$target>),* } impl MouseManager { @@ -101,41 +59,36 @@ macro_rules! define_bindings { /// Constructor which takes the exact element to set listener as a separate argument. /// - /// Sometimes we want to listen for mouse event for element without ResizeObserver. Thus - /// some html element may be passed as a size provider, and another one where we attach - /// listeners (for example `body` and `window` respectively). + /// Sometimes we want to listen for mouse event for element without ResizeObserver. + /// Thus, some html element may be passed as a size provider, and another one where we + /// attach listeners (for example `body` and `window` respectively). pub fn new_separated (dom:&web::dom::WithKnownShape,target:&web::EventTarget) -> Self { let dispatchers = MouseManagerDispatchers::default(); - let dom = dom.clone(); - let target = target.clone(); + let dom = dom.clone(); $( - let shape = dom.shape.clone_ref(); + let shape = dom.shape.clone_ref(); let dispatcher = dispatchers.$name.clone_ref(); - let $name : MouseEventJsClosure = Closure::wrap(Box::new(move |event:JsValue| { + let closure : MouseEventJsClosure = Closure::new(move |event:JsValue| { let shape = shape.value(); - let event = event.unchecked_into::(); - dispatcher.dispatch(&event::$target::new(event,shape)) - })); - let js_closure = $name.as_ref().unchecked_ref(); - let js_name = stringify!($js_name); - let options = event_listener_options(); - let result = - target.add_event_listener_with_callback_and_add_event_listener_options - (js_name,js_closure,&options); - if let Err(e) = result { panic!("Cannot add event listener. {:?}",e) } + let event = event.unchecked_into::(); + dispatcher.run_all(&event::$target::new(event,shape)) + }); + let js_name = stringify!($js_name); + let opt = event_listener_options(); + let $name = web::add_event_listener_with_options(&target,js_name,closure,&opt); )* - let closures = Rc::new(MouseManagerClosures {target,$($name),*}); - Self {dispatchers,closures,dom} + let handles = Rc::new(MouseManagerEventListenerHandles {$($name),*}); + Self {dispatchers,handles,dom} } } }; } -/// Retrun options for addEventListener function. See also +/// Return options for addEventListener function. See also /// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener -fn event_listener_options() -> web_sys::AddEventListenerOptions { - let mut options = web_sys::AddEventListenerOptions::new(); +fn event_listener_options() -> web::AddEventListenerOptions { + let mut options = web::AddEventListenerOptions::new(); // We listen for events in capture phase, so we can decide ourself if it should be passed // further. options.capture(true); diff --git a/lib/rust/ensogl/core/src/control/io/mouse/event.rs b/lib/rust/ensogl/core/src/control/io/mouse/event.rs index a0af5122b6..faa4989e38 100644 --- a/lib/rust/ensogl/core/src/control/io/mouse/event.rs +++ b/lib/rust/ensogl/core/src/control/io/mouse/event.rs @@ -2,10 +2,11 @@ use crate::prelude::*; -use crate::system::web::dom::Shape; +use crate::system::web; use enso_frp::io::mouse; -use wasm_bindgen::JsCast; +use web::dom::Shape; +use web::traits::*; @@ -19,13 +20,13 @@ macro_rules! define_events { #[derive(Debug,Clone,From,Shrinkwrap)] pub struct $name { #[shrinkwrap(main_field)] - raw : web_sys::$js_event, + raw : web::$js_event, shape : Shape, } impl $name { /// Constructor. - pub fn new(raw:web_sys::$js_event,shape:Shape) -> Self { + pub fn new(raw:web::$js_event,shape:Shape) -> Self { Self {raw,shape} } @@ -66,15 +67,15 @@ macro_rules! define_events { /// Return the event handler that caught this event if it exists and if it is an /// html element. Returns `None` if the event was caught, for example, byt the window. - fn try_get_current_target_element(&self) -> Option { + fn try_get_current_target_element(&self) -> Option { let target = self.current_target()?; - target.value_of().dyn_into::().ok() + target.value_of().dyn_into::().ok() } /// Return the position relative to the given element. /// /// Note: causes reflow of the JS layout. - pub fn relative_position_with_reflow(&self, element:&web_sys::Element) -> Vector2 { + pub fn relative_position_with_reflow(&self, element:&web::Element) -> Vector2 { let rect = element.get_bounding_client_rect(); let x = self.client_x() as f64 - rect.left(); let y = self.client_y() as f64 - rect.top(); @@ -83,9 +84,9 @@ macro_rules! define_events { } - impl AsRef for $name { - fn as_ref(&self) -> &web_sys::Event { - let js_event = AsRef::::as_ref(self); + impl AsRef for $name { + fn as_ref(&self) -> &web::Event { + let js_event = AsRef::::as_ref(self); js_event.as_ref() } } diff --git a/lib/rust/ensogl/core/src/debug/monitor.rs b/lib/rust/ensogl/core/src/debug/monitor.rs index c7877c5b95..eba6f95fdc 100644 --- a/lib/rust/ensogl/core/src/debug/monitor.rs +++ b/lib/rust/ensogl/core/src/debug/monitor.rs @@ -4,14 +4,12 @@ use crate::prelude::*; use crate::debug::stats::StatsData; use crate::system::web; -use crate::system::web::StyleSetter; +use crate::system::web::traits::*; +use crate::system::web::JsValue; use num_traits::cast::AsPrimitive; use std::collections::VecDeque; use std::f64; -use wasm_bindgen; -use wasm_bindgen::prelude::*; -use wasm_bindgen::JsCast; @@ -105,7 +103,7 @@ impl Default for Config { impl Config { /// Translates the configuration to JS values. pub fn to_js_config(&self) -> SamplerConfig { - let ratio = web::window().device_pixel_ratio(); + let ratio = web::window.device_pixel_ratio(); SamplerConfig { background_color: (&self.background_color).into(), label_color_ok: (&self.label_color_ok).into(), @@ -165,19 +163,19 @@ impl DomData { /// Constructor. #[allow(clippy::new_without_default)] pub fn new() -> Self { - let root = web::create_div(); + let root = web::document.create_div_or_panic(); root.set_class_name("performance-monitor"); - root.set_style_or_panic("position", "absolute"); - root.set_style_or_panic("z-index", "100"); - root.set_style_or_panic("left", "8px"); - root.set_style_or_panic("top", "8px"); - root.set_style_or_panic("overflow", "hidden"); - root.set_style_or_panic("border-radius", "6px"); - root.set_style_or_panic("box-shadow", "0px 0px 20px -4px rgba(0,0,0,0.44)"); - web::body().prepend_with_node_1(&root).unwrap(); + root.set_style_or_warn("position", "absolute"); + root.set_style_or_warn("z-index", "100"); + root.set_style_or_warn("left", "8px"); + root.set_style_or_warn("top", "8px"); + root.set_style_or_warn("overflow", "hidden"); + root.set_style_or_warn("border-radius", "6px"); + root.set_style_or_warn("box-shadow", "0px 0px 20px -4px rgba(0,0,0,0.44)"); + web::document.body_or_panic().prepend_with_node_1(&root).unwrap(); - let canvas = web::create_canvas(); - canvas.set_style_or_panic("display", "block"); + let canvas = web::document.create_canvas_or_panic(); + canvas.set_style_or_warn("display", "block"); let context = canvas.get_context("2d").unwrap().unwrap(); let context: web::CanvasRenderingContext2d = context.dyn_into().unwrap(); @@ -337,7 +335,7 @@ impl Renderer { fn resize(&mut self) { if let Some(dom) = &self.dom { - let ratio = web::window().device_pixel_ratio(); + let ratio = web::window.device_pixel_ratio(); let width = self.config.labels_width + self.config.results_width + self.config.plots_width @@ -355,8 +353,8 @@ impl Renderer { self.height = height; dom.canvas.set_width(u_width); dom.canvas.set_height(u_height); - dom.canvas.set_style_or_panic("width", format!("{}px", width / ratio)); - dom.canvas.set_style_or_panic("height", format!("{}px", height / ratio)); + dom.canvas.set_style_or_warn("width", format!("{}px", width / ratio)); + dom.canvas.set_style_or_warn("height", format!("{}px", height / ratio)); } } diff --git a/lib/rust/ensogl/core/src/display/camera/camera2d.rs b/lib/rust/ensogl/core/src/display/camera/camera2d.rs index 891b4b1429..8bc71d9561 100644 --- a/lib/rust/ensogl/core/src/display/camera/camera2d.rs +++ b/lib/rust/ensogl/core/src/display/camera/camera2d.rs @@ -4,6 +4,7 @@ use crate::prelude::*; use crate::control::callback; +use crate::control::callback::traits::*; use crate::data::dirty; use crate::data::dirty::traits::*; use crate::display; @@ -158,10 +159,10 @@ impl Default for Matrix { // ==================== /// Function used to return the updated screen dimensions. -pub trait ScreenUpdateFn = callback::CallbackMut1Fn>; +pub trait ScreenUpdateFn = Fn(Vector2) + 'static; /// Function used to return the updated `Camera2d`'s zoom. -pub trait ZoomUpdateFn = callback::CallbackMut1Fn; +pub trait ZoomUpdateFn = Fn(f32) + 'static; /// Internal `Camera2d` representation. Please see `Camera2d` for full documentation. #[derive(Debug)] @@ -174,8 +175,8 @@ struct Camera2dData { clipping: Clipping, matrix: Matrix, dirty: Dirty, - zoom_update_registry: callback::Registry1, - screen_update_registry: callback::Registry1>, + zoom_update_registry: callback::registry::CopyMut1, + screen_update_registry: callback::registry::CopyMut1>, } type ProjectionDirty = dirty::SharedBool<()>; @@ -271,8 +272,7 @@ impl Camera2dData { } if changed { self.matrix.view_projection = self.matrix.projection * self.matrix.view; - let zoom = self.zoom; - self.zoom_update_registry.run_all(&zoom); + self.zoom_update_registry.run_all(self.zoom); } changed } @@ -311,7 +311,7 @@ impl Camera2dData { _ => unimplemented!(), }; let dimensions = Vector2::new(width, height); - self.screen_update_registry.run_all(&dimensions); + self.screen_update_registry.run_all(dimensions); } fn reset_zoom(&mut self) { @@ -406,6 +406,8 @@ impl Camera2d { self.data.borrow_mut().update(scene) } + // FIXME: This can fail, for example, when during calling the callback another callback is + // being registered. /// Adds a callback to notify when `zoom` is updated. pub fn add_zoom_update_callback(&self, f: F) -> callback::Handle { self.data.borrow_mut().add_zoom_update_callback(f) diff --git a/lib/rust/ensogl/core/src/display/navigation/navigator.rs b/lib/rust/ensogl/core/src/display/navigation/navigator.rs index 8b7eb83877..1b36d36455 100644 --- a/lib/rust/ensogl/core/src/display/navigation/navigator.rs +++ b/lib/rust/ensogl/core/src/display/navigation/navigator.rs @@ -91,18 +91,18 @@ impl NavigatorModel { let distance_to_zoom_factor_of_1 = distance_to_zoom_factor_of_1(&camera); let pan_speed = pan_speed.get().into_on().unwrap_or(0.0); let movement_scale_for_distance = distance / distance_to_zoom_factor_of_1; - let diff = pan_speed * Vector3::new(pan.movement.x, pan.movement.y, 0.0) * movement_scale_for_distance; + let movement = Vector3::new(pan.movement.x, pan.movement.y, 0.0); + let diff = pan_speed * movement * movement_scale_for_distance; simulator.update_target_value(|p| p - diff); }); - let resize_callback = camera.add_screen_update_callback( - enclose!((mut simulator,camera) move |_:&Vector2| { + let resize_callback = + camera.add_screen_update_callback(enclose!((mut simulator,camera) move |_| { let position = camera.position(); simulator.set_value(position); simulator.set_target_value(position); simulator.set_velocity(default()); - }), - ); + })); let zoom_callback = f!([camera,simulator,max_zoom] (zoom:ZoomEvent) { let point = zoom.focus; diff --git a/lib/rust/ensogl/core/src/display/object/class.rs b/lib/rust/ensogl/core/src/display/object/class.rs index 7b9871347d..f261e8d919 100644 --- a/lib/rust/ensogl/core/src/display/object/class.rs +++ b/lib/rust/ensogl/core/src/display/object/class.rs @@ -183,21 +183,7 @@ fn on_dirty_callback(f: &Rc>>) -> OnDirtyCallback { /// A hierarchical representation of object containing information about transformation in 3D space, /// list of children, and set of utils for dirty flag propagation. /// -/// ## Host -/// The model is parametrized with a `Host`. In real life use cases, host will be instantiated with -/// `Scene`. For the needs of tests, its often instantiated with empty tuple for simplicity. Host -/// has a very important role in decoupling the architecture. You need to provide the `update` -/// method with a reference to the host, which is then passed to `on_show` and `on_hide` callbacks -/// when a particular display objects gets shown or hidden respectively. This can be used for a -/// dynamic management of GPU-side sprites. For example, after adding a display object to a scene, -/// a new sprites can be created to display it visually. After removing the objects, and adding it -/// to a different scene (second GPU context), the sprites in the first context can be removed, and -/// new sprites in the new context can be created. Thus, abstracting over `Host` allows users of -/// this library to define a view model (like few sliders in a box) without the need to contain -/// reference to a particular renderer, and attach the renderer on-demand, when the objects will be -/// placed on the stage. -/// -/// Please note, that this functionality is fairly new, and the library do not use it like this yet. +/// See the documentation of [`Instance`] to learn more. #[derive(Derivative)] #[derivative(Debug(bound = ""))] pub struct Model { @@ -610,18 +596,33 @@ pub struct Id(usize); /// list of children, and set of utils for dirty flag propagation. /// /// ## Host -/// The structure is parametrized with a `Host`. In real life use cases, host will be instantiated -/// with [`Scene`]. For simplicity, it is instantiated to empty tuple in tests. Host has a very -/// important role in decoupling the architecture. You need to provide the `update` method with a -/// reference to the host, which is then passed to `on_show` and `on_hide` callbacks when a -/// particular display objects gets shown or hidden respectively. This can be used for a dynamic -/// management of GPU-side sprites. For example, after adding a display object to a scene, a new -/// sprites can be created to display it visually. After removing the objects, and adding it to a -/// different scene (second GPU context), the sprites in the first context can be removed, and new -/// sprites in the new context can be created. Thus, abstracting over `Host` allows users of this -/// library to define a view model (like few sliders in a box) without the need to contain reference -/// to a particular renderer, and attach the renderer on-demand, when the objects will be placed on -/// the stage. +/// The model is parametrized with a `Host`. In real life use cases, host is **ALWAYS** instantiated +/// with `Scene`. For the needs of tests, its often instantiated with empty tuple for simplicity. +/// Host has a very important role in decoupling the architecture. You need to provide the `update` +/// method with a reference to the host, which is then passed to `on_show` and `on_hide` callbacks +/// when a particular display objects gets shown or hidden respectively. +/// +/// This can be used for a dynamic management of GPU-side rendering. After adding a display object +/// to a scene, a new sprite can be created to display it visually. After removing the object and +/// adding it to a different scene (another GPU context), the sprite in the first context can be +/// trashed, and a new sprite in the new context can be created instead. This mechanism is currently +/// also used for sprites layer management, but this functionality is inherently connected to the +/// sprites creation in a given context (moving object to a different layer of the same [`Scene`] is +/// similar to moving it to a layer of a different [`Scene`]. +/// +/// Thus, abstracting over `Host` allows users of this library to define a view model (like few +/// sliders in a box) without the need to contain reference to a particular renderer, and attach the +/// renderer on-demand, when the objects will be placed on the stage. +/// +/// Please note, that moving an object between two Scenes (two WebGL contexts) is not implemented +/// yet. +/// +/// ## Possible changes to the Host parametrization design +/// Instead of parametrizing the Display Object, the [`Host`] could be implemented as dyn trait +/// object exposing a few options to registering sprites in Scene layers. This is a way less generic +/// solution than the one implemented currently, but it would allow [`Scene`] parametrization. For +/// example, if we would like to implement [`Scene`], where the [`Context`] is either a +/// WebGL or an OpenGL context (currently contexts are implemented as dyn traits instead). /// /// ## Scene Layers /// Each display object instance contains an optional list of [`scene::LayerId`]. During object @@ -630,13 +631,6 @@ pub struct Id(usize); /// plays a very important role in decoupling the architecture. It allows objects and their children /// to be assigned to a particular [`scene::Layer`], and thus allows for easy to use depth /// management. -/// -/// ## Future Development -/// Please note, that currently, the design is abstract over [`Host`], but it is not abstract over -/// scene layers. This may change in the future, but first, the [`Scene`] implementation has to be -/// refactored to allow the creation of [`Symbol`]s without requirement of a [`Scene`] instance -/// existence. See this ticket to learn more: https://github.com/enso-org/ide/issues/1129 . - #[derive(Derivative)] #[derive(CloneRef)] #[derivative(Clone(bound = ""))] diff --git a/lib/rust/ensogl/core/src/display/scene.rs b/lib/rust/ensogl/core/src/display/scene.rs index 4b6cc9c2d5..e0776628b7 100644 --- a/lib/rust/ensogl/core/src/display/scene.rs +++ b/lib/rust/ensogl/core/src/display/scene.rs @@ -10,6 +10,7 @@ pub use layer::Layer; pub use crate::system::web::dom::Shape; use crate::prelude::*; +use web::traits::*; use crate::animation; use crate::control::callback; @@ -30,20 +31,20 @@ use crate::display::style::data::DataMatch; use crate::display::symbol::registry::SymbolRegistry; use crate::display::symbol::Symbol; use crate::display::symbol::SymbolId; +use crate::system; use crate::system::gpu::data::attribute; use crate::system::gpu::data::uniform::Uniform; use crate::system::gpu::data::uniform::UniformScope; -use crate::system::gpu::shader::Context; use crate::system::web; -use crate::system::web::IgnoreContextMenuHandle; -use crate::system::web::NodeInserter; -use crate::system::web::StyleSetter; +use crate::system::web::EventListenerHandle; +use crate::system::Context; +use crate::system::ContextLostHandler; use enso_frp as frp; use enso_frp::io::js::CurrentJsEvent; use enso_shapely::shared; use std::any::TypeId; -use web_sys::HtmlElement; +use web::HtmlElement; pub trait MouseTarget: Debug + 'static { @@ -326,7 +327,7 @@ impl Mouse { let position = variables.add_or_panic("mouse_position", Vector2::new(0, 0)); let hover_ids = variables.add_or_panic("mouse_hover_ids", target.to_internal(&logger)); let target = Rc::new(Cell::new(target)); - let mouse_manager = MouseManager::new_separated(&root.clone_ref().into(), &web::window()); + let mouse_manager = MouseManager::new_separated(&root.clone_ref().into(), &web::window); let frp = frp::io::Mouse::new(); let on_move = mouse_manager.on_move.add(current_js_event.make_event_handler( f!([frp,scene_frp,position,last_position] (event:&mouse::OnMove) { @@ -414,10 +415,8 @@ pub struct Keyboard { impl Keyboard { pub fn new(current_event: &CurrentJsEvent) -> Self { - let logger = Logger::new("keyboard"); let frp = enso_frp::io::keyboard::Keyboard::default(); - let bindings = - Rc::new(enso_frp::io::keyboard::DomBindings::new(&logger, &frp, current_event)); + let bindings = Rc::new(enso_frp::io::keyboard::DomBindings::new(&frp, current_event)); Self { frp, bindings } } } @@ -440,12 +439,12 @@ pub struct Dom { impl Dom { /// Constructor. pub fn new(logger: &Logger) -> Self { - let root = web::create_div(); + let root = web::document.create_div_or_panic(); let layers = DomLayers::new(logger, &root); root.set_class_name("scene"); - root.set_style_or_panic("height", "100vh"); - root.set_style_or_panic("width", "100vw"); - root.set_style_or_panic("display", "block"); + root.set_style_or_warn("height", "100vh"); + root.set_style_or_warn("width", "100vw"); + root.set_style_or_warn("display", "block"); let root = web::dom::WithKnownShape::new(&root); Self { root, layers } } @@ -488,42 +487,42 @@ pub struct DomLayers { /// Front DOM scene layer. pub front: DomScene, /// The WebGL scene layer. - pub canvas: web_sys::HtmlCanvasElement, + pub canvas: web::HtmlCanvasElement, } impl DomLayers { /// Constructor. - pub fn new(logger: &Logger, dom: &web_sys::HtmlDivElement) -> Self { + pub fn new(logger: &Logger, dom: &web::HtmlDivElement) -> Self { let welcome_screen = DomScene::new(logger); welcome_screen.dom.set_class_name("welcome_screen"); - welcome_screen.dom.set_style_or_warn("z-index", "0", logger); - dom.append_or_panic(&welcome_screen.dom); + welcome_screen.dom.set_style_or_warn("z-index", "0"); + dom.append_or_warn(&welcome_screen.dom); let back = DomScene::new(logger); back.dom.set_class_name("back"); - back.dom.set_style_or_warn("z-index", "1", logger); - dom.append_or_panic(&back.dom); + back.dom.set_style_or_warn("z-index", "1"); + dom.append_or_warn(&back.dom); let fullscreen_vis = DomScene::new(logger); fullscreen_vis.dom.set_class_name("fullscreen_vis"); - fullscreen_vis.dom.set_style_or_warn("z-index", "2", logger); - dom.append_or_panic(&fullscreen_vis.dom); + fullscreen_vis.dom.set_style_or_warn("z-index", "2"); + dom.append_or_warn(&fullscreen_vis.dom); - let canvas = web::create_canvas(); - canvas.set_style_or_warn("display", "block", logger); - canvas.set_style_or_warn("z-index", "3", logger); + let canvas = web::document.create_canvas_or_panic(); + canvas.set_style_or_warn("display", "block"); + canvas.set_style_or_warn("z-index", "3"); // These properties are set by `DomScene::new` constuctor for other layers. // See its documentation for more info. - canvas.set_style_or_warn("position", "absolute", logger); - canvas.set_style_or_warn("height", "100vh", logger); - canvas.set_style_or_warn("width", "100vw", logger); - canvas.set_style_or_warn("pointer-events", "none", logger); - dom.append_or_panic(&canvas); + canvas.set_style_or_warn("position", "absolute"); + canvas.set_style_or_warn("height", "100vh"); + canvas.set_style_or_warn("width", "100vw"); + canvas.set_style_or_warn("pointer-events", "none"); + dom.append_or_warn(&canvas); let front = DomScene::new(logger); front.dom.set_class_name("front"); - front.dom.set_style_or_warn("z-index", "4", logger); - dom.append_or_panic(&front.dom); + front.dom.set_style_or_warn("z-index", "4"); + dom.append_or_warn(&front.dom); Self { back, welcome_screen, fullscreen_vis, front, canvas } } @@ -565,74 +564,107 @@ pub struct Dirty { shape: ShapeDirty, } +impl Dirty { + pub fn new(logger: &Logger, on_mut: OnMut) -> Self { + let sub_logger = Logger::new_sub(logger, "shape_dirty"); + let shape = ShapeDirty::new(sub_logger, Box::new(on_mut.clone())); + let sub_logger = Logger::new_sub(logger, "symbols_dirty"); + let symbols = SymbolRegistryDirty::new(sub_logger, Box::new(on_mut)); + Self { symbols, shape } + } +} + // ================ // === Renderer === // ================ +/// Scene renderer. Manages the initialization and lifetime of both [`render::Pipeline`] and the +/// [`render::Composer`]. +/// +/// Please note that the composer can be empty if the context was either not provided yet or it was +/// lost. #[derive(Clone, CloneRef, Debug)] +#[allow(missing_docs)] pub struct Renderer { - pub logger: Logger, - dom: Dom, - context: Context, - variables: UniformScope, - + pub logger: Logger, + dom: Dom, + variables: UniformScope, pub pipeline: Rc>, - pub composer: Rc>, + pub composer: Rc>>, } impl Renderer { - fn new(logger: impl AnyLogger, dom: &Dom, context: &Context, variables: &UniformScope) -> Self { + fn new(logger: impl AnyLogger, dom: &Dom, variables: &UniformScope) -> Self { let logger = Logger::new_sub(logger, "renderer"); let dom = dom.clone_ref(); - let context = context.clone_ref(); let variables = variables.clone_ref(); let pipeline = default(); - let shape = dom.shape().device_pixels(); - let width = shape.width as i32; - let height = shape.height as i32; - let composer = render::Composer::new(&pipeline, &context, &variables, width, height); - let pipeline = Rc::new(CloneCell::new(pipeline)); - let composer = Rc::new(RefCell::new(composer)); + let composer = default(); + Self { logger, dom, variables, pipeline, composer } + } - context.enable(Context::BLEND); - // To learn more about the blending equations used here, please see the following articles: - // - http://www.realtimerendering.com/blog/gpus-prefer-premultiplication - // - https://www.khronos.org/opengl/wiki/Blending#Colors - context.blend_equation_separate(Context::FUNC_ADD, Context::FUNC_ADD); - context.blend_func_separate( - Context::ONE, - Context::ONE_MINUS_SRC_ALPHA, - Context::ONE, - Context::ONE_MINUS_SRC_ALPHA, - ); + fn set_context(&self, context: Option<&Context>) { + let composer = context.map(|context| { + // To learn more about the blending equations used here, please see the following + // articles: + // - http://www.realtimerendering.com/blog/gpus-prefer-premultiplication + // - https://www.khronos.org/opengl/wiki/Blending#Colors + context.enable(Context::BLEND); + context.blend_equation_separate(Context::FUNC_ADD, Context::FUNC_ADD); + context.blend_func_separate( + Context::ONE, + Context::ONE_MINUS_SRC_ALPHA, + Context::ONE, + Context::ONE_MINUS_SRC_ALPHA, + ); - Self { logger, dom, context, variables, pipeline, composer } + let (width, height) = self.view_size(); + let pipeline = self.pipeline.get(); + render::Composer::new(&pipeline, context, &self.variables, width, height) + }); + *self.composer.borrow_mut() = composer; + self.update_composer_pipeline(); } /// Set the pipeline of this renderer. - pub fn set_pipeline>(&self, pipeline: P) { - let pipeline = pipeline.into(); - self.composer.borrow_mut().set_pipeline(&pipeline); + pub fn set_pipeline(&self, pipeline: render::Pipeline) { self.pipeline.set(pipeline); + self.update_composer_pipeline() + } + + /// Reload the composer pipeline. + fn update_composer_pipeline(&self) { + if let Some(composer) = &mut *self.composer.borrow_mut() { + composer.set_pipeline(&self.pipeline.get()); + } } /// Reload the composer after scene shape change. fn resize_composer(&self) { + if let Some(composer) = &mut *self.composer.borrow_mut() { + let (width, height) = self.view_size(); + composer.resize(width, height); + } + } + + // The width and height in device pixels should be integers. If they are not then this is due to + // rounding errors. We round to the nearest integer to compensate for those errors. + fn view_size(&self) -> (i32, i32) { let shape = self.dom.shape().device_pixels(); - // The width and height in device pixels should be integers. If they are not then this is - // due to rounding errors. We round to the nearest integer to compensate for those errors. let width = shape.width.round() as i32; let height = shape.height.round() as i32; - self.composer.borrow_mut().resize(width, height); + (width, height) } /// Run the renderer. pub fn run(&self) { - debug!(self.logger, "Running.", || { - self.composer.borrow_mut().run(); - }) + if let Some(composer) = &mut *self.composer.borrow_mut() { + debug!(self.logger, "Running.", || { + composer.run(); + }) + } } } @@ -643,7 +675,7 @@ impl Renderer { // ============== /// Please note that currently the `Layers` structure is implemented in a hacky way. It assumes the -/// existence of several layers, which are needed for the GUI to display shapes properly. This \ +/// existence of several layers, which are needed for the GUI to display shapes properly. This /// should be abstracted away in the future. #[derive(Clone, CloneRef, Debug)] pub struct HardcodedLayers { @@ -801,33 +833,33 @@ impl Extensions { #[derive(Clone, CloneRef, Debug)] pub struct SceneData { - pub display_object: display::object::Instance, - pub dom: Dom, - pub context: Context, - pub symbols: SymbolRegistry, - pub variables: UniformScope, - pub current_js_event: CurrentJsEvent, - pub mouse: Mouse, - pub keyboard: Keyboard, - pub uniforms: Uniforms, - pub shapes: ShapeRegistry, - pub stats: Stats, - pub dirty: Dirty, - pub logger: Logger, - pub renderer: Renderer, - pub layers: HardcodedLayers, - pub style_sheet: style::Sheet, - pub bg_color_var: style::Var, - pub bg_color_change: callback::Handle, - pub frp: Frp, - extensions: Extensions, - disable_context_menu: Rc, + pub display_object: display::object::Instance, + pub dom: Dom, + pub context: Rc>>, + pub context_lost_handler: Rc>>, + pub symbols: SymbolRegistry, + pub variables: UniformScope, + pub current_js_event: CurrentJsEvent, + pub mouse: Mouse, + pub keyboard: Keyboard, + pub uniforms: Uniforms, + pub shapes: ShapeRegistry, + pub stats: Stats, + pub dirty: Dirty, + pub logger: Logger, + pub renderer: Renderer, + pub layers: HardcodedLayers, + pub style_sheet: style::Sheet, + pub bg_color_var: style::Var, + pub bg_color_change: callback::Handle, + pub frp: Frp, + extensions: Extensions, + disable_context_menu: Rc, } impl SceneData { /// Create new instance with the provided on-dirty callback. pub fn new( - parent_dom: &HtmlElement, logger: Logger, stats: &Stats, on_mut: OnMut, @@ -835,37 +867,24 @@ impl SceneData { debug!(logger, "Initializing."); let dom = Dom::new(&logger); - parent_dom.append_child(&dom.root).unwrap(); - dom.recompute_shape_with_reflow(); - let display_object = display::object::Instance::new(&logger); display_object.force_set_visibility(true); - let context = web::get_webgl2_context(&dom.layers.canvas); - let sub_logger = Logger::new_sub(&logger, "shape_dirty"); - let shape_dirty = ShapeDirty::new(sub_logger, Box::new(on_mut.clone())); - let sub_logger = Logger::new_sub(&logger, "symbols_dirty"); - let dirty_flag = SymbolRegistryDirty::new(sub_logger, Box::new(on_mut)); - let on_change = enclose!((dirty_flag) move || dirty_flag.set()); let var_logger = Logger::new_sub(&logger, "global_variables"); let variables = UniformScope::new(var_logger); - let symbols = SymbolRegistry::mk(&variables, stats, &logger, on_change); - // FIXME: This should be abstracted away and should also handle context loss when Symbol - // definition will be finally refactored in such way, that it would not require - // Scene instance to be created. - symbols.set_context(Some(&context)); - let symbols_dirty = dirty_flag; + let dirty = Dirty::new(&logger, on_mut); + let symbols_dirty = &dirty.symbols; + let symbols = SymbolRegistry::mk(&variables, stats, &logger, f!(symbols_dirty.set())); let layers = HardcodedLayers::new(&logger); let stats = stats.clone(); let shapes = ShapeRegistry::default(); let uniforms = Uniforms::new(&variables); - let dirty = Dirty { symbols: symbols_dirty, shape: shape_dirty }; - let renderer = Renderer::new(&logger, &dom, &context, &variables); + let renderer = Renderer::new(&logger, &dom, &variables); let style_sheet = style::Sheet::new(); let current_js_event = CurrentJsEvent::new(); let frp = Frp::new(&dom.root.shape); let mouse_logger = Logger::new_sub(&logger, "mouse"); let mouse = Mouse::new(&frp, &dom.root, &variables, ¤t_js_event, mouse_logger); - let disable_context_menu = Rc::new(web::ignore_context_menu(&dom.root).unwrap()); + let disable_context_menu = Rc::new(web::ignore_context_menu(&dom.root)); let keyboard = Keyboard::new(¤t_js_event); let network = &frp.network; let extensions = Extensions::default(); @@ -873,7 +892,7 @@ impl SceneData { let bg_color_change = bg_color_var.on_change(f!([dom](change){ change.color().for_each(|color| { let color = color.to_javascript_string(); - dom.root.set_style_or_panic("background-color",color); + dom.root.set_style_or_warn("background-color",color); }) })); @@ -883,10 +902,13 @@ impl SceneData { } uniforms.pixel_ratio.set(dom.shape().pixel_ratio); + let context = default(); + let context_lost_handler = default(); Self { display_object, dom, context, + context_lost_handler, symbols, variables, current_js_event, @@ -908,6 +930,13 @@ impl SceneData { } } + pub fn set_context(&self, context: Option<&Context>) { + self.symbols.set_context(context); + *self.context.borrow_mut() = context.cloned(); + self.dirty.shape.set(); + self.renderer.set_context(context); + } + pub fn shape(&self) -> &frp::Sampler { &self.dom.root.shape } @@ -990,12 +1019,18 @@ impl SceneData { let width = canvas.width.round() as i32; let height = canvas.height.round() as i32; debug!(self.logger, "Resized to {screen.width}px x {screen.height}px.", || { - self.dom.layers.canvas.set_attribute("width", &width.to_string()).unwrap(); - self.dom.layers.canvas.set_attribute("height", &height.to_string()).unwrap(); - self.context.viewport(0, 0, width, height); + self.dom.layers.canvas.set_attribute_or_warn("width", &width.to_string()); + self.dom.layers.canvas.set_attribute_or_warn("height", &height.to_string()); + if let Some(context) = &*self.context.borrow() { + context.viewport(0, 0, width, height); + } }); } + pub fn render(&self) { + self.renderer.run() + } + pub fn screen_to_scene_coordinates(&self, position: Vector3) -> Vector3 { let position = position / self.camera().zoom(); let position = Vector4::new(position.x, position.y, position.z, 1.0); @@ -1043,26 +1078,54 @@ pub struct Scene { impl Scene { pub fn new( - parent_dom: &HtmlElement, logger: impl AnyLogger, stats: &Stats, on_mut: OnMut, ) -> Self { let logger = Logger::new_sub(logger, "scene"); - let no_mut_access = SceneData::new(parent_dom, logger, stats, on_mut); + let no_mut_access = SceneData::new(logger, stats, on_mut); let this = Self { no_mut_access }; - // FIXME MEMORY LEAK in all lines below: + // FIXME MEMORY LEAK: this.no_mut_access.shapes.rc.borrow_mut().scene = Some(this.clone_ref()); - this } + pub fn display_in(&self, parent_dom: impl DomPath) { + match parent_dom.try_into_dom_element() { + None => error!(&self.logger, "The scene host element could not be found."), + Some(parent_dom) => { + parent_dom.append_or_warn(&self.dom.root); + self.dom.recompute_shape_with_reflow(); + self.uniforms.pixel_ratio.set(self.dom.shape().pixel_ratio); + self.init(); + } + } + } + + fn init(&self) { + let context_loss_handler = crate::system::context::init_webgl_2_context(self); + match context_loss_handler { + Err(err) => error!(self.logger, "{err}"), + Ok(handler) => *self.context_lost_handler.borrow_mut() = Some(handler), + } + } + pub fn extension(&self) -> T { self.extensions.get(self) } } +impl system::context::Display for Scene { + fn device_context_handler(&self) -> &system::context::DeviceContextHandler { + &self.dom.layers.canvas + } + + fn set_context(&self, context: Option<&Context>) { + self.no_mut_access.set_context(context) + } +} + impl AsRef for Scene { fn as_ref(&self) -> &SceneData { &self.no_mut_access @@ -1084,17 +1147,19 @@ impl Deref for Scene { impl Scene { pub fn update(&self, t: animation::TimeInfo) { - debug!(self.logger, "Updating.", || { - self.frp.frame_time_source.emit(t.local); - // Please note that `update_camera` is called first as it may trigger FRP events which - // may change display objects layout. - self.update_camera(self); - self.display_object.update(self); - self.layers.update(); - self.update_shape(); - self.update_symbols(); - self.handle_mouse_events(); - }) + if self.context.borrow().is_some() { + debug!(self.logger, "Updating.", || { + self.frp.frame_time_source.emit(t.local); + // Please note that `update_camera` is called first as it may trigger FRP events + // which may change display objects layout. + self.update_camera(self); + self.display_object.update(self); + self.layers.update(); + self.update_shape(); + self.update_symbols(); + self.handle_mouse_events(); + }) + } } } @@ -1109,3 +1174,46 @@ impl display::Object for Scene { &self.display_object } } + + + +// =============== +// === DomPath === +// =============== + +/// Abstraction for DOM path. It can be either a specific HTML element or a DOM id, a string used +/// to query the DOM structure to find the corresponding HTML node. +#[allow(missing_docs)] +pub trait DomPath { + fn try_into_dom_element(self) -> Option; +} + +impl DomPath for HtmlElement { + fn try_into_dom_element(self) -> Option { + Some(self) + } +} + +impl<'t> DomPath for &'t HtmlElement { + fn try_into_dom_element(self) -> Option { + Some(self.clone()) + } +} + +impl DomPath for String { + fn try_into_dom_element(self) -> Option { + web::document.get_html_element_by_id(&self) + } +} + +impl<'t> DomPath for &'t String { + fn try_into_dom_element(self) -> Option { + web::document.get_html_element_by_id(self) + } +} + +impl<'t> DomPath for &'t str { + fn try_into_dom_element(self) -> Option { + web::document.get_html_element_by_id(self) + } +} diff --git a/lib/rust/ensogl/core/src/display/scene/dom.rs b/lib/rust/ensogl/core/src/display/scene/dom.rs index f083015a1f..805bb43279 100644 --- a/lib/rust/ensogl/core/src/display/scene/dom.rs +++ b/lib/rust/ensogl/core/src/display/scene/dom.rs @@ -1,6 +1,7 @@ //! This module defines a DOM management utilities. use crate::prelude::*; +use web::traits::*; use crate::display::camera::camera2d::Projection; use crate::display::camera::Camera2d; @@ -8,13 +9,13 @@ use crate::display::object::traits::*; use crate::display::symbol::dom::eps; use crate::display::symbol::dom::inverse_y_translation; use crate::display::symbol::DomSymbol; -use crate::system::gpu::data::JsBufferView; use crate::system::web; -use crate::system::web::NodeInserter; -use crate::system::web::StyleSetter; +use web::HtmlDivElement; +#[cfg(target_arch = "wasm32")] +use crate::system::gpu::data::JsBufferView; +#[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::wasm_bindgen; -use web_sys::HtmlDivElement; @@ -22,6 +23,7 @@ use web_sys::HtmlDivElement; // === Js Bindings === // =================== +#[cfg(target_arch = "wasm32")] mod js { use super::*; #[wasm_bindgen(inline_js = " @@ -64,6 +66,8 @@ mod js { } } + +#[cfg(target_arch = "wasm32")] #[allow(unsafe_code)] fn setup_camera_perspective(dom: &web::JsValue, near: f32, matrix: &Matrix4) { // Views to WASM memory are only valid as long the backing buffer isn't @@ -75,6 +79,7 @@ fn setup_camera_perspective(dom: &web::JsValue, near: f32, matrix: &Matrix4 } } +#[cfg(target_arch = "wasm32")] #[allow(unsafe_code)] fn setup_camera_orthographic(dom: &web::JsValue, matrix: &Matrix4) { // Views to WASM memory are only valid as long the backing buffer isn't @@ -86,6 +91,18 @@ fn setup_camera_orthographic(dom: &web::JsValue, matrix: &Matrix4) { } } +#[cfg(not(target_arch = "wasm32"))] +mod js { + use super::*; + pub fn setup_perspective(_dom: &web::JsValue, _znear: &web::JsValue) {} +} + +#[cfg(not(target_arch = "wasm32"))] +fn setup_camera_perspective(_dom: &web::JsValue, _near: f32, _matrix: &Matrix4) {} + +#[cfg(not(target_arch = "wasm32"))] +fn setup_camera_orthographic(_dom: &web::JsValue, _matrix: &Matrix4) {} + // ============= @@ -149,27 +166,27 @@ impl DomScene { /// Constructor. pub fn new(logger: impl AnyLogger) -> Self { let logger = Logger::new_sub(logger, "DomScene"); - let dom = web::create_div(); - let view_projection_dom = web::create_div(); + let dom = web::document.create_div_or_panic(); + let view_projection_dom = web::document.create_div_or_panic(); dom.set_class_name("dom-scene-layer"); // z-index works on positioned elements only. - dom.set_style_or_warn("position", "absolute", &logger); - dom.set_style_or_warn("top", "0px", &logger); - dom.set_style_or_warn("overflow", "hidden", &logger); - dom.set_style_or_warn("overflow", "hidden", &logger); - dom.set_style_or_warn("width", "100%", &logger); - dom.set_style_or_warn("height", "100%", &logger); + dom.set_style_or_warn("position", "absolute"); + dom.set_style_or_warn("top", "0px"); + dom.set_style_or_warn("overflow", "hidden"); + dom.set_style_or_warn("overflow", "hidden"); + dom.set_style_or_warn("width", "100%"); + dom.set_style_or_warn("height", "100%"); // We ignore pointer events to avoid stealing them from other DomScenes. // See https://github.com/enso-org/enso/blob/develop/lib/rust/ensogl/doc/mouse-handling.md. - dom.set_style_or_warn("pointer-events", "none", &logger); + dom.set_style_or_warn("pointer-events", "none"); view_projection_dom.set_class_name("view_projection"); - view_projection_dom.set_style_or_warn("width", "100%", &logger); - view_projection_dom.set_style_or_warn("height", "100%", &logger); - view_projection_dom.set_style_or_warn("transform-style", "preserve-3d", &logger); + view_projection_dom.set_style_or_warn("width", "100%"); + view_projection_dom.set_style_or_warn("height", "100%"); + view_projection_dom.set_style_or_warn("transform-style", "preserve-3d"); - dom.append_or_warn(&view_projection_dom, &logger); + dom.append_or_warn(&view_projection_dom); let data = DomSceneData::new(dom, view_projection_dom, logger); let data = Rc::new(data); @@ -183,13 +200,13 @@ impl DomScene { /// Sets the z-index of this DOM element. pub fn set_z_index(&self, z: i32) { - self.data.dom.set_style_or_warn("z-index", z.to_string(), &self.logger); + self.data.dom.set_style_or_warn("z-index", z.to_string()); } /// Sets the CSS property `filter: grayscale({value})` on this element. A value of 0.0 displays /// the element normally. A value of 1.0 will make the element completely gray. pub fn filter_grayscale(&self, value: f32) { - self.data.dom.set_style_or_warn("filter", format!("grayscale({})", value), &self.logger); + self.data.dom.set_style_or_warn("filter", format!("grayscale({})", value)); } /// Creates a new instance of DomSymbol and adds it to parent. @@ -197,11 +214,11 @@ impl DomScene { let dom = object.dom(); let data = &self.data; if object.is_visible() { - self.view_projection_dom.append_or_panic(dom); + self.view_projection_dom.append_or_warn(dom); } object.display_object().set_on_hide(f_!(dom.remove())); object.display_object().set_on_show(f__!([data,dom] { - data.view_projection_dom.append_or_panic(&dom) + data.view_projection_dom.append_or_warn(&dom) })); } diff --git a/lib/rust/ensogl/core/src/display/shape/primitive/shader/overload.rs b/lib/rust/ensogl/core/src/display/shape/primitive/shader/overload.rs index 05b4bcb5b6..c31a9b78d4 100644 --- a/lib/rust/ensogl/core/src/display/shape/primitive/shader/overload.rs +++ b/lib/rust/ensogl/core/src/display/shape/primitive/shader/overload.rs @@ -4,14 +4,33 @@ use wasm_bindgen::prelude::*; -#[wasm_bindgen(module = "/src/display/shape/primitive/glsl/overload.js")] -extern "C" { - /// Returns GLSL code which redirects mangled function names to their original primitive - /// definitions. - #[allow(unsafe_code)] - pub fn builtin_redirections() -> String; +mod js { + use super::*; + #[wasm_bindgen(module = "/src/display/shape/primitive/glsl/overload.js")] + extern "C" { + /// Returns GLSL code which redirects mangled function names to their original primitive + /// definitions. + #[allow(unsafe_code)] + pub fn builtin_redirections() -> String; - /// Mangles the provided GLSL code to allow primitive definitions overloading. - #[allow(unsafe_code)] - pub fn allow_overloading(s: &str) -> String; + /// Mangles the provided GLSL code to allow primitive definitions overloading. + #[allow(unsafe_code)] + pub fn allow_overloading(s: &str) -> String; + } } + +#[allow(missing_docs)] +mod mock { + pub fn builtin_redirections() -> String { + "".into() + } + pub fn allow_overloading(_: &str) -> String { + "".into() + } +} + +#[cfg(target_arch = "wasm32")] +pub use js::*; + +#[cfg(not(target_arch = "wasm32"))] +pub use mock::*; diff --git a/lib/rust/ensogl/core/src/display/style/javascript.rs b/lib/rust/ensogl/core/src/display/style/javascript.rs index 63d72153bd..94c1e4acbf 100644 --- a/lib/rust/ensogl/core/src/display/style/javascript.rs +++ b/lib/rust/ensogl/core/src/display/style/javascript.rs @@ -7,11 +7,14 @@ use wasm_bindgen::prelude::*; use super::sheet::Data; use super::sheet::Value; use super::theme::Manager; - -use crate::system::web; -use js_sys; use wasm_bindgen::prelude::Closure; +#[cfg(target_arch = "wasm32")] +use crate::system::web; + +#[cfg(target_arch = "wasm32")] +use js_sys; + // =========================== @@ -128,8 +131,6 @@ mod js { /// Expose the `window.theme` variable which can be used to inspect and change the theme directly /// from the JavaScript console. pub fn expose_to_window(manager: &Manager) { - let window = web::window(); - let list: js::List = Closure::new(f!([manager]() format!("{:?}",manager.keys()))); let choose: js::Choose = Closure::new(f!((name) manager.set_enabled(&[name]))); let snapshot: js::Snapshot = Closure::new(f!((name) manager.snapshot(name))); @@ -168,13 +169,16 @@ pub fn expose_to_window(manager: &Manager) { theme_ref }); + #[cfg(target_arch = "wasm32")] + let window = web::window; + #[cfg(target_arch = "wasm32")] let theme_manger_ref = js::create_theme_manager_ref(&list, &choose, &get, &snapshot, &diff); + #[cfg(target_arch = "wasm32")] + js_sys::Reflect::set(&window, &"theme".into(), &theme_manger_ref).ok(); mem::forget(list); mem::forget(choose); mem::forget(snapshot); mem::forget(diff); mem::forget(get); - - js_sys::Reflect::set(&window, &"theme".into(), &theme_manger_ref).ok(); } diff --git a/lib/rust/ensogl/core/src/display/style/sheet.rs b/lib/rust/ensogl/core/src/display/style/sheet.rs index 25f5f489f1..443a9844f3 100644 --- a/lib/rust/ensogl/core/src/display/style/sheet.rs +++ b/lib/rust/ensogl/core/src/display/style/sheet.rs @@ -3,6 +3,7 @@ use crate::prelude::*; use crate::control::callback; +use crate::control::callback::traits::*; use crate::data::HashMapTree; use crate::data::Index; use crate::data::OptVec; @@ -885,7 +886,7 @@ pub struct Sheet { } /// Type of callback registry used in `Sheet`. -pub type CallbackRegistry = callback::SharedRegistryMut1>; +pub type CallbackRegistry = callback::registry::RefMut1>; impl Sheet { /// Constructor. diff --git a/lib/rust/ensogl/core/src/display/style/theme.rs b/lib/rust/ensogl/core/src/display/style/theme.rs index 006a7ed97d..2997edd456 100644 --- a/lib/rust/ensogl/core/src/display/style/theme.rs +++ b/lib/rust/ensogl/core/src/display/style/theme.rs @@ -10,6 +10,7 @@ use super::sheet::Change; use super::sheet::Path; use super::sheet::Value; use crate::control::callback; +use crate::control::callback::traits::*; use crate::data::dirty; use crate::data::dirty::traits::*; @@ -25,7 +26,7 @@ use crate::data::dirty::traits::*; #[derive(Clone, CloneRef, Debug, Default)] pub struct Theme { tree: Rc>>>, - on_mut: callback::SharedRegistryMut, + on_mut: callback::registry::MutNoArgs, } impl Theme { @@ -60,7 +61,7 @@ impl Theme { } /// Add a new callback which will be triggered everytime this theme is modified. - pub fn on_mut(&self, callback: impl callback::CallbackMutFn) -> callback::Handle { + pub fn on_mut(&self, callback: impl callback::MutNoArgs) -> callback::Handle { self.on_mut.add(callback) } diff --git a/lib/rust/ensogl/core/src/display/symbol/dom.rs b/lib/rust/ensogl/core/src/display/symbol/dom.rs index b954e22fc8..8d6fb0d6de 100644 --- a/lib/rust/ensogl/core/src/display/symbol/dom.rs +++ b/lib/rust/ensogl/core/src/display/symbol/dom.rs @@ -2,16 +2,20 @@ //! elements on the scene. use crate::prelude::*; +use web::traits::*; use crate::display; use crate::display::object::traits::*; +#[cfg(target_arch = "wasm32")] use crate::system::gpu::data::JsBufferView; use crate::system::web; -use crate::system::web::NodeInserter; -use crate::system::web::StyleSetter; +// #[cfg(target_arch = "wasm32")] +// use crate::system::web::traits::*; + +#[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::wasm_bindgen; -use web_sys::HtmlDivElement; +use web::HtmlDivElement; @@ -19,6 +23,7 @@ use web_sys::HtmlDivElement; // === Js Bindings === // =================== +#[cfg(target_arch = "wasm32")] mod js { use super::*; #[wasm_bindgen(inline_js = " @@ -38,9 +43,15 @@ mod js { } } +#[cfg(not(target_arch = "wasm32"))] +mod js { + use super::*; + pub fn set_object_transform(_dom: &web::JsValue, _matrix_array: &web::Object) {} +} /// Sets the object transform as the CSS style property. #[allow(unsafe_code)] +#[cfg(target_arch = "wasm32")] pub fn set_object_transform(dom: &web::JsValue, matrix: &Matrix4) { // Views to WASM memory are only valid as long the backing buffer isn't // resized. Check documentation of IntoFloat32ArrayView trait for more @@ -51,6 +62,10 @@ pub fn set_object_transform(dom: &web::JsValue, matrix: &Matrix4) { } } +#[cfg(not(target_arch = "wasm32"))] +#[allow(missing_docs)] +pub fn set_object_transform(_dom: &web::JsValue, _matrix: &Matrix4) {} + // ============= @@ -98,14 +113,14 @@ pub struct DomSymbol { impl DomSymbol { /// Constructor. - pub fn new(content: &web_sys::Node) -> Self { + pub fn new(content: &web::Node) -> Self { let logger = Logger::new("DomSymbol"); let size = Rc::new(Cell::new(Vector2::new(0.0, 0.0))); - let dom = web::create_div(); - dom.set_style_or_warn("position", "absolute", &logger); - dom.set_style_or_warn("width", "0px", &logger); - dom.set_style_or_warn("height", "0px", &logger); - dom.append_or_panic(content); + let dom = web::document.create_div_or_panic(); + dom.set_style_or_warn("position", "absolute"); + dom.set_style_or_warn("width", "0px"); + dom.set_style_or_warn("height", "0px"); + dom.append_or_warn(content); let display_object = display::object::Instance::new(logger); let guard = Rc::new(Guard::new(&display_object, &dom)); display_object.set_on_updated(enclose!((dom) move |t| { @@ -130,8 +145,8 @@ impl DomSymbol { /// Size setter. pub fn set_size(&self, size: Vector2) { self.size.set(size); - self.dom.set_style_or_panic("width", format!("{}px", size.x)); - self.dom.set_style_or_panic("height", format!("{}px", size.y)); + self.dom.set_style_or_warn("width", format!("{}px", size.x)); + self.dom.set_style_or_warn("height", format!("{}px", size.y)); } } diff --git a/lib/rust/ensogl/core/src/display/symbol/gpu.rs b/lib/rust/ensogl/core/src/display/symbol/gpu.rs index ff11d7afd7..76c107cf1e 100644 --- a/lib/rust/ensogl/core/src/display/symbol/gpu.rs +++ b/lib/rust/ensogl/core/src/display/symbol/gpu.rs @@ -268,7 +268,6 @@ pub struct Symbol { surface_dirty: GeometryDirty, shader_dirty: ShaderDirty, variables: UniformScope, - global_variables: UniformScope, /// Please note that changing the uniform type to `u32` breaks node ID encoding in GLSL, as the /// functions are declared to work on `int`s, not `uint`s. This might be improved one day. symbol_id_uniform: Uniform, @@ -281,13 +280,8 @@ pub struct Symbol { impl Symbol { /// Create new instance with the provided on-dirty callback. - pub fn new( - logger: Logger, - stats: &Stats, - id: SymbolId, - global_variables: &UniformScope, - on_mut: OnMut, - ) -> Self { + pub fn new(stats: &Stats, id: SymbolId, on_mut: OnMut) -> Self { + let logger = Logger::new(format!("symbol_{}", id)); let init_logger = logger.clone(); debug!(init_logger, "Initializing.", || { let on_mut2 = on_mut.clone(); @@ -302,7 +296,6 @@ impl Symbol { let shader = Shader::new(shader_logger, stats, shader_on_mut); let surface = Mesh::new(surface_logger, stats, surface_on_mut); let variables = UniformScope::new(Logger::new_sub(&logger, "uniform_scope")); - let global_variables = global_variables.clone_ref(); let bindings = default(); let stats = SymbolStats::new(stats); let context = default(); @@ -317,7 +310,6 @@ impl Symbol { surface_dirty, shader_dirty, variables, - global_variables, symbol_id_uniform, context, logger, @@ -352,27 +344,33 @@ impl Symbol { } /// Check dirty flags and update the state accordingly. - pub fn update(&self) { - debug!(self.logger, "Updating.", || { - if self.surface_dirty.check() { - self.surface.update(); - self.surface_dirty.unset(); - } - if self.shader_dirty.check() { - let var_bindings = self.discover_variable_bindings(); - self.shader.update(&var_bindings); - self.init_variable_bindings(&var_bindings); - self.shader_dirty.unset(); - } - }) + pub fn update(&self, global_variables: &UniformScope) { + if self.context.borrow().is_some() { + debug!(self.logger, "Updating.", || { + if self.surface_dirty.check() { + self.surface.update(); + self.surface_dirty.unset(); + } + if self.shader_dirty.check() { + let var_bindings = self.discover_variable_bindings(global_variables); + self.shader.update(&var_bindings); + self.init_variable_bindings(&var_bindings, global_variables); + self.shader_dirty.unset(); + } + }) + } } - pub fn lookup_variable(&self, name: S) -> Option { + pub fn lookup_variable( + &self, + name: S, + global_variables: &UniformScope, + ) -> Option { let name = name.as_ref(); self.surface.lookup_variable(name).map(ScopeType::Mesh).or_else(|| { if self.variables.contains(name) { Some(ScopeType::Symbol) - } else if self.global_variables.contains(name) { + } else if global_variables.contains(name) { Some(ScopeType::Global) } else { None @@ -484,7 +482,11 @@ impl Symbol { impl Symbol { /// Creates a new VertexArrayObject, discovers all variable bindings from shader to geometry, /// and initializes the VAO with the bindings. - fn init_variable_bindings(&self, var_bindings: &[shader::VarBinding]) { + fn init_variable_bindings( + &self, + var_bindings: &[shader::VarBinding], + global_variables: &UniformScope, + ) { if let Some(context) = &*self.context.borrow() { let max_texture_units = context.get_parameter(Context::MAX_TEXTURE_IMAGE_UNITS); let max_texture_units = max_texture_units.unwrap_or_else(|num| { @@ -511,6 +513,7 @@ impl Symbol { program, binding, &mut texture_unit_iter, + global_variables, ), None => {} } @@ -548,6 +551,7 @@ impl Symbol { program: &WebGlProgram, binding: &shader::VarBinding, texture_unit_iter: &mut dyn Iterator, + global_variables: &UniformScope, ) { let name = &binding.name; let uni_name = shader::builder::mk_uniform_name(name); @@ -556,7 +560,7 @@ impl Symbol { opt_location.map(|location| { let uniform = match &binding.scope { Some(ScopeType::Symbol) => self.variables.get(name), - Some(ScopeType::Global) => self.global_variables.get(name), + Some(ScopeType::Global) => global_variables.get(name), _ => todo!(), }; let uniform = uniform.unwrap_or_else(|| { @@ -588,12 +592,15 @@ impl Symbol { } /// For each variable from the shader definition, looks up its position in geometry scopes. - fn discover_variable_bindings(&self) -> Vec { + fn discover_variable_bindings( + &self, + global_variables: &UniformScope, + ) -> Vec { let var_decls = self.shader.collect_variables(); var_decls .into_iter() .map(|(var_name, var_decl)| { - let target = self.lookup_variable(&var_name); + let target = self.lookup_variable(&var_name, global_variables); if target.is_none() { warning!( self.logger, diff --git a/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/primitive/mesh.rs b/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/primitive/mesh.rs index 7941b151d4..40fbb183d5 100644 --- a/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/primitive/mesh.rs +++ b/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/primitive/mesh.rs @@ -2,11 +2,11 @@ use crate::prelude::*; -use crate::control::callback::CallbackFn; +use crate::control::callback; use crate::data::dirty; use crate::data::dirty::traits::*; use crate::debug::stats::Stats; -use crate::system::gpu::shader::Context; +use crate::system::Context; use num_enum::IntoPrimitive; @@ -119,7 +119,7 @@ pub struct MeshData { impl { /// Creates new mesh with attached dirty callback. - pub fn new + pub fn new (logger:Logger, stats:&Stats, on_mut:OnMut) -> Self { stats.inc_mesh_count(); let stats = stats.clone(); diff --git a/lib/rust/ensogl/core/src/display/symbol/gpu/registry.rs b/lib/rust/ensogl/core/src/display/symbol/gpu/registry.rs index 91323ea3f5..e5d4fef265 100644 --- a/lib/rust/ensogl/core/src/display/symbol/gpu/registry.rs +++ b/lib/rust/ensogl/core/src/display/symbol/gpu/registry.rs @@ -11,7 +11,7 @@ use crate::display::symbol::Symbol; use crate::display::symbol::SymbolId; use crate::system::gpu::data::uniform::Uniform; use crate::system::gpu::data::uniform::UniformScope; -use crate::system::gpu::shader::Context; +use crate::system::Context; use data::opt_vec::OptVec; @@ -69,14 +69,11 @@ impl SymbolRegistry { /// Creates a new `Symbol` instance and returns its id. pub fn new_get_id(&self) -> SymbolId { let symbol_dirty = self.symbol_dirty.clone(); - let variables = &self.variables; - let logger = &self.logger; let stats = &self.stats; let index = self.symbols.borrow_mut().insert_with_ix_(|ix| { let id = SymbolId::new(ix as u32); let on_mut = move || symbol_dirty.set(id); - let logger = Logger::new_sub(logger, format!("symbol_{}", ix)); - let symbol = Symbol::new(logger, stats, id, variables, on_mut); + let symbol = Symbol::new(stats, id, on_mut); symbol.set_context(self.context.borrow().as_ref()); symbol }); @@ -107,7 +104,7 @@ impl SymbolRegistry { pub fn update(&self) { debug!(self.logger, "Updating.", || { for id in self.symbol_dirty.take().iter() { - self.symbols.borrow()[(**id) as usize].update() + self.symbols.borrow()[(**id) as usize].update(&self.variables) } self.symbol_dirty.unset_all(); }) diff --git a/lib/rust/ensogl/core/src/display/symbol/gpu/shader.rs b/lib/rust/ensogl/core/src/display/symbol/gpu/shader.rs index 6934c5652b..5329ceeb51 100644 --- a/lib/rust/ensogl/core/src/display/symbol/gpu/shader.rs +++ b/lib/rust/ensogl/core/src/display/symbol/gpu/shader.rs @@ -5,16 +5,17 @@ pub mod builder; use crate::prelude::*; -use crate::control::callback::CallbackFn; +use crate::control::callback; use crate::data::dirty; use crate::data::dirty::traits::*; use crate::debug::stats::Stats; use crate::display::symbol::material::Material; use crate::display::symbol::material::VarDecl; use crate::display::symbol::shader; +use crate::display::symbol::shader::ContextLossOrError; use crate::display::symbol::ScopeType; -use crate::system::gpu::shader::Context; use crate::system::gpu::shader::*; +use crate::system::Context; use web_sys::WebGlProgram; @@ -85,7 +86,7 @@ impl { } /// Creates new shader with attached callback. - pub fn new(logger:Logger, stats:&Stats, on_mut:OnMut) -> Self { + pub fn new(logger:Logger, stats:&Stats, on_mut:OnMut) -> Self { stats.inc_shader_count(); let context = default(); let geometry_material = default(); @@ -134,22 +135,17 @@ impl { shader_cfg.add_output(name,&decl.tp); }); - let vertex_code = self.geometry_material.code().clone(); + let vertex_code = self.geometry_material.code().clone(); let fragment_code = self.surface_material.code().clone(); shader_builder.compute(&shader_cfg,vertex_code,fragment_code); - let shader = shader_builder.build(); - let vert_shader = compile_vertex_shader (context,&shader.vertex); - let frag_shader = compile_fragment_shader(context,&shader.fragment); - if let Err(ref err) = frag_shader { - error!(self.logger,"{err}") + let shader = shader_builder.build(); + let program = compile_program(context,&shader.vertex,&shader.fragment); + match program { + Ok(program) => { self.program = Some(program);} + Err(ContextLossOrError::Error(err)) => error!(self.logger, "{err}"), + Err(ContextLossOrError::ContextLoss) => {} } - let vert_shader = vert_shader.unwrap(); - let frag_shader = frag_shader.unwrap(); - let program = link_program(context,&vert_shader,&frag_shader); - - let program = program.unwrap(); - self.program = Some(program); self.dirty.unset_all(); } } diff --git a/lib/rust/ensogl/core/src/display/world.rs b/lib/rust/ensogl/core/src/display/world.rs index 4a6da5d604..aa688ae2e9 100644 --- a/lib/rust/ensogl/core/src/display/world.rs +++ b/lib/rust/ensogl/core/src/display/world.rs @@ -7,6 +7,7 @@ use crate::prelude::*; use crate::animation; use crate::control::callback; +use crate::control::callback::traits::*; use crate::data::dirty; use crate::data::dirty::traits::*; use crate::debug; @@ -16,12 +17,14 @@ use crate::display; use crate::display::render; use crate::display::render::passes::SymbolsRenderPass; use crate::display::render::*; +use crate::display::scene::DomPath; use crate::display::scene::Scene; use crate::system::web; +use crate::system::web::traits::*; -use wasm_bindgen::prelude::Closure; -use wasm_bindgen::JsCast; -use wasm_bindgen::JsValue; +use web::prelude::Closure; +use web::JsCast; +use web::JsValue; @@ -51,107 +54,184 @@ impl Uniforms { // === World === // ============= -/// World is the top-level application structure. It used to manage several instances of -/// `Scene`, and there is probability that we will get back to this design in the future. -/// It is responsible for updating the system on every animation frame. -#[derive(Clone, CloneRef, Debug)] +/// The root object for EnsoGL scenes. +#[derive(Clone, CloneRef, Debug, Default)] pub struct World { - logger: Logger, - scene: Scene, - scene_dirty: dirty::SharedBool, - main_loop: animation::DynamicLoop, - uniforms: Uniforms, - stats: Stats, - stats_monitor: debug::monitor::Monitor, - stats_draw_handle: callback::Handle, - main_loop_frame: callback::Handle, - on_before_frame: callback::SharedRegistryMut1, - on_after_frame: callback::SharedRegistryMut1, - on_stats_available: callback::SharedRegistryMut1, + rc: Rc, } impl World { + /// Constructor. + pub fn new() -> Self { + Self::default() + } + + /// Constructor modifier. Displays the default scene in the provided path. + pub fn displayed_in(self, dom: impl DomPath) -> Self { + self.default_scene.display_in(dom); + self + } + + /// Keeps the world alive even when all references are dropped. Use only if you want to keep one + /// instance of the world forever. + pub fn keep_alive_forever(&self) { + mem::forget(self.clone_ref()) + } +} + +impl Deref for World { + type Target = WorldDataWithLoop; + fn deref(&self) -> &Self::Target { + &*self.rc + } +} + +impl display::Object for World { + fn display_object(&self) -> &display::object::Instance { + self.default_scene.display_object() + } +} + +impl<'t> From<&'t World> for &'t Scene { + fn from(world: &'t World) -> Self { + &world.default_scene + } +} + + + +// ========================= +// === WorldDataWithLoop === +// ========================= + +/// Main loop closure type. +pub type MainLoop = animation::Loop>; + +/// World data with a main loop implementation. +/// +/// # Main Loop Performance +/// Any code repeated on each iteration of the Main Loop (each "frame") must be written with a high +/// care for performance. Any changes that has a chance of negatively impacting the constant +/// overhead of the main loop needs *explicit* explanation, review, and acceptance *at the design +/// stage* of the proposed new implementation, from performance perspective, with an +/// explicit note of the fact of Main Loop impact. +/// +/// Rationale: the "Main Loop" contains the code comprising a GUI rendering "frame" (term +/// originating from a "still frame" term in filmmaking). The speed at which the Main Loop executes +/// directly translates to the perceived performance of the GUI, and the FPS (frames per second) +/// metric, impacting Users' experience with the application. +#[derive(Debug)] +pub struct WorldDataWithLoop { + main_loop: MainLoop, + data: WorldData, +} + +impl WorldDataWithLoop { + /// Constructor. + pub fn new() -> Self { + let data = WorldData::new(); + let main_loop = MainLoop::new(Box::new(f!([data](t) data.go_to_next_frame_with_time(t)))); + Self { main_loop, data } + } +} + +impl Default for WorldDataWithLoop { + fn default() -> Self { + Self::new() + } +} + +impl Deref for WorldDataWithLoop { + type Target = WorldData; + fn deref(&self) -> &Self::Target { + &self.data + } +} + + + +// ================= +// === Callbacks === +// ================= + +/// Callbacks that are run during rendering of the frame. +#[derive(Clone, CloneRef, Debug, Default)] +#[allow(missing_docs)] +pub struct Callbacks { + pub prev_frame_stats: callback::registry::RefMut1, + pub before_frame: callback::registry::CopyMut1, + pub after_frame: callback::registry::CopyMut1, +} + + + +// ================= +// === WorldData === +// ================= + +/// The data kept by the [`World`]. +#[derive(Debug, Clone, CloneRef)] +#[allow(missing_docs)] +pub struct WorldData { + logger: Logger, + pub default_scene: Scene, + scene_dirty: dirty::SharedBool, + uniforms: Uniforms, + stats: Stats, + stats_monitor: debug::monitor::Monitor, + stats_draw_handle: callback::Handle, + pub on: Callbacks, + debug_hotkeys_handle: Rc>>, +} + +impl WorldData { /// Create and initialize new world instance. - #[allow(clippy::new_ret_no_self)] - pub fn new(dom: &web_sys::HtmlElement) -> World { + pub fn new() -> Self { let logger = Logger::new("world"); - let stats = debug::stats::Stats::new(web::performance()); + let stats = debug::stats::Stats::new(web::window.performance_or_panic()); + let stats_monitor = debug::monitor::Monitor::new(); + let on = Callbacks::default(); let scene_dirty = dirty::SharedBool::new(Logger::new_sub(&logger, "scene_dirty"), ()); let on_change = enclose!((scene_dirty) move || scene_dirty.set()); - let scene = Scene::new(dom, &logger, &stats, on_change); - let uniforms = Uniforms::new(&scene.variables); - let main_loop = animation::DynamicLoop::new(); - let stats_monitor = debug::monitor::Monitor::new(); - let on_before_frame = >::new(); - let on_after_frame = >::new(); - let on_stats_available = >::new(); - let stats_draw_handle = on_stats_available.add(f!([stats_monitor] (stats: &StatsData) { + let default_scene = Scene::new(&logger, &stats, on_change); + let uniforms = Uniforms::new(&default_scene.variables); + let debug_hotkeys_handle = default(); + + let stats_draw_handle = on.prev_frame_stats.add(f!([stats_monitor] (stats: &StatsData) { stats_monitor.sample_and_draw(stats); })); - let main_loop_frame = main_loop.on_frame( - f!([stats,on_before_frame,on_after_frame,on_stats_available,uniforms,scene_dirty,scene] - (t:animation::TimeInfo) { - // Note [Main Loop Performance] - - let previous_frame_stats = stats.begin_frame(); - on_before_frame.run_all(&t); - if let Some(stats) = previous_frame_stats { - on_stats_available.run_all(&stats); - } - - uniforms.time.set(t.local); - scene_dirty.unset_all(); - scene.update(t); - scene.renderer.run(); - - on_after_frame.run_all(&t); - stats.end_frame(); - }), - ); Self { logger, - scene, + default_scene, scene_dirty, - main_loop, uniforms, stats, + on, + debug_hotkeys_handle, stats_monitor, stats_draw_handle, - main_loop_frame, - on_before_frame, - on_after_frame, - on_stats_available, } .init() } - // Note [Main Loop Performance] - // ============================ - // Any code repeated on each iteration of the Main Loop (each "frame") must be written with - // high care for performance. Any changes that has a chance of negatively impacting the - // constant overhead of the main loop needs *explicit* explanation, review, and acceptance *at - // design stage* of the proposed new implementation, from performance perspective, with an - // explicit note of the fact of Main Loop impact. - // - // Rationale: the "Main Loop" contains the code comprising a GUI rendering "frame" (term - // originating from a "still frame" term in filmmaking). The speed at which the Main Loop - // executes directly translates to the perceived performance of the GUI, and the FPS (frames - // per second) metric, impacting Users' experience with the application. - fn init(self) -> Self { + self.init_environment(); self.init_composer(); - self.init_hotkeys(); + self.init_debug_hotkeys(); self } - fn init_hotkeys(&self) { - // ----------------------------------------------------------------------------------------- - // FIXME[WD]: Hacky way of switching display_mode. To be fixed and refactored out. + fn init_environment(&self) { + web::forward_panic_hook_to_console(); + web::set_stack_trace_limit(); + } + + fn init_debug_hotkeys(&self) { let stats_monitor = self.stats_monitor.clone_ref(); let display_mode = self.uniforms.display_mode.clone_ref(); - let c: Closure = Closure::wrap(Box::new(move |val| { - let event = val.unchecked_into::(); + let closure: Closure = Closure::new(move |val: JsValue| { + let event = val.unchecked_into::(); if event.alt_key() && event.ctrl_key() { let key = event.code(); if key == "Backquote" { @@ -164,88 +244,72 @@ impl World { display_mode.set(2) } } - })); - web::window() - .add_event_listener_with_callback_and_bool("keydown", c.as_ref().unchecked_ref(), true) - .unwrap(); - c.forget(); - // ----------------------------------------------------------------------------------------- + }); + let handle = web::add_event_listener_with_bool(&web::window, "keydown", closure, true); + *self.debug_hotkeys_handle.borrow_mut() = Some(handle); } fn init_composer(&self) { - let mouse_hover_ids = self.scene.mouse.hover_ids.clone_ref(); - let mut pixel_read_pass = PixelReadPass::::new(&self.scene.mouse.position); + let mouse_hover_ids = self.default_scene.mouse.hover_ids.clone_ref(); + let mut pixel_read_pass = PixelReadPass::::new(&self.default_scene.mouse.position); pixel_read_pass.set_callback(move |v| { mouse_hover_ids.set(Vector4::from_iterator(v.iter().map(|value| *value as u32))) }); // TODO: We may want to enable it on weak hardware. // pixel_read_pass.set_threshold(1); - let logger = &self.scene.renderer.logger; + let logger = Logger::new("renderer"); let pipeline = render::Pipeline::new() .add(SymbolsRenderPass::new( &logger, - &self.scene, - self.scene.symbols(), - &self.scene.layers, + &self.default_scene, + self.default_scene.symbols(), + &self.default_scene.layers, )) - .add(ScreenRenderPass::new(&self.scene)) + .add(ScreenRenderPass::new(&self.default_scene)) .add(pixel_read_pass); - self.scene.renderer.set_pipeline(pipeline); + self.default_scene.renderer.set_pipeline(pipeline); } - /// Scene accessor. - pub fn scene(&self) -> &Scene { - &self.scene - } - - /// Register a callback which should be run before each animation frame. - pub fn on_before_frame( - &self, - mut callback: F, - ) -> callback::Handle { - self.on_before_frame.add(move |time: &animation::TimeInfo| callback(*time)) - } - - /// Register a callback which should be run after each animation frame. - pub fn on_after_frame( - &self, - mut callback: F, - ) -> callback::Handle { - self.on_before_frame.add(move |time: &animation::TimeInfo| callback(*time)) - } - - /// Register a callback which should be run after each animation frame. - pub fn on_frame( - &self, - mut callback: F, - ) -> callback::Handle { - self.on_after_frame.add(move |time: &animation::TimeInfo| callback(*time)) - } - - /// Register a callback which should be run when runtime stats of the previous animation frame - /// are available. - pub fn on_stats_available( - &self, - mut callback: F, - ) -> callback::Handle { - self.on_stats_available.add(move |stats: &StatsData| callback(*stats)) - } - - /// Keeps the world alive even when all references are dropped. Use only if you want to keep one - /// instance of the world forever. - pub fn keep_alive_forever(&self) { - mem::forget(self.clone_ref()) + /// Perform to the next frame with the provided time information. + /// + /// Please note that the provided time information from the [`requestAnimationFrame`] JS + /// function is more precise than time obtained from the [`window.performance().now()`] one. + /// Follow this link to learn more: + /// https://stackoverflow.com/questions/38360250/requestanimationframe-now-vs-performance-now-time-discrepancy. + pub fn go_to_next_frame_with_time(&self, time: animation::TimeInfo) { + let previous_frame_stats = self.stats.begin_frame(); + if let Some(stats) = previous_frame_stats { + self.on.prev_frame_stats.run_all(&stats); + } + self.on.before_frame.run_all(time); + self.uniforms.time.set(time.local); + self.scene_dirty.unset_all(); + self.default_scene.update(time); + self.default_scene.render(); + self.on.after_frame.run_all(time); + self.stats.end_frame(); } } -impl display::Object for World { - fn display_object(&self) -> &display::object::Instance { - self.scene.display_object() +impl Default for WorldData { + fn default() -> Self { + Self::new() } } -impl<'t> From<&'t World> for &'t Scene { - fn from(world: &'t World) -> Self { - &world.scene + + +// ============= +// === Tests === +// ============= + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn native_compilation_in_test_mode() { + let _world = World::new().displayed_in("root"); + let _scene = &_world.default_scene; } } diff --git a/lib/rust/ensogl/core/src/lib.rs b/lib/rust/ensogl/core/src/lib.rs index 20bc1fa9cf..c64da5e9d1 100644 --- a/lib/rust/ensogl/core/src/lib.rs +++ b/lib/rust/ensogl/core/src/lib.rs @@ -2,9 +2,20 @@ //! contains the core utilities necessary for the rendering engine to run correctly. See thr docs //! of the `ensogl` crate to learn more. -#![allow(dead_code)] +// === Linter configuration === +#![warn(missing_copy_implementations)] +#![warn(missing_debug_implementations)] +#![warn(missing_docs)] +#![warn(trivial_casts)] +#![warn(trivial_numeric_casts)] +#![warn(unsafe_code)] +#![warn(unused_import_braces)] +#![warn(unused_qualifications)] +// === Features === +#![allow(incomplete_features)] #![deny(unconditional_recursion)] #![feature(associated_type_defaults)] +#![feature(bool_to_option)] #![feature(cell_update)] #![feature(const_type_id)] #![feature(drain_filter)] @@ -14,18 +25,12 @@ #![feature(marker_trait_attr)] #![feature(type_alias_impl_trait)] #![feature(unboxed_closures)] -#![allow(incomplete_features)] // To be removed, see: https://github.com/enso-org/ide/issues/1559 -#![warn(missing_copy_implementations)] -#![warn(missing_debug_implementations)] -#![warn(missing_docs)] -#![warn(trivial_casts)] -#![warn(trivial_numeric_casts)] -#![warn(unsafe_code)] -#![warn(unused_import_braces)] -#![warn(unused_qualifications)] +// === Macro expansion === #![recursion_limit = "512"] // To be removed after this gets resolved: https://github.com/rust-lang/cargo/issues/5034 #![allow(clippy::option_map_unit_fn)] +// FIXME: this should be removed: +#![allow(dead_code)] diff --git a/lib/rust/ensogl/core/src/system.rs b/lib/rust/ensogl/core/src/system.rs index 2ef9099129..e02c6aa5f2 100644 --- a/lib/rust/ensogl/core/src/system.rs +++ b/lib/rust/ensogl/core/src/system.rs @@ -1,6 +1,16 @@ //! Root module for system specific implementations. Please note that system is a broad term here, //! including the native system, JS world, GPU runtime, etc. +pub mod context; pub mod gpu; pub mod js; pub mod web; + + + +// ================= +// === Reexports === +// ================= + +pub use context::Context; +pub use context::ContextLostHandler; diff --git a/lib/rust/ensogl/core/src/system/context.rs b/lib/rust/ensogl/core/src/system/context.rs new file mode 100644 index 0000000000..ba8cc0271d --- /dev/null +++ b/lib/rust/ensogl/core/src/system/context.rs @@ -0,0 +1,118 @@ +//! This module provides an abstraction for the rendering context, such as WebGL or OpenGL one. + +use crate::prelude::*; +use web::traits::*; + +use crate::system::web; + +use web::Closure; +use web_sys::WebGl2RenderingContext; + + + +// =============== +// === Context === +// =============== + +/// The rendering context. Currently, we support only the WebGL 2.0 context. In case we would like +/// to support other contexts, this is the type that should be changed to an enum of supported +/// contexts. +/// +/// ## Context Loss +/// +/// **You can lose the context AT ANY TIME! In other words, you can lose the context part way +/// through initialization. You can also lose the context immediately after calling +/// `canvas.getContext`. You can lose the context between any 2 WebGL function calls.** +/// +/// The GPU is a shared resource and as such there are times when it might be taken away from your +/// program. Examples: +/// - Another web page does something that takes the GPU too long and the browser or the OS decides +/// to reset the GPU to get control back. +/// - Tow or more pages use too many resources and the browser decides to tell all the pages they +/// lost the context and then restore it only to the front page for now. +/// - The user switches graphics cards (Turns on/off one or more in the control panel) or updates +/// their driver (no reboot required on Windows7+). +/// - Too many web pages use the GPU context and the browser decides to tell some of the pages they +/// lost the context in order to allow the newly opened ones to get it. +/// +/// In all these cases and more your program may lose its WebGL context. By default when a WebGL +/// program loses the context it never gets it back. To recover from a lost context you must to add +/// a lost context handler and tell it to prevent the default behavior, and then re-setup all your +/// WebGL state and re-create all your WebGL resources when the context is restored. +/// +/// This process is pretty complex and touches many places of your program, including WebGL error +/// handling, shaders and programs compilation and linking, WebGL-related variables null-checkers, +/// and many others. To learn more, see https://www.khronos.org/webgl/wiki/HandlingContextLost. +pub type Context = WebGl2RenderingContext; + +/// Abstraction for Device Context Handler. This name is used in OpenGL / DirectX implementations. +/// For the web target, this is simply the canvas. As we currently support WebGL 2.0 only, this is +/// simply an alias to a canvas type. See the docs of [`Context`] to learn more about future +/// extension plans. +pub type DeviceContextHandler = web::HtmlCanvasElement; + +/// Handler for closures taking care of context restoration. After dropping this handler and losing +/// the context, the context will not be restored automaticaly. +#[derive(Debug)] +pub struct ContextLostHandler { + on_lost: web::EventListenerHandle, + on_restored: web::EventListenerHandle, +} + + + +// ====================== +// === ContextHandler === +// ====================== + +/// Abstraction for entities which contain [`DeviceContextHandler`] and are able to handle context +/// loss. In most cases, these are top-level entities, such as a scene. +#[allow(missing_docs)] +pub trait Display: CloneRef { + fn device_context_handler(&self) -> &DeviceContextHandler; + fn set_context(&self, context: Option<&Context>); +} + + + +// ============== +// === Errors === +// ============== + +/// Error about unsupported standard implementation, like unsupported WebGL 2.0. +#[derive(Copy, Clone, Debug)] +pub struct UnsupportedStandard(&'static str); + +impl std::error::Error for UnsupportedStandard {} + +impl fmt::Display for UnsupportedStandard { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} is not supported.", self.0) + } +} + + + +// ============================ +// === Initialization Utils === +// ============================ + +/// Initialize WebGL 2.0 context. +pub fn init_webgl_2_context( + display: &D, +) -> Result { + let hdc = display.device_context_handler(); + let opt_context = hdc.get_webgl2_context(); + match opt_context { + None => Err(UnsupportedStandard("WebGL 2.0")), + Some(context) => { + type Handler = web::JsEventHandler; + display.set_context(Some(&context)); + let lost: Handler = Closure::new(f_!(display.set_context(None))); + let restored: Handler = Closure::new(f_!(display.set_context(Some(&context)))); + let on_lost = web::add_event_listener(hdc, "webglcontextlost", lost); + let on_restored = web::add_event_listener(hdc, "webglcontextrestored", restored); + Ok(ContextLostHandler { on_lost, on_restored }) + } + } +} diff --git a/lib/rust/ensogl/core/src/system/gpu/data/attribute.rs b/lib/rust/ensogl/core/src/system/gpu/data/attribute.rs index 2fd532940a..2cf05c0faa 100644 --- a/lib/rust/ensogl/core/src/system/gpu/data/attribute.rs +++ b/lib/rust/ensogl/core/src/system/gpu/data/attribute.rs @@ -2,7 +2,7 @@ use crate::prelude::*; -use crate::control::callback::CallbackFn; +use crate::control::callback; use crate::data::dirty; use crate::data::OptVec; use crate::debug::Stats; @@ -102,7 +102,7 @@ pub struct AttributeScopeData { impl { /// Create a new scope with the provided dirty callback. - pub fn new + pub fn new (lgr:Logger, stats:&Stats, on_mut:OnMut) -> Self { info!(lgr,"Initializing.",|| { let logger = lgr.clone(); diff --git a/lib/rust/ensogl/core/src/system/gpu/data/buffer.rs b/lib/rust/ensogl/core/src/system/gpu/data/buffer.rs index a78749f44c..0f2cb66f67 100644 --- a/lib/rust/ensogl/core/src/system/gpu/data/buffer.rs +++ b/lib/rust/ensogl/core/src/system/gpu/data/buffer.rs @@ -6,8 +6,7 @@ pub mod usage; use crate::prelude::*; use crate::closure; -use crate::control::callback::Callback; -use crate::control::callback::CallbackFn; +use crate::control::callback; use crate::data::dirty; use crate::data::seq::observable::Observable; use crate::debug::stats::Stats; @@ -15,7 +14,7 @@ use crate::system::gpu::data::attribute; use crate::system::gpu::data::attribute::Attribute; use crate::system::gpu::data::buffer::item::JsBufferView; use crate::system::gpu::data::buffer::usage::BufferUsage; -use crate::system::gpu::shader::Context; +use crate::system::Context; use crate::data::dirty::traits::*; use crate::system::gpu::data::default::gpu_default; @@ -39,10 +38,10 @@ pub use crate::system::gpu::data::Storable; pub type ObservableVec = Observable, OnMut, OnResize>; /// Dirty flag keeping track of the range of modified elements. -pub type MutDirty = dirty::SharedRange; +pub type MutDirty = dirty::SharedRange>; /// Dirty flag keeping track of whether the buffer was resized. -pub type ResizeDirty = dirty::SharedBool; +pub type ResizeDirty = dirty::SharedBool>; closure! { fn on_resize_fn(dirty:ResizeDirty) -> OnResize { @@ -113,15 +112,15 @@ pub struct BufferData { impl { /// Constructor. - pub fn new + pub fn new (logger:Logger, stats:&Stats, on_mut:OnMut, on_resize:OnResize) -> Self { info!(logger,"Creating new {T::type_display()} buffer.", || { stats.inc_buffer_count(); let logger = logger.clone(); let sublogger = Logger::new_sub(&logger,"mut_dirty"); - let mut_dirty = MutDirty::new(sublogger,Callback(on_mut)); + let mut_dirty = MutDirty::new(sublogger,Box::new(on_mut)); let sublogger = Logger::new_sub(&logger,"resize_dirty"); - let resize_dirty = ResizeDirty::new(sublogger, Callback(on_resize)); + let resize_dirty = ResizeDirty::new(sublogger, Box::new(on_resize)); resize_dirty.set(); let on_resize_fn = on_resize_fn(resize_dirty.clone_ref()); let on_mut_fn = on_mut_fn(mut_dirty.clone_ref()); diff --git a/lib/rust/ensogl/core/src/system/gpu/data/buffer/usage.rs b/lib/rust/ensogl/core/src/system/gpu/data/buffer/usage.rs index 05a43d1377..5263677413 100644 --- a/lib/rust/ensogl/core/src/system/gpu/data/buffer/usage.rs +++ b/lib/rust/ensogl/core/src/system/gpu/data/buffer/usage.rs @@ -3,7 +3,7 @@ use crate::prelude::*; use crate::system::gpu::data::gl_enum::GlEnum; -use crate::system::gpu::shader::Context; +use crate::system::Context; diff --git a/lib/rust/ensogl/core/src/system/gpu/data/gl_enum.rs b/lib/rust/ensogl/core/src/system/gpu/data/gl_enum.rs index 60541e4d5e..18e1b2e82b 100644 --- a/lib/rust/ensogl/core/src/system/gpu/data/gl_enum.rs +++ b/lib/rust/ensogl/core/src/system/gpu/data/gl_enum.rs @@ -2,7 +2,7 @@ use crate::prelude::*; use crate::system::gpu::data::prim::*; -use crate::system::gpu::shader::Context; +use crate::system::Context; diff --git a/lib/rust/ensogl/core/src/system/gpu/data/texture/storage/remote_image.rs b/lib/rust/ensogl/core/src/system/gpu/data/texture/storage/remote_image.rs index f5657293d8..7aa4322070 100644 --- a/lib/rust/ensogl/core/src/system/gpu/data/texture/storage/remote_image.rs +++ b/lib/rust/ensogl/core/src/system/gpu/data/texture/storage/remote_image.rs @@ -6,10 +6,11 @@ use crate::system::gpu::data::texture::class::*; use crate::system::gpu::data::texture::storage::*; use crate::system::gpu::data::texture::types::*; use crate::system::gpu::Context; +#[cfg(target_arch = "wasm32")] use crate::system::web; - -use wasm_bindgen::prelude::Closure; -use wasm_bindgen::JsCast; +#[cfg(target_arch = "wasm32")] +use web::Closure; +#[cfg(target_arch = "wasm32")] use web_sys::HtmlImageElement; @@ -81,10 +82,12 @@ impl Texture { impl TextureReload for Texture { /// Loads or re-loads the texture data from the provided url. /// This action will be performed asynchronously. + #[allow(trivial_casts)] + #[cfg(target_arch = "wasm32")] fn reload(&self) { let url = &self.storage().url; let image = HtmlImageElement::new().unwrap(); - let no_callback = >>::None; + let no_callback = >::None; let callback_ref = Rc::new(RefCell::new(no_callback)); let image_ref = Rc::new(RefCell::new(image)); let callback_ref2 = callback_ref.clone(); @@ -92,7 +95,7 @@ impl TextureReload for Texture = Closure::once(move || { + let callback: web::JsEventHandler = Closure::once(move |_| { let _keep_alive = callback_ref2; let image = image_ref_opt.borrow(); let target = Context::TEXTURE_2D; @@ -113,14 +116,16 @@ impl TextureReload for Texture TextureReload for Texture = std::result::Result; - #[derive(Debug, Fail, From)] pub enum Error { #[fail(display = "Unable to create {}.", target)] @@ -61,37 +58,53 @@ pub enum ErrorTarget { -// ================== -// === HasInfoLog === -// ================== +// =================== +// === Compilation === +// =================== +/// Compilation error containing detailed error message. +#[derive(Debug)] +pub struct CompilationError(String); + +impl std::error::Error for CompilationError {} + +impl Display for CompilationError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Abstraction for [`Shader`] and [`Program`] error handling. pub trait CompilationTarget { - fn check(&self, ctx: &Context) -> bool; - fn logs(&self, ctx: &Context) -> String; + /// Check whether the target was assembled correctly. In the context of [`Shader`], it checks if + /// the compilation was successful. In the context of [`Program`], it checks whether it was + /// linked successfully. In case of lost context, this function will succeed. For more + /// information of why, see: https://www.khronos.org/webgl/wiki/HandlingContextLost. + fn check(&self, ctx: &Context) -> Result<(), CompilationError>; } impl CompilationTarget for Shader { - fn check(&self, ctx: &Context) -> bool { - ctx.get_shader_parameter(self, Context::COMPILE_STATUS).as_bool().unwrap_or(false) - } - - fn logs(&self, ctx: &Context) -> String { - unwrap_error(ctx.get_shader_info_log(self)) + fn check(&self, ctx: &Context) -> Result<(), CompilationError> { + let status = Context::COMPILE_STATUS; + let status_ok = ctx.get_shader_parameter(self, status).as_bool().unwrap_or(false); + let context_lost = ctx.is_context_lost(); + let ok = (status_ok || context_lost).then_some(()); + ok.ok_or_else(|| CompilationError(unwrap_error(ctx.get_shader_info_log(self)))) } } impl CompilationTarget for Program { - fn check(&self, ctx: &Context) -> bool { - ctx.get_program_parameter(self, Context::LINK_STATUS).as_bool().unwrap_or(false) - } - - fn logs(&self, ctx: &Context) -> String { - unwrap_error(ctx.get_program_info_log(self)) + fn check(&self, ctx: &Context) -> Result<(), CompilationError> { + let status = Context::LINK_STATUS; + let status_ok = ctx.get_program_parameter(self, status).as_bool().unwrap_or(false); + let context_lost = ctx.is_context_lost(); + let ok = (status_ok || context_lost).then_some(()); + ok.ok_or_else(|| CompilationError(unwrap_error(ctx.get_program_info_log(self)))) } } fn unwrap_error(opt_err: Option) -> String { - opt_err.unwrap_or_else(|| "Unknown error.".to_string()) + opt_err.unwrap_or_else(|| "Unknown error.".into()) } @@ -100,56 +113,86 @@ fn unwrap_error(opt_err: Option) -> String { // === Compile / Link === // ====================== -pub fn compile_vertex_shader(ctx: &Context, src: &str) -> Result { +pub fn compile_vertex_shader(ctx: &Context, src: &str) -> Result { compile_shader(ctx, Context::VERTEX_SHADER, src) } -pub fn compile_fragment_shader(ctx: &Context, src: &str) -> Result { +pub fn compile_fragment_shader(ctx: &Context, src: &str) -> Result { compile_shader(ctx, Context::FRAGMENT_SHADER, src) } -// TODO: This is a very work-in-progress function. It should be refactored into helper functions. -pub fn compile_shader(ctx: &Context, tp: u32, src: &str) -> Result { +pub fn compile_shader(ctx: &Context, tp: u32, src: &str) -> Result { let target = ErrorTarget::Shader; let shader = ctx.create_shader(tp).ok_or(Error::Create { target })?; ctx.shader_source(&shader, src); ctx.compile_shader(&shader); - if shader.check(ctx) { - Ok(shader) - } else { - let code: String = src.into(); - let lines = code.split('\n').collect::>(); - let lines_num = lines.len(); - let lines_str_len = (lines_num as f32).log10().ceil() as usize; - let lines_enum = lines.into_iter().enumerate(); - let lines_with_num = - lines_enum.map(|(n, l)| format!("{1:0$} : {2}", lines_str_len, n + 1, l)); - let lines_with_num = lines_with_num.collect::>(); - let code_with_num = lines_with_num.join("\n"); - let message = shader.logs(ctx); - let error_loc_pfx = "ERROR: 0:"; - let out = if let Some(msg) = message.strip_prefix(error_loc_pfx) { - let line_num: String = msg.chars().take_while(|c| c.is_digit(10)).collect(); - let line_num = line_num.parse::().unwrap() - 1; - let preview_radius = 5; - let preview_line_start = std::cmp::max(0, line_num - preview_radius); - let preview_line_end = std::cmp::min(lines_num, line_num + preview_radius); - lines_with_num[preview_line_start..preview_line_end].join("\n") - } else { - code_with_num - }; - Err(Error::Compile { target, message, code: out }) + match shader.check(ctx) { + Ok(_) => Ok(shader), + Err(CompilationError(message)) => { + let code: String = src.into(); + let lines = code.split('\n').collect::>(); + let lines_num = lines.len(); + let lines_str_len = (lines_num as f32).log10().ceil() as usize; + let lines_enum = lines.into_iter().enumerate(); + let lines_with_num = + lines_enum.map(|(n, l)| format!("{1:0$} : {2}", lines_str_len, n + 1, l)); + let lines_with_num = lines_with_num.collect::>(); + let code_with_num = lines_with_num.join("\n"); + let error_loc_pfx = "ERROR: 0:"; + let out = if let Some(msg) = message.strip_prefix(error_loc_pfx) { + let line_num: String = msg.chars().take_while(|c| c.is_digit(10)).collect(); + let line_num = line_num.parse::().unwrap() - 1; + let preview_radius = 5; + let preview_line_start = std::cmp::max(0, line_num - preview_radius); + let preview_line_end = std::cmp::min(lines_num, line_num + preview_radius); + lines_with_num[preview_line_start..preview_line_end].join("\n") + } else { + code_with_num + }; + Err(Error::Compile { target, message, code: out }) + } } } -pub fn link_program(ctx: &Context, vert_shader: &Shader, frag_shader: &Shader) -> Result { +/// Structure representing one of two states – an error, or lack of values because of a lost +/// context. +#[derive(Debug)] +pub enum ContextLossOrError { + ContextLoss, + Error(Error), +} + +/// Link the provided vertex and fragment shaders into a program. +pub fn link_program( + ctx: &Context, + vert_shader: &Shader, + frag_shader: &Shader, +) -> Result { let target = ErrorTarget::Program; - let program = ctx.create_program().ok_or(Error::Create { target })?; - ctx.attach_shader(&program, vert_shader); - ctx.attach_shader(&program, frag_shader); - ctx.link_program(&program); - // TODO: handle errors - Ok(program) + match ctx.create_program() { + None => Err(if ctx.is_context_lost() { + ContextLossOrError::ContextLoss + } else { + ContextLossOrError::Error(Error::Create { target }) + }), + Some(program) => { + ctx.attach_shader(&program, vert_shader); + ctx.attach_shader(&program, frag_shader); + ctx.link_program(&program); + Ok(program) + } + } +} + +/// Compile the provided vertex and fragment shader sources and then link them into a program. +pub fn compile_program( + ctx: &Context, + vert_src: &str, + frag_src: &str, +) -> Result { + let vert_shader = compile_vertex_shader(ctx, vert_src).map_err(ContextLossOrError::Error)?; + let frag_shader = compile_fragment_shader(ctx, frag_src).map_err(ContextLossOrError::Error)?; + link_program(ctx, &vert_shader, &frag_shader) } @@ -167,13 +210,13 @@ pub fn set_buffer_data(gl_context: &Context, buffer: &WebGlBuffer, data: &[f32]) set_bound_buffer_data(gl_context, target, data); } -/// Set data in currently bound buffer +/// Set data in currently bound buffer. /// /// # Safety /// The Float32Array::view is safe as long there are no allocations done /// until it is destroyed. This way of creating buffers were taken from /// wasm-bindgen examples -/// (https://rustwasm.github.io/wasm-bindgen/examples/webgl.html) +/// (https://rustwasm.github.io/wasm-bindgen/examples/webgl.html). #[allow(unsafe_code)] fn set_bound_buffer_data(gl_context: &Context, target: u32, data: &[f32]) { let usage = Context::STATIC_DRAW; @@ -190,13 +233,13 @@ pub fn set_buffer_subdata(gl_context: &Context, buffer: &WebGlBuffer, offset: us set_bound_buffer_subdata(gl_context, target, offset as i32, data); } -/// Set subdata in currently bound buffer +/// Set subdata in currently bound buffer. /// /// # Safety /// The Float32Array::view is safe as long there are no allocations done /// until it is destroyed. This way of creating buffers were taken from /// wasm-bindgen examples -/// (https://rustwasm.github.io/wasm-bindgen/examples/webgl.html) +/// (https://rustwasm.github.io/wasm-bindgen/examples/webgl.html). #[allow(unsafe_code)] fn set_bound_buffer_subdata(gl_context: &Context, target: u32, offset: i32, data: &[f32]) { unsafe { diff --git a/lib/rust/ensogl/core/src/system/web.rs b/lib/rust/ensogl/core/src/system/web.rs index 887afd924a..4ff0db2bfe 100644 --- a/lib/rust/ensogl/core/src/system/web.rs +++ b/lib/rust/ensogl/core/src/system/web.rs @@ -3,7 +3,3 @@ pub mod dom; pub use enso_web::*; -pub use js_sys::Object; -pub use wasm_bindgen::JsValue; -pub use web_sys::HtmlDivElement; -pub use web_sys::HtmlElement; diff --git a/lib/rust/ensogl/core/src/system/web/dom/shape.rs b/lib/rust/ensogl/core/src/system/web/dom/shape.rs index 9f8523bf36..4d2f35bd28 100644 --- a/lib/rust/ensogl/core/src/system/web/dom/shape.rs +++ b/lib/rust/ensogl/core/src/system/web/dom/shape.rs @@ -9,7 +9,7 @@ use crate::system::web; use crate::system::web::resize_observer::ResizeObserver; use nalgebra::Vector2; -use wasm_bindgen::prelude::Closure; +use web::Closure; @@ -65,7 +65,7 @@ impl Default for Shape { fn default() -> Self { let width = 100.0; let height = 100.0; - let pixel_ratio = web::device_pixel_ratio() as f32; + let pixel_ratio = web::window.device_pixel_ratio() as f32; Self { width, height, pixel_ratio } } } @@ -93,7 +93,7 @@ impl From<&Shape> for Vector2 { #[derive(Clone, CloneRef, Debug, Shrinkwrap)] #[clone_ref(bound = "T:CloneRef")] #[allow(missing_docs)] -pub struct WithKnownShape { +pub struct WithKnownShape { #[shrinkwrap(main_field)] dom: T, network: frp::Network, @@ -105,7 +105,7 @@ pub struct WithKnownShape { impl WithKnownShape { /// Constructor. pub fn new(dom: &T) -> Self - where T: Clone + AsRef + Into { + where T: Clone + AsRef + Into { let dom = dom.clone(); let element = dom.clone().into(); frp::new_network! { network @@ -125,7 +125,7 @@ impl WithKnownShape { /// Recompute the shape. Note that this function causes reflow. pub fn recompute_shape_with_reflow(&self) - where T: Clone + Into { + where T: Clone + Into { self.shape_source.emit(Shape::new_from_element_with_reflow(&self.dom.clone().into())) } } diff --git a/lib/rust/ensogl/doc/mouse-handling.md b/lib/rust/ensogl/doc/mouse-handling.md index a3939f098d..c6c2f4cdc1 100644 --- a/lib/rust/ensogl/doc/mouse-handling.md +++ b/lib/rust/ensogl/doc/mouse-handling.md @@ -26,7 +26,7 @@ event types: These event listeners are attached to [`window`][window] object. Every DOM object can setup specific listeners for these event types using -`application.display.scene().mouse.frp` FRP network. +`application.display.default_scene.mouse.frp` FRP network. This method is flexible and allows consistent mouse event handling across all Enso GUI parts. However, it also requires registering `Shapes` for every diff --git a/lib/rust/ensogl/example/animation/Cargo.toml b/lib/rust/ensogl/example/animation/Cargo.toml index fa0cdd89e5..3b304a62f3 100644 --- a/lib/rust/ensogl/example/animation/Cargo.toml +++ b/lib/rust/ensogl/example/animation/Cargo.toml @@ -12,4 +12,4 @@ enso-frp = { path = "../../../frp" } ensogl-core = { path = "../../core" } ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" } enso-prelude = { path = "../../../prelude" } -wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] } +wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] } diff --git a/lib/rust/ensogl/example/animation/src/lib.rs b/lib/rust/ensogl/example/animation/src/lib.rs index 39dc57e682..97f41fa2b4 100644 --- a/lib/rust/ensogl/example/animation/src/lib.rs +++ b/lib/rust/ensogl/example/animation/src/lib.rs @@ -19,7 +19,6 @@ use ensogl_core::prelude::*; use ensogl_core::application::Application; -use ensogl_core::system::web; use ensogl_core::DEPRECATED_Animation; use ensogl_text_msdf_sys::run_once_initialized; use logger::TraceLogger as Logger; @@ -35,31 +34,21 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] #[allow(dead_code)] pub fn entry_point_animation() { - web::forward_panic_hook_to_console(); - web::set_stack_trace_limit(); run_once_initialized(|| { - let app = Application::new(&web::get_html_element_by_id("root").unwrap()); - init(); + let app = Application::new("root"); + let logger: Logger = Logger::new("AnimationTest"); + let network = enso_frp::Network::new("test"); + let animation = DEPRECATED_Animation::::new(&network); + animation.set_target_value(-259_830.0); + + enso_frp::extend! {network + eval animation.value([logger](value) { + info!(logger, "Value {value}") + }); + } + + mem::forget(animation); + mem::forget(network); mem::forget(app); }); } - - -// ======================== -// === Init Application === -// ======================== - -fn init() { - let logger: Logger = Logger::new("AnimationTest"); - let network = enso_frp::Network::new("test"); - let animation = DEPRECATED_Animation::::new(&network); - animation.set_target_value(-259_830.0); - - enso_frp::extend! {network - eval animation.value([logger](value) { - info!(logger, "Value {value}") - }); - } - std::mem::forget(animation); - std::mem::forget(network); -} diff --git a/lib/rust/ensogl/example/complex-shape-system/Cargo.toml b/lib/rust/ensogl/example/complex-shape-system/Cargo.toml index c8f8a0ebfd..24455d88ee 100644 --- a/lib/rust/ensogl/example/complex-shape-system/Cargo.toml +++ b/lib/rust/ensogl/example/complex-shape-system/Cargo.toml @@ -10,4 +10,4 @@ crate-type = ["cdylib", "rlib"] [dependencies] ensogl-core = { path = "../../core" } -wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] } +wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] } diff --git a/lib/rust/ensogl/example/complex-shape-system/src/lib.rs b/lib/rust/ensogl/example/complex-shape-system/src/lib.rs index 469ef67ca6..6dac05e08f 100644 --- a/lib/rust/ensogl/example/complex-shape-system/src/lib.rs +++ b/lib/rust/ensogl/example/complex-shape-system/src/lib.rs @@ -9,7 +9,6 @@ use ensogl_core::display::scene; use ensogl_core::display::shape::*; use ensogl_core::display::style::theme; use ensogl_core::display::world::*; -use ensogl_core::system::web; use wasm_bindgen::prelude::*; @@ -55,11 +54,8 @@ mod mask { #[wasm_bindgen] #[allow(dead_code)] pub fn entry_point_complex_shape_system() { - web::forward_panic_hook_to_console(); - web::set_stack_trace_limit(); - - let world = World::new(&web::get_html_element_by_id("root").unwrap()); - let scene = world.scene(); + 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 logger = Logger::new("ShapeView"); @@ -109,11 +105,13 @@ pub fn entry_point_complex_shape_system() { world.add_child(&mask); world.keep_alive_forever(); - let scene = world.scene().clone_ref(); + let scene = world.default_scene.clone_ref(); let mut frame = 0; world - .on_frame(move |_time| { + .on + .before_frame + .add(move |_time| { mask.set_position_x(((frame as f32) / 30.0).sin() * 100.0); let _keep_alive = &navigator; diff --git a/lib/rust/ensogl/example/dom-symbols/Cargo.toml b/lib/rust/ensogl/example/dom-symbols/Cargo.toml index 04746826c3..5d8f5c7c13 100644 --- a/lib/rust/ensogl/example/dom-symbols/Cargo.toml +++ b/lib/rust/ensogl/example/dom-symbols/Cargo.toml @@ -10,5 +10,5 @@ crate-type = ["cdylib", "rlib"] [dependencies] ensogl-core = { path = "../../core" } -wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] } +wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] } nalgebra = { version = "0.26.1" } diff --git a/lib/rust/ensogl/example/dom-symbols/src/lib.rs b/lib/rust/ensogl/example/dom-symbols/src/lib.rs index 91391085b5..5b6ff80479 100644 --- a/lib/rust/ensogl/example/dom-symbols/src/lib.rs +++ b/lib/rust/ensogl/example/dom-symbols/src/lib.rs @@ -21,8 +21,7 @@ use ensogl_core::display::symbol::geometry::SpriteSystem; use ensogl_core::display::symbol::DomSymbol; use ensogl_core::display::world::*; use ensogl_core::system::web; -use ensogl_core::system::web::NodeInserter; -use web::StyleSetter; +use ensogl_core::system::web::traits::*; use nalgebra::Vector2; use nalgebra::Vector3; @@ -32,9 +31,8 @@ use wasm_bindgen::prelude::*; #[allow(dead_code)] #[allow(clippy::many_single_char_names)] pub fn entry_point_dom_symbols() { - web::forward_panic_hook_to_console(); - let world = World::new(&web::get_html_element_by_id("root").unwrap()); - let scene = world.scene(); + let world = World::new().displayed_in("root"); + let scene = &world.default_scene; let camera = scene.camera(); let screen = camera.screen(); let navigator = Navigator::new(scene, &camera); @@ -61,9 +59,9 @@ pub fn entry_point_dom_symbols() { sprite.mod_position(|t| *t = position); sprites.push(sprite); } else { - let div = web::create_div(); - div.set_style_or_panic("width", "100%"); - div.set_style_or_panic("height", "100%"); + let div = web::document.create_div_or_panic(); + div.set_style_or_warn("width", "100%"); + div.set_style_or_warn("height", "100%"); div.set_inner_html("top-left"); let size = Vector2::new(width, height); @@ -75,9 +73,9 @@ pub fn entry_point_dom_symbols() { let g = ((x + 2.0) * 32.0) as u8; let b = ((x + 4.0) * 64.0) as u8; let color = iformat!("rgb({r},{g},{b})"); - div.set_style_or_panic("background-color", color); + div.set_style_or_warn("background-color", color); - object.dom().append_or_panic(&div); + object.dom().append_or_warn(&div); object.set_size(size); object.mod_position(|t| *t = position); css3d_objects.push(object); @@ -89,7 +87,9 @@ pub fn entry_point_dom_symbols() { let mut i = 0; world.keep_alive_forever(); world - .on_frame(move |_| { + .on + .before_frame + .add(move |_| { let _keep_alive = &navigator; let _keep_alive = &sprites; let _keep_alive = &sprite_system; diff --git a/lib/rust/ensogl/example/drop-manager/Cargo.toml b/lib/rust/ensogl/example/drop-manager/Cargo.toml index a01df469a5..f83f89ae82 100644 --- a/lib/rust/ensogl/example/drop-manager/Cargo.toml +++ b/lib/rust/ensogl/example/drop-manager/Cargo.toml @@ -13,5 +13,5 @@ enso-frp = { path = "../../../frp" } ensogl-core = { path = "../../core" } ensogl-drop-manager = { path = "../../component/drop-manager" } enso-prelude = { path = "../../../prelude" } -wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] } +wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] } wasm-bindgen-futures = { version = "0.4.8" } diff --git a/lib/rust/ensogl/example/drop-manager/src/lib.rs b/lib/rust/ensogl/example/drop-manager/src/lib.rs index f3594ebff1..7b4afcde57 100644 --- a/lib/rust/ensogl/example/drop-manager/src/lib.rs +++ b/lib/rust/ensogl/example/drop-manager/src/lib.rs @@ -49,10 +49,8 @@ fn download_file(file: ensogl_drop_manager::File) { #[wasm_bindgen] #[allow(dead_code)] pub fn entry_point_drop_manager() { - web::forward_panic_hook_to_console(); - - let world = World::new(&web::get_html_element_by_id("root").unwrap()); - let drop_manager = ensogl_drop_manager::Manager::new(world.scene().dom.root.as_ref()); + let world = World::new().displayed_in("root"); + let drop_manager = ensogl_drop_manager::Manager::new(world.default_scene.dom.root.as_ref()); let network = enso_frp::Network::new("Debug Scene"); enso_frp::extend! { network let file_received = drop_manager.files_received().clone_ref(); @@ -61,11 +59,13 @@ pub fn entry_point_drop_manager() { let mut loader_hidden = false; world - .on_frame(move |_| { + .on + .before_frame + .add(move |_| { if !loader_hidden { - web::get_element_by_id("loader") - .map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap())) - .ok(); + web::document + .get_element_by_id("loader") + .map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap())); loader_hidden = true; } }) diff --git a/lib/rust/ensogl/example/easing-animator/Cargo.toml b/lib/rust/ensogl/example/easing-animator/Cargo.toml index bfc5c831a4..70aca7b206 100644 --- a/lib/rust/ensogl/example/easing-animator/Cargo.toml +++ b/lib/rust/ensogl/example/easing-animator/Cargo.toml @@ -10,7 +10,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] ensogl-core = { path = "../../core" } -wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] } +wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] } js-sys = { version = "0.3.28" } nalgebra = { version = "0.26.1" } web-sys = { version = "0.3.4" } diff --git a/lib/rust/ensogl/example/easing-animator/src/lib.rs b/lib/rust/ensogl/example/easing-animator/src/lib.rs index 731a495633..aac9517115 100644 --- a/lib/rust/ensogl/example/easing-animator/src/lib.rs +++ b/lib/rust/ensogl/example/easing-animator/src/lib.rs @@ -21,21 +21,15 @@ use ensogl_core::prelude::*; use ensogl_core::animation; use ensogl_core::animation::easing::*; use ensogl_core::system::web; -use ensogl_core::system::web::create_element; -use ensogl_core::system::web::get_element_by_id; -use ensogl_core::system::web::AttributeSetter; -use ensogl_core::system::web::NodeInserter; -use ensogl_core::system::web::StyleSetter; +use ensogl_core::system::web::traits::*; use js_sys::Math; use nalgebra::Vector2; use std::ops::Add; use std::ops::Mul; use std::rc::Rc; -use wasm_bindgen::prelude::*; -use wasm_bindgen::JsCast; -use web_sys::CanvasRenderingContext2d; -use web_sys::HtmlCanvasElement; -use web_sys::HtmlElement; +use wasm_bindgen::prelude::wasm_bindgen; +use web::CanvasRenderingContext2d; +use web::HtmlCanvasElement; @@ -101,16 +95,16 @@ pub struct Canvas { impl Canvas { /// Constructor. pub fn new(container_id: &str) -> Self { - let canvas = web::create_canvas(); - canvas.set_style_or_panic("border", "1px solid black"); + let canvas = web::document.create_canvas_or_panic(); + canvas.set_style_or_warn("border", "1px solid black"); canvas.set_width(256); canvas.set_height(256); let context = canvas.get_context("2d").unwrap().unwrap(); let context: CanvasRenderingContext2d = context.dyn_into().unwrap(); - let app: HtmlElement = get_element_by_id(container_id).unwrap().dyn_into().unwrap(); - app.append_or_panic(&canvas); + let app = web::document.get_html_element_by_id(container_id).unwrap(); + app.append_or_warn(&canvas); Self { canvas, context } } @@ -249,16 +243,16 @@ impl Example { ease_out: impl CloneableFnEasing, ease_in_out: impl CloneableFnEasing, ) -> Self { - let example = web::create_div(); - example.set_attribute_or_panic("id", name); - example.set_style_or_panic("margin", "10px"); - let container: HtmlElement = get_element_by_id("examples").unwrap().dyn_into().unwrap(); - let header: HtmlElement = create_element("center").dyn_into().unwrap(); - header.set_style_or_panic("background-color", "black"); - header.set_style_or_panic("color", "white"); + let example = web::document.create_div_or_panic(); + example.set_attribute_or_warn("id", name); + example.set_style_or_warn("margin", "10px"); + let container = web::document.get_html_element_by_id("examples").unwrap(); + let header = web::document.get_html_element_by_id("center").unwrap(); + header.set_style_or_warn("background-color", "black"); + header.set_style_or_warn("color", "white"); header.set_inner_html(name); - example.append_or_panic(&header); - container.append_or_panic(&example); + example.append_or_warn(&header); + container.append_or_warn(&example); let left_canvas = Canvas::new(name); let right_canvas = Canvas::new(name); let mut sampler1 = Sampler::new("green", &left_canvas, &right_canvas, ease_in); @@ -293,12 +287,12 @@ macro_rules! examples { pub fn entry_point_easing_animator() { web::forward_panic_hook_to_console(); web::set_stack_trace_limit(); - let container = web::create_div(); - container.set_attribute_or_panic("id", "examples"); - container.set_style_or_panic("display", "flex"); - container.set_style_or_panic("flex-wrap", "wrap"); - container.set_style_or_panic("position", "absolute"); - container.set_style_or_panic("top", "0px"); - web::body().append_or_panic(&container); + let container = web::document.create_div_or_panic(); + container.set_attribute_or_warn("id", "examples"); + container.set_style_or_warn("display", "flex"); + container.set_style_or_warn("flex-wrap", "wrap"); + container.set_style_or_warn("position", "absolute"); + container.set_style_or_warn("top", "0px"); + web::document.body_or_panic().append_or_warn(&container); examples![expo, bounce, circ, quad, cubic, quart, quint, sine, back, elastic]; } diff --git a/lib/rust/ensogl/example/glyph-system/Cargo.toml b/lib/rust/ensogl/example/glyph-system/Cargo.toml index ba1e7cdb4d..5d45da165e 100644 --- a/lib/rust/ensogl/example/glyph-system/Cargo.toml +++ b/lib/rust/ensogl/example/glyph-system/Cargo.toml @@ -12,4 +12,4 @@ crate-type = ["cdylib", "rlib"] ensogl-core = { path = "../../core" } ensogl-text = { path = "../../component/text" } ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" } -wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] } +wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] } diff --git a/lib/rust/ensogl/example/glyph-system/src/lib.rs b/lib/rust/ensogl/example/glyph-system/src/lib.rs index ca0e150b77..44bc2432df 100644 --- a/lib/rust/ensogl/example/glyph-system/src/lib.rs +++ b/lib/rust/ensogl/example/glyph-system/src/lib.rs @@ -20,7 +20,6 @@ use ensogl_core::prelude::*; use ensogl_core::data::color; use ensogl_core::display::world::*; -use ensogl_core::system::web; use ensogl_text::typeface::*; use ensogl_text_msdf_sys::run_once_initialized; use wasm_bindgen::prelude::*; @@ -31,14 +30,13 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] #[allow(dead_code)] pub fn entry_point_glyph_system() { - web::forward_panic_hook_to_console(); - run_once_initialized(|| init(&World::new(&web::get_html_element_by_id("root").unwrap()))); + run_once_initialized(|| init(&World::new().displayed_in("root"))); } fn init(world: &World) { - let fonts = world.scene().extension::(); + let fonts = world.default_scene.extension::(); let font = fonts.load("DejaVuSans"); - let glyph_system = glyph::System::new(world.scene(), font); + let glyph_system = glyph::System::new(&world.default_scene, font); let height = 32.0; let color = color::Rgba::new(0.5, 0.0, 0.0, 1.0); let glyph = glyph_system.new_glyph(); diff --git a/lib/rust/ensogl/example/list-view/Cargo.toml b/lib/rust/ensogl/example/list-view/Cargo.toml index c66bfca10e..957babc7e4 100644 --- a/lib/rust/ensogl/example/list-view/Cargo.toml +++ b/lib/rust/ensogl/example/list-view/Cargo.toml @@ -15,4 +15,4 @@ ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" } ensogl-list-view = { path = "../../component/list-view" } ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" } enso-text = { path = "../../../text" } -wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] } +wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] } diff --git a/lib/rust/ensogl/example/list-view/src/lib.rs b/lib/rust/ensogl/example/list-view/src/lib.rs index 528805768b..92044c1ac5 100644 --- a/lib/rust/ensogl/example/list-view/src/lib.rs +++ b/lib/rust/ensogl/example/list-view/src/lib.rs @@ -21,7 +21,6 @@ use ensogl_core::prelude::*; use enso_text::unit::Bytes; use ensogl_core::application::Application; use ensogl_core::display::object::ObjectOps; -use ensogl_core::system::web; use ensogl_hardcoded_theme as theme; use ensogl_list_view as list_view; use ensogl_text_msdf_sys::run_once_initialized; @@ -38,10 +37,8 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] #[allow(dead_code)] pub fn entry_point_list_view() { - web::forward_panic_hook_to_console(); - web::set_stack_trace_limit(); run_once_initialized(|| { - let app = Application::new(&web::get_html_element_by_id("root").unwrap()); + let app = Application::new("root"); init(&app); mem::forget(app); }); @@ -97,7 +94,7 @@ fn init(app: &Application) { list_view.frp.set_entries(provider); app.display.add_child(&list_view); // FIXME[WD]: This should not be needed after text gets proper depth-handling. - app.display.scene().layers.below_main.add_exclusive(&list_view); + app.display.default_scene.layers.below_main.add_exclusive(&list_view); let logger: Logger = Logger::new("SelectDebugScene"); let network = enso_frp::Network::new("test"); diff --git a/lib/rust/ensogl/example/mouse-events/Cargo.toml b/lib/rust/ensogl/example/mouse-events/Cargo.toml index a0255a6d04..b3fbb3ac0b 100644 --- a/lib/rust/ensogl/example/mouse-events/Cargo.toml +++ b/lib/rust/ensogl/example/mouse-events/Cargo.toml @@ -12,4 +12,4 @@ crate-type = ["cdylib", "rlib"] enso-frp = { path = "../../../frp" } ensogl-core = { path = "../../core" } ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" } -wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] } +wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] } diff --git a/lib/rust/ensogl/example/mouse-events/src/lib.rs b/lib/rust/ensogl/example/mouse-events/src/lib.rs index 4b4c791577..f1623d69e4 100644 --- a/lib/rust/ensogl/example/mouse-events/src/lib.rs +++ b/lib/rust/ensogl/example/mouse-events/src/lib.rs @@ -27,7 +27,6 @@ use ensogl_core::display; use ensogl_core::display::navigation::navigator::Navigator; use ensogl_core::display::object::ObjectOps; use ensogl_core::display::shape::*; -use ensogl_core::system::web; use enso_frp as frp; use ensogl_text_msdf_sys::run_once_initialized; @@ -158,15 +157,13 @@ impl application::View for View { #[wasm_bindgen] #[allow(dead_code)] pub fn entry_point_mouse_events() { - web::forward_panic_hook_to_console(); run_once_initialized(|| { - let app = Application::new(&web::get_html_element_by_id("root").unwrap()); - + let app = Application::new("root"); let shape: View = app.new_view(); shape.model.shape.size.set(Vector2::new(300.0, 300.0)); app.display.add_child(&shape); - let scene = app.display.scene(); + let scene = &app.display.default_scene; let camera = scene.camera().clone_ref(); let navigator = Navigator::new(scene, &camera); diff --git a/lib/rust/ensogl/example/profiling-run-graph/src/lib.rs b/lib/rust/ensogl/example/profiling-run-graph/src/lib.rs index e3a6ba04a9..899ca2a811 100644 --- a/lib/rust/ensogl/example/profiling-run-graph/src/lib.rs +++ b/lib/rust/ensogl/example/profiling-run-graph/src/lib.rs @@ -37,9 +37,9 @@ pub fn entry_point_profiling_run_graph() { web::forward_panic_hook_to_console(); web::set_stack_trace_limit(); - let app = &Application::new(&web::get_html_element_by_id("root").unwrap()); + let app = &Application::new("root"); let world = &app.display; - let scene = world.scene(); + let scene = &world.default_scene; let camera = scene.camera().clone_ref(); let navigator = Navigator::new(scene, &camera); @@ -56,10 +56,12 @@ pub fn entry_point_profiling_run_graph() { scene.layers.main.add_exclusive(&flame_graph); world.keep_alive_forever(); - let scene = world.scene().clone_ref(); + let scene = world.default_scene.clone_ref(); world - .on_frame(move |_time| { + .on + .before_frame + .add(move |_time| { let _keep_alive = &navigator; let _keep_alive = &scene; let _keep_alive = &flame_graph; diff --git a/lib/rust/ensogl/example/scroll-area/Cargo.toml b/lib/rust/ensogl/example/scroll-area/Cargo.toml index f1a75b7da4..90858478c4 100644 --- a/lib/rust/ensogl/example/scroll-area/Cargo.toml +++ b/lib/rust/ensogl/example/scroll-area/Cargo.toml @@ -13,4 +13,4 @@ ensogl-core = { path = "../../core" } ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" } ensogl-scroll-area = { path = "../../component/scroll-area" } ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" } -wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] } +wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] } diff --git a/lib/rust/ensogl/example/scroll-area/src/lib.rs b/lib/rust/ensogl/example/scroll-area/src/lib.rs index 15b5a98211..dab5ab74a9 100644 --- a/lib/rust/ensogl/example/scroll-area/src/lib.rs +++ b/lib/rust/ensogl/example/scroll-area/src/lib.rs @@ -28,7 +28,6 @@ use ensogl_core::display::shape::Rect; use ensogl_core::display::shape::ShapeOps; use ensogl_core::display::shape::ShapeSystem; use ensogl_core::display::Sprite; -use ensogl_core::system::web; use ensogl_hardcoded_theme as theme; use ensogl_scroll_area::ScrollArea; use ensogl_text_msdf_sys::run_once_initialized; @@ -42,10 +41,8 @@ use ensogl_text_msdf_sys::run_once_initialized; /// An entry point. #[wasm_bindgen] pub fn entry_point_scroll_area() { - web::forward_panic_hook_to_console(); - web::set_stack_trace_limit(); run_once_initialized(|| { - let app = Application::new(&web::get_html_element_by_id("root").unwrap()); + let app = Application::new("root"); init(&app); mem::forget(app); }); @@ -62,7 +59,7 @@ fn init(app: &Application) { theme::builtin::light::register(&app); theme::builtin::light::enable(&app); - let scene = app.display.scene(); + let scene = &app.display.default_scene; scene.camera().set_position_xy(Vector2(100.0, -100.0)); diff --git a/lib/rust/ensogl/example/shape-system/Cargo.toml b/lib/rust/ensogl/example/shape-system/Cargo.toml index 4b5f6939d6..e2b595818a 100644 --- a/lib/rust/ensogl/example/shape-system/Cargo.toml +++ b/lib/rust/ensogl/example/shape-system/Cargo.toml @@ -10,4 +10,4 @@ crate-type = ["cdylib", "rlib"] [dependencies] ensogl-core = { path = "../../core" } -wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] } +wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] } diff --git a/lib/rust/ensogl/example/shape-system/src/lib.rs b/lib/rust/ensogl/example/shape-system/src/lib.rs index 894d2c6f2c..ffa05d4668 100644 --- a/lib/rust/ensogl/example/shape-system/src/lib.rs +++ b/lib/rust/ensogl/example/shape-system/src/lib.rs @@ -24,7 +24,6 @@ use ensogl_core::display::object::ObjectOps; use ensogl_core::display::shape::ShapeSystem; use ensogl_core::display::shape::*; use ensogl_core::display::world::*; -use ensogl_core::system::web; use wasm_bindgen::prelude::*; @@ -54,10 +53,8 @@ pub fn shape() -> AnyShape { #[wasm_bindgen] #[allow(dead_code)] pub fn entry_point_shape_system() { - web::forward_panic_hook_to_console(); - - let world = World::new(&web::get_html_element_by_id("root").unwrap()); - let scene = world.scene(); + 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 sprite_system = ShapeSystem::new(&world, &shape()); @@ -70,7 +67,9 @@ pub fn entry_point_shape_system() { world.keep_alive_forever(); world - .on_frame(move |_time| { + .on + .before_frame + .add(move |_time| { let _keep_alive = &sprite; let _keep_alive = &navigator; }) diff --git a/lib/rust/ensogl/example/slider/Cargo.toml b/lib/rust/ensogl/example/slider/Cargo.toml index 8d173be5ca..4252e5dde3 100644 --- a/lib/rust/ensogl/example/slider/Cargo.toml +++ b/lib/rust/ensogl/example/slider/Cargo.toml @@ -13,4 +13,4 @@ ensogl-core = { path = "../../core" } ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" } ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" } ensogl-selector = { path = "../../component/selector" } -wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] } +wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] } diff --git a/lib/rust/ensogl/example/slider/src/lib.rs b/lib/rust/ensogl/example/slider/src/lib.rs index 44bcc91bcc..c66d1c2cbe 100644 --- a/lib/rust/ensogl/example/slider/src/lib.rs +++ b/lib/rust/ensogl/example/slider/src/lib.rs @@ -22,7 +22,6 @@ use wasm_bindgen::prelude::*; use ensogl_core::application::Application; use ensogl_core::data::color; use ensogl_core::display::object::ObjectOps; -use ensogl_core::system::web; use ensogl_hardcoded_theme as theme; use ensogl_selector as selector; use ensogl_selector::Bounds; @@ -38,10 +37,8 @@ use ensogl_text_msdf_sys::run_once_initialized; #[wasm_bindgen] #[allow(dead_code)] pub fn entry_point_slider() { - web::forward_panic_hook_to_console(); - web::set_stack_trace_limit(); run_once_initialized(|| { - let app = Application::new(&web::get_html_element_by_id("root").unwrap()); + let app = Application::new("root"); init(&app); mem::forget(app); }); diff --git a/lib/rust/ensogl/example/sprite-system-benchmark/Cargo.toml b/lib/rust/ensogl/example/sprite-system-benchmark/Cargo.toml index 07568be339..96488dde99 100644 --- a/lib/rust/ensogl/example/sprite-system-benchmark/Cargo.toml +++ b/lib/rust/ensogl/example/sprite-system-benchmark/Cargo.toml @@ -11,4 +11,4 @@ crate-type = ["cdylib", "rlib"] [dependencies] ensogl-core = { path = "../../core" } nalgebra = { version = "0.26.1" } -wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] } +wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] } diff --git a/lib/rust/ensogl/example/sprite-system-benchmark/src/lib.rs b/lib/rust/ensogl/example/sprite-system-benchmark/src/lib.rs index f0eadbea6d..084c9cbd6f 100644 --- a/lib/rust/ensogl/example/sprite-system-benchmark/src/lib.rs +++ b/lib/rust/ensogl/example/sprite-system-benchmark/src/lib.rs @@ -22,8 +22,6 @@ use ensogl_core::display::symbol::geometry::Sprite; use ensogl_core::display::symbol::geometry::SpriteSystem; use ensogl_core::display::world::*; use ensogl_core::prelude::*; -use ensogl_core::system::web; -use ensogl_core::system::web::forward_panic_hook_to_console; use nalgebra::Vector2; use nalgebra::Vector3; use wasm_bindgen::prelude::*; @@ -32,10 +30,8 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] #[allow(dead_code)] pub fn entry_point_sprite_system_benchmark() { - forward_panic_hook_to_console(); - - let world = World::new(&web::get_html_element_by_id("root").unwrap()); - let scene = world.scene(); + 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 sprite_system = SpriteSystem::new(&world); @@ -59,7 +55,9 @@ pub fn entry_point_sprite_system_benchmark() { let mut iter: i32 = 0; let mut i = 0; world - .on_frame(move |time| { + .on + .before_frame + .add(move |time| { i += 1; if i <= 100 { sprite1.mod_position(|p| p.x += 1.0); diff --git a/lib/rust/ensogl/example/sprite-system/Cargo.toml b/lib/rust/ensogl/example/sprite-system/Cargo.toml index ee157aaa9e..56b5817b1d 100644 --- a/lib/rust/ensogl/example/sprite-system/Cargo.toml +++ b/lib/rust/ensogl/example/sprite-system/Cargo.toml @@ -10,4 +10,5 @@ crate-type = ["cdylib", "rlib"] [dependencies] ensogl-core = { path = "../../core" } -wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] } +enso-web = { path = "../../../web" } +wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] } diff --git a/lib/rust/ensogl/example/sprite-system/src/lib.rs b/lib/rust/ensogl/example/sprite-system/src/lib.rs index 07ebf4bb89..f11a1c75d4 100644 --- a/lib/rust/ensogl/example/sprite-system/src/lib.rs +++ b/lib/rust/ensogl/example/sprite-system/src/lib.rs @@ -19,19 +19,13 @@ use wasm_bindgen::prelude::*; use ensogl_core::display::navigation::navigator::Navigator; use ensogl_core::display::symbol::geometry::SpriteSystem; use ensogl_core::display::world::*; -use ensogl_core::system::web; -use ensogl_core::system::web::forward_panic_hook_to_console; #[wasm_bindgen] #[allow(dead_code)] pub fn entry_point_sprite_system() { - forward_panic_hook_to_console(); - - let world = World::new(&web::get_html_element_by_id("root").unwrap()); - let scene = world.scene(); - let camera = scene.camera().clone_ref(); - let navigator = Navigator::new(scene, &camera); + let world = World::new().displayed_in("root"); + let navigator = Navigator::new(&world.default_scene, &world.default_scene.camera()); let sprite_system = SpriteSystem::new(&world); let sprite2 = sprite_system.new_instance(); @@ -39,12 +33,14 @@ pub fn entry_point_sprite_system() { sprite1.size.set(Vector2::new(15.0, 15.0)); sprite2.size.set(Vector2::new(15.0, 15.0)); - scene.add_child(&sprite_system); + world.default_scene.add_child(&sprite_system); world.keep_alive_forever(); let mut i = 0; world - .on_frame(move |_| { + .on + .after_frame + .add(move |_| { i += 1; let _keep_alive = &navigator; let _keep_alive = &sprite1; diff --git a/lib/rust/ensogl/example/text-area/Cargo.toml b/lib/rust/ensogl/example/text-area/Cargo.toml index 222fbc24f9..946e0a7ec9 100644 --- a/lib/rust/ensogl/example/text-area/Cargo.toml +++ b/lib/rust/ensogl/example/text-area/Cargo.toml @@ -13,4 +13,4 @@ ensogl-core = { path = "../../core" } ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" } ensogl-text = { path = "../../component/text" } ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" } -wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] } +wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] } diff --git a/lib/rust/ensogl/example/text-area/src/lib.rs b/lib/rust/ensogl/example/text-area/src/lib.rs index 07f18a3a21..f81ac0ab51 100644 --- a/lib/rust/ensogl/example/text-area/src/lib.rs +++ b/lib/rust/ensogl/example/text-area/src/lib.rs @@ -20,7 +20,6 @@ use ensogl_core::prelude::*; use ensogl_core::application::Application; use ensogl_core::display::navigation::navigator::Navigator; -use ensogl_core::system::web; use ensogl_text::Area; use ensogl_text_msdf_sys::run_once_initialized; use wasm_bindgen::prelude::*; @@ -30,16 +29,12 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] #[allow(dead_code)] pub fn entry_point_text_area() { - web::forward_panic_hook_to_console(); - web::set_stack_trace_limit(); run_once_initialized(|| { - let app = Application::new(&web::get_html_element_by_id("root").unwrap()); - init(&app); - mem::forget(app); + init(Application::new("root")); }); } -fn init(app: &Application) { +fn init(app: Application) { let area = app.new_view::(); area.set_position_x(-100.0); area.set_content( @@ -49,15 +44,18 @@ fn init(app: &Application) { area.hover(); area.set_cursor_at_end(); - let scene = app.display.scene(); + let scene = &app.display.default_scene; let navigator = Navigator::new(scene, &scene.camera()); - app.display.scene().add_child(&area); + app.display.default_scene.add_child(&area); let keep = Some(area); app.display - .on_frame(move |_frame| { + .on + .before_frame + .add(move |_frame| { let _ = &keep; }) .forget(); - std::mem::forget(navigator); + mem::forget(navigator); + mem::forget(app); } diff --git a/lib/rust/frp/Cargo.toml b/lib/rust/frp/Cargo.toml index e521cebceb..f58448cb13 100644 --- a/lib/rust/frp/Cargo.toml +++ b/lib/rust/frp/Cargo.toml @@ -19,7 +19,7 @@ percent-encoding = { version = "2.1.0" } unicode-segmentation = { version = "1.6.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.58", features = ["nightly"] } +wasm-bindgen = { version = "0.2.78", features = ["nightly"] } [dependencies.web-sys] version = "0.3.4" diff --git a/lib/rust/frp/src/debug.rs b/lib/rust/frp/src/debug.rs index 14db53f2bb..f15ce4517c 100644 --- a/lib/rust/frp/src/debug.rs +++ b/lib/rust/frp/src/debug.rs @@ -176,7 +176,7 @@ pub trait GraphvizBuilder { let code = self.to_graphviz(); let url = percent_encoding::utf8_percent_encode(&code, percent_encoding::NON_ALPHANUMERIC); let url = format!("https://dreampuf.github.io/GraphvizOnline/#{}", url); - crate::web::window().open_with_url_and_target(&url, "_blank").unwrap(); + crate::web::window.open_with_url_and_target(&url, "_blank").unwrap(); } } @@ -184,7 +184,7 @@ pub fn display_graphviz(viz: Graphviz) { let code: String = viz.into(); let url = percent_encoding::utf8_percent_encode(&code, percent_encoding::NON_ALPHANUMERIC); let url = format!("https://dreampuf.github.io/GraphvizOnline/#{}", url); - crate::web::window().open_with_url_and_target(&url, "_blank").unwrap(); + crate::web::window.open_with_url_and_target(&url, "_blank").unwrap(); } diff --git a/lib/rust/frp/src/io/js.rs b/lib/rust/frp/src/io/js.rs index 0418c75ff2..96fa582193 100644 --- a/lib/rust/frp/src/io/js.rs +++ b/lib/rust/frp/src/io/js.rs @@ -1,12 +1,8 @@ //! A module with Javascript IO bindings utilities. -use crate::prelude::*; - use crate as frp; -use enso_logger::WarningLogger as Logger; use enso_web as web; -use wasm_bindgen::prelude::*; -use wasm_bindgen::JsCast; +use enso_web::prelude::*; @@ -15,76 +11,53 @@ use wasm_bindgen::JsCast; // ================ /// Callback for keyboard events. -pub trait KeyboardEventCallback = FnMut(&web_sys::KeyboardEvent) + 'static; +pub trait KeyboardEventCallback = FnMut(&enso_web::KeyboardEvent) + 'static; /// Callback for js events. -pub trait EventCallback = FnMut(&web_sys::Event) + 'static; +pub trait EventCallback = FnMut(&enso_web::Event) + 'static; /// Keyboard event listener which calls the callback function as long it lives. #[derive(Derivative)] #[derivative(Debug(bound = ""))] -pub struct Listener { - logger: Logger, - callback: Closure, - element: web::Window, - event_type: String, +pub struct Listener { + handle: web::EventListenerHandle, + element: web::Window, } -impl Listener { +impl Listener { /// Constructor. - pub fn new(logger: impl AnyLogger, event_type: impl Str, callback: Closure) -> Self { - let element = web::window(); - let js_function = callback.as_ref().unchecked_ref(); - let logger = Logger::new_sub(logger, "Listener"); + pub fn new(event_type: impl Str, callback: Closure) -> Self { + let element = web::window.clone(); let event_type = event_type.as_ref(); let options = event_listener_options(); - let result = element.add_event_listener_with_callback_and_add_event_listener_options( - event_type, - js_function, - &options, - ); - if let Err(err) = result { - warning!(logger, "Couldn't add {event_type} event listener: {err:?}."); - } - let event_type = event_type.into(); - Self { logger, callback, element, event_type } + let handle = web::add_event_listener_with_options(&element, event_type, callback, &options); + Self { handle, element } } } -impl Listener { +impl Listener { /// Creates a new key down event listener. - pub fn new_key_down(logger: impl AnyLogger, f: F) -> Self + pub fn new_key_down(f: F) -> Self where F: KeyboardEventCallback { let boxed = Box::new(f); let closure = Closure::::wrap(boxed); - Self::new(logger, "keydown", closure) + Self::new("keydown", closure) } /// Creates a new key up event listener. - pub fn new_key_up(logger: impl AnyLogger, f: F) -> Self + pub fn new_key_up(f: F) -> Self where F: KeyboardEventCallback { let boxed = Box::new(f); let closure = Closure::::wrap(boxed); - Self::new(logger, "keyup", closure) + Self::new("keyup", closure) } -} -impl Listener { /// Creates a blur event listener. - pub fn new_blur(logger: impl AnyLogger, f: F) -> Self + pub fn new_blur(f: F) -> Self where F: EventCallback { let boxed = Box::new(f); let closure = Closure::::wrap(boxed); - Self::new(logger, "blur", closure) - } -} - -impl Drop for Listener { - fn drop(&mut self) { - let callback = self.callback.as_ref().unchecked_ref(); - if self.element.remove_event_listener_with_callback(&self.event_type, callback).is_err() { - warning!(self.logger, "Couldn't remove event listener."); - } + Self::new("blur", closure) } } @@ -93,8 +66,8 @@ impl Drop for Listener { /// Retrun options for addEventListener function. See also /// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener -fn event_listener_options() -> web_sys::AddEventListenerOptions { - let mut options = web_sys::AddEventListenerOptions::new(); +fn event_listener_options() -> enso_web::AddEventListenerOptions { + let mut options = enso_web::AddEventListenerOptions::new(); // We listen for events in capture phase, so we can decide ourself if it should be passed // further. options.capture(true); @@ -119,11 +92,11 @@ fn event_listener_options() -> web_sys::AddEventListenerOptions { #[derive(Clone, CloneRef, Debug)] pub struct CurrentJsEvent { /// Currently handled js event. - pub event: frp::Stream>, + pub event: frp::Stream>, /// Emitting this signal while handling js event (`current_js_event` is Some) makes this event /// pass to the DOM elements. Otherwise the js event propagation will be stopped. pub pass_to_dom: frp::Source, - event_source: frp::Source>, + event_source: frp::Source>, network: frp::Network, } @@ -161,12 +134,11 @@ impl CurrentJsEvent { mut processing_fn: impl FnMut(&Event), ) -> impl FnMut(&Event) where - Event: AsRef, + Event: AsRef, { let event_source = self.event_source.clone_ref(); move |event| { let js_event = event.as_ref().clone(); - event_source.emit(Some(js_event)); processing_fn(event); event_source.emit(None); @@ -177,10 +149,10 @@ impl CurrentJsEvent { // The bool is passed by reference to match the signatures expected by FRP eval. #[allow(clippy::trivially_copy_pass_by_ref)] fn on_event_change( - new: &Option, - current: &Option, + new: &Option, + current: &Option, is_passed: &bool, - ) -> Option { + ) -> Option { // Whenever the current js event change, we pass the processed one to the dom if someone // asked to. if let Some(e) = current { diff --git a/lib/rust/frp/src/io/keyboard.rs b/lib/rust/frp/src/io/keyboard.rs index e3fe600eea..76a342c509 100644 --- a/lib/rust/frp/src/io/keyboard.rs +++ b/lib/rust/frp/src/io/keyboard.rs @@ -4,13 +4,11 @@ use crate::prelude::*; use crate as frp; use crate::io::js::CurrentJsEvent; -use crate::io::js::EventCallback; -use crate::io::js::KeyboardEventCallback; use crate::io::js::Listener; +use enso_web::KeyboardEvent; use inflector::Inflector; use unicode_segmentation::UnicodeSegmentation; -use web_sys::KeyboardEvent; @@ -417,34 +415,23 @@ impl Default for Keyboard { #[derive(Debug)] pub struct DomBindings { #[allow(dead_code)] - key_down: Listener, + key_down: Listener, #[allow(dead_code)] - key_up: Listener, + key_up: Listener, #[allow(dead_code)] - blur: Listener, + blur: Listener, } impl DomBindings { /// Create new Keyboard and Frp bindings. - pub fn new( - logger: impl AnyLogger, - keyboard: &Keyboard, - current_event: &CurrentJsEvent, - ) -> Self { - let key_down = Listener::new_key_down( - &logger, - current_event.make_event_handler( - f!((event:&KeyboardEvent) keyboard.source.down.emit(KeyWithCode::from(event))), - ), - ); - let key_up = Listener::new_key_up( - &logger, - current_event.make_event_handler( - f!((event:&KeyboardEvent) keyboard.source.up.emit(KeyWithCode::from(event))), - ), - ); + pub fn new(keyboard: &Keyboard, current_event: &CurrentJsEvent) -> Self { + let key_down = Listener::new_key_down(current_event.make_event_handler( + f!((event:&KeyboardEvent) keyboard.source.down.emit(KeyWithCode::from(event))), + )); + let key_up = Listener::new_key_up(current_event.make_event_handler( + f!((event:&KeyboardEvent) keyboard.source.up.emit(KeyWithCode::from(event))), + )); let blur = Listener::new_blur( - &logger, current_event.make_event_handler(f_!(keyboard.source.window_defocused.emit(()))), ); Self { key_down, key_up, blur } diff --git a/lib/rust/logger/Cargo.toml b/lib/rust/logger/Cargo.toml index ce420f1e89..305fe675cc 100644 --- a/lib/rust/logger/Cargo.toml +++ b/lib/rust/logger/Cargo.toml @@ -23,7 +23,7 @@ default = [] [dependencies] enso-prelude = { version = "^0.2.1", path = "../prelude" } enso-shapely = { version = "^0.2.0", path = "../shapely" } -wasm-bindgen = { version = "0.2.58", features = ["nightly"] } +wasm-bindgen = { version = "0.2.78", features = ["nightly"] } js-sys = { version = "0.3.28" } [dependencies.web-sys] diff --git a/lib/rust/not-used/eval-tt/Cargo.toml b/lib/rust/not-used/eval-tt/Cargo.toml deleted file mode 100644 index 9cf42d5de9..0000000000 --- a/lib/rust/not-used/eval-tt/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "eval-tt" -version = "0.1.0" -authors = ["Enso Team "] -edition = "2021" - -[lib] - -[dependencies] -paste = "1.0.5" diff --git a/lib/rust/not-used/eval-tt/src/lib.rs b/lib/rust/not-used/eval-tt/src/lib.rs deleted file mode 100644 index 7abe169785..0000000000 --- a/lib/rust/not-used/eval-tt/src/lib.rs +++ /dev/null @@ -1,150 +0,0 @@ -//! # Introduction -//! -//! This library defines `eval` macro and several helpers. The macro allows -//! writing macros like they were macro-level functions. Example is always worth -//! more than 1000 words, so let's consider the following code: -//! -//! ```compile_fail -//! eval!{ drop(1,split_comma([a,b,c]),3) } -//! ``` -//! -//! It will first evaluate macro `split_comma!{ [a,b,c] }` to `[[a][b][c]]`, -//! and then evaluate `drop` with the resukts as `drop!{ [1] [[a][b][c]] [3] }`. -//! -//! Because this library uses tt-munchers, each argument to a macro is provided -//! inside braces, like in the example above. -//! -//! # Debugging -//! The best way to debug a macro is to enable `#![feature(trace_macros)]` and -//! use the `drop` macro to discard the results, as shown above. -//! -//! # Limitations -//! -//! This library is in a very early shape, so changes may apply soon. There is -//! also a limitation right now. Functions nested deeper than in the example -//! above are not evaluated, so if you would use -//! `eval!{ drop(1,split_comma(foo(5)),3) }`, the `foo` function would not be -//! evaluated. This is just because the implementation is not finished and -//! there are comments in code where it should be added. - -#![warn(unsafe_code)] -#![warn(missing_copy_implementations)] -#![warn(missing_debug_implementations)] - -// ============ -// === Eval === -// ============ - -#[macro_export] -macro_rules! eval_step { - ( $name:ident - $evaled:tt - [ [$f:ident($($f_args:tt)*)] $($not_evaled_args:tt)* ]) => - { - // TODO Here we just call the argument function. Instead we should - // TODO eval it and all its arguments first. - $f!{[ $name $evaled [$($not_evaled_args)*] ] $($f_args)*} - }; - ($name:ident [$($evaled:tt)*] [$arg:tt $($remaining_args:tt)*]) => { - $crate::eval_step!{$name [$($evaled)* $arg] [$($remaining_args)*]} - }; - ($name:ident [$($evaled:tt)*] []) => { - $name! {$($evaled)*} - }; -} - -#[macro_export] -macro_rules! eval_tt { - ($name:ident [$($args:tt)*]) => { - $crate::eval_step!{$name [] [$($args)*]} - }; -} - -#[macro_export] -macro_rules! eval { - ($name:ident($($args:tt)*)) => { - $crate::eval_tt! { eval_tt [$name [split_comma([$($args)*])]] } - }; -} - -#[macro_export] -macro_rules! apply_result { - ([$name:ident [$($evaled:tt)*] $not_evaled_args:tt], $result:tt) => { - $crate::eval_step!{$name [$($evaled)* $result] $not_evaled_args} - }; -} - -// ============ -// === Drop === -// ============ - -#[macro_export] -macro_rules! drop { - ($($toks:tt)*) => {}; -} - -// ================== -// === SplitComma === -// ================== - -#[macro_export] -macro_rules! split_comma { - ($f:tt [$($rest:tt)*]) => { - $crate::split_comma_helper!{$f [] [] [] $($rest)*} - }; -} - -#[macro_export] -macro_rules! split_comma_helper { - ($f:tt [] [$($items:tt)*] $this:tt , $($rest:tt)*) => { - $crate::split_comma_helper!{$f [] [$($items)* $this] [] $($rest)*} - }; - ($f:tt [$($depth:tt)*] $items:tt [$($this:tt)*] < $($rest:tt)*) => { - $crate::split_comma_helper!{$f [. $($depth)*] $items [$($this)* <] $($rest)*} - }; - ($f:tt [$($depth:tt)*] $items:tt [$($this:tt)*] << $($rest:tt)*) => { - $crate::split_comma_helper!{$f [. . $($depth)*] $items [$($this)* <<] $($rest)*} - }; - ($f:tt [$($depth:tt)*] $items:tt [$($this:tt)*] <<< $($rest:tt)*) => { - $crate::split_comma_helper!{$f [. . . $($depth)*] $items [$($this)* <<<] $($rest)*} - }; - ($f:tt [$($depth:tt)*] $items:tt [$($this:tt)*] <<<< $($rest:tt)*) => { - $crate::split_comma_helper!{$f [. . . . $($depth)*] $items [$($this)* <<<<] $($rest)*} - }; - ($f:tt [$($depth:tt)*] $items:tt [$($this:tt)*] <<<<< $($rest:tt)*) => { - $crate::split_comma_helper!{$f [. . . . . $($depth)*] $items [$($this)* <<<<<] $($rest)*} - }; - ($f:tt [. $($depth:tt)*] $items:tt [$($this:tt)*] > $($rest:tt)*) => { - $crate::split_comma_helper!{$f [$($depth)*] $items [$($this)* >] $($rest)*} - }; - ($f:tt [. . $($depth:tt)*] $items:tt [$($this:tt)*] >> $($rest:tt)*) => { - $crate::split_comma_helper!{$f [$($depth)*] $items [$($this)* >>] $($rest)*} - }; - ($f:tt [. . . $($depth:tt)*] $items:tt [$($this:tt)*] >>> $($rest:tt)*) => { - $crate::split_comma_helper!{$f [$($depth)*] $items [$($this)* >>>] $($rest)*} - }; - ($f:tt [. . . . $($depth:tt)*] $items:tt [$($this:tt)*] >>>> $($rest:tt)*) => { - $crate::split_comma_helper!{$f [$($depth)*] $items [$($this)* >>>>] $($rest)*} - }; - ($f:tt [. . . . . $($depth:tt)*] $items:tt [$($this:tt)*] >>>>> $($rest:tt)*) => { - $crate::split_comma_helper!{$f [$($depth)*] $items [$($this)* >>>>>] $($rest)*} - }; - ($f:tt $depth:tt $items:tt [$($this:tt)*] , $($rest:tt)*) => { - $crate::split_comma_helper!{$f $depth $items [$($this)* ,] $($rest)*} - }; - ($f:tt $depth:tt $items:tt [$($this:tt)*] $new:tt $($rest:tt)*) => { - $crate::split_comma_helper!{$f $depth $items [$($this)* $new] $($rest)*} - }; - ($f:tt [] $result:tt []) => { - $crate::apply_result!{$f,$result} - }; - ($f:tt $depth:tt $items:tt $this:tt) => { - $crate::split_comma_helper!{$f $depth $items $this,} - }; -} - -// ================ -// === Examples === -// ================ - -eval! { drop(1,split_comma([a,b,c]),3) } diff --git a/lib/rust/not-used/web-test-proc-macro/Cargo.toml b/lib/rust/not-used/web-test-proc-macro/Cargo.toml deleted file mode 100644 index 030c2faeeb..0000000000 --- a/lib/rust/not-used/web-test-proc-macro/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "web-test-proc-macro" -version = "0.1.0" -authors = ["Enso Team "] -edition = "2021" - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = { version = "1.0.1" } -syn = { version = "1.0.2", features = ["full"] } -quote = { version = "1.0" } -wasm-bindgen-test = { version = "0.3.8" } diff --git a/lib/rust/not-used/web-test-proc-macro/src/lib.rs b/lib/rust/not-used/web-test-proc-macro/src/lib.rs deleted file mode 100644 index 9c62fe4c2b..0000000000 --- a/lib/rust/not-used/web-test-proc-macro/src/lib.rs +++ /dev/null @@ -1,111 +0,0 @@ -#![warn(unsafe_code)] -#![warn(missing_copy_implementations)] -#![warn(missing_debug_implementations)] - -extern crate proc_macro; - -use proc_macro::TokenStream; -use quote::quote; -use syn::*; - -// FIXME: Parse proc_macro args to read the following info: -// #[web_test(dimensions(320.0, 240.0)) and -// #[web_test(no_container)]. - -// =================== -// === #[web_test] === -// =================== - -/// #[web_test] creates a [320.0, 240.0] div with id = fn_name and appends it -/// into the document. -/// # Example -/// ```rust,compile_fail -/// use web_test::web_test; -/// use web_test::web_configure; -/// use ensogl::system::web::get_element_by_id; -/// -/// web_configure!(run_in_browser); -/// -/// #[web_test] -/// fn get_identified_element() { -/// // #[web_test] creates a
-/// assert!(get_element_by_id("get_identified_element").is_ok()); -/// } -/// ``` -#[proc_macro_attribute] -pub fn web_test(_args: TokenStream, input: TokenStream) -> TokenStream { - if let Ok(mut parsed) = syn::parse::(input.clone()) { - let fn_string = format!("{}", parsed.sig.ident); - let code = format!("Container::new(\"Tests\", \"{}\", 320.0, 240.0);", fn_string); - - if let Ok(stmt) = parse_str::(&code) { - // We insert Container::new("Tests", fn_name, 320.0, 240.0) - // at the beginning of the function block. - parsed.block.stmts.insert(0, stmt); - - let output = quote! { - #[wasm_bindgen_test] - #parsed - }; - output.into() - } else { - input - } - } else { - input - } -} - - -// ==================== -// === #[web_bench] === -// ==================== - -/// #[web_bench] creates a benchmark div with a toggle button. -/// # Example -/// ```rust,compile_fail -/// use web_test::web_bench; -/// use web_test::web_configure; -/// use ensogl::system::web::get_element_by_id; -/// use ensogl::system::web::dyn_into; -/// use web_sys::HtmlElement; -/// -/// web_configure!(run_in_browser); -/// -/// #[web_bench] -/// fn test_performance(b: &mut Bencher) { -/// let element = get_element_by_id("test_performance").expect("div"); -/// let element : HtmlElement = dyn_into(element).expect("HtmlElement"); -/// -/// let numbers : Vec<_> = (1 ..= 1000).collect(); -/// b.iter(move || { -/// let ans = numbers.iter().fold(0, |acc, x| acc + x); -/// element.set_inner_html(&format!("Answer: {}", ans)); -/// }) -/// } -/// ``` -#[proc_macro_attribute] -pub fn web_bench(_args: TokenStream, input: TokenStream) -> TokenStream { - if let Ok(parsed) = parse::(input.clone()) { - use proc_macro2::*; - let input: TokenStream = input.into(); - - let fn_ident = parsed.sig.ident; - let fn_string = format!("{}", fn_ident); - let fn_benchmark_str = format!("{}_benchmark", fn_string); - let fn_benchmark = Ident::new(&fn_benchmark_str, Span::call_site()); - - let output = quote! { - #[wasm_bindgen_test] - fn #fn_benchmark() { - let container = BenchContainer::new(#fn_string, 320.0, 240.0); - let mut b = Bencher::new(container); - #fn_ident(&mut b); - } - #input - }; - output.into() - } else { - input - } -} diff --git a/lib/rust/not-used/web-test/Cargo.toml b/lib/rust/not-used/web-test/Cargo.toml deleted file mode 100644 index bf0997fcf4..0000000000 --- a/lib/rust/not-used/web-test/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "web-test" -version = "0.1.0" -authors = ["Enso Team "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dependencies] -enso-prelude = { path = "../../prelude"} -ensogl = { path = "../../ensogl" } -enso-web = { path = "../../web" } -web-test-proc-macro = { path = "../web-test-proc-macro" } -wasm-bindgen = { version = "0.2.58", features = ["nightly"] } -wasm-bindgen-test = { version = "0.3.8" } -js-sys = { version = "0.3.28" } -shrinkwraprs = { version = "0.3.0" } - -[dependencies.web-sys] -version = "0.3.4" diff --git a/lib/rust/not-used/web-test/src/bench_container.rs b/lib/rust/not-used/web-test/src/bench_container.rs deleted file mode 100644 index 6a6bdd1333..0000000000 --- a/lib/rust/not-used/web-test/src/bench_container.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::prelude::*; - -use super::Container; -use crate::system::web; -use crate::system::web::NodeInserter; -use crate::system::web::StyleSetter; -use wasm_bindgen::JsCast; - - - -// ====================== -// === BenchContainer === -// ====================== - -/// Html container displaying benchmark results. -#[derive(Shrinkwrap, Debug)] -pub struct BenchContainer { - #[shrinkwrap(main_field)] - container: Container, - pub measurement: web::HtmlDivElement, - pub time: web::HtmlElement, - pub iter: web::HtmlElement, - pub button: web::HtmlElement, -} - -impl BenchContainer { - /// Creates an identificable container with provided dimensions. - pub fn new(name: &str, width: f32, height: f32) -> Self { - let div = web::create_div(); - div.set_style_or_panic("margin", "0px 2px"); - div.set_style_or_panic("height", "24px"); - div.set_style_or_panic("bottom-border", "1px solid black"); - div.set_style_or_panic("display", "flex"); - div.set_style_or_panic("justify-content", "space-between"); - div.set_style_or_panic("align-items", "center"); - - div.set_inner_html( - "
00.00ms
\ -
0 iterations
\ - ", - ); - - let children = div.children(); - let time = children.item(0).expect("time div"); - let iter = children.item(1).expect("iter div"); - let button = children.item(2).expect("button div"); - let time: web::HtmlElement = time.dyn_into().expect("time HtmlElement"); - let iter: web::HtmlElement = iter.dyn_into().expect("iter HtmlElement"); - let button: web::HtmlElement = button.dyn_into().expect("buttn HtmlElement"); - - let container = Container::new("Benchmarks", name, width, height); - let header_height = 17.0; - let height = format!("{}px", height + header_height + 25.0); - - container.div.set_style_or_panic("height", height); - container.div.insert_before_or_panic(&div, &container.container); - - let measurement = div; - Self { container, measurement, time, iter, button } - } -} diff --git a/lib/rust/not-used/web-test/src/bencher.rs b/lib/rust/not-used/web-test/src/bencher.rs deleted file mode 100644 index 8a705ddf93..0000000000 --- a/lib/rust/not-used/web-test/src/bencher.rs +++ /dev/null @@ -1,174 +0,0 @@ -use crate::prelude::*; - -use super::BenchContainer; -use crate::system::web; -use ensogl::animation; -use ensogl::control::callback; - -use std::cell::RefCell; -use std::rc::Rc; -use wasm_bindgen::prelude::Closure; -use wasm_bindgen::JsCast; - - -// ========================= -// === BencherProperties === -// ========================= - -/// Cell, used to hold Bencher's data -#[derive(Derivative)] -#[derivative(Debug)] -pub struct BencherProperties { - #[derivative(Debug = "ignore")] - callback: Box, - container: BenchContainer, - iterations: usize, - total_time: f64, - event_loop: animation::DynamicLoop, - callback_guard: Option, -} - -impl BencherProperties { - pub fn new( - event_loop: animation::DynamicLoop, - callback: T, - container: BenchContainer, - ) -> Self { - let iterations = 0; - let total_time = 0.0; - let callback_guard = None; - let callback = Box::new(callback); - Self { callback, container, iterations, total_time, event_loop, callback_guard } - } - - /// Adds the duration of the next iteration and updates the UI. - pub fn add_iteration_time(&mut self, time: f64) { - self.iterations += 1; - self.total_time += time; - let iterations = format!("{} iterations", self.iterations); - let average = self.total_time / self.iterations as f64; - let display = format!("{:.2}ms", average); - - self.container.iter.set_inner_html(&iterations); - self.container.time.set_inner_html(&display); - } -} - - - -// =================== -// === BencherData === -// =================== - -#[derive(Shrinkwrap, Debug)] -pub struct BencherData { - properties: RefCell, -} - -impl BencherData { - pub fn new( - event_loop: animation::DynamicLoop, - callback: T, - container: BenchContainer, - ) -> Rc { - let properties = RefCell::new(BencherProperties::new(event_loop, callback, container)); - Rc::new(Self { properties }) - } - - /// Starts the benchmarking loop. - fn start(self: &Rc) { - let data_clone = self.clone(); - let performance = web::performance(); - let mut t0 = performance.now(); - let callback_guard = self.event_loop().on_frame(Box::new(move |_| { - let mut data = data_clone.borrow_mut(); - - (&mut data.callback)(); - - let t1 = performance.now(); - let dt = t1 - t0; - t0 = t1; - - data.add_iteration_time(dt); - })); - self.properties.borrow_mut().callback_guard = Some(callback_guard); - } - - /// Stops the benchmarking loop. - fn stop(&self) { - self.properties.borrow_mut().callback_guard = None; - } - - fn iter T + 'static>(&self, mut callback: F) { - self.properties.borrow_mut().callback = Box::new(move || { - callback(); - }); - } -} - - -// === Getters === - -impl BencherData { - fn event_loop(&self) -> animation::DynamicLoop { - self.properties.borrow().event_loop.clone() - } - - /// Check if the loop is running. - fn is_running(self: &Rc) -> bool { - self.properties.borrow().callback_guard.is_some() - } -} - - - -// =============== -// === Bencher === -// =============== - -/// The Bencher struct with an API compatible to Rust's test Bencher. -#[derive(Clone, Debug)] -pub struct Bencher { - data: Rc, -} - -impl Bencher { - /// Creates a Bencher with a html test container. - pub fn new(container: BenchContainer) -> Self { - let func = Box::new(|| ()); - let event_loop = animation::DynamicLoop::new(); - let data = BencherData::new(event_loop, func, container); - - let data_clone = data.clone(); - let closure = Box::new(move || { - if data_clone.is_running() { - data_clone.stop(); - } else { - data_clone.start(); - } - }) as Box; - - { - let closure = Closure::wrap(closure); - let cell = data.properties.borrow(); - let measurement = &cell.container.measurement; - measurement.set_onclick(Some(closure.as_ref().unchecked_ref())); - closure.forget(); - } - - Self { data } - } - - pub fn is_running(&self) -> bool { - self.data.is_running() - } - - /// Callback for benchmark functions to run in their body. - pub fn iter T + 'static>(&mut self, callback: F) { - self.data.iter(callback); - } - - pub fn event_loop(&self) -> animation::DynamicLoop { - self.data.event_loop() - } -} diff --git a/lib/rust/not-used/web-test/src/container.rs b/lib/rust/not-used/web-test/src/container.rs deleted file mode 100644 index 3bf0e35b38..0000000000 --- a/lib/rust/not-used/web-test/src/container.rs +++ /dev/null @@ -1,55 +0,0 @@ -use super::Group; -use crate::system::web; -use crate::system::web::AttributeSetter; -use crate::system::web::NodeInserter; -use crate::system::web::StyleSetter; -use wasm_bindgen::JsCast; - - - -// ================= -// === Container === -// ================= - -/// A container to hold tests in `wasm-pack test`. -#[derive(Clone, Debug)] -pub struct Container { - pub div: web::HtmlDivElement, - pub header: web::HtmlElement, - pub container: web::HtmlElement, -} - -impl Container { - /// Creates an identificable container with provided dimensions. - pub fn new(group: &str, name: &str, width: f32, height: f32) -> Self { - let div = web::create_div(); - let width = format!("{}px", width); - let header = web::create_element("center"); - let header: web::HtmlElement = header.dyn_into().expect("HtmlElement"); - - div.set_style_or_panic("width", &width); - div.set_style_or_panic("height", format!("{}px", height + 17.0)); - div.set_style_or_panic("border", "1px solid black"); - div.set_style_or_panic("position", "relative"); - div.set_style_or_panic("margin", "10px"); - header.set_inner_html(name); - header.set_style_or_panic("width", &width); - header.set_style_or_panic("height", format!("{}px", 16.0)); - header.set_style_or_panic("border-bottom", "1px solid black"); - header.set_style_or_panic("position", "relative"); - div.append_or_panic(&header); - - let container = web::create_div(); - let container: web::HtmlElement = container.dyn_into().expect("HtmlElement"); - - container.set_style_or_panic("width", width); - container.set_style_or_panic("height", format!("{}px", height)); - container.set_attribute_or_panic("id", name); - container.set_style_or_panic("position", "relative"); - - div.append_or_panic(&container); - - Group::new(group).div.append_or_panic(&div); - Self { div, header, container } - } -} diff --git a/lib/rust/not-used/web-test/src/group.rs b/lib/rust/not-used/web-test/src/group.rs deleted file mode 100644 index 9eb2f3462b..0000000000 --- a/lib/rust/not-used/web-test/src/group.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::system::web; -use crate::system::web::AttributeSetter; -use crate::system::web::NodeInserter; -use crate::system::web::StyleSetter; -use wasm_bindgen::JsCast; - - -// ============= -// === Group === -// ============= - -/// Helper to group test containers -#[derive(Clone, Debug)] -pub struct Group { - pub div: web::HtmlDivElement, -} - -impl Group { - pub fn new(name: &str) -> Self { - let div: web::HtmlDivElement = match web::get_element_by_id(name) { - // If id=name exists, we use it. - Ok(div) => div.dyn_into().expect("div should be a HtmlElement"), - // If it doesn't exist, we create a new element. - Err(_) => { - let div = web::create_div(); - div.set_attribute_or_panic("id", name); - div.set_style_or_panic("display", "flex"); - div.set_style_or_panic("flex-wrap", "wrap"); - div.set_style_or_panic("border", "1px solid black"); - div.set_style_or_panic("margin-bottom", "10px"); - - let header = web::create_element("center"); - let header = header.dyn_into(); - let header: web::HtmlElement = header.expect("HtmlElement"); - let border = "1px solid black"; - - header.set_inner_html(name); - header.set_style_or_panic("border-bottom", border); - header.set_style_or_panic("width", "100%"); - div.append_or_panic(&header); - web::body().append_or_panic(&div); - div - } - }; - Self { div } - } -} diff --git a/lib/rust/not-used/web-test/src/lib.rs b/lib/rust/not-used/web-test/src/lib.rs deleted file mode 100644 index b9019424d5..0000000000 --- a/lib/rust/not-used/web-test/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -#![feature(arbitrary_self_types)] -#![warn(unsafe_code)] -#![warn(missing_copy_implementations)] -#![warn(missing_debug_implementations)] - -mod system { - pub use enso_web as web; -} - -use enso_prelude as prelude; - -pub use wasm_bindgen_test::wasm_bindgen_test; -pub use wasm_bindgen_test::wasm_bindgen_test_configure as web_configure; -pub use web_test_proc_macro::*; - -mod bench_container; -mod bencher; -mod container; -mod group; - -pub use bench_container::BenchContainer; -pub use bencher::Bencher; -pub use container::Container; -pub use group::Group; diff --git a/lib/rust/prelude/Cargo.toml b/lib/rust/prelude/Cargo.toml index 88727d412f..62313e8915 100644 --- a/lib/rust/prelude/Cargo.toml +++ b/lib/rust/prelude/Cargo.toml @@ -42,12 +42,12 @@ shrinkwraprs = "0.3.0" serde = { version = "1.0.126", features = ["derive", "rc"], optional = true } serde_json = { version = "1.0", optional = true } smallvec = "1.0.0" -wasm-bindgen = { version = "0.2.58" , features = ["nightly"], optional = true } +wasm-bindgen = { version = "0.2.78" , features = ["nightly"], optional = true } weak-table = "0.3.0" nalgebra = { version = "0.26.2", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen = { version = "0.2.58" , features = ["nightly"] } +wasm-bindgen = { version = "0.2.78" , features = ["nightly"] } [dependencies.web-sys] version = "0.3.4" diff --git a/lib/rust/profiler/Cargo.toml b/lib/rust/profiler/Cargo.toml index 430b0dd727..5e3de1682b 100644 --- a/lib/rust/profiler/Cargo.toml +++ b/lib/rust/profiler/Cargo.toml @@ -7,7 +7,7 @@ authors = ["Enso Team "] [dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0.59", features = ["raw_value"] } -wasm-bindgen = { version = "0.2.58", features = ["nightly"] } +wasm-bindgen = { version = "0.2.78", features = ["nightly"] } enso-profiler-macros = { path = "macros" } enso-web = { path = "../web" } diff --git a/lib/rust/profiler/src/internal.rs b/lib/rust/profiler/src/internal.rs index bb54217a73..32f4db6a47 100644 --- a/lib/rust/profiler/src/internal.rs +++ b/lib/rust/profiler/src/internal.rs @@ -319,7 +319,8 @@ impl Default for Timestamp { #[cfg(target_arch = "wasm32")] fn now() -> f64 { use enso_web as web; - web::performance().now() + use enso_web::traits::*; + web::window.performance_or_panic().now() } #[cfg(not(target_arch = "wasm32"))] fn now() -> f64 { diff --git a/lib/rust/shortcuts/Cargo.toml b/lib/rust/shortcuts/Cargo.toml index 511c6cc584..6eb7730c76 100644 --- a/lib/rust/shortcuts/Cargo.toml +++ b/lib/rust/shortcuts/Cargo.toml @@ -13,7 +13,7 @@ enso-frp = { path = "../frp" } enso-logger = { path = "../logger" } enso-prelude = { path = "../prelude" } enso-web = { path = "../web" } -wasm-bindgen = { version = "0.2.58", features = [ +wasm-bindgen = { version = "0.2.78", features = [ "nightly", "serde-serialize" ] } diff --git a/lib/rust/shortcuts/example/Cargo.toml b/lib/rust/shortcuts/example/Cargo.toml index 33dd20bcfd..31c459f37b 100644 --- a/lib/rust/shortcuts/example/Cargo.toml +++ b/lib/rust/shortcuts/example/Cargo.toml @@ -14,7 +14,7 @@ enso-prelude = { path = "../../prelude" } enso-frp = { path = "../../frp" } enso-shortcuts = { path = "../../shortcuts" } enso-web = { path = "../../web" } -wasm-bindgen = { version = "0.2.58", features = [ +wasm-bindgen = { version = "0.2.78", features = [ "nightly", "serde-serialize" ] } diff --git a/lib/rust/web/Cargo.toml b/lib/rust/web/Cargo.toml index 012e54cf66..54f3f69994 100644 --- a/lib/rust/web/Cargo.toml +++ b/lib/rust/web/Cargo.toml @@ -18,7 +18,7 @@ failure = { version = "0.1.5" } gloo-timers = { version = "0.2.1", features = ["futures"] } js-sys = { version = "0.3.28" } nalgebra = { version = "0.26.1" } -wasm-bindgen = { version = "0.2.58", features = ["nightly"] } +wasm-bindgen = { version = "0.2.78", features = ["nightly"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] async-std = { version = "1.5.0" } diff --git a/lib/rust/web/js/rust_panic.js b/lib/rust/web/js/rust_panic.js deleted file mode 100644 index 36d084c8e4..0000000000 --- a/lib/rust/web/js/rust_panic.js +++ /dev/null @@ -1,10 +0,0 @@ -class RustPanic extends Error { - constructor(message) { - super(message) - this.name = 'RustPanic' - } -} - -export function new_panic_error(message) { - return new RustPanic(message) -} diff --git a/lib/rust/web/src/binding.rs b/lib/rust/web/src/binding.rs new file mode 100644 index 0000000000..5f29730141 --- /dev/null +++ b/lib/rust/web/src/binding.rs @@ -0,0 +1,6 @@ +//! Parent module for web API bindings. It contains both native, WASM bindings and mock ones, which +//! allow compilation of the API to native code without throwing panics in order for it to be useful +//! in native tests. + +pub mod mock; +pub mod wasm; diff --git a/lib/rust/web/src/binding/mock.rs b/lib/rust/web/src/binding/mock.rs new file mode 100644 index 0000000000..dc8527dd24 --- /dev/null +++ b/lib/rust/web/src/binding/mock.rs @@ -0,0 +1,681 @@ +//! Mocked bindings to the web-api allowing its compilation for the native target without throwing +//! panics. + +#![allow(clippy::boxed_local)] + + +use enso_prelude::*; + +use std::marker::Unsize; + + + +// =================== +// === MockDefault === +// =================== + +/// Default value provider. Similar to [`Default`] but with additional implementations. +#[allow(missing_docs)] +pub trait MockDefault { + fn mock_default() -> Self; +} + +/// [`MockDefault::mock_default`] accessor. +pub fn mock_default() -> T { + T::mock_default() +} + +impl MockDefault for () { + fn mock_default() -> Self {} +} + +impl MockDefault for Option { + fn mock_default() -> Self { + Some(mock_default()) + } +} + +impl MockDefault for Result { + fn mock_default() -> Self { + Ok(mock_default()) + } +} + +/// Macro which generates [`MockDefault`] impls which redirect the call to [`Default::default`]. +macro_rules! auto_impl_mock_default { + ( $($tp:ident $(< $($arg:ident),* >)? ),* ) => { + $( + impl $(<$($arg),*>)? MockDefault for $tp $(<$($arg),*>)? { + fn mock_default() -> Self { + default() + } + } + )* + }; +} + +auto_impl_mock_default!(bool, i16, i32, u32, f64, String); + + + +// ================ +// === MockData === +// ================ + +/// Every mock structure implements this trait. +pub trait MockData {} + +/// Macro used to generate mock structures. See the expansion of generated structures to learn more. +macro_rules! mock_struct { + ( $([$opt:ident])? + $name:ident $(<$( $param:ident $(: ?$param_tp:ident)? ),*>)? $(=> $deref:ident)? + ) => { + #[allow(missing_copy_implementations)] + #[allow(non_snake_case)] + #[allow(missing_docs)] + pub struct $name $(<$($param $(:?$param_tp)?),*>)? { + $($( $param : PhantomData<$param> ),*)? + } + + /// # Safety + /// The usage of [`mem::transmute`] is safe here as we transmute ZST types. + #[allow(unsafe_code)] + impl$(<$($param $(:?$param_tp)?),*>)? + $name $(<$($param),*>)? { + /// Const constructor. + pub const fn const_new() -> Self { + unsafe { mem::transmute(()) } + } + } + + impl$(<$($param $(:?$param_tp)?),*>)? + Debug for $name $(<$($param),*>)? { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, stringify!($name)) + } + } + + #[allow(unsafe_code)] + impl $(<$($param $(:?$param_tp)?),*>)? + Default for $name $(<$($param),*>)? { + fn default() -> Self { + Self::const_new() + } + } + + impl $(<$($param $(:?$param_tp)?),*>)? + MockDefault for $name $(<$($param),*>)? { + fn mock_default() -> Self { + default() + } + } + + impl $(<$($param $(:?$param_tp)?),*>)? + Clone for $name $(<$($param),*>)? { + fn clone(&self) -> Self { + default() + } + } + + impl $(<$($param $(:?$param_tp)?),*>)? + CloneRef for $name $(<$($param),*>)? { + fn clone_ref(&self) -> Self { + default() + } + } + + impl $(<$($param $(:?$param_tp)?),*>)? + MockData for $name $(<$($param),*>)? {} + + mock_struct_deref! {[$($deref)?] $name $(<$( $param $(:?$param_tp)?),*>)?} + mock_struct_as_ref! {[$($opt)?] $name $(<$( $param $(:?$param_tp)?),*>)? $(=> $deref)?} + }; +} + +macro_rules! mock_struct_as_ref { + ([NO_AS_REF] $($ts:tt)*) => {}; + ([] $name:ident $(<$( $param:ident $(: ?$param_tp:ident)? ),*>)? + $(=> $deref:ident)? + ) => { + /// # Safety + /// The usage of [`mem::transmute`] is safe here as we transmute ZST types. + #[allow(unsafe_code)] + impl<__T__: MockData, $($($param $(:?$param_tp)? ),*)?> + AsRef<__T__> for $name $(<$($param),*>)? { + fn as_ref(&self) -> &__T__ { + unsafe { mem::transmute(self) } + } + } + }; +} + +macro_rules! mock_struct_deref { + ([] $($ts:tt)*) => {}; + ([$deref:ident] $name:ident $(<$( $param:ident $(: ?$param_tp:ident)? ),*>)?) => { + impl $(<$($param $(:?$param_tp)?),*>)? + Deref for $name $(<$($param),*>)? { + type Target = $deref; + fn deref(&self) -> &Self::Target { + self.as_ref() + } + } + + impl $(<$($param $(:?$param_tp)?),*>)? + From<$name $(<$($param),*>)?> for $deref { + fn from(_: $name) -> Self { + default() + } + } + }; +} + + + +// =============== +// === mock_fn === +// =============== + +/// Create a mock implementation of a non-public function. Read the docs of [`mock_fn_gen`] to learn +/// more. +#[macro_export(local_inner_macros)] +macro_rules! mock_fn { + ( $($ts:tt)* ) => { + mock_fn_gen! {[] $($ts)*} + }; +} + +/// Create a mock implementation of a public function. Read the docs of [`mock_fn_gen`] to learn +/// more. +#[macro_export(local_inner_macros)] +macro_rules! mock_pub_fn { + ( $($ts:tt)* ) => { + mock_fn_gen! {[pub] $($ts)*} + }; +} + +/// Macro used to generate mock methods. Methods look just like their provided signature with a body +/// returning `mock_default()`. There are two special cases: for functions returning `&Self`, and +/// `&mut Self`, which just pass `&self` and `&mut self` to the output, respectively. +#[macro_export(local_inner_macros)] +macro_rules! mock_fn_gen { + ($viz:tt $name:ident $(<$($fn_tp:ident),*>)? (&self $($args:tt)*) -> &Self ) => { + $crate::mock_fn_gen_print! { + $viz $name $(<$($fn_tp),*>)? (&self $($args)*) -> &Self {self} + } + }; + + ($viz:tt $name:ident $(<$($fn_tp:ident),*>)? (&mut self $($args:tt)*) -> &mut Self ) => { + $crate::mock_fn_gen_print! { + $viz $name $(<$($fn_tp),*>)? (&mut self $($args)*) -> &mut Self {self} + } + }; + + ($viz:tt $name:ident $(<$($fn_tp:ident),*>)? (&self $($args:tt)*) -> &$out:ty ) => { + $crate::mock_fn_gen_print! { + $viz $name $(<$($fn_tp),*>)? (&self $($args)*) -> &$out {self.as_ref()} + } + }; + + ($viz:tt $name:ident $(<$($fn_tp:ident),*>)? ($($args:tt)*) $(-> $out:ty)? ) => { + $crate::mock_fn_gen_print! { + $viz $name $(<$($fn_tp),*>)? ($($args)*) $(-> $out)? {mock_default()} + } + }; +} + +/// Macro used to print the final version of the function. +#[macro_export] +macro_rules! mock_fn_gen_print { + ([$($viz:ident)?] $name:ident $(<$($fn_tp:ident),*>)? + ( $($args:tt)* ) $(-> $out:ty)? {$($body:tt)*} ) => { + #[allow(unused_variables)] + #[allow(clippy::too_many_arguments)] + #[allow(missing_docs)] + $($viz)? fn $name $(<$($fn_tp),*>)? ( $($args)* ) $(-> $out)? { + $($body)* + } + }; +} + +/// Combination of [`mock_struct`] and [`mock_pub_fn`]. +macro_rules! mock_data { + ( $([$opt:ident])? + $name:ident $(<$( $param:ident $(: ?$param_tp:ident)? ),*>)? $(=> $deref:ident)? + $( + fn $fn_name:ident $(<$($fn_tp:ident),*>)? ($($args:tt)*) $(-> $out:ty)?; + )* + ) => { + mock_struct!{$([$opt])? $name $(<$($param $(:?$param_tp)?),*>)? $(=> $deref)?} + impl $(<$($param $(:?$param_tp)?),*>)? $name $(<$($param),*>)? { + $( + mock_pub_fn!{$fn_name $(<$($fn_tp),*>)? ($($args)*) $(-> $out)?} + )* + } + }; +} + + + +// ============== +// === JsCast === +// ============== + +/// Mock of [`JsCast`] is implemented for all mocked types. +impl + Into> JsCast for T {} + +/// Mock of [`wasm_bindgen::JsCast`]. +#[allow(missing_docs)] +pub trait JsCast +where Self: MockData + MockDefault + AsRef + Into { + fn has_type(&self) -> bool { + true + } + + fn dyn_into(self) -> Result + where T: JsCast { + Ok(self.unchecked_into()) + } + + fn dyn_ref(&self) -> Option<&T> + where T: JsCast { + Some(self.unchecked_ref()) + } + + fn unchecked_into(self) -> T + where T: JsCast { + T::unchecked_from_js(self.into()) + } + + fn unchecked_ref(&self) -> &T + where T: JsCast { + T::unchecked_from_js_ref(self.as_ref()) + } + + fn is_instance_of(&self) -> bool { + true + } + fn instanceof(_val: &JsValue) -> bool { + true + } + fn is_type_of(_val: &JsValue) -> bool { + true + } + fn unchecked_from_js(_val: JsValue) -> Self { + mock_default() + } + + fn unchecked_from_js_ref(val: &JsValue) -> &Self { + val.as_ref() + } +} + + +// =============== +// === JsValue === +// =============== + +mock_data! { JsValue + fn is_undefined(&self) -> bool; +} + +impl JsValue { + /// NULL value mock. + pub const NULL: JsValue = JsValue {}; +} + +/// All JS types can be converted to `JsValue` and thus it implements a generic conversion trait. +auto trait IsNotJsValue {} +impl !IsNotJsValue for JsValue {} +impl From for JsValue { + default fn from(_: A) -> Self { + default() + } +} + +impl AsRef for wasm_bindgen::JsValue { + fn as_ref(&self) -> &JsValue { + &JsValue::NULL + } +} + + + +// =============== +// === Closure === +// =============== + +mock_data! { [NO_AS_REF] Closure + fn wrap(_data: Box) -> Closure; + fn once(_fn_once: F) -> Closure; +} + +#[allow(missing_docs)] +impl Closure { + pub fn new(_t: F) -> Closure + where F: Unsize + 'static { + mock_default() + } +} + +/// The generated structure does not implement a generic [`AsRef`] impl, as the usages base on the +/// fact that there exist exactly one such an impl (provided below), so the type inferencer can +/// monomoprphise more free variables. +#[allow(unsafe_code)] +impl AsRef for Closure { + fn as_ref(&self) -> &JsValue { + unsafe { mem::transmute(self) } + } +} + + + +// ================ +// === Js Prims === +// ================ + +// === String === +mock_data! { JsString => Object + fn to_string(&self) -> String; +} + +impl From for String { + fn from(_: JsString) -> Self { + "JsString".into() + } +} + +impl From<&JsString> for String { + fn from(_: &JsString) -> Self { + "JsString".into() + } +} + + +// === Array === +mock_data! { Array => Object } + + +// === Error === + +mock_data! { Error => Object + fn new(message: &str) -> Self; +} + +impl From for JsValue { + fn from(_: Error) -> Self { + mock_default() + } +} + + + +// ==================== +// === DOM Elements === +// ==================== + +// === WebGl2RenderingContext === +/// The [`WebGl2RenderingContext`] is not a mocked structure because it defines tons of +/// constants that we use heavily. Instead, the rendering engine runs context-less when +/// compiled to native tests. +pub use web_sys::WebGl2RenderingContext; + +// === Object === +mock_data! { Object => JsValue + fn new() -> Self; + fn value_of(&self) -> Object; + fn keys(object: &Object) -> Array; +} + + +// === EventTarget === +mock_data! { EventTarget => Object + fn remove_event_listener_with_callback + (&self, tp:&str, f:&Function) -> Result<(), JsValue>; + fn add_event_listener_with_callback + (&self, tp:&str, f:&Function) -> Result<(), JsValue>; + fn add_event_listener_with_callback_and_bool + (&self, tp:&str, f:&Function, opt:bool) -> Result<(), JsValue>; + fn add_event_listener_with_callback_and_add_event_listener_options + (&self, tp:&str, f:&Function, opt:&AddEventListenerOptions) + -> Result<(), JsValue>; +} + + +// === Document === +mock_data! { Document => EventTarget + fn body(&self) -> Option; + fn create_element(&self, local_name: &str) -> Result; + fn get_element_by_id(&self, element_id: &str) -> Option; +} + + +// === Window === +mock_data! { Window => EventTarget + fn open_with_url_and_target(&self, url: &str, target: &str) + -> Result, JsValue>; + fn request_animation_frame(&self, callback: &Function) -> Result; + fn cancel_animation_frame(&self, handle: i32) -> Result<(), JsValue>; + fn performance(&self) -> Option; + fn device_pixel_ratio(&self) -> f64; +} + + +// === Function === +mock_data! { Function + fn call1(&self, context: &JsValue, arg1: &JsValue) -> Result; + fn call2(&self, context: &JsValue, arg1: &JsValue, arg2: &JsValue) -> Result; + fn call3(&self, context: &JsValue, arg1: &JsValue, arg2: &JsValue, arg3: &JsValue) + -> Result; +} + + +// === AddEventListenerOptions === +mock_data! { AddEventListenerOptions + fn new() -> Self; +} +impl AddEventListenerOptions { + mock_pub_fn!(capture(&mut self, val:bool) -> &mut Self); + mock_pub_fn!(passive(&mut self, val:bool) -> &mut Self); +} + + +// === Event === +mock_data! { Event => Object + fn prevent_default(&self); + fn stop_propagation(&self); + fn current_target(&self) -> Option; +} + + +// === KeyboardEvent === +mock_data! { KeyboardEvent => Event + fn key(&self) -> String; + fn code(&self) -> String; + fn alt_key(&self) -> bool; + fn ctrl_key(&self) -> bool; +} + + +// === MouseEvent === +mock_data! { MouseEvent => Event + fn button(&self) -> i16; + fn alt_key(&self) -> bool; + fn ctrl_key(&self) -> bool; + fn client_x(&self) -> i32; + fn client_y(&self) -> i32; + fn offset_x(&self) -> i32; + fn offset_y(&self) -> i32; + fn screen_x(&self) -> i32; + fn screen_y(&self) -> i32; +} + + +// === WheelEvent === +mock_data! { WheelEvent => MouseEvent + fn delta_x(&self) -> f64; + fn delta_y(&self) -> f64; +} + + +// === HtmlCollection === +mock_data! { HtmlCollection + fn length(&self) -> u32; + fn get_with_index(&self, index: u32) -> Option; +} + + +// === DomRect === +mock_data! { DomRect + fn width(&self) -> f64; + fn height(&self) -> f64; + fn left(&self) -> f64; + fn right(&self) -> f64; + fn top(&self) -> f64; + fn bottom(&self) -> f64; +} + + +// === Element === +mock_data! { Element => Node + fn remove(&self); + fn children(&self) -> HtmlCollection; + fn get_bounding_client_rect(&self) -> DomRect; + fn set_inner_html(&self, value: &str); + fn set_class_name(&self, value: &str); + fn set_id(&self, value: &str); + fn set_attribute(&self, name: &str, value: &str) -> Result<(), JsValue>; + fn prepend_with_node_0(&self) -> Result<(), JsValue>; + fn prepend_with_node_1(&self, n1: &Node) -> Result<(), JsValue>; + fn prepend_with_node_2(&self, n1: &Node, n2:&Node) -> Result<(), JsValue>; + fn prepend_with_node_3(&self, n1: &Node, n2:&Node, n3:&Node) -> Result<(), JsValue>; +} + +// === HtmlElement === +mock_data! { HtmlElement => Element + fn set_class_name(&self, n: &str); + fn set_inner_text(&self, value: &str); + fn inner_text(&self) -> String; + fn get_elements_by_class_name(&self, class_names: &str) -> HtmlCollection; + fn style(&self) -> CssStyleDeclaration; +} +impl From for EventTarget { + fn from(_: HtmlElement) -> Self { + default() + } +} + + +// === HtmlDivElement === +mock_data! { HtmlDivElement => HtmlElement } +impl From for EventTarget { + fn from(_: HtmlDivElement) -> Self { + default() + } +} + + +// === HtmlCanvasElement === +mock_data! { HtmlCanvasElement => HtmlElement + fn width(&self) -> u32; + fn height(&self) -> u32; + fn set_width(&self, value: u32); + fn set_height(&self, value: u32); + fn get_context(&self, context_id: &str) -> Result, JsValue>; + fn get_context_with_context_options( + &self, + context_id: &str, + context_options: &JsValue + ) -> Result, JsValue>; +} + + +// === CanvasRenderingContext2d === +mock_data! { CanvasRenderingContext2d + fn save(&self); + fn restore(&self); + fn begin_path(&self); + fn stroke(&self); + fn move_to(&self, x: f64, y: f64); + fn line_to(&self, x: f64, y: f64); + fn scale(&self, x: f64, y: f64) -> Result<(), JsValue>; + fn set_fill_style(&self, value: &JsValue); + fn set_stroke_style(&self, value: &JsValue); + fn clear_rect(&self, x: f64, y: f64, w: f64, h: f64); + fn set_line_width(&self, value: f64); + fn translate(&self, x: f64, y: f64) -> Result<(), JsValue>; + fn fill_rect(&self, x: f64, y: f64, w: f64, h: f64); + fn set_font(&self, font: &str); + fn set_text_align(&self, text_align: &str); + fn fill_text(&self, text: &str, x: f64, y: f64) -> Result<(), JsValue>; + fn draw_image_with_html_canvas_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh( + &self, image: &HtmlCanvasElement, + sx: f64, sy: f64, sw: f64, sh: f64, dx: f64, dy: f64, dw: f64, dh: f64 + ) -> Result<(), JsValue>; +} + + +// === Node === +mock_data! { Node => EventTarget + fn parent_node(&self) -> Option; + fn remove_child(&self, child: &Node) -> Result; + fn set_text_content(&self, value: Option<&str>); + fn append_child(&self, node: &Node) -> Result; + fn first_child(&self) -> Option; + fn last_child(&self) -> Option; + fn insert_before( + &self, + node: &Node, + child: Option<&Node> + ) -> Result; +} + + + +// =========== +// === CSS === +// =========== + +// === CssStyleDeclaration === +mock_data! { CssStyleDeclaration => Object + fn set_property(&self, property: &str, value: &str) -> Result<(), JsValue>; +} + + + +// ============= +// === Other === +// ============= + +// === Performance === +mock_data! { Performance => EventTarget + fn now(&self) -> f64; +} + + + +// =============== +// === Reflect === +// =============== + +mock_data! { Reflect + fn get(target: &JsValue, key: &JsValue) -> Result; + fn set( + target: &JsValue, + property_key: &JsValue, + value: &JsValue + ) -> Result; +} + + + +// =========================== +// === Window and Document === +// =========================== + +#[allow(non_upper_case_globals)] +#[allow(missing_docs)] +pub static window: Window = Window {}; + +#[allow(non_upper_case_globals)] +#[allow(missing_docs)] +pub static document: Document = Document::const_new(); diff --git a/lib/rust/web/src/binding/wasm.rs b/lib/rust/web/src/binding/wasm.rs new file mode 100644 index 0000000000..90caecbd61 --- /dev/null +++ b/lib/rust/web/src/binding/wasm.rs @@ -0,0 +1,123 @@ +//! Native bindings to the web-api. + +use enso_prelude::*; + +pub use js_sys::Error; +pub use js_sys::Function; +pub use js_sys::JsString; +pub use js_sys::Object; +pub use wasm_bindgen::prelude::Closure; +pub use wasm_bindgen::prelude::*; +pub use wasm_bindgen::JsCast; +pub use wasm_bindgen::JsValue; +pub use web_sys::console; +pub use web_sys::AddEventListenerOptions; +pub use web_sys::CanvasRenderingContext2d; +pub use web_sys::Document; +pub use web_sys::Element; +pub use web_sys::Event; +pub use web_sys::EventTarget; +pub use web_sys::HtmlCanvasElement; +pub use web_sys::HtmlCollection; +pub use web_sys::HtmlDivElement; +pub use web_sys::HtmlElement; +pub use web_sys::KeyboardEvent; +pub use web_sys::MouseEvent; +pub use web_sys::Node; +pub use web_sys::Performance; +pub use web_sys::WebGl2RenderingContext; +pub use web_sys::WheelEvent; +pub use web_sys::Window; + +pub use std::time::Duration; +pub use std::time::Instant; + + + +// ================ +// === Function === +// ================ + +#[wasm_bindgen(inline_js = " + export function new_function_with_args(args, body) { + return new Function(args, body) + } +")] +extern "C" { + /// See the docs of [`crate::FunctionOps`]. + #[allow(unsafe_code)] + #[wasm_bindgen(catch)] + pub fn new_function_with_args(args: &str, body: &str) -> Result; +} + + + +// =============== +// === Reflect === +// =============== + +/// [`wasm-bindgen`] defines [`Reflect`] as a module. This library needs to extend it with new +/// functions and thus it is mocked as this phantom structure. + +#[derive(Copy, Clone, Debug)] +pub struct Reflect {} + +#[allow(missing_docs)] +impl Reflect { + pub fn get(target: &JsValue, key: &JsValue) -> Result { + js_sys::Reflect::get(target, key) + } + + pub fn set(target: &JsValue, key: &JsValue, value: &JsValue) -> Result { + js_sys::Reflect::set(target, key, value) + } +} + + + +// =========================== +// === Window and Document === +// =========================== + +#[cfg(target_arch = "wasm32")] +/// Similar to [`lazy_static`], but does not require the type to be synced between threads (WASM32 +/// target is single-threaded. +macro_rules! wasm_lazy_global { + ($name:ident : $tp:ty = $expr:expr) => { + #[allow(missing_docs)] + pub mod $name { + use super::*; + pub static mut STORE: Option<$tp> = None; + + // [`Copy`] and [`Clone`] are not implemented on purpose, so when the value is cloned, + // the operation will deref to it's target type. + #[allow(missing_copy_implementations)] + #[derive(Debug)] + pub struct Ref {} + } + + impl Deref for $name::Ref { + type Target = $tp; + #[allow(unsafe_code)] + fn deref(&self) -> &Self::Target { + unsafe { + $name::STORE.as_ref().unwrap_or_else(|| { + let val = $expr; + $name::STORE = Some(val); + $name::STORE.as_ref().unwrap() + }) + } + } + } + + #[allow(non_upper_case_globals)] + #[allow(missing_docs)] + pub const $name: $name::Ref = $name::Ref {}; + }; +} + +#[cfg(target_arch = "wasm32")] +wasm_lazy_global! { window : Window = web_sys::window().unwrap() } + +#[cfg(target_arch = "wasm32")] +wasm_lazy_global! { document : Document = window.document().unwrap() } diff --git a/lib/rust/web/src/clipboard.rs b/lib/rust/web/src/clipboard.rs index 19e2f64fea..0f68736548 100644 --- a/lib/rust/web/src/clipboard.rs +++ b/lib/rust/web/src/clipboard.rs @@ -69,10 +69,10 @@ pub fn write_text(text: impl Into) { pub fn read_text(callback: impl Fn(String) + 'static) { let handler: Rc>> = default(); let handler_clone = handler.clone_ref(); - let closure = Closure::wrap(Box::new(move |result| { + let closure: Closure = Closure::new(move |result| { *handler_clone.borrow_mut() = None; callback(result); - }) as Box); + }); *handler.borrow_mut() = Some(closure); readText(handler.borrow().as_ref().unwrap()); } diff --git a/lib/rust/web/src/closure/storage.rs b/lib/rust/web/src/closure/storage.rs index b588504683..e211767de5 100644 --- a/lib/rust/web/src/closure/storage.rs +++ b/lib/rust/web/src/closure/storage.rs @@ -1,7 +1,10 @@ +#![allow(missing_docs)] + use crate::prelude::*; use js_sys::Function; use wasm_bindgen::convert::FromWasmAbi; +pub use wasm_bindgen::prelude::Closure; use wasm_bindgen::JsCast; diff --git a/lib/rust/web/src/event.rs b/lib/rust/web/src/event.rs index effa080795..9e5d278eee 100644 --- a/lib/rust/web/src/event.rs +++ b/lib/rust/web/src/event.rs @@ -2,9 +2,9 @@ pub mod listener; -use crate::prelude::*; - use js_sys::Function; +use wasm_bindgen::JsValue; +use web_sys::Event; use web_sys::EventTarget; @@ -27,7 +27,7 @@ pub trait Type { /// The event value -- i.e. the Rust type of a value that will be passed as an argument /// to the listener. /// For example `web_sys::CloseEvent`. - type Interface: AsRef; + type Interface: AsRef; /// The type of the EventTarget object that fires this type of event, e.g. `web_sys::WebSocket`. type Target: AsRef + AsRef + Clone + PartialEq; diff --git a/lib/rust/web/src/event/listener.rs b/lib/rust/web/src/event/listener.rs index 9615b0039b..20619b27a6 100644 --- a/lib/rust/web/src/event/listener.rs +++ b/lib/rust/web/src/event/listener.rs @@ -1,8 +1,10 @@ -use crate::prelude::*; +#![allow(missing_docs)] use crate::closure::storage::ClosureFn; use crate::closure::storage::OptionalFmMutClosure; +use crate::prelude::*; + // ============ diff --git a/lib/rust/web/src/lib.rs b/lib/rust/web/src/lib.rs index ddb6f52aa4..b23c40d69d 100644 --- a/lib/rust/web/src/lib.rs +++ b/lib/rust/web/src/lib.rs @@ -1,8 +1,33 @@ -#![warn(unsafe_code)] +//! This module implements web bindings. It heavily uses [`wasm_bindgen`] and extends it with many +//! high-level features and bug-fixes. It also provides a mock API version allowing the native +//! compilation in order to run native tests of code which uses this API. + +// === Linter configuration === #![warn(missing_copy_implementations)] #![warn(missing_debug_implementations)] +#![warn(missing_docs)] +#![warn(trivial_casts)] +#![warn(trivial_numeric_casts)] +#![warn(unsafe_code)] +#![warn(unused_import_braces)] +#![warn(unused_qualifications)] +// === Features === +#![allow(incomplete_features)] #![feature(trait_alias)] +#![feature(negative_impls)] +#![feature(specialization)] +#![feature(auto_traits)] +#![feature(unsize)] + +use crate::prelude::*; + +use wasm_bindgen::prelude::wasm_bindgen; + +pub use std::time::Duration; +pub use std::time::Instant; + +pub mod binding; pub mod clipboard; pub mod closure; pub mod event; @@ -12,83 +37,599 @@ pub mod stream; /// Common types that should be visible across the whole crate. pub mod prelude { + pub use super::traits::*; + + pub use super::Closure; + pub use super::EventTarget; + pub use super::Function; + pub use super::HtmlDivElement; + pub use super::HtmlElement; + pub use super::JsCast; + pub use super::JsValue; + pub use super::Object; pub use enso_logger::DefaultInfoLogger as Logger; pub use enso_logger::*; pub use enso_prelude::*; - pub use wasm_bindgen::prelude::*; } -use crate::prelude::*; -use enso_logger::warning; -use enso_logger::WarningLogger as Logger; -use js_sys::Function; -use wasm_bindgen::prelude::Closure; -use wasm_bindgen::JsCast; -pub use web_sys::console; -pub use std::time::Duration; -pub use std::time::Instant; -pub use web_sys::CanvasRenderingContext2d; -pub use web_sys::Document; -pub use web_sys::Element; -pub use web_sys::EventTarget; -pub use web_sys::HtmlCanvasElement; -pub use web_sys::HtmlCollection; -pub use web_sys::HtmlDivElement; -pub use web_sys::HtmlElement; -pub use web_sys::MouseEvent; -pub use web_sys::Node; -pub use web_sys::Performance; -pub use web_sys::WebGl2RenderingContext; -pub use web_sys::Window; +// =================== +// === API Imports === +// =================== + +#[cfg(target_arch = "wasm32")] +pub use binding::wasm::*; + +#[cfg(not(target_arch = "wasm32"))] +pub use binding::mock::*; -// ============= -// === Error === -// ============= +// ============== +// === Traits === +// ============== -/// Generic error representation. We may want to support errors in form of structs and enums, but it -/// requires significant work, so a simpler solution was chosen for now. -#[derive(Debug, Fail)] -#[fail(display = "{}", message)] -pub struct Error { - message: String, +macro_rules! gen_trait_modules { + ( $($name:ident),* $(,)?) => { + /// WASM-target oriented traits. Extending the possibilities of wasm-bindgen structures. + pub mod wasm_traits { + $(pub use super::$name::WasmTrait as $name;)* + pub use super::binding::wasm::JsCast; + } + + /// Mock traits. Counterpart of [`wasm_traits`]. + pub mod mock_traits { + $(pub use super::$name::MockTrait as $name;)* + pub use super::binding::mock::JsCast; + } + + /// Both wasm and mock traits, unnamed. + pub mod anonymous_traits { + $(pub use super::$name::WasmTrait as _;)* + $(pub use super::$name::MockTrait as _;)* + } + }; } -#[allow(non_snake_case)] -pub fn Error>(message: S) -> Error { - let message = message.into(); - Error { message } +gen_trait_modules! { + ClosureOps, + DocumentOps, + ElementOps, + FunctionOps, + HtmlCanvasElementOps, + HtmlElementOps, + JsValueOps, + NodeOps, + ObjectOps, + ReflectOps, + WindowOps, } -pub type Result = std::result::Result; +/// All traits defined in this module. +pub mod traits { + pub use super::anonymous_traits::*; + #[cfg(not(target_arch = "wasm32"))] + pub use super::mock_traits::*; + #[cfg(target_arch = "wasm32")] + pub use super::wasm_traits::*; +} -impl From for Error { - fn from(t: JsValue) -> Self { - let message = format!("{:?}", t); - Self { message } +/// Helper for generating extensions to web API targeting the Wasm32 architecture (defined by +/// [`wasm_bindgen`]) and the mock API defined in this library (imitating [`wasm-bindgen`]). +/// +/// For each extension definition, it creates two traits, each for one of these APIs. These traits +/// are then re-exported in modules generated by [`gen_trait_modules`]. In particular, all Wasm32 +/// extensions are grouped in the [`wasm_traits`] module, the mock extensions are grouped in the +/// [`mock_traits`] module, and they are merged together in the [`anonymous_traits`] module. The +/// [`traits`] module contains [`wasm_traits`] or [`mock_traits`] if it was compiled for Wasm32 +/// or native architecture, respectively. +/// +/// This macro usage contains and required and three optional sections: +/// - The `trait` section (required) section provides functions, just like ordinary trait +/// definition. +/// - The `impl` section (optional) provides implementations of the trait functions which are copied +/// to both the Wasm32 and the mock traits. +/// - The `wasm_impl` section (optional) provides implementations of the trait functions which are +/// copied to both the Wasm32 trait only. +/// - The `wasm_mock` section (optional) provides implementations of the trait functions which are +/// copied to both the mock trait only. +/// +/// For example, the following usage: +/// +/// ```text +/// ops! { JsValueOps for JsValue +/// trait { +/// fn print_to_string(&self) -> String; +/// fn test(&self); +/// } +/// +/// impl { +/// fn test() { +/// println!("test"); +/// } +/// } +/// +/// wasm_impl { +/// fn print_to_string(&self) -> String { +/// super::js_print_to_string(self) +/// } +/// } +/// +/// mock_impl { +/// fn print_to_string(&self) -> String { +/// "JsValue".into() +/// } +/// } +/// } +/// ``` +/// +/// Generates the following output: +/// +/// ```text +/// pub mod JsValueOps { +/// use super::*; +/// +/// pub mod wasm { +/// use super::binding::wasm::*; +/// use super::wasm_traits::*; +/// use enso_prelude::*; +/// +/// pub trait Trait { +/// fn print_to_string(&self) -> String; +/// fn test(&self); +/// } +/// +/// +/// impl Trait for JsValue { +/// fn test() { +/// println!("test") +/// } +/// +/// fn print_to_string(&self) -> String { +/// super::js_print_to_string(self) +/// } +/// } +/// } +/// +/// pub mod mock { +/// use super::binding::mock::*; +/// use super::mock_traits::*; +/// use enso_prelude::*; +/// +/// pub trait Trait { +/// fn print_to_string(&self) -> String; +/// fn test(&self); +/// } +/// +/// impl Trait for JsValue { +/// fn test() { +/// println!("test") +/// } +/// +/// fn print_to_string(&self) -> String { +/// "JsValue".into() +/// } +/// } +/// } +/// +/// pub use self::mock::Trait as MockTrait; +/// pub use self::wasm::Trait as WasmTrait; +/// } +/// ``` +macro_rules! ops { + ($(<$($arg:ident : ($($arg_tp:tt)*)),*>)? $trait:ident for $target:ident + trait $defs:tt + $(impl {$($body:tt)*})? + $(wasm_impl {$($wasm_body:tt)*})? + $(mock_impl {$($mock_body:tt)*})? + ) => { + /// [`$trait`] extensions. + #[allow(non_snake_case)] + #[allow(missing_docs)] + #[allow(unused_imports)] + pub mod $trait { + use super::*; + + /// WASM bindings. + pub mod wasm { + use enso_prelude::*; + use super::binding::wasm::*; + use super::wasm_traits::*; + /// Extensions to the [`$target`] type. + pub trait Trait $defs + impl $(<$($arg: $($arg_tp)*),*>)? Trait for $target $(<$($arg),*>)? + {$($($body)*)? $($($wasm_body)*)?} + } + + /// Mock bindings. + pub mod mock { + use enso_prelude::*; + use super::binding::mock::*; + use super::mock_traits::*; + /// Extensions to the [`$target`] type. + pub trait Trait $defs + impl $(<$($arg: $($arg_tp)*),*>)? Trait for $target $(<$($arg),*>)? + {$($($body)*)? $($($mock_body)*)?} + } + pub use self::wasm::Trait as WasmTrait; + pub use self::mock::Trait as MockTrait; + } + }; +} + + + +// ================== +// === JsValueOps === +// ================== + +ops! { JsValueOps for JsValue + trait { + /// Converts **any** `JsValue` into a `String`. Uses JS's `String` function, + /// see: https://www.w3schools.com/jsref/jsref_string.asp + fn print_to_string(&self) -> String; + } + + wasm_impl { + fn print_to_string(&self) -> String { + super::js_print_to_string(self) + } + } + + mock_impl { + fn print_to_string(&self) -> String { + "JsValue".into() + } } } - - -// ============== -// === String === -// ============== - #[wasm_bindgen] extern "C" { #[allow(unsafe_code)] #[wasm_bindgen(js_name = "String")] - fn js_to_string_inner(s: &JsValue) -> String; + #[allow(unused_qualifications)] + fn js_print_to_string(s: &binding::wasm::JsValue) -> String; } -/// Converts given `JsValue` into a `String`. Uses JS's `String` function, -/// see: https://www.w3schools.com/jsref/jsref_string.asp -pub fn js_to_string(s: impl AsRef) -> String { - js_to_string_inner(s.as_ref()) + + +// ================== +// === ClosureOps === +// ================== + +ops! { ClosureOps for Closure + trait { + fn as_js_function(&self) -> &Function; + } + impl { + fn as_js_function(&self) -> &Function { + self.as_ref().unchecked_ref() + } + } +} + + + +// =================== +// === FunctionOps === +// =================== + + +ops! { FunctionOps for Function + trait { + /// The `wasm-bindgen` version of this function panics if the JS code contains errors. This + /// issue was reported and never fixed (https://github.com/rustwasm/wasm-bindgen/issues/2496). + /// There is also a long-standing PR with the fix that was not fixed either + /// (https://github.com/rustwasm/wasm-bindgen/pull/2497). + fn new_with_args_fixed(args: &str, body: &str) -> Result; + } + + wasm_impl { + fn new_with_args_fixed(args: &str, body: &str) -> Result { + new_function_with_args(args, body) + } + } + + mock_impl { + crate::mock_fn! {new_with_args_fixed(_args: &str, _body: &str) -> Result} + } +} + + +// ================== +// === ReflectOps === +// ================== + +ops! { ReflectOps for Reflect + trait { + /// Get the nested value of the provided object. This is similar to writing `foo.bar.baz` in + /// JavaScript, but in a safe manner, while checking if the value exists on each level. + fn get_nested(target: &JsValue, keys: &[&str]) -> Result; + + /// Get the nested value of the provided object and cast it to [`Object`]. See docs of + /// [`get_nested`] to learn more. + fn get_nested_object(target: &JsValue, keys: &[&str]) -> Result; + + /// Get the nested value of the provided object and cast it to [`String`]. See docs of + /// [`get_nested`] to learn more. + fn get_nested_object_printed_as_string(target: &JsValue, keys: &[&str]) + -> Result; + } + + impl { + fn get_nested(target: &JsValue, keys: &[&str]) -> Result { + let mut tgt = target.clone(); + for key in keys { + let obj = tgt.dyn_into::()?; + let key = (*key).into(); + tgt = Reflect::get(&obj, &key)?; + } + Ok(tgt) + } + + fn get_nested_object(target: &JsValue, keys: &[&str]) -> Result { + let tgt = Self::get_nested(target, keys)?; + tgt.dyn_into() + } + + fn get_nested_object_printed_as_string + (target: &JsValue, keys: &[&str]) -> Result { + let tgt = Self::get_nested(target, keys)?; + if tgt.is_undefined() { + Err(Error::new("Key was not present in the target.").into()) + } else { + Ok(tgt.print_to_string()) + } + } + } +} + + +// ================= +// === WindowOps === +// ================= + +ops! { WindowOps for Window + trait { + fn request_animation_frame_with_closure( + &self, + f: &Closure, + ) -> Result; + fn request_animation_frame_with_closure_or_panic(&self, f: &Closure) -> i32; + fn cancel_animation_frame_or_panic(&self, id: i32); + fn performance_or_panic(&self) -> Performance; + } + + impl { + fn request_animation_frame_with_closure( + &self, + f: &Closure, + ) -> Result { + self.request_animation_frame(f.as_js_function()) + } + + fn request_animation_frame_with_closure_or_panic + (&self, f: &Closure) -> i32 { + self.request_animation_frame_with_closure(f).unwrap() + } + + fn cancel_animation_frame_or_panic(&self, id: i32) { + self.cancel_animation_frame(id).unwrap(); + } + + fn performance_or_panic(&self) -> Performance { + self.performance().unwrap_or_else(|| panic!("Cannot access window.performance.")) + } + } +} + + + +// ================= +// === ObjectOps === +// ================= + +ops! { ObjectOps for Object + trait { + /// Get all the keys of the provided [`Object`]. + fn keys_vec(obj: &Object) -> Vec; + } + + wasm_impl { + fn keys_vec(obj: &Object) -> Vec { + // The [`unwrap`] is safe, the `Object::keys` API guarantees it. + Object::keys(obj) + .iter() + .map(|key| key.dyn_into::().unwrap().into()) + .collect() + } + } + + mock_impl { + fn keys_vec(_obj: &Object) -> Vec { + default() + } + } +} + + + +// =================== +// === DocumentOps === +// =================== + +ops! { DocumentOps for Document + trait { + fn body_or_panic(&self) -> HtmlElement; + fn create_element_or_panic(&self, local_name: &str) -> Element; + fn create_div_or_panic(&self) -> HtmlDivElement; + fn create_canvas_or_panic(&self) -> HtmlCanvasElement; + fn get_html_element_by_id(&self, id: &str) -> Option; + fn with_element_by_id_or_warn(&self, id: &str, f: F); + } + + impl { + fn body_or_panic(&self) -> HtmlElement { + self.body().unwrap() + } + + fn create_element_or_panic(&self, local_name: &str) -> Element { + self.create_element(local_name).unwrap() + } + + fn create_div_or_panic(&self) -> HtmlDivElement { + self.create_element_or_panic("div").unchecked_into() + } + + fn create_canvas_or_panic(&self) -> HtmlCanvasElement { + self.create_element_or_panic("canvas").unchecked_into() + } + + fn get_html_element_by_id(&self, id: &str) -> Option { + self.get_element_by_id(id).and_then(|t| t.dyn_into().ok()) + } + + fn with_element_by_id_or_warn(&self, id: &str, f: F) { + let root_elem = self.get_element_by_id(id); + match root_elem { + Some(v) => f(v), + None => WARNING!("Failed to get element by ID."), + } + } + } +} + + + +// =============== +// === NodeOps === +// =============== + +ops! { NodeOps for Node + trait { + fn append_or_warn(&self, node: &Self); + fn prepend_or_warn(&self, node: &Self); + fn insert_before_or_warn(&self, node: &Self, reference_node: &Self); + fn remove_from_parent_or_warn(&self); + fn remove_child_or_warn(&self, node: &Self); + } + + impl { + fn append_or_warn(&self, node: &Self) { + let warn_msg: &str = &format!("Failed to append child {:?} to {:?}", node, self); + if self.append_child(node).is_err() { + WARNING!(warn_msg) + }; + } + + fn prepend_or_warn(&self, node: &Self) { + let warn_msg: &str = &format!("Failed to prepend child \"{:?}\" to \"{:?}\"", node, self); + let first_c = self.first_child(); + if self.insert_before(node, first_c.as_ref()).is_err() { + WARNING!(warn_msg) + } + } + + fn insert_before_or_warn(&self, node: &Self, ref_node: &Self) { + let warn_msg: &str = + &format!("Failed to insert {:?} before {:?} in {:?}", node, ref_node, self); + if self.insert_before(node, Some(ref_node)).is_err() { + WARNING!(warn_msg) + } + } + + fn remove_from_parent_or_warn(&self) { + if let Some(parent) = self.parent_node() { + let warn_msg: &str = &format!("Failed to remove {:?} from parent", self); + if parent.remove_child(self).is_err() { + WARNING!(warn_msg) + } + } + } + + fn remove_child_or_warn(&self, node: &Self) { + let warn_msg: &str = &format!("Failed to remove child {:?} from {:?}", node, self); + if self.remove_child(node).is_err() { + WARNING!(warn_msg) + } + } + } +} + + + +// ================== +// === ElementOps === +// ================== + +ops! { ElementOps for Element + trait { + fn set_attribute_or_warn(&self, name: T, value: U); + } + + impl { + fn set_attribute_or_warn(&self, name: T, value: U) { + let name = name.as_ref(); + let value = value.as_ref(); + let values = format!("\"{}\" = \"{}\" on \"{:?}\"", name, value, self); + let warn_msg: &str = &format!("Failed to set attribute {}", values); + if self.set_attribute(name, value).is_err() { + WARNING!(warn_msg) + } + } + } +} + + + +// ====================== +// === HtmlElementOps === +// ====================== + +ops! { HtmlElementOps for HtmlElement + trait { + fn set_style_or_warn(&self, name: impl AsRef, value: impl AsRef); + } + + impl { + fn set_style_or_warn(&self, name: impl AsRef, value: impl AsRef) { + let name = name.as_ref(); + let value = value.as_ref(); + let values = format!("\"{}\" = \"{}\" on \"{:?}\"", name, value, self); + let warn_msg: &str = &format!("Failed to set style {}", values); + if self.style().set_property(name, value).is_err() { + WARNING!(warn_msg); + } + } + } +} + + + +// ========================= +// === HtmlCanvasElement === +// ========================= + +ops! { HtmlCanvasElementOps for HtmlCanvasElement + trait { + fn get_webgl2_context(&self) -> Option; + } + + wasm_impl { + fn get_webgl2_context(&self) -> Option { + let options = Object::new(); + Reflect::set(&options, &"antialias".into(), &false.into()).unwrap(); + let context = self.get_context_with_context_options("webgl2", &options).ok().flatten(); + context.and_then(|obj| obj.dyn_into::().ok()) + } + } + + mock_impl { + fn get_webgl2_context(&self) -> Option { + None + } + } } @@ -97,40 +638,238 @@ pub fn js_to_string(s: impl AsRef) -> String { // === Utils === // ============= -/// Handle returned from `ignore_context_menu`. It unignores when the handle is dropped. -#[derive(Debug)] -pub struct IgnoreContextMenuHandle { - target: EventTarget, - closure: Closure, -} - -impl Drop for IgnoreContextMenuHandle { - fn drop(&mut self) { - let callback: &Function = self.closure.as_ref().unchecked_ref(); - self.target.remove_event_listener_with_callback("contextmenu", callback).ok(); - } -} - /// Ignores context menu when clicking with the right mouse button. -pub fn ignore_context_menu(target: &EventTarget) -> Option { - let closure = move |event: MouseEvent| { +pub fn ignore_context_menu(target: &EventTarget) -> EventListenerHandle { + let closure: Closure = Closure::new(move |event: MouseEvent| { const RIGHT_MOUSE_BUTTON: i16 = 2; if event.button() == RIGHT_MOUSE_BUTTON { event.prevent_default(); } - }; - let closure = Closure::wrap(Box::new(closure) as Box); - let callback: &Function = closure.as_ref().unchecked_ref(); - match target.add_event_listener_with_callback_and_bool("contextmenu", callback, true) { - Ok(_) => { - let target = target.clone(); - let handle = IgnoreContextMenuHandle { target, closure }; - Some(handle) - } - Err(_) => None, + }); + add_event_listener_with_bool(target, "contextmenu", closure, true) +} + + + +// ======================= +// === Event Listeners === +// ======================= + +/// The type of closures used for 'add_event_listener_*' functions. +pub type JsEventHandler = Closure; + +/// Handler for event listeners. Unregisters the listener when the last clone is dropped. +#[derive(Clone, CloneRef)] +pub struct EventListenerHandle { + rc: Rc, +} + +impl Debug for EventListenerHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "EventListenerHandle") } } +impl EventListenerHandle { + /// Constructor. + pub fn new( + target: EventTarget, + name: ImString, + closure: Closure, + ) -> Self { + let closure = Box::new(closure); + let data = EventListenerHandleData { target, name, closure }; + let rc = Rc::new(data); + Self { rc } + } +} + +/// Internal structure for [`EventListenerHandle`]. +/// +/// # Implementation Notes +/// The [`_closure`] field contains a wasm_bindgen's [`Closure`]. Dropping it causes the +/// associated function to be pruned from memory. +struct EventListenerHandleData { + target: EventTarget, + name: ImString, + closure: Box, +} + +impl Drop for EventListenerHandleData { + fn drop(&mut self) { + let function = self.closure.as_js_function(); + self.target.remove_event_listener_with_callback(&self.name, function).ok(); + } +} + +macro_rules! gen_add_event_listener { + ($name:ident, $wbindgen_name:ident $(,$arg:ident : $tp:ty)*) => { + /// Wrapper for the function defined in web_sys which allows passing wasm_bindgen + /// [`Closure`] directly. + pub fn $name( + target: &EventTarget, + name: &str, + closure: Closure + $(,$arg : $tp)* + ) -> EventListenerHandle { + // Please note that using [`ok`] is safe here, as according to MDN this function never + // fails: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener. + target.$wbindgen_name(name, closure.as_js_function() $(,$arg)*).ok(); + let target = target.clone(); + let name = name.into(); + EventListenerHandle::new(target, name, closure) + } + }; +} + +gen_add_event_listener!(add_event_listener, add_event_listener_with_callback); +gen_add_event_listener!( + add_event_listener_with_bool, + add_event_listener_with_callback_and_bool, + options: bool +); +gen_add_event_listener!( + add_event_listener_with_options, + add_event_listener_with_callback_and_add_event_listener_options, + options: &AddEventListenerOptions +); + + + +// ========================= +// === Stack Trace Limit === +// ========================= + +/// Increases the JavaScript stack trace limit to make errors more understandable. +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen(inline_js = " + export function set_stack_trace_limit() { + Error.stackTraceLimit = 100 + } +")] +extern "C" { + #[allow(unsafe_code)] + pub fn set_stack_trace_limit(); +} + +/// Increases the JavaScript stack trace limit to make errors more understandable. +#[cfg(not(target_arch = "wasm32"))] +pub fn set_stack_trace_limit() {} + + + +// ============ +// === Time === +// ============ + +static mut START_TIME: Option = None; +static mut TIME_OFFSET: f64 = 0.0; + +// FIXME: This is strange design + no one is calling it on init ... + +/// Initializes global stats of the program, like its start time. This function should be called +/// exactly once, as the first operation of a program. +/// +/// # Safety +/// This function modifies a global variable, however, it should be safe as it should be called +/// exactly once on program entry point. +#[allow(unsafe_code)] +pub fn init() -> Instant { + unsafe { + let now = Instant::now(); + START_TIME = Some(now); + now + } +} + +/// Start time of the program. Please note that the program should call the `init` function as +/// its first operation. +/// +/// # Safety +/// The following modifies a global variable, however, even in case of a race condition, nothing +/// bad should happen (the variable may be initialized several times). Moreover, the variable +/// should be initialized on program start, so this should be always safe. +#[allow(unsafe_code)] +pub fn start_time() -> Instant { + unsafe { + match START_TIME { + Some(time) => time, + None => init(), + } + } +} + +/// Time difference between the start time and current point in time. +/// +/// # Safety +/// The following code will always be safe if the program called the `init` function on entry. +/// Even if that did not happen, the worst thing that may happen is re-initialization of the +/// program start time variable. +#[allow(unsafe_code)] +#[cfg(target_arch = "wasm32")] +pub fn time_from_start() -> f64 { + unsafe { window.performance_or_panic().now() + TIME_OFFSET } +} + +/// Time difference between the start time and current point in time. +/// +/// # Safety +/// The following code will always be safe if the program called the `init` function on entry. +/// Even if that did not happen, the worst thing that may happen is re-initialization of the +/// program start time variable. +#[allow(unsafe_code)] +#[cfg(not(target_arch = "wasm32"))] +pub fn time_from_start() -> f64 { + unsafe { start_time().elapsed().as_millis() as f64 + TIME_OFFSET } +} + +/// Simulates a time interval. This function will exit immediately, but the next time you will +/// check the `time_from_start`, it will be increased. +/// +/// # Safety +/// This function is safe only in single-threaded environments. +#[allow(unsafe_code)] +pub fn simulate_sleep(duration: f64) { + unsafe { TIME_OFFSET += duration } +} + + + +// ============= +// === Panic === +// ============= + +/// Enables forwarding panic messages to `console.error`. +pub fn forward_panic_hook_to_console() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + console_error_panic_hook::set_once(); +} + + + +// ============= +// === Sleep === +// ============= + +#[cfg(target_arch = "wasm32")] +/// Sleeps for the specified amount of time. +/// +/// This function might sleep for slightly longer than the specified duration but never less. This +/// function is an async version of std::thread::sleep, its timer starts just after the function +/// call. +pub async fn sleep(duration: Duration) { + use gloo_timers::future::TimeoutFuture; + TimeoutFuture::new(duration.as_millis() as u32).await +} + +#[cfg(not(target_arch = "wasm32"))] +pub use async_std::task::sleep; + // ==================== @@ -148,548 +887,3 @@ impl TimeProvider for Performance { self.now() } } - - - -// =================== -// === DOM Helpers === -// =================== - -//#[cfg(target_arch = "wasm32")] - -static mut START_TIME: Option = None; -static mut TIME_OFFSET: f64 = 0.0; - -/// Initializes global stats of the program, like its start time. This function should be called -/// exactly once, as the first operation of a program. -/// -/// # Safety -/// This function modifies a global variable, however, it should be safe as it should be called -/// exactly once on program entry point. -#[allow(unsafe_code)] -pub fn init() -> std::time::Instant { - unsafe { - let now = std::time::Instant::now(); - START_TIME = Some(now); - now - } -} - -/// Start time of the program. Please note that the program should call the `init` function as its -/// first operation. -/// -/// # Safety -/// The following modifies a global variable, however, even in case of a race condition, nothing -/// bad should happen (the variable may be initialized several times). Moreover, the variable should -/// be initialized on program start, so this should be always safe. -#[allow(unsafe_code)] -pub fn start_time() -> std::time::Instant { - unsafe { - match START_TIME { - Some(time) => time, - None => init(), - } - } -} - -/// Time difference between the start time and current point in time. -/// -/// # Safety -/// The following code will always be safe if the program called the `init` function on entry. Even -/// if that did not happen, the worst thing that may happen is re-initialization of the program -/// start time variable. -#[allow(unsafe_code)] -#[cfg(target_arch = "wasm32")] -pub fn time_from_start() -> f64 { - unsafe { performance().now() + TIME_OFFSET } -} - -/// Time difference between the start time and current point in time. -/// -/// # Safety -/// The following code will always be safe if the program called the `init` function on entry. Even -/// if that did not happen, the worst thing that may happen is re-initialization of the program -/// start time variable. -#[allow(unsafe_code)] -#[cfg(not(target_arch = "wasm32"))] -pub fn time_from_start() -> f64 { - unsafe { start_time().elapsed().as_millis() as f64 + TIME_OFFSET } -} - -/// Simulates a time interval. This function will exit immediately, but the next time you will check -/// the `time_from_start`, it will be increased. -/// -/// # Safety -/// This function is safe only in single-threaded environments. -#[allow(unsafe_code)] -pub fn simulate_sleep(duration: f64) { - unsafe { TIME_OFFSET += duration } -} - -/// Access the `window` object if exists. -pub fn try_window() -> Result { - web_sys::window().ok_or_else(|| Error("Cannot access 'window'.")) -} - -/// Access the `window` object or panic if it does not exist. -pub fn window() -> Window { - try_window().unwrap() -} - -/// Access the `window.document` object if exists. -pub fn try_document() -> Result { - try_window().and_then(|w| w.document().ok_or_else(|| Error("Cannot access 'window.document'."))) -} - -/// Access the `window.document` object or panic if it does not exist. -pub fn document() -> Document { - try_document().unwrap() -} - -/// Access the `window.document.body` object if exists. -pub fn try_body() -> Result { - try_document() - .and_then(|d| d.body().ok_or_else(|| Error("Cannot access 'window.document.body'."))) -} - -/// Access the `window.document.body` object or panic if it does not exist. -pub fn body() -> HtmlElement { - try_body().unwrap() -} - -/// Access the `window.devicePixelRatio` value if the window exists. -pub fn try_device_pixel_ratio() -> Result { - try_window().map(|window| window.device_pixel_ratio()) -} - -/// Access the `window.devicePixelRatio` or panic if the window does not exist. -pub fn device_pixel_ratio() -> f64 { - window().device_pixel_ratio() -} - -/// Access the `window.performance` or panics if it does not exist. -pub fn performance() -> Performance { - window().performance().unwrap_or_else(|| panic!("Cannot access window.performance.")) -} - -/// Gets `Element` by ID. -pub fn get_element_by_id(id: &str) -> Result { - try_document()? - .get_element_by_id(id) - .ok_or_else(|| Error(format!("Element with id '{}' not found.", id))) -} - -/// Tries to get `Element` by ID, and runs function on it. -pub fn with_element_by_id_or_warn(logger: &Logger, id: &str, f: F) -where F: FnOnce(Element) { - let root_elem = get_element_by_id(id); - match root_elem { - Ok(v) => f(v), - Err(_) => warning!(logger, "Failed to get element by ID."), - } -} - -/// Gets `Element`s by class name. -pub fn get_elements_by_class_name(name: &str) -> Result> { - let collection = try_document()?.get_elements_by_class_name(name); - let indices = 0..collection.length(); - let elements = indices.flat_map(|index| collection.get_with_index(index)).collect(); - Ok(elements) -} - -pub fn get_html_element_by_id(id: &str) -> Result { - let elem = get_element_by_id(id)?; - elem.dyn_into().map_err(|_| Error("Type cast error.")) -} - -pub fn try_create_element(name: &str) -> Result { - try_document()? - .create_element(name) - .map_err(|_| Error(format!("Cannot create element '{}'", name))) -} - -pub fn create_element(name: &str) -> Element { - try_create_element(name).unwrap() -} - -pub fn try_create_div() -> Result { - try_create_element("div").map(|t| t.unchecked_into()) -} - -pub fn create_div() -> HtmlDivElement { - create_element("div").unchecked_into() -} - -pub fn try_create_canvas() -> Result { - try_create_element("canvas").map(|t| t.unchecked_into()) -} - -pub fn create_canvas() -> HtmlCanvasElement { - create_element("canvas").unchecked_into() -} - -pub fn get_webgl2_context(canvas: &HtmlCanvasElement) -> WebGl2RenderingContext { - let options = js_sys::Object::new(); - js_sys::Reflect::set(&options, &"antialias".into(), &false.into()).unwrap(); - let context = canvas.get_context_with_context_options("webgl2", &options).unwrap().unwrap(); - let context: WebGl2RenderingContext = context.dyn_into().unwrap(); - context -} - -pub fn try_request_animation_frame(f: &Closure) -> Result { - try_window()? - .request_animation_frame(f.as_ref().unchecked_ref()) - .map_err(|_| Error("Cannot access 'requestAnimationFrame'.")) -} - -pub fn request_animation_frame(f: &Closure) -> i32 { - window().request_animation_frame(f.as_ref().unchecked_ref()).unwrap() -} - -pub fn cancel_animation_frame(id: i32) { - window().cancel_animation_frame(id).unwrap(); -} - - - -// ===================== -// === Other Helpers === -// ===================== - -/// Trait used to set HtmlElement attributes. -pub trait AttributeSetter { - fn set_attribute_or_panic(&self, name: T, value: U); - - fn set_attribute_or_warn(&self, name: T, value: U, logger: &Logger); -} - -impl AttributeSetter for web_sys::Element { - fn set_attribute_or_panic(&self, name: T, value: U) { - let name = name.as_ref(); - let value = value.as_ref(); - let values = format!("\"{}\" = \"{}\" on \"{:?}\"", name, value, self); - self.set_attribute(name, value) - .unwrap_or_else(|_| panic!("Failed to set attribute {}", values)); - } - - fn set_attribute_or_warn(&self, name: T, value: U, logger: &Logger) { - let name = name.as_ref(); - let value = value.as_ref(); - let values = format!("\"{}\" = \"{}\" on \"{:?}\"", name, value, self); - let warn_msg: &str = &format!("Failed to set attribute {}", values); - if self.set_attribute(name, value).is_err() { - warning!(logger, warn_msg) - } - } -} - -/// Trait used to set css styles. -pub trait StyleSetter { - fn set_style_or_panic(&self, name: T, value: U); - fn set_style_or_warn(&self, name: T, value: U, logger: &Logger); -} - -impl StyleSetter for web_sys::HtmlElement { - fn set_style_or_panic(&self, name: T, value: U) { - let name = name.as_ref(); - let value = value.as_ref(); - let values = format!("\"{}\" = \"{}\" on \"{:?}\"", name, value, self); - let panic_msg = |_| panic!("Failed to set style {}", values); - self.style().set_property(name, value).unwrap_or_else(panic_msg); - } - - fn set_style_or_warn(&self, name: T, value: U, logger: &Logger) { - let name = name.as_ref(); - let value = value.as_ref(); - let values = format!("\"{}\" = \"{}\" on \"{:?}\"", name, value, self); - let warn_msg: &str = &format!("Failed to set style {}", values); - if self.style().set_property(name, value).is_err() { - warning!(logger, warn_msg); - } - } -} - -/// Trait used to insert `Node`s. -pub trait NodeInserter { - fn append_or_panic(&self, node: &Node); - - fn append_or_warn(&self, node: &Node, logger: &Logger); - - fn prepend_or_panic(&self, node: &Node); - - fn prepend_or_warn(&self, node: &Node, logger: &Logger); - - fn insert_before_or_panic(&self, node: &Node, reference_node: &Node); - - fn insert_before_or_warn(&self, node: &Node, reference_node: &Node, logger: &Logger); -} - -impl NodeInserter for Node { - fn append_or_panic(&self, node: &Node) { - let panic_msg = |_| panic!("Failed to append child {:?} to {:?}", node, self); - self.append_child(node).unwrap_or_else(panic_msg); - } - - fn append_or_warn(&self, node: &Node, logger: &Logger) { - let warn_msg: &str = &format!("Failed to append child {:?} to {:?}", node, self); - if self.append_child(node).is_err() { - warning!(logger, warn_msg) - }; - } - - fn prepend_or_panic(&self, node: &Node) { - let panic_msg = |_| panic!("Failed to prepend child \"{:?}\" to \"{:?}\"", node, self); - let first_c = self.first_child(); - self.insert_before(node, first_c.as_ref()).unwrap_or_else(panic_msg); - } - - fn prepend_or_warn(&self, node: &Node, logger: &Logger) { - let warn_msg: &str = &format!("Failed to prepend child \"{:?}\" to \"{:?}\"", node, self); - let first_c = self.first_child(); - if self.insert_before(node, first_c.as_ref()).is_err() { - warning!(logger, warn_msg) - } - } - - fn insert_before_or_panic(&self, node: &Node, ref_node: &Node) { - let panic_msg = - |_| panic!("Failed to insert {:?} before {:?} in {:?}", node, ref_node, self); - self.insert_before(node, Some(ref_node)).unwrap_or_else(panic_msg); - } - - fn insert_before_or_warn(&self, node: &Node, ref_node: &Node, logger: &Logger) { - let warn_msg: &str = - &format!("Failed to insert {:?} before {:?} in {:?}", node, ref_node, self); - if self.insert_before(node, Some(ref_node)).is_err() { - warning!(logger, warn_msg) - } - } -} - -/// Trait used to remove `Node`s. -pub trait NodeRemover { - fn remove_from_parent_or_panic(&self); - - fn remove_from_parent_or_warn(&self, logger: &Logger); - - fn remove_child_or_panic(&self, node: &Node); - - fn remove_child_or_warn(&self, node: &Node, logger: &Logger); -} - -impl NodeRemover for Node { - fn remove_from_parent_or_panic(&self) { - if let Some(parent) = self.parent_node() { - let panic_msg = |_| panic!("Failed to remove {:?} from parent", self); - parent.remove_child(self).unwrap_or_else(panic_msg); - } - } - - fn remove_from_parent_or_warn(&self, logger: &Logger) { - if let Some(parent) = self.parent_node() { - let warn_msg: &str = &format!("Failed to remove {:?} from parent", self); - if parent.remove_child(self).is_err() { - warning!(logger, warn_msg) - } - } - } - - fn remove_child_or_panic(&self, node: &Node) { - let panic_msg = |_| panic!("Failed to remove child {:?} from {:?}", node, self); - self.remove_child(node).unwrap_or_else(panic_msg); - } - - fn remove_child_or_warn(&self, node: &Node, logger: &Logger) { - let warn_msg: &str = &format!("Failed to remove child {:?} from {:?}", node, self); - if self.remove_child(node).is_err() { - warning!(logger, warn_msg) - } - } -} - -#[wasm_bindgen( - inline_js = "export function request_animation_frame2(f) { requestAnimationFrame(f) }" -)] -extern "C" { - #[allow(unsafe_code)] - pub fn request_animation_frame2(closure: &Closure) -> i32; -} - - - -// =============== -// === Printer === -// =============== - -#[wasm_bindgen(inline_js = " -export function set_stack_trace_limit() { - Error.stackTraceLimit = 100 -} -")] -extern "C" { - #[allow(unsafe_code)] - pub fn set_stack_trace_limit(); -} - - -/// Enables forwarding panic messages to `console.error`. -pub fn forward_panic_hook_to_console() { - // When the `console_error_panic_hook` feature is enabled, we can call the - // `set_panic_hook` function at least once during initialization, and then - // we will get better error messages if our code ever panics. - // - // For more details see - // https://github.com/rustwasm/console_error_panic_hook#readme - console_error_panic_hook::set_once(); -} - - -/// Enables throwing a descriptive JavaScript error on panics. -pub fn forward_panic_hook_to_error() { - std::panic::set_hook(Box::new(error_throwing_panic_hook)); -} - -#[wasm_bindgen(module = "/js/rust_panic.js")] -extern "C" { - #[allow(unsafe_code)] - fn new_panic_error(message: String) -> JsValue; -} - -fn error_throwing_panic_hook(panic_info: &std::panic::PanicInfo) { - wasm_bindgen::throw_val(new_panic_error(panic_info.to_string())); -} - -#[wasm_bindgen] -pub fn entry_point_panic() { - forward_panic_hook_to_error(); - panic!(); -} - - -/// Common traits. -pub mod traits { - pub use super::NodeInserter; - pub use super::NodeRemover; -} - -/// Sleeps for the specified amount of time. -/// -/// This function might sleep for slightly longer than the specified duration but never less. -/// -/// This function is an async version of std::thread::sleep, its timer starts just after the -/// function call. -#[cfg(target_arch = "wasm32")] -pub async fn sleep(duration: Duration) { - use gloo_timers::future::TimeoutFuture; - - TimeoutFuture::new(duration.as_millis() as u32).await -} - -#[cfg(not(target_arch = "wasm32"))] -pub use async_std::task::sleep; - -/// Get the nested value of the provided object. This is similar to writing `foo.bar.baz` in -/// JavaScript, but in a safe manner, while checking if the value exists on each level. -pub fn reflect_get_nested(target: &JsValue, keys: &[&str]) -> Result { - let mut tgt = target.clone(); - for key in keys { - let obj = tgt.dyn_into::()?; - let key = (*key).into(); - tgt = js_sys::Reflect::get(&obj, &key)?; - } - Ok(tgt) -} - -/// Get the nested value of the provided object and cast it to [`Object`]. See docs of -/// [`reflect_get_nested`] to learn more. -pub fn reflect_get_nested_object(target: &JsValue, keys: &[&str]) -> Result { - let tgt = reflect_get_nested(target, keys)?; - Ok(tgt.dyn_into()?) -} - -/// Get the nested value of the provided object and cast it to [`String`]. See docs of -/// [`reflect_get_nested`] to learn more. -pub fn reflect_get_nested_string(target: &JsValue, keys: &[&str]) -> Result { - let tgt = reflect_get_nested(target, keys)?; - if tgt.is_undefined() { - Err(Error("Key was not present in the target.")) - } else { - Ok(js_to_string(&tgt)) - } -} - -/// Get all the keys of the provided [`Object`]. -pub fn object_keys(target: &JsValue) -> Vec { - target - .clone() - .dyn_into::() - .ok() - .map(|obj| { - js_sys::Object::keys(&obj) - .iter() - .map(|key| { - // The unwrap is safe, the `Object::keys` API guarantees it. - let js_str = key.dyn_into::().unwrap(); - js_str.into() - }) - .collect() - }) - .unwrap_or_default() -} - - - -// ============ -// === Test === -// ============ - -#[cfg(test)] -mod tests { - use super::*; - - use wasm_bindgen_test::wasm_bindgen_test; - use wasm_bindgen_test::wasm_bindgen_test_configure; - - wasm_bindgen_test_configure!(run_in_browser); - - #[cfg(target_arch = "wasm32")] - mod helpers { - type Instant = f64; - - pub fn now() -> Instant { - super::performance().now() - } - - pub fn elapsed(instant: Instant) -> f64 { - super::performance().now() - instant - } - } - - #[cfg(not(target_arch = "wasm32"))] - mod helpers { - use std::time::Instant; - - pub fn now() -> Instant { - Instant::now() - } - - pub fn elapsed(instant: Instant) -> f64 { - instant.elapsed().as_secs_f64() - } - } - - #[wasm_bindgen_test(async)] - async fn async_sleep() { - let instant = helpers::now(); - sleep(Duration::new(1, 0)).await; - assert!(helpers::elapsed(instant) >= 1.0); - sleep(Duration::new(2, 0)).await; - assert!(helpers::elapsed(instant) >= 3.0); - } - - #[test] - #[cfg(not(target_arch = "wasm32"))] - fn async_sleep_native() { - async_std::task::block_on(async_sleep()) - } -} diff --git a/lib/rust/web/src/platform.rs b/lib/rust/web/src/platform.rs index 945a68521b..f8a5515642 100644 --- a/lib/rust/web/src/platform.rs +++ b/lib/rust/web/src/platform.rs @@ -10,6 +10,7 @@ use std::convert::TryFrom; /// This enumeration lists all the supported platforms. #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[allow(missing_docs)] pub enum Platform { Android, FreeBSD, @@ -46,6 +47,7 @@ impl Platform { } } +/// An error indicating that the platform was not recognized. #[derive(Clone, Copy, Debug)] pub struct UnknownPlatform; @@ -61,7 +63,7 @@ impl TryFrom<&str> for Platform { } else if name.contains("linux") { Ok(Linux) } - // CAREFUL: this matches also "darwin" (that's why its declared below): + // CAREFUL: this matches also "darwin" (that's why it's declared below the "darwin" match). else if name.contains("win") { Ok(Windows) } else if name.contains("ios") { @@ -117,9 +119,9 @@ pub fn current() -> Option { /// Queries which platform we are on. #[allow(clippy::if_same_then_else)] +#[cfg(target_arch = "wasm32")] pub fn current_wasm() -> Option { use super::window; - let window = window(); let navigator = window.navigator(); let platform = navigator.platform().unwrap_or_default().to_lowercase(); let agent = navigator.user_agent().unwrap_or_default().to_lowercase(); diff --git a/lib/rust/web/src/resize_observer.rs b/lib/rust/web/src/resize_observer.rs index ffb12adcae..80f17261c2 100644 --- a/lib/rust/web/src/resize_observer.rs +++ b/lib/rust/web/src/resize_observer.rs @@ -1,8 +1,9 @@ +//! Binding to the https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver. + use crate::prelude::*; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::prelude::Closure; -use wasm_bindgen::JsValue; +use crate::Closure; +use crate::JsValue; @@ -10,6 +11,7 @@ use wasm_bindgen::JsValue; // === Types === // ============= +/// Listener closure for the [`ResizeObserver`]. pub type Listener = Closure; @@ -18,6 +20,10 @@ pub type Listener = Closure; // === JS Bindings === // =================== +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::wasm_bindgen; + +#[cfg(target_arch = "wasm32")] #[wasm_bindgen(module = "/js/resize_observer.js")] extern "C" { #[allow(unsafe_code)] @@ -27,20 +33,26 @@ extern "C" { fn resize_unobserve(id: usize); } +#[cfg(not(target_arch = "wasm32"))] +fn resize_observe(_target: &JsValue, _closure: &Listener) -> usize { + 0 +} +#[cfg(not(target_arch = "wasm32"))] +fn resize_unobserve(_id: usize) {} // ====================== // === ResizeObserver === // ====================== -/// The ResizeObserver interface reports changes to the dimensions of an -/// DOM Element's content or border box. ResizeObserver avoids infinite callback -/// loops and cyclic dependencies that are often created when resizing via a -/// callback function. It does this by only processing elements deeper in the -/// DOM in subsequent frames. +/// The ResizeObserver interface reports changes to the dimensions of an DOM Element's content or +/// border box. ResizeObserver avoids infinite callback loops and cyclic dependencies that are often +/// created when resizing via a callback function. It does this by only processing elements deeper +/// in the DOM in subsequent frames. /// -/// See also https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver +/// See also https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver. #[derive(Debug)] +#[allow(missing_docs)] pub struct ResizeObserver { pub target: JsValue, pub listener: Listener, @@ -48,6 +60,7 @@ pub struct ResizeObserver { } impl ResizeObserver { + /// Constructor. pub fn new(target: &JsValue, listener: Listener) -> Self { let target = target.clone_ref(); let observer_id = resize_observe(&target, &listener); diff --git a/lib/rust/web/src/stream.rs b/lib/rust/web/src/stream.rs index 04fa4bf50b..035b6f913d 100644 --- a/lib/rust/web/src/stream.rs +++ b/lib/rust/web/src/stream.rs @@ -2,9 +2,10 @@ use crate::prelude::*; -use crate::Error; - use wasm_bindgen::JsCast; +use wasm_bindgen::JsValue; + +use wasm_bindgen::prelude::wasm_bindgen; @@ -39,27 +40,29 @@ extern "C" { pub trait BlobExt { /// Returns a ReadableStream which upon reading returns the data contained within the Blob. /// See https://developer.mozilla.org/en-US/docs/Web/API/Blob/stream. - fn stream(&self) -> Result; + fn stream(&self) -> Result; /// Returns a Reader of the Blob data. It assumes that the reader is of /// [`ReadableStreamDefaultReader`] type. See also /// https://developer.mozilla.org/en-US/docs/Web/API/Blob/stream and /// https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/getReader - fn stream_reader(&self) -> Result; + fn stream_reader(&self) -> Result; } impl BlobExt for web_sys::Blob { - fn stream(&self) -> Result { + #[allow(unused_qualifications)] + fn stream(&self) -> Result { let this = self.as_ref(); let method_as_value = js_sys::Reflect::get(this, &"stream".into())?; let method = method_as_value.dyn_into::()?; - Ok(method.call0(this)?.dyn_into()?) + method.call0(this)?.dyn_into() } - fn stream_reader(&self) -> Result { + #[allow(unused_qualifications)] + fn stream_reader(&self) -> Result { let stream = self.stream(); let method_as_value = js_sys::Reflect::get(&stream, &"getReader".into())?; let method = method_as_value.dyn_into::()?; - Ok(method.call0(&stream)?.dyn_into()?) + method.call0(&stream)?.dyn_into() } }