From 72b202b7d09753d9af1adc8815ed0872396b0d7b Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Mon, 5 Jun 2023 18:01:06 +0200 Subject: [PATCH] Fix visualisation FRP bugs. (#6831) Fixes * Empty Visualization when opening a full-screen visualization directly without opening the visualization before. #6770 https://github.com/enso-org/enso/assets/1428930/5812ed03-652c-4a27-8e33-b85512ca11b6 * Empty visualization when opening the full-screen visualization before the data for the visualization has arrived. #6561 https://github.com/enso-org/enso/assets/1428930/d8e58f2d-f1b6-4b70-84fa-e917f6c0af1f * Visualization is reset to default when reconnecting nodes #6673 https://github.com/enso-org/enso/assets/1428930/ac6cf79a-7147-4f13-9045-52599fb39900 * Redundant internal open/lose events caused by logic loops around the show/hide button, as well as many redundant layer setting/unsetting issues internal to the visualization code. Generally improves the logic around the visualization API by avoiding decentralized logic in different places and removing old code that is no longer needed. --- Cargo.lock | 1 + app/gui/src/presenter/graph/visualization.rs | 10 +- app/gui/view/graph-editor/Cargo.toml | 1 + .../view/graph-editor/src/component/node.rs | 90 +++--- .../src/component/node/action_bar.rs | 5 + .../src/component/visualization/container.rs | 266 ++++++++++-------- .../visualization/container/action_bar.rs | 5 + .../visualization/container/fullscreen.rs | 40 +-- .../src/component/visualization/layer.rs | 3 + app/gui/view/graph-editor/src/lib.rs | 26 +- build-config.yaml | 2 +- .../ensogl/component/toggle-button/src/lib.rs | 37 ++- 12 files changed, 245 insertions(+), 241 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7fe5ea8176..248f8425ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4347,6 +4347,7 @@ dependencies = [ "base64 0.13.1", "bimap", "bitflags 2.2.1", + "derivative", "engine-protocol", "enso-config", "enso-frp", diff --git a/app/gui/src/presenter/graph/visualization.rs b/app/gui/src/presenter/graph/visualization.rs index 2d8ee8d20d..53a29d0294 100644 --- a/app/gui/src/presenter/graph/visualization.rs +++ b/app/gui/src/presenter/graph/visualization.rs @@ -186,12 +186,12 @@ impl Visualization { eval view.visualization_preprocessor_changed (((node, preprocessor)) model.visualization_preprocessor_changed(*node, preprocessor.clone_ref())); eval view.set_node_error_status (((node, error)) model.error_on_node_changed(*node, error)); - update <- source::<(ViewNodeId, visualization_view::Data)>(); + set_data <- source::<(ViewNodeId, visualization_view::Data)>(); error_update <- source::<(ViewNodeId, visualization_view::Data)>(); visualization_failure <- source::(); error_vis_failure <- source::(); - view.set_visualization_data <+ update; + view.set_visualization_data <+ set_data; view.set_error_visualization_data <+ error_update; view.disable_visualization <+ visualization_failure; @@ -199,7 +199,7 @@ impl Visualization { } Self { model, _network: network } - .spawn_visualization_handler(notifications, manager, update, visualization_failure) + .spawn_visualization_handler(notifications, manager, set_data, visualization_failure) .spawn_visualization_handler( error_notifications, error_manager, @@ -213,7 +213,7 @@ impl Visualization { self, notifier: impl Stream + Unpin + 'static, manager: Rc, - update_endpoint: frp::Source<(ViewNodeId, visualization_view::Data)>, + set_data_endpoint: frp::Source<(ViewNodeId, visualization_view::Data)>, failure_endpoint: frp::Source, ) -> Self { let weak = Rc::downgrade(&self.model); @@ -221,7 +221,7 @@ impl Visualization { info!("Received update for visualization: {notification:?}"); match notification { manager::Notification::ValueUpdate { target, data, .. } => { - model.handle_value_update(&update_endpoint, target, data); + model.handle_value_update(&set_data_endpoint, target, data); } manager::Notification::FailedToAttach { visualization, error } => { error!("Visualization {} failed to attach: {error}.", visualization.id); diff --git a/app/gui/view/graph-editor/Cargo.toml b/app/gui/view/graph-editor/Cargo.toml index ac1a90cb0e..5adbcd12f2 100644 --- a/app/gui/view/graph-editor/Cargo.toml +++ b/app/gui/view/graph-editor/Cargo.toml @@ -12,6 +12,7 @@ analytics = { path = "../../analytics" } ast = { path = "../../language/ast/impl" } base64 = "0.13" bimap = { version = "0.4.0" } +derivative = "2.2.0" engine-protocol = { path = "../../controller/engine-protocol" } enso-config = { path = "../../config" } enso-frp = { path = "../../../../lib/rust/frp" } diff --git a/app/gui/view/graph-editor/src/component/node.rs b/app/gui/view/graph-editor/src/component/node.rs index 7a5479570a..3b6a5e0cd1 100644 --- a/app/gui/view/graph-editor/src/component/node.rs +++ b/app/gui/view/graph-editor/src/component/node.rs @@ -259,6 +259,7 @@ ensogl::define_endpoints_2! { select (), deselect (), enable_visualization (), + enable_fullscreen_visualization (), disable_visualization (), set_visualization (Option), set_disabled (bool), @@ -318,12 +319,6 @@ ensogl::define_endpoints_2! { freeze (bool), hover (bool), error (Option), - /// Whether visualization was permanently enabled (e.g. by pressing the button). - visualization_enabled (bool), - /// Visualization can be visible even when it is not enabled, e.g. when showing preview. - /// Visualization can be invisible even when enabled, e.g. when the node has an error. - visualization_visible (bool), - visualization_path (Option), expression_label_visible (bool), /// The [`display::object::Model::position`] of the Node. Emitted when the Display Object /// hierarchy is updated (see: [`ensogl_core::display::object::Instance::update`]). @@ -607,12 +602,6 @@ impl NodeModel { size } - #[profile(Debug)] - #[allow(missing_docs)] // FIXME[everyone] All pub functions should have docs. - pub fn visualization(&self) -> &visualization::Container { - &self.visualization - } - #[profile(Debug)] fn set_error(&self, error: Option<&Error>) { if let Some(error) = error { @@ -753,7 +742,6 @@ impl Node { // === Action Bar === - let visualization_button_state = action_bar.action_visibility.clone_ref(); out.context_switch <+ action_bar.action_context_switch; out.skip <+ action_bar.action_skip; out.freeze <+ action_bar.action_freeze; @@ -790,7 +778,10 @@ impl Node { hover_onset_delay.set_delay(VIS_PREVIEW_ONSET_MS); hover_onset_delay.set_duration(0.0); + let visualization = &model.visualization.frp; + frp::extend! { network + enabled <- bool(&input.disable_visualization, &input.enable_visualization); out.error <+ input.set_error; is_error_set <- input.set_error.map( @@ -806,11 +797,23 @@ impl Node { } )); - eval input.set_visualization ((t) model.visualization.frp.set_visualization.emit(t)); - visualization_enabled_frp <- bool(&input.disable_visualization,&input.enable_visualization); - eval visualization_enabled_frp ((enabled) - model.action_bar.set_action_visibility_state(enabled) - ); + viz_enabled <- enabled && no_error_set; + visualization.set_view_state <+ viz_enabled.on_true().constant(visualization::ViewState::Enabled); + visualization.set_view_state <+ viz_enabled.on_false().constant(visualization::ViewState::Disabled); + + // Integration between visualization and action bar. + visualization.set_visualization <+ input.set_visualization; + is_enabled <- visualization.view_state.map(|state|{ + matches!(state,visualization::ViewState::Enabled) + }); + action_bar.set_action_visibility_state <+ is_enabled; + button_set_to_true <- action_bar.user_action_visibility.on_true(); + button_set_to_true_without_error <- button_set_to_true.gate_not(&is_error_set); + button_set_to_true_with_error <- button_set_to_true.gate(&is_error_set); + visualization.set_view_state <+ button_set_to_true_without_error.constant(visualization::ViewState::Enabled); + action_bar.set_action_visibility_state <+ button_set_to_true_with_error.constant(false); + + visualization.set_view_state <+ action_bar.user_action_visibility.on_false().constant(visualization::ViewState::Disabled); // Show preview visualisation after some delay, depending on whether we show an error // or are in quick preview mode. Also, omit the preview if we don't have an @@ -840,38 +843,10 @@ impl Node { hide_preview <+ editing_finished; preview_enabled <- bool(&hide_preview, &input.show_preview); preview_visible <- hover_preview_visible || preview_enabled; - preview_visible <- preview_visible.on_change(); - - // If the preview is visible while the visualization button is disabled, clicking the - // visualization button hides the preview and keeps the visualization button disabled. - vis_button_on <- visualization_button_state.filter(|e| *e).constant(()); - vis_button_off <- visualization_button_state.filter(|e| !*e).constant(()); - visualization_on <- vis_button_on.gate_not(&preview_visible); - vis_button_on_while_preview_visible <- vis_button_on.gate(&preview_visible); - hide_preview <+ vis_button_on_while_preview_visible; - hide_preview <+ vis_button_off; - action_bar.set_action_visibility_state <+ - vis_button_on_while_preview_visible.constant(false); - visualization_enabled <- bool(&vis_button_off, &visualization_on); - - visualization_visible <- visualization_enabled || preview_visible; - visualization_visible <- visualization_visible && no_error_set; - visualization_visible_on_change <- visualization_visible.on_change(); - out.visualization_visible <+ visualization_visible_on_change; - out.visualization_enabled <+ visualization_enabled; - eval visualization_visible_on_change ((is_visible) - model.visualization.frp.set_visibility(is_visible) - ); - out.visualization_path <+ model.visualization.frp.visualisation.all_with(&init,|def_opt,_| { - def_opt.as_ref().map(|def| def.signature.path.clone_ref()) - }); - - // Ensure the preview is visible above all other elements, but the normal visualisation - // is below nodes. - layer_on_hover <- hover_preview_visible.on_false().map(|_| visualization::Layer::Default); - layer_on_not_hover <- hover_preview_visible.on_true().map(|_| visualization::Layer::Front); - layer <- any(layer_on_hover,layer_on_not_hover); - model.visualization.frp.set_layer <+ layer; + vis_preview_visible <- preview_visible && no_error_set; + vis_preview_visible <- vis_preview_visible.on_change(); + visualization.set_view_state <+ vis_preview_visible.on_true().constant(visualization::ViewState::Preview); + visualization.set_view_state <+ vis_preview_visible.on_false().constant(visualization::ViewState::Disabled); update_error <- all(input.set_error,preview_visible); eval update_error([model]((error,visible)){ @@ -883,6 +858,10 @@ impl Node { }); eval error_color_anim.value ((value) model.set_error_color(value)); + visualization.set_view_state <+ input.set_error.is_some().constant(visualization::ViewState::Disabled); + + enable_fullscreen <- frp.enable_fullscreen_visualization.gate(&no_error_set); + visualization.set_view_state <+ enable_fullscreen.constant(visualization::ViewState::Fullscreen); } @@ -949,16 +928,14 @@ impl Node { // === Type Labels === model.output.set_type_label_visibility - <+ visualization_visible.not().and(&no_error_set); + <+ visualization.visible.not().and(&no_error_set); // === Bounding Box === let visualization_size = &model.visualization.frp.size; - // Visualization can be enabled and not visible when the node has an error. - visualization_enabled_and_visible <- visualization_enabled && visualization_visible; bbox_input <- all4( - &out.position,&new_size,&visualization_enabled_and_visible,visualization_size); + &out.position,&new_size,&visualization.visible,visualization_size); out.bounding_box <+ bbox_input.map(|(a,b,c,d)| bounding_box(*a,*b,c.then(|| *d))); inner_bbox_input <- all2(&out.position,&new_size); @@ -997,6 +974,11 @@ impl Node { color::Lcha::transparent() } } + + /// FRP API of the visualization container attached to this node. + pub fn visualization(&self) -> &visualization::container::Frp { + &self.model().visualization.frp + } } impl display::Object for Node { 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 0d49c0293f..42b6d0488d 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 @@ -70,6 +70,7 @@ ensogl::define_endpoints! { Input { set_size (Vector2), set_visibility (bool), + /// Set whether the `visibility` icon should be toggled on or off. set_action_visibility_state (bool), set_action_skip_state (bool), set_action_freeze_state (bool), @@ -86,6 +87,9 @@ ensogl::define_endpoints! { mouse_over (), mouse_out (), action_visibility (bool), + /// The last visibility selection by the user. Ignores changes to the + /// visibility chooser icon made through the input API. + user_action_visibility (bool), action_context_switch (bool), action_freeze (bool), action_skip (bool), @@ -412,6 +416,7 @@ impl ActionBar { // === Icon Actions === frp.source.action_visibility <+ model.icons.visibility.state; + frp.source.user_action_visibility <+ model.icons.visibility.last_user_state; frp.source.action_skip <+ model.icons.skip.state; frp.source.action_freeze <+ model.icons.freeze.state; disable_context_button_clicked <- model.icons.context_switch.disable_button.is_pressed.on_true(); 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 cc135403a8..d1a5f0ad78 100644 --- a/app/gui/view/graph-editor/src/component/visualization/container.rs +++ b/app/gui/view/graph-editor/src/component/visualization/container.rs @@ -1,4 +1,12 @@ -//! This module defines the `Container` struct and related functionality. +//! This module defines the `Container` struct and related functionality. This represent the view +//! a visualisation in the graph editor and includes a visual box that contains the visualisation, +//! and action bar that allows setting the visualisation type. +//! +//! The `[Container]` struct is responsible for managing the visualisation and action bar and +//! providing a unified interface to the graph editor. This includes ensuring that the visualisation +//! is correctly positioned, sized and layouted in its different [ViewState]s (which include the +//! `Enabled`, `Fullscreen` and `Preview` states). Importantly, this also includes EnsoGL layer +//! management to ensure correct occlusion of the visualisation with respect to other scene objects. // FIXME There is a serious performance problem in this implementation. It assumes that the // FIXME visualization is a child of the container. However, this is very inefficient. Consider a @@ -24,6 +32,7 @@ use ensogl::data::color::Rgba; use ensogl::display; use ensogl::display::scene; use ensogl::display::scene::Scene; +use ensogl::display::DomScene; use ensogl::display::DomSymbol; use ensogl::system::web; use ensogl::Animation; @@ -125,29 +134,59 @@ pub mod background { // === Frp === // =========== -ensogl::define_endpoints! { +/// Indicates the visibility state of the visualisation. +#[derive(Clone, Copy, Debug, PartialEq, Derivative)] +#[derivative(Default)] +pub enum ViewState { + /// Visualisation is permanently enabled and visible in the graph editor. It is attached to a + /// single node and can be moved and interacted with when selected. + Enabled, + /// Visualisation is disabled and hidden in the graph editor. + #[derivative(Default)] + Disabled, + /// Visualisation is temporarily enabled and visible in the graph editor. It should be placed + /// above other scene elements to allow quick inspection. + Preview, + /// Visualisation is enabled and visible in the graph editor in fullscreen mode. It occludes + /// the whole graph and can be interacted with. + Fullscreen, +} + +impl ViewState { + /// Indicates whether the visualisation is visible in the graph editor. It is always visible + /// when not disabled. + pub fn is_visible(&self) -> bool { + !matches!(self, ViewState::Disabled) + } + + /// Indicates whether the visualisation is fullscreen mode. + pub fn is_fullscreen(&self) -> bool { + matches!(self, ViewState::Fullscreen) + } +} + + +ensogl::define_endpoints_2! { Input { - set_visibility (bool), - toggle_visibility (), + set_view_state (ViewState), set_visualization (Option), cycle_visualization (), - set_data (visualization::Data), + set_data (Option), select (), deselect (), set_size (Vector2), - enable_fullscreen (), - disable_fullscreen (), set_vis_input_type (Option), - set_layer (visualization::Layer), } - Output { preprocessor (PreprocessorConfiguration), visualisation (Option), + visualization_path (Option), size (Vector2), is_selected (bool), + vis_input_type (Option), + fullscreen (bool), visible (bool), - vis_input_type (Option) + view_state (ViewState), } } @@ -161,8 +200,7 @@ ensogl::define_endpoints! { #[derive(Debug)] #[allow(missing_docs)] pub struct View { - display_object: display::object::Instance, - + display_object: display::object::Instance, background: background::View, overlay: overlay::View, background_dom: DomSymbol, @@ -277,7 +315,6 @@ pub struct ContainerModel { scene: Scene, view: View, fullscreen_view: fullscreen::Panel, - is_fullscreen: Rc>, registry: visualization::Registry, size: Rc>, action_bar: ActionBar, @@ -294,7 +331,6 @@ impl ContainerModel { let view = View::new(scene.clone_ref()); let fullscreen_view = fullscreen::Panel::new(scene); let scene = scene.clone_ref(); - let is_fullscreen = default(); let size = default(); let action_bar = ActionBar::new(app, registry.clone_ref()); view.add_child(&action_bar); @@ -307,7 +343,6 @@ impl ContainerModel { scene, view, fullscreen_view, - is_fullscreen, registry, size, action_bar, @@ -318,21 +353,16 @@ impl ContainerModel { fn init(self) -> Self { self.display_object.add_child(&self.drag_root); self.scene.layers.above_nodes.add(&self.action_bar); - - self.update_shape_sizes(); + self.update_shape_sizes(ViewState::default()); self.init_corner_roundness(); - // FIXME: These 2 lines fix a bug with display objects visible on stage. - self.set_visibility(true); - self.set_visibility(false); - self.view.show_waiting_screen(); self } - /// Indicates whether the visualization container is visible and active. - /// Note: can't be called `is_visible` due to a naming conflict with `display::object::class`. - pub fn is_active(&self) -> bool { - self.view.has_parent() + fn set_visualization_layer(&self, layer: visualization::Layer) { + if let Some(vis) = self.visualization.borrow().as_ref() { + vis.set_layer.emit(layer) + } } } @@ -340,54 +370,60 @@ impl ContainerModel { // === Private API === impl ContainerModel { - fn set_visibility(&self, visibility: bool) { + fn apply_view_state(&self, view_state: ViewState) { // This is a workaround for #6600. It ensures the action bar is removed // and receive no further mouse events. - if visibility { + if view_state.is_visible() { self.view.add_child(&self.action_bar); } else { self.action_bar.unset_parent(); } // Show or hide the visualization. - if visibility { + if view_state.is_visible() { self.drag_root.add_child(&self.view); - self.show_visualisation(); } else { self.drag_root.remove_child(&self.view); } + + match view_state { + ViewState::Enabled => self.enable_default_view(), + ViewState::Disabled => {} + ViewState::Preview => self.enable_preview(), + ViewState::Fullscreen => self.enable_fullscreen(), + } } - fn enable_fullscreen(&self) { - self.is_fullscreen.set(true); + fn set_vis_parents(&self, parent: &dyn display::Object, dom_parent: &DomScene) { if let Some(viz) = &*self.visualization.borrow() { - self.fullscreen_view.add_child(viz); + parent.add_child(viz); if let Some(dom) = viz.root_dom() { - self.scene.dom.layers.fullscreen_vis.manage(dom); + dom_parent.manage(dom); } viz.inputs.activate.emit(()); } } - fn disable_fullscreen(&self) { - self.is_fullscreen.set(false); - if let Some(viz) = &*self.visualization.borrow() { - self.view.add_child(viz); - if let Some(dom) = viz.root_dom() { - self.scene.dom.layers.back.manage(dom); - } - viz.inputs.deactivate.emit(()); - } + fn enable_fullscreen(&self) { + self.set_visualization_layer(visualization::Layer::Fullscreen); + self.set_vis_parents(&self.fullscreen_view, &self.scene.dom.layers.fullscreen_vis) } - fn toggle_visibility(&self) { - self.set_visibility(!self.is_active()) + fn enable_default_view(&self) { + self.set_visualization_layer(visualization::Layer::Default); + self.set_vis_parents(&self.view, &self.scene.dom.layers.back) + } + + fn enable_preview(&self) { + self.set_visualization_layer(visualization::Layer::Front); + self.set_vis_parents(&self.view, &self.scene.dom.layers.front); } fn set_visualization( &self, visualization: visualization::Instance, preprocessor: &frp::Any, + view_state: ViewState, ) { let size = self.size.get(); visualization.frp.set_size.emit(size); @@ -399,31 +435,27 @@ impl ContainerModel { vis_preprocessor_change <- visualization.on_preprocessor_change.map(|x| x.clone()); preprocessor <+ vis_preprocessor_change; } - preprocessor.emit(visualization.on_preprocessor_change.value()); - if self.is_fullscreen.get() { - self.fullscreen_view.add_child(&visualization) - } else { - self.view.add_child(&visualization); - } - self.visualization.replace(Some(visualization)); + self.visualization.replace(Some(visualization.clone_ref())); self.vis_frp_connection.replace(Some(vis_frp_connection)); + self.apply_view_state(view_state); + preprocessor.emit(visualization.on_preprocessor_change.value()); } fn set_visualization_data(&self, data: &visualization::Data) { self.visualization.borrow().for_each_ref(|vis| vis.send_data.emit(data)) } - fn update_shape_sizes(&self) { + fn update_shape_sizes(&self, view_state: ViewState) { let size = self.size.get(); - self.set_size(size); + self.update_layout(size, view_state); } - fn set_size(&self, size: impl Into) { + fn update_layout(&self, size: impl Into, view_state: ViewState) { let dom = self.view.background_dom.dom(); let bg_dom = self.fullscreen_view.background_dom.dom(); let size = size.into(); self.size.set(size); - if self.is_fullscreen.get() { + if view_state.is_fullscreen() { self.view.overlay.set_size(Vector2(0.0, 0.0)); dom.set_style_or_warn("width", "0"); dom.set_style_or_warn("height", "0"); @@ -461,16 +493,6 @@ impl ContainerModel { self.view.background.roundness.set(value); } - fn show_visualisation(&self) { - if let Some(vis) = self.visualization.borrow().as_ref() { - if self.is_fullscreen.get() { - self.fullscreen_view.add_child(vis); - } else { - self.view.add_child(vis); - } - } - } - /// Check if given mouse-event-target means this visualization. fn is_this_target(&self, target: scene::PointerTargetId) -> bool { self.view.overlay.is_this_target(target) @@ -526,7 +548,9 @@ impl Container { } fn init(self, app: &Application) -> Self { - let frp = &self.frp; + let frp = &self.frp.private; + let input = &frp.input; + let output = &frp.output; let network = &self.frp.network; let model = &self.model; let scene = &self.model.scene; @@ -536,30 +560,27 @@ impl Container { let selection = Animation::new(network); frp::extend! { network - eval frp.set_visibility ((v) model.set_visibility(*v)); - eval_ frp.toggle_visibility (model.toggle_visibility()); + eval input.set_view_state((state) model.apply_view_state(*state)); + output.view_state <+ input.set_view_state.on_change(); + output.fullscreen <+ output.view_state.map(|state| state.is_fullscreen()).on_change(); + output.visible <+ output.view_state.map(|state| state.is_visible()).on_change(); + output.size <+ input.set_size.on_change(); - visualisation_uninitialised <- frp.set_visualization.map(|t| t.is_none()); - default_visualisation <- visualisation_uninitialised.on_true().map(|_| { + visualisation_not_selected <- input.set_visualization.map(|t| t.is_none()); + input_type_not_set <- input.set_vis_input_type.is_some().not(); + uninitialised <- visualisation_not_selected && input_type_not_set; + set_default_visualisation <- uninitialised.on_change().on_true().map(|_| { Some(visualization::Registry::default_visualisation()) }); - vis_input_type <- frp.set_vis_input_type.on_change(); - vis_input_type <- vis_input_type.gate(&visualisation_uninitialised).unwrap(); - default_visualisation_for_type <- vis_input_type.map(f!((tp) { + vis_input_type_changed <- input.set_vis_input_type.on_change(); + vis_input_type_changed_without_selection <- + vis_input_type_changed.gate(&visualisation_not_selected).unwrap(); + set_default_visualisation_for_type <- vis_input_type_changed_without_selection.map(f!((tp) { registry.default_visualization_for_type(tp) })); - default_visualisation <- any(&default_visualisation, &default_visualisation_for_type); + set_default_visualisation <- any( + &set_default_visualisation, &set_default_visualisation_for_type); - eval frp.set_data ((t) model.set_visualization_data(t)); - frp.source.size <+ frp.set_size; - frp.source.visible <+ frp.set_visibility; - frp.source.visible <+ frp.toggle_visibility.map(f!((()) model.is_active())); - eval frp.set_layer ([model](l) { - if let Some(vis) = model.visualization.borrow().as_ref() { - vis.set_layer.emit(l) - } - model.view.set_layer(*l); - }); } @@ -569,15 +590,17 @@ impl Container { selected_definition <- action_bar.visualisation_selection.map(f!([registry](path) path.as_ref().and_then(|path| registry.definition_from_path(path)) )); - action_bar.set_vis_input_type <+ frp.set_vis_input_type; - frp.source.vis_input_type <+ frp.set_vis_input_type; + action_bar.hide_icons <+ selected_definition.constant(()); + output.vis_input_type <+ input.set_vis_input_type; + let chooser = &model.action_bar.visualization_chooser(); + chooser.frp.set_vis_input_type <+ input.set_vis_input_type; } // === Cycling Visualizations === frp::extend! { network - vis_after_cycling <- frp.cycle_visualization.map3(&frp.visualisation,&frp.vis_input_type, + vis_after_cycling <- input.cycle_visualization.map3(&output.visualisation, &output.vis_input_type, f!(((),vis,input_type) model.next_visualization(vis,input_type)) ); } @@ -587,19 +610,19 @@ impl Container { frp::extend! { network vis_definition_set <- any( - frp.set_visualization, + input.set_visualization, selected_definition, vis_after_cycling, - default_visualisation); + set_default_visualisation); new_vis_definition <- vis_definition_set.on_change(); - let preprocessor = &frp.source.preprocessor; - frp.source.visualisation <+ new_vis_definition.map(f!( - [model,action_bar,app,preprocessor](vis_definition) { + let preprocessor = &output.preprocessor; + output.visualisation <+ new_vis_definition.map2(&output.view_state, f!( + [model,action_bar,app,preprocessor](vis_definition, view_state) { if let Some(definition) = vis_definition { match definition.new_instance(&app) { Ok(vis) => { - model.set_visualization(vis,&preprocessor); + model.set_visualization(vis,&preprocessor, *view_state); let path = Some(definition.signature.path.clone()); action_bar.set_selected_visualization.emit(path); }, @@ -611,21 +634,24 @@ impl Container { vis_definition.clone() })); + output.visualization_path <+ output.visualisation.map(|definition| { + definition.as_ref().map(|def| def.signature.path.clone_ref()) + }); + } // === Visualisation Loading Spinner === - eval_ frp.source.visualisation ( model.view.show_waiting_screen() ); - eval_ frp.set_data ( model.view.disable_waiting_screen() ); - + frp::extend! { network + eval_ output.visualisation ( model.view.show_waiting_screen() ); + eval_ input.set_data ( model.view.disable_waiting_screen() ); } - // === Selecting Visualization === frp::extend! { network mouse_down_target <- scene.mouse.frp_deprecated.down.map(f_!(scene.mouse.target.get())); - selected_by_click <= mouse_down_target.map(f!([model] (target){ + selected_by_click <= mouse_down_target.map2(&output.view_state, f!([model] (target,view_state){ let vis = &model.visualization; let activate = || vis.borrow().as_ref().map(|v| v.activate.clone_ref()); let deactivate = || vis.borrow().as_ref().map(|v| v.deactivate.clone_ref()); @@ -634,7 +660,7 @@ impl Container { activate.emit(()); return Some(true); } - } else if !model.is_fullscreen.get() { + } else if !view_state.is_fullscreen() { if let Some(deactivate) = deactivate() { deactivate.emit(()); return Some(false); @@ -645,34 +671,28 @@ impl Container { selection_after_click <- selected_by_click.map(|sel| if *sel {1.0} else {0.0}); selection.target <+ selection_after_click; eval selection.value ((selection) model.view.background.selection.set(*selection)); - - selected_by_going_fullscreen <- bool(&frp.disable_fullscreen,&frp.enable_fullscreen); - selected <- any(selected_by_click,selected_by_going_fullscreen); - - is_selected_changed <= selected.map2(&frp.output.is_selected, |&new,&old| { - (new != old).as_some(new) - }); - frp.source.is_selected <+ is_selected_changed; + is_selected <- selected_by_click || output.fullscreen; + output.is_selected <+ is_selected.on_change(); } // === Fullscreen View === frp::extend! { network - eval_ frp.enable_fullscreen (model.enable_fullscreen()); - eval_ frp.disable_fullscreen (model.disable_fullscreen()); - fullscreen_enabled_weight <- frp.enable_fullscreen.constant(1.0); - fullscreen_disabled_weight <- frp.disable_fullscreen.constant(0.0); - fullscreen_weight <- any(fullscreen_enabled_weight,fullscreen_disabled_weight); - frp.source.size <+ frp.set_size; + enable_fullscreen <- output.fullscreen.on_true(); + disable_fullscreen <- output.fullscreen.on_false(); - _eval <- fullscreen_weight.all_with3(&frp.size,scene_shape, - f!([model] (weight,viz_size,scene_size) { + fullscreen_enabled_weight <- enable_fullscreen.constant(1.0); + fullscreen_disabled_weight <- disable_fullscreen.constant(0.0); + fullscreen_weight <- any(fullscreen_enabled_weight,fullscreen_disabled_weight); + + _eval <- fullscreen_weight.all_with4(&output.size,scene_shape,&output.view_state, + f!([model] (weight,viz_size,scene_size,view_state) { let weight_inv = 1.0 - weight; let scene_size : Vector2 = scene_size.into(); let current_size = viz_size * weight_inv + scene_size * *weight; model.set_corner_roundness(weight_inv); - model.set_size(current_size); + model.update_layout(current_size,*view_state); let m1 = model.scene.layers.panel.camera().inversed_view_matrix(); let m2 = model.scene.layers.viz.camera().view_matrix(); @@ -683,6 +703,16 @@ impl Container { let current_pos = pp * weight_inv; model.fullscreen_view.set_position(current_pos); })); + + + // === Data Update === + + data <- input.set_data.unwrap(); + has_data <- input.set_data.is_some(); + reset_data <- data.sample(&new_vis_definition).gate(&has_data); + data_update <- any(&data,&reset_data); + eval data_update ((t) model.set_visualization_data(t)); + } @@ -709,8 +739,8 @@ impl Container { // // This is not optimal the optimal solution to this problem, as it also means that we have // an animation on an invisible component running. - frp.set_size.emit(Vector2(DEFAULT_SIZE.0, DEFAULT_SIZE.1)); - frp.set_visualization.emit(None); + self.frp.public.set_size(Vector2(DEFAULT_SIZE.0, DEFAULT_SIZE.1)); + self.frp.public.set_visualization(None); self } 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 7e7db0845f..6163c35057 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 @@ -432,6 +432,11 @@ impl ActionBar { } self } + + /// Visualization Chooser component getter. + pub fn visualization_chooser(&self) -> &VisualizationChooser { + &self.model.visualization_chooser + } } impl display::Object for ActionBar { 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 110b7ac242..35df480ffa 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 @@ -9,39 +9,6 @@ use ensogl::display; use ensogl::display::scene::Scene; use ensogl::display::DomSymbol; use ensogl::system::web; -use ensogl_hardcoded_theme as theme; - - - -// ============== -// === Shapes === -// ============== - -/// Container background shape definition. -/// -/// Provides a backdrop and outline for visualisations. Can indicate the selection status of the -/// container. -/// TODO : We do not use backgrounds because otherwise they would overlap JS -/// visualizations. Instead we added a HTML background to the `View`. -/// This should be further investigated while fixing rust visualization displaying. (#526) -pub mod background { - use super::*; - - ensogl::shape! { - alignment = center; - (style:Style,selected:f32,radius:f32,roundness:f32) { - let width : Var = "input_size.x".into(); - let height : Var = "input_size.y".into(); - let radius = 1.px() * &radius; - let color_path = theme::graph_editor::visualization::background; - let color_bg = style.get_color(color_path); - let corner_radius = &radius * &roundness; - let background = Rect((&width,&height)).corners_radius(corner_radius); - let background = background.fill(color_bg); - background.into() - } - } -} @@ -54,9 +21,9 @@ pub mod background { #[allow(missing_docs)] pub struct Panel { display_object: display::object::Instance, + // Note: We use a HTML background, because a EnsoGL background would be + // overlapping the JS visualization. pub background_dom: DomSymbol, - // TODO: See TODO above. - // background : background::View, } impl Panel { @@ -76,9 +43,6 @@ impl Panel { 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"); background_dom.dom().set_style_or_warn("height", "0"); background_dom.dom().set_style_or_warn("z-index", "1"); diff --git a/app/gui/view/graph-editor/src/component/visualization/layer.rs b/app/gui/view/graph-editor/src/component/visualization/layer.rs index f34635bb9f..45e355928f 100644 --- a/app/gui/view/graph-editor/src/component/visualization/layer.rs +++ b/app/gui/view/graph-editor/src/component/visualization/layer.rs @@ -16,6 +16,8 @@ pub enum Layer { Default, /// Display the visualisation over the scene. Front, + /// Display the visualisation in fullscreen mode. + Fullscreen, } impl Layer { @@ -24,6 +26,7 @@ impl Layer { match self { Layer::Default => scene.dom.layers.back.manage(dom), Layer::Front => scene.dom.layers.front.manage(dom), + Layer::Fullscreen => scene.dom.layers.fullscreen_vis.manage(dom), } } } diff --git a/app/gui/view/graph-editor/src/lib.rs b/app/gui/view/graph-editor/src/lib.rs index a1755d483a..6a2dc41f1f 100644 --- a/app/gui/view/graph-editor/src/lib.rs +++ b/app/gui/view/graph-editor/src/lib.rs @@ -1596,6 +1596,7 @@ impl GraphEditorModelWithNetwork { let touch = &self.touch_state; let model = &self.model; let NodeCreationContext { pointer_style, output_press, input_press, output } = ctx; + let visualisation = node.visualization(); if let Some(network) = self.network.upgrade_or_warn() { frp::new_bridge_network! { [network, node_network] graph_node_bridge @@ -1697,8 +1698,8 @@ impl GraphEditorModelWithNetwork { // === Visualizations === - visualization_shown <- node.visualization_visible.gate(&node.visualization_visible); - visualization_hidden <- node.visualization_visible.gate_not(&node.visualization_visible); + visualization_shown <- visualisation.visible.on_true(); + visualization_hidden <- visualisation.visible.on_false(); let vis_is_selected = node_model.visualization.frp.is_selected.clone_ref(); @@ -1711,7 +1712,7 @@ impl GraphEditorModelWithNetwork { node_model.visualization.frp.preprocessor.map(move |preprocessor| { (node_id,preprocessor.clone()) }); - output.visualization_preprocessor_changed <+ preprocessor_changed.gate(&node.visualization_visible); + output.visualization_preprocessor_changed <+ preprocessor_changed; metadata <- any(...); @@ -1729,7 +1730,7 @@ impl GraphEditorModelWithNetwork { init <- source::<()>(); enabled_visualization_path <- init.all_with3( - &node.visualization_enabled, &node.visualization_path, + &visualisation.visible, &visualisation.visualization_path, move |_init, is_enabled, path| (node_id, is_enabled.and_option(path.clone())) ); output.enabled_visualization_path <+ enabled_visualization_path; @@ -2004,17 +2005,20 @@ impl GraphEditorModel { } } - fn enable_visualization_fullscreen(&self, node_id: impl Into) { + fn enable_visualization_fullscreen(&self, node_id: impl Into) -> bool { let node_id = node_id.into(); if let Some(node) = self.nodes.get_cloned_ref(&node_id) { - node.model().visualization.frp.enable_fullscreen.emit(()); + node.frp().enable_fullscreen_visualization(); + node.visualization().fullscreen.value() + } else { + false } } fn disable_visualization_fullscreen(&self, node_id: impl Into) { let node_id = node_id.into(); if let Some(node) = self.nodes.get_cloned_ref(&node_id) { - node.model().visualization.frp.disable_fullscreen.emit(()); + node.model().visualization.frp.set_view_state(visualization::ViewState::Enabled); } } @@ -3581,7 +3585,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { viz_tgt_nodes_off <- viz_tgt_nodes.map(f!([model](node_ids) { node_ids.iter().cloned().filter(|node_id| { model.nodes.get_cloned_ref(node_id) - .map(|node| !node.visualization_enabled.value()) + .map(|node| !node.visualization().visible.value()) .unwrap_or_default() }).collect_vec() })); @@ -3597,7 +3601,9 @@ fn new_graph_editor(app: &Application) -> GraphEditor { eval viz_enable ((id) model.enable_visualization(id)); eval viz_disable ((id) model.disable_visualization(id)); eval viz_preview_disable ((id) model.disable_visualization(id)); - eval viz_fullscreen_on ((id) model.enable_visualization_fullscreen(id)); + fullscreen_vis_was_enabled <- viz_fullscreen_on.map(f!((id) + model.enable_visualization_fullscreen(id).then(|| *id)) + ).unwrap(); viz_fs_to_close <- out.visualization_fullscreen.sample(&inputs.close_fullscreen_visualization); eval viz_fs_to_close ([model](vis) { @@ -3607,7 +3613,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { } }); - out.visualization_fullscreen <+ viz_fullscreen_on.map(|id| Some(*id)); + out.visualization_fullscreen <+ fullscreen_vis_was_enabled.map(|id| Some(*id)); out.visualization_fullscreen <+ inputs.close_fullscreen_visualization.constant(None); out.is_fs_visualization_displayed <+ out.visualization_fullscreen.map(Option::is_some); diff --git a/build-config.yaml b/build-config.yaml index 175838ecda..d51cbb3f88 100644 --- a/build-config.yaml +++ b/build-config.yaml @@ -1,6 +1,6 @@ # Options intended to be common for all developers. -wasm-size-limit: 15.91 MiB +wasm-size-limit: 15.92 MiB required-versions: # NB. The Rust version is pinned in rust-toolchain.toml. diff --git a/lib/rust/ensogl/component/toggle-button/src/lib.rs b/lib/rust/ensogl/component/toggle-button/src/lib.rs index 2859b58b67..940cd9ca41 100644 --- a/lib/rust/ensogl/component/toggle-button/src/lib.rs +++ b/lib/rust/ensogl/component/toggle-button/src/lib.rs @@ -54,7 +54,7 @@ pub trait ColorableShape: ShapeWithDefaultableData { // === Frp === // =========== -ensogl_core::define_endpoints! { +ensogl_core::define_endpoints_2! { Input { set_visibility (bool), set_color_scheme (ColorScheme), @@ -65,7 +65,11 @@ ensogl_core::define_endpoints! { set_read_only (bool), } Output { + /// Current state of the button, as visible in the scene, state (bool), + /// Last state of the button from a user interaction. + /// This ignores state changes based on the `set_state` input. + last_user_state (bool), visible (bool), mouse_over (), mouse_out (), @@ -232,6 +236,8 @@ impl ToggleButton { fn init_frp(self, app: &Application, tooltip_style: tooltip::Style) -> Self { let network = &self.frp.network; let frp = &self.frp; + let input = &self.frp.private.input; + let output = &self.frp.private.output; let model = &self.model; let color = color::Animation::new(network); let icon = &model.icon.events_deprecated; @@ -249,36 +255,37 @@ impl ToggleButton { // === Input Processing === - eval frp.set_size ((size) model.icon.set_size(*size);); + eval input.set_size ((size) model.icon.set_size(*size);); // === State === - clicked <- icon.mouse_down_primary.gate_not(&frp.set_read_only); - toggle <- any_(frp.toggle, clicked); - frp.source.state <+ frp.state.not().sample(&toggle); - frp.source.state <+ frp.set_state; + clicked <- icon.mouse_down_primary.gate_not(&input.set_read_only); + toggle <- any_(input.toggle, clicked); + output.state <+ output.state.not().sample(&toggle); + output.state <+ input.set_state; + output.last_user_state <+ output.state.sample(&clicked); // === Mouse Interactions === - frp.source.mouse_over <+ icon.mouse_over; - frp.source.mouse_out <+ icon.mouse_out; - frp.source.is_hovered <+ bool(&icon.mouse_out, &icon.mouse_over); - frp.source.is_pressed <+ bool(&icon.mouse_up_primary, &icon.mouse_down_primary); + output.mouse_over <+ icon.mouse_over; + output.mouse_out <+ icon.mouse_out; + output.is_hovered <+ bool(&icon.mouse_out, &icon.mouse_over); + output.is_pressed <+ bool(&icon.mouse_up_primary, &icon.mouse_down_primary); // === Color === - invisible <- frp.set_visibility.on_false().constant(0.0); + invisible <- input.set_visibility.on_false().constant(0.0); color.target_alpha <+ invisible; - frp.source.visible <+ frp.set_visibility; + output.visible <+ input.set_visibility; - button_state <- all_with4(&frp.visible,&frp.state,&frp.is_hovered,&frp.is_pressed, + button_state <- all_with4(&output.visible,&output.state,&output.is_hovered,&output.is_pressed, |a,b,c,d| ButtonState::new(*a,*b,*c,*d)); - color_target <- all_with(&frp.set_color_scheme,&button_state, + color_target <- all_with(&input.set_color_scheme,&button_state, |colors,state| colors.query(*state)); color.target <+ color_target; @@ -287,7 +294,7 @@ impl ToggleButton { // === Tooltip === - tooltip <- frp.is_hovered.map(move |is_hovered| { + tooltip <- output.is_hovered.map(move |is_hovered| { if *is_hovered { tooltip_style.clone() } else {