From cdcc852e03371e7b13c99d228063689f438be4a4 Mon Sep 17 00:00:00 2001 From: Ilya Bogdanov Date: Thu, 17 Mar 2022 13:38:18 +0300 Subject: [PATCH] Node searcher zoom & edited node growth/shrink animation (#3327) In this PR two things are implemented: 1. Node Searcher zoom factor (and therefore its size) is fixed no matter how you move the main camera. The node searcher is also positioned directly below currently edited node at all times. 2. Node growth/shrink animation when you start/finish node editing. After animation end the edited node zoom factor is also fixed and matches the zoom factor of the node searcher. See attached video with different ways of editing/creating nodes: https://user-images.githubusercontent.com/6566674/157348758-2880aa2b-494d-46e6-8eee-a22be84081ed.mp4 #### Technical details 1. Added several additional scene layers for separate rendering: `node_searcher`, `node_searcher_text`, `edited_node`, `edited_node_text`. Searcher is always rendered by `node_searcher` camera, edited node moves between its usual layers and `edited_node` layer. Because text rendering uses different API, all node components were modified to support change of the layer. 2. Also added `node_searcher` DOM layer, because documentation is implemented as a DOM object. 3. Added two FRP endpoints for `ensogl::Animation`: `on_end` and `set_value`. These endpoints are useful while implementing growth/shrink animation. 4. Added FRP endpoints for the `Camera2d`: `position` and `zoom` outputs. This allows to synchronize cameras easily using FRP networks. 5. Growth/shrink animation implemented in GraphEditor by blending two animations, similar to Node Snapping implementation. However, shrinking animation is a bit tricky to implement correctly, as we must always return node back to the `main` scene layer after editing is done. --- CHANGELOG.md | 4 + .../view/graph-editor/src/component/node.rs | 41 ++++++ .../src/component/node/growth_animation.rs | 125 ++++++++++++++++++ .../src/component/node/input/area.rs | 9 ++ .../src/component/node/output/area.rs | 9 ++ .../src/component/node/profiling.rs | 5 + app/gui/view/graph-editor/src/lib.rs | 5 + app/gui/view/src/documentation.rs | 4 +- app/gui/view/src/project.rs | 29 ++++ app/gui/view/src/searcher.rs | 4 +- lib/rust/ensogl/core/src/animation/easing.rs | 1 - .../core/src/animation/frp/animation.rs | 39 +++--- .../core/src/animation/physics/inertia.rs | 2 +- .../core/src/display/camera/camera2d.rs | 28 +++- .../core/src/display/navigation/navigator.rs | 3 +- lib/rust/ensogl/core/src/display/scene.rs | 38 +++++- .../example/complex-shape-system/src/lib.rs | 1 - 17 files changed, 313 insertions(+), 34 deletions(-) create mode 100644 app/gui/view/graph-editor/src/component/node/growth_animation.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ae1c3a290a..6b0446bef62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ #### Visual Environment +- [Node Searcher preserves its zoom factor.][3327] The visible size of the node + searcher and edited node is now fixed. It simplifies node editing on + non-standard zoom levels. - [Nodes can be added to the graph by clicking (+) button on the screen][3278]. The button is in the bottom-left corner. Node is added at the center or pushed down if the center is already occupied by nodes. @@ -108,6 +111,7 @@ [3317]: https://github.com/enso-org/enso/pull/3317 [3318]: https://github.com/enso-org/enso/pull/3318 [3324]: https://github.com/enso-org/enso/pull/3324 +[3327]: https://github.com/enso-org/enso/pull/3327 [3339]: https://github.com/enso-org/enso/pull/3339 #### Enso Compiler diff --git a/app/gui/view/graph-editor/src/component/node.rs b/app/gui/view/graph-editor/src/component/node.rs index 9d51e6e8319..aeb58de3f12 100644 --- a/app/gui/view/graph-editor/src/component/node.rs +++ b/app/gui/view/graph-editor/src/component/node.rs @@ -20,6 +20,7 @@ use ensogl::animation::delayed::DelayedAnimation; use ensogl::application::Application; use ensogl::data::color; use ensogl::display; +use ensogl::display::scene::Layer; use ensogl::Animation; use ensogl_component::shadow; use ensogl_component::text; @@ -38,6 +39,7 @@ pub mod action_bar; #[warn(missing_docs)] pub mod error; pub mod expression; +pub mod growth_animation; pub mod input; pub mod output; #[warn(missing_docs)] @@ -537,6 +539,45 @@ impl NodeModel { self } + fn set_layers(&self, layer: &Layer, text_layer: &Layer, action_bar_layer: &Layer) { + layer.add_exclusive(&self.display_object); + action_bar_layer.add_exclusive(&self.action_bar); + self.output.set_label_layer(text_layer); + self.input.set_label_layer(text_layer); + self.profiling_label.set_label_layer(text_layer); + self.comment.add_to_scene_layer(text_layer); + } + + /// Move all sub-components to `edited_node` layer. + /// + /// A simple [`Layer::add_exclusive`] wouldn't work because text rendering in ensogl uses a + /// separate layer management API. + /// + /// `action_bar` is moved to the `edited_node` layer as well, though normally it lives on a + /// separate `above_nodes` layer, unlike every other node component. + pub fn move_to_edited_node_layer(&self) { + let scene = &self.app.display.default_scene; + let layer = &scene.layers.edited_node; + let text_layer = &scene.layers.edited_node_text; + let action_bar_layer = &scene.layers.edited_node; + self.set_layers(layer, text_layer, action_bar_layer); + } + + /// Move all sub-components to `main` layer. + /// + /// A simple [`Layer::add_exclusive`] wouldn't work because text rendering in ensogl uses a + /// separate layer management API. + /// + /// `action_bar` is handled separately, as it uses `above_nodes` scene layer unlike any other + /// node component. + pub fn move_to_main_layer(&self) { + let scene = &self.app.display.default_scene; + let layer = &scene.layers.main; + let text_layer = &scene.layers.label; + let action_bar_layer = &scene.layers.above_nodes; + self.set_layers(layer, text_layer, action_bar_layer); + } + #[allow(missing_docs)] // FIXME[everyone] All pub functions should have docs. pub fn width(&self) -> f32 { self.input.width.value() diff --git a/app/gui/view/graph-editor/src/component/node/growth_animation.rs b/app/gui/view/graph-editor/src/component/node/growth_animation.rs new file mode 100644 index 00000000000..d804558b715 --- /dev/null +++ b/app/gui/view/graph-editor/src/component/node/growth_animation.rs @@ -0,0 +1,125 @@ +//! Edited node growth/shrink animation implementation. +//! +//! When the user starts editing of the node - it smoothly growth in size to match the 1.0x zoom +//! factor size. After editing is finished, the node smothly shrinks to its original size. This is +//! implemented by using a separate `edited_node` camera that is moved in synchronization with +//! `node_searcher` camera. + +use ensogl::prelude::*; + +use crate::GraphEditorModelWithNetwork; +use crate::NodeId; +use enso_frp as frp; +use ensogl::animation::easing::EndStatus::Normal; +use ensogl::display::Scene; +use ensogl::Animation; +use ensogl::Easing; + +/// Describes the "speed" of growth/shrink animation. +/// +/// To determine the duration of the blending animation, we divide the length of the camera path by +/// this value. This is primarily used to move the edited node back to the `main` layer once editing +/// is done. If the camera is already at its destination – the duration would be close to zero, so +/// we would immediately change the layer of the node. If the camera needs to travel a lot - we +/// increase the animation duration proportionally so that the layer would be changed later. +/// +/// The exact value is selected empirically. The maximum camera travel distance is about 9000.0 +/// units, so our coefficient determines the maximum animation duration as 600 ms. +const ANIMATION_LENGTH_COEFFIENT: f32 = 15.0; + +/// Initialize edited node growth/shrink animator. It would handle scene layer change for the edited +/// node as well. +pub fn initialize_edited_node_animator( + model: &GraphEditorModelWithNetwork, + frp: &crate::Frp, + scene: &Scene, +) { + let network = &frp.network; + let out = &frp.output; + let searcher_cam = scene.layers.node_searcher.camera(); + let edited_node_cam = scene.layers.edited_node.camera(); + let main_cam = scene.layers.main.camera(); + + let growth_animation = Animation::new(network); + let animation_blending = Easing::new(network); + + frp::extend! { network + let searcher_cam_frp = searcher_cam.frp(); + let main_cam_frp = main_cam.frp(); + + + // === Starting node editing === + + previous_edited_node <- out.node_editing_started.previous(); + _eval <- out.node_editing_started.map2(&previous_edited_node, f!([model] (current, previous) { + model.move_node_to_main_layer(*previous); + model.move_node_to_edited_node_layer(*current); + })); + + + // === Edited node camera position animation === + + is_growing <- bool(&out.node_editing_finished, &out.node_editing_started); + edited_node_cam_target <- switch(&is_growing, &main_cam_frp.position, &searcher_cam_frp.position); + growth_animation.target <+ edited_node_cam_target; + + camera_path_length <- all_with + (&growth_animation.value, &growth_animation.target, |v, t| (v - t).magnitude()); + on_node_editing_start_or_finish <- any(&out.node_editing_started, &out.node_editing_finished); + start_animation_blending <- camera_path_length.sample(&on_node_editing_start_or_finish); + eval start_animation_blending ((length) { + animation_blending.set_duration(*length / ANIMATION_LENGTH_COEFFIENT); + animation_blending.stop_and_rewind(0.0); + animation_blending.target(1.0); + }); + + // We want to: + // 1. Smothly animate edited node camera from `main_cam` position to `searcher_cam` position when we + // start/finish node editing so that the edited node "grows" or "shrinks" to reach the correct + // visible size. This is `growth_animation`. + // 2. Keep `searcher_cam` and `edited_node_cam` at the same position everywhen else so that the + // searcher and the edited node are at the same visible position at all times. This is + // `edited_node_cam_target`. + // + // Enabling/disabling "follow me" mode for the edited node camera is hard + // to implement and leads to serious visualization lags. To avoid that, we blend these two + // components together using `animation_blending` as a weight coefficient. This allows a very smooth + // transition between "follow me" mode and node growth/shrink animation. + edited_node_cam_position <- all_with3 + (&edited_node_cam_target, &growth_animation.value, &animation_blending.value, |target,animation,weight| { + let weight = Vector3::from_element(*weight); + let inv_weight = Vector3::from_element(1.0) - weight; + target.component_mul(&weight) + animation.component_mul(&inv_weight) + }); + eval edited_node_cam_position([edited_node_cam] (pos) edited_node_cam.set_position(*pos)); + + + // === Finishing shrinking animation === + + on_animation_end <- animation_blending.on_end.filter(|end_status| *end_status == Normal); + shrinking_finished <- on_animation_end.gate_not(&is_growing); + node_that_finished_editing <- out.node_editing_started.sample(&shrinking_finished); + eval node_that_finished_editing ([model] (id) { + model.move_node_to_main_layer(*id); + }); + } +} + + +// === Helpers === + +impl GraphEditorModelWithNetwork { + /// Move node to the `edited_node` scene layer, so that it is rendered by the separate camera. + fn move_node_to_edited_node_layer(&self, node_id: NodeId) { + if let Some(node) = self.nodes.get_cloned(&node_id) { + node.model.move_to_edited_node_layer(); + } + } + + /// Move node to the `main` scene layer, so that it is rendered by the main camera. + fn move_node_to_main_layer(&self, node_id: NodeId) { + if let Some(node) = self.nodes.get_cloned(&node_id) { + node.model.move_to_main_layer(); + } + } +} 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 90a1e698031..37e4c103b54 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 @@ -286,6 +286,10 @@ impl Model { self } + fn set_label_layer(&self, layer: &display::scene::Layer) { + self.label.add_to_scene_layer(layer); + } + fn scene(&self) -> &Scene { &self.app.display.default_scene } @@ -475,6 +479,11 @@ impl Area { pub fn label(&self) -> &text::Area { &self.model.label } + + /// Set a scene layer for text rendering. + pub fn set_label_layer(&self, layer: &display::scene::Layer) { + self.model.set_label_layer(layer); + } } 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 214346cb86d..90e3db13d02 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 @@ -216,6 +216,10 @@ impl Model { self } + fn set_label_layer(&self, layer: &display::scene::Layer) { + self.label.add_to_scene_layer(layer); + } + fn set_label(&self, content: impl Into) { let str = if ARGS.node_labels.unwrap_or(true) { content.into() } else { default() }; self.label.set_content(str); @@ -493,6 +497,11 @@ impl Area { Self { frp, model } } + /// Set a scene layer for text rendering. + pub fn set_label_layer(&self, layer: &display::scene::Layer) { + self.model.set_label_layer(layer); + } + #[allow(missing_docs)] // FIXME[everyone] All pub functions should have docs. pub fn port_type(&self, crumbs: &Crumbs) -> Option { let expression = self.model.expression.borrow(); 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 efecf0cdd67..bad1b73b25b 100644 --- a/app/gui/view/graph-editor/src/component/node/profiling.rs +++ b/app/gui/view/graph-editor/src/component/node/profiling.rs @@ -247,6 +247,11 @@ impl ProfilingLabel { ProfilingLabel { root, label, frp, styles } } + + /// Set a scene layer for text rendering. + pub fn set_label_layer(&self, layer: &display::scene::Layer) { + self.label.add_to_scene_layer(layer); + } } impl display::Object for ProfilingLabel { diff --git a/app/gui/view/graph-editor/src/lib.rs b/app/gui/view/graph-editor/src/lib.rs index 9fc373cd90e..f5dcf2a6b25 100644 --- a/app/gui/view/graph-editor/src/lib.rs +++ b/app/gui/view/graph-editor/src/lib.rs @@ -2875,6 +2875,11 @@ fn new_graph_editor(app: &Application) -> GraphEditor { } + // === Edited node growth/shrink animation === + + component::node::growth_animation::initialize_edited_node_animator(&model, &frp, scene); + + // === Event Propagation === // See the docs of `Node` to learn about how the graph - nodes event propagation works. diff --git a/app/gui/view/src/documentation.rs b/app/gui/view/src/documentation.rs index 09bf8a00b41..a33269f38cb 100644 --- a/app/gui/view/src/documentation.rs +++ b/app/gui/view/src/documentation.rs @@ -111,8 +111,8 @@ impl Model { display_object.add_child(&outer_dom); outer_dom.add_child(&inner_dom); display_object.add_child(&overlay); - scene.dom.layers.front.manage(&outer_dom); - scene.dom.layers.front.manage(&inner_dom); + scene.dom.layers.node_searcher.manage(&outer_dom); + scene.dom.layers.node_searcher.manage(&inner_dom); let code_copy_closures = default(); Model { logger, outer_dom, inner_dom, size, overlay, display_object, code_copy_closures } diff --git a/app/gui/view/src/project.rs b/app/gui/view/src/project.rs index 14588f2c39f..2d5747e4426 100644 --- a/app/gui/view/src/project.rs +++ b/app/gui/view/src/project.rs @@ -402,11 +402,40 @@ impl View { } let shape = scene.shape().clone_ref(); + frp::extend! { network eval shape ((shape) model.on_dom_shape_changed(shape)); // === Searcher Position and Size === + let main_cam = app.display.default_scene.layers.main.camera(); + let searcher_cam = app.display.default_scene.layers.node_searcher.camera(); + let main_cam_frp = &main_cam.frp(); + // We want to: + // 1. Preserve the zoom factor of the searcher. + // 2. Keep it directly below edited node at all times. + // We do that by placing `node_searcher` camera in a position calculated by the + // following equations: + // ``` + // xy = main_cam.xy * main_cam.zoom + // move_to_edited_node = edited_node.xy - edited_node.xy * main_cam.zoom + // searcher_cam.z = const + // searcher_cam.xy = xy + move_to_edited_node + // ``` + // To understand the `move_to_edited_node` equation, consider the following example: + // If edited_node.x = 100, zoom = 0.1, then the node is positioned at + // x = 100 * 0.1 = 10 in searcher_cam-space. To compensate for that, we need to move + // searcher (or rather searcher_cam) by 90 units, so that the node is at x = 100 both + // in searcher_cam- and in main_cam-space. + searcher_cam_pos <- all_with3 + (&main_cam_frp.position, &main_cam_frp.zoom, &searcher_left_top_position.value, + |&main_cam_pos, &zoom, &searcher_pos| { + let preserve_zoom = (main_cam_pos * zoom).xy(); + let move_to_edited_node = searcher_pos * (1.0 - zoom); + preserve_zoom + move_to_edited_node + }); + eval searcher_cam_pos ((pos) searcher_cam.set_position_xy(*pos)); + _eval <- all_with(&searcher_left_top_position.value,&searcher.size,f!([model](lt,size) { let x = lt.x + size.x / 2.0; let y = lt.y - size.y / 2.0; diff --git a/app/gui/view/src/searcher.rs b/app/gui/view/src/searcher.rs index fdf18a100ac..842c45e53ff 100644 --- a/app/gui/view/src/searcher.rs +++ b/app/gui/view/src/searcher.rs @@ -126,7 +126,7 @@ impl Model { let list = app.new_view::>(); let documentation = documentation::View::new(scene); let doc_provider = default(); - scene.layers.above_nodes.add_exclusive(&list); + scene.layers.node_searcher.add_exclusive(&list); display_object.add_child(&documentation); display_object.add_child(&list); @@ -135,7 +135,7 @@ impl Model { 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()); + list.set_label_layer(scene.layers.node_searcher_text.id()); list.set_position_y(-action_list_gap); list.set_position_x(ACTION_LIST_X); documentation.set_position_x(DOCUMENTATION_X); diff --git a/lib/rust/ensogl/core/src/animation/easing.rs b/lib/rust/ensogl/core/src/animation/easing.rs index 8c402df5667..6427e96a1e7 100644 --- a/lib/rust/ensogl/core/src/animation/easing.rs +++ b/lib/rust/ensogl/core/src/animation/easing.rs @@ -425,7 +425,6 @@ where data.step(time.local) } else if let Some(animation_loop) = animation_loop.upgrade() { animation_loop.set(None); - data.on_end.call(EndStatus::Normal); } } } diff --git a/lib/rust/ensogl/core/src/animation/frp/animation.rs b/lib/rust/ensogl/core/src/animation/frp/animation.rs index 44a37c94576..61d5d44575b 100644 --- a/lib/rust/ensogl/core/src/animation/frp/animation.rs +++ b/lib/rust/ensogl/core/src/animation/frp/animation.rs @@ -21,21 +21,19 @@ pub mod hysteretic; // === Animation === // ================= -// crate::define_endpoints! { -// Input { -// target (f32), -// precision (f32), -// skip (), -// } -// Output { -// value (f32), -// on_end (inertia::EndStatus), -// } -// } - /// Simulator used to run the animation. pub type AnimationSimulator = inertia::DynSimulator>; +/// Default animation precision. +/// +/// This value defines the threshold of how close the current animation value should be to the +/// target value so that the animation is considered finished. +/// FIXME[WD]: The precision should should be increased in all simulators +/// that work with pixels. The reason is that by default the simulator should +/// give nice results for animations in the range of 0 .. 1, while it should not +/// make too many steps when animating bigger values (like pixels). +pub const DEFAULT_PRECISION: f32 = 0.001; + /// Smart animation handler. Contains of dynamic simulation and frp endpoint. Whenever a new value /// is computed, it is emitted via the endpoint. #[derive(CloneRef, Derivative, Debug)] @@ -46,6 +44,7 @@ pub struct Animation { pub precision: frp::Any, pub skip: frp::Any, pub value: frp::Stream, + pub on_end: frp::Stream<()>, } #[allow(missing_docs)] @@ -56,14 +55,12 @@ where mix::Repr: inertia::Value pub fn new(network: &frp::Network) -> Self { frp::extend! { network value_src <- any_mut::(); + on_end_src <- any_mut(); } let on_step = Box::new(f!((t) value_src.emit(mix::from_space::(t)))); - let simulator = AnimationSimulator::::new(on_step, (), ()); - // FIXME[WD]: The precision should become default and should be increased in all simulators - // that work with pixels. The reason is that by default the simulator should - // give nice results for animations in the range of 0 .. 1, while it should not - // make too many steps when animating bigger values (like pixels). - simulator.set_precision(0.001); + let on_end = Box::new(f!((_) on_end_src.emit(()))); + let simulator = AnimationSimulator::::new(on_step, (), on_end); + simulator.set_precision(DEFAULT_PRECISION); frp::extend! { network target <- any_mut::(); precision <- any_mut::(); @@ -73,8 +70,9 @@ where mix::Repr: inertia::Value eval_ skip (simulator.skip()); } let value = value_src.into(); + let on_end = on_end_src.into(); network.store(&simulator); - Self { target, precision, skip, value } + Self { target, precision, skip, value, on_end } } /// Constructor. The initial value is provided explicitly. @@ -141,7 +139,8 @@ where mix::Repr: inertia::Value def target = source::(); } let on_step = Box::new(f!((t) target.emit(mix::from_space::(t)))); - let simulator = inertia::DynSimulator::::new(on_step, (), ()); + let on_end = Box::new(|_| ()); + let simulator = inertia::DynSimulator::::new(on_step, (), on_end); let value = target.into(); Self { simulator, value } } diff --git a/lib/rust/ensogl/core/src/animation/physics/inertia.rs b/lib/rust/ensogl/core/src/animation/physics/inertia.rs index 06a162578dd..34d49e087bb 100644 --- a/lib/rust/ensogl/core/src/animation/physics/inertia.rs +++ b/lib/rust/ensogl/core/src/animation/physics/inertia.rs @@ -617,7 +617,7 @@ where // ================= /// Handy alias for `Simulator` with a boxed closure callback. -pub type DynSimulator = Simulator, (), ()>; +pub type DynSimulator = Simulator, (), Box>; /// The `SimulationDataCell` with an associated animation loop. The simulation is updated every /// frame in an efficient way – when the simulation finishes, it automatically unregisters the diff --git a/lib/rust/ensogl/core/src/display/camera/camera2d.rs b/lib/rust/ensogl/core/src/display/camera/camera2d.rs index 2229ec0f0bd..299c53d4793 100644 --- a/lib/rust/ensogl/core/src/display/camera/camera2d.rs +++ b/lib/rust/ensogl/core/src/display/camera/camera2d.rs @@ -9,6 +9,7 @@ use crate::control::callback; use crate::data::dirty; use crate::display; use crate::display::scene::Scene; +use crate::frp; use nalgebra::Perspective3; @@ -158,6 +159,16 @@ impl Default for Matrix { // === Camera2dData === // ==================== +/// Frp outputs of the Camera2d. +#[derive(Debug, Clone, CloneRef)] +pub struct Frp { + network: frp::Network, + /// Camera position. + pub position: frp::Source>, + /// Camera zoom factor. + pub zoom: frp::Source, +} + /// Function used to return the updated screen dimensions. pub trait ScreenUpdateFn = Fn(Vector2) + 'static; @@ -177,6 +188,7 @@ struct Camera2dData { dirty: Dirty, zoom_update_registry: callback::registry::CopyMut1, screen_update_registry: callback::registry::CopyMut1>, + frp: Frp, } type ProjectionDirty = dirty::SharedBool<()>; @@ -197,7 +209,14 @@ impl Camera2dData { display_object.set_on_updated(f_!(dirty.transform.set())); display_object.mod_position(|p| p.z = 1.0); dirty.projection.set(); + let network = frp::Network::new("Camera2d"); + frp::extend! { network + frp_position <- source(); + frp_zoom <- source(); + } + let frp = Frp { network, position: frp_position, zoom: frp_zoom }; Self { + frp, display_object, screen, zoom, @@ -272,7 +291,10 @@ impl Camera2dData { } if changed { self.matrix.view_projection = self.matrix.projection * self.matrix.view; - self.zoom_update_registry.run_all(self.zoom); + let zoom = self.zoom; + self.zoom_update_registry.run_all(zoom); + self.frp.position.emit(self.display_object.position()); + self.frp.zoom.emit(zoom); } changed } @@ -424,6 +446,10 @@ impl Camera2d { #[allow(missing_docs)] impl Camera2d { + pub fn frp(&self) -> Frp { + self.data.borrow().frp.clone_ref() + } + pub fn clipping(&self) -> Clipping { self.data.borrow().clipping } diff --git a/lib/rust/ensogl/core/src/display/navigation/navigator.rs b/lib/rust/ensogl/core/src/display/navigation/navigator.rs index 1b36d36455b..93fbf2c262a 100644 --- a/lib/rust/ensogl/core/src/display/navigation/navigator.rs +++ b/lib/rust/ensogl/core/src/display/navigation/navigator.rs @@ -64,7 +64,8 @@ impl NavigatorModel { fn create_simulator(camera: &Camera2d) -> physics::inertia::DynSimulator { let camera_ref = camera.clone_ref(); let on_step = Box::new(move |p: Vector3| camera_ref.set_position(p)); - let simulator = physics::inertia::DynSimulator::new(on_step, (), ()); + let on_end = Box::new(|_| ()); + let simulator = physics::inertia::DynSimulator::new(on_step, (), on_end); // FIXME[WD]: This one is emitting camera position in next frame, which is not intended. // Should be fixed when reworking navigator to use FRP events. simulator.set_value(camera.position()); diff --git a/lib/rust/ensogl/core/src/display/scene.rs b/lib/rust/ensogl/core/src/display/scene.rs index 7077370ddad..9637827b1e3 100644 --- a/lib/rust/ensogl/core/src/display/scene.rs +++ b/lib/rust/ensogl/core/src/display/scene.rs @@ -470,8 +470,8 @@ impl Dom { // === DomLayers === // ================= -/// DOM DomLayers of the scene. It contains a 2 CSS 3D layers and a canvas layer in the middle. The -/// CSS layers are used to manage DOM elements and to simulate depth-sorting of DOM and canvas +/// DOM DomLayers of the scene. It contains several CSS 3D layers and a canvas layer in the middle. +/// The CSS layers are used to manage DOM elements and to simulate depth-sorting of DOM and canvas /// elements. /// /// Each DomLayer is created with `pointer-events: none` CSS property to avoid "stealing" mouse @@ -492,6 +492,9 @@ pub struct DomLayers { pub fullscreen_vis: DomScene, /// Front DOM scene layer. pub front: DomScene, + /// DOM scene layer for Node Searcher DOM elements. This layer should probably be removed once + /// all parts of Node Searcher will use ensogl primitives instead of DOM objects for rendering. + pub node_searcher: DomScene, /// The WebGL scene layer. pub canvas: web::HtmlCanvasElement, } @@ -525,12 +528,17 @@ impl DomLayers { canvas.set_style_or_warn("pointer-events", "none"); dom.append_or_warn(&canvas); + let node_searcher = DomScene::new(logger); + node_searcher.dom.set_class_name("node-searcher"); + node_searcher.dom.set_style_or_warn("z-index", "4"); + dom.append_or_warn(&node_searcher.dom); + let front = DomScene::new(logger); front.dom.set_class_name("front"); - front.dom.set_style_or_warn("z-index", "4"); + front.dom.set_style_or_warn("z-index", "5"); dom.append_or_warn(&front.dom); - Self { back, welcome_screen, fullscreen_vis, front, canvas } + Self { back, welcome_screen, fullscreen_vis, front, node_searcher, canvas } } } @@ -698,6 +706,9 @@ pub struct HardcodedLayers { pub panel: Layer, pub panel_text: Layer, pub node_searcher: Layer, + pub node_searcher_text: Layer, + pub edited_node: Layer, + pub edited_node_text: Layer, pub node_searcher_mask: Layer, pub tooltip: Layer, pub tooltip_text: Layer, @@ -726,13 +737,19 @@ impl HardcodedLayers { let panel = Layer::new(logger.sub("panel")); let panel_text = Layer::new(logger.sub("panel_text")); let node_searcher = Layer::new(logger.sub("node_searcher")); + let node_searcher_cam = node_searcher.camera(); + let searcher_text_logger = logger.sub("node_searcher_text"); + let node_searcher_text = Layer::new_with_cam(searcher_text_logger, &node_searcher_cam); + let edited_node = Layer::new(logger.sub("edited_node")); + let edited_node_cam = edited_node.camera(); + let edited_node_text_logger = logger.sub("edited_node_text"); + let edited_node_text = Layer::new_with_cam(edited_node_text_logger, &edited_node_cam); let node_searcher_mask = Layer::new(logger.sub("node_searcher_mask")); let tooltip = Layer::new_with_cam(logger.sub("tooltip"), main_cam); let tooltip_text = Layer::new_with_cam(logger.sub("tooltip_text"), main_cam); let cursor = Layer::new(logger.sub("cursor")); let mask = Layer::new_with_cam(logger.sub("mask"), main_cam); - node_searcher.set_mask(&node_searcher_mask); root.set_sublayers(&[ &viz, &below_main, @@ -744,6 +761,9 @@ impl HardcodedLayers { &panel, &panel_text, &node_searcher, + &node_searcher_text, + &edited_node, + &edited_node_text, &tooltip, &tooltip_text, &cursor, @@ -760,6 +780,9 @@ impl HardcodedLayers { panel, panel_text, node_searcher, + node_searcher_text, + edited_node, + edited_node_text, node_searcher_mask, tooltip, tooltip_text, @@ -1008,6 +1031,11 @@ impl SceneData { self.dom.layers.fullscreen_vis.update_view_projection(&fullscreen_vis_camera); self.dom.layers.welcome_screen.update_view_projection(&welcome_screen_camera); } + let node_searcher_camera = self.layers.node_searcher.camera(); + let node_searcher_camera_changed = node_searcher_camera.update(scene); + if node_searcher_camera_changed { + self.dom.layers.node_searcher.update_view_projection(&node_searcher_camera); + } // Updating all other cameras (the main camera was already updated, so it will be skipped). self.layers.iter_sublayers_and_masks_nested(|layer| { 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 ab14e68a354..c5569d46cd5 100644 --- a/lib/rust/ensogl/example/complex-shape-system/src/lib.rs +++ b/lib/rust/ensogl/example/complex-shape-system/src/lib.rs @@ -117,7 +117,6 @@ pub fn entry_point_complex_shape_system() { .before_frame .add(move |_time| { mask.set_position_x(((frame as f32) / 30.0).sin() * 100.0); - let _keep_alive = &navigator; let _keep_alive = &style_watch; let _keep_alive = &theme_manager;