diff --git a/ide/CHANGELOG.md b/ide/CHANGELOG.md index b68af204bbf4..33184a4a72ca 100644 --- a/ide/CHANGELOG.md +++ b/ide/CHANGELOG.md @@ -11,8 +11,17 @@ will be lost. In this build we added notification in statusbar to signalize that the connection was lost and IDE must be restarted. In future IDE will try to automatically reconnect. -- [Visualization can be extended to the whole screen][1355] by selecting the - node and pressing space twice. To quit this view, press space again. + - [Visualization can be extended to the whole screen][1355] by selecting the + node and pressing space twice. To quit this view, press space again. +- [Visualization preview on output port hover.][1363] There is now a quick + preview for visualizations and error descriptions. Hovering a node output will + first show a tooltip with the type information and then after some time, will + show the visualization of the node. The preview visualization will be located + above other nodes, whereas the normal view, will be shown below nodes. Errors + will show the preview visualization immediately. Nodes without type + information will also show the visualization immediately. You can enter a + quick preview mode by pressing ctrl (or command on macOS), which will show the + preview visualization immediately for any hovered node output. - [Database Visualizations][1335]. Visualizations for the Database library have been added. The Table visualization now automatically executes the underlying query to display its results in a table. In addition, the SQL Query @@ -66,7 +75,7 @@ in JavaScript. You can query it for all IDE colors, including the colors used to represent types. - [You can now start the IDE service without window again.][1353] The command - line arguyment `--no-window` now starts all the required backend services + line argument `--no-window` now starts all the required backend services again, and prints the port on the command line, allowing you to open the IDE with a browser of your choice. - [JS visualizations have consistent gestures with the IDE][1291]. Panning and @@ -115,6 +124,7 @@ you can find their release notes [1341]: https://github.com/enso-org/ide/pull/1341 [1348]: https://github.com/enso-org/ide/pull/1348 [1353]: https://github.com/enso-org/ide/pull/1353 +[1363]: https://github.com/enso-org/ide/pull/1363 [1384]: https://github.com/enso-org/ide/pull/1384 [1385]: https://github.com/enso-org/ide/pull/1385 [1390]: https://github.com/enso-org/ide/pull/1390 diff --git a/ide/src/rust/ensogl/lib/components/src/label.rs b/ide/src/rust/ensogl/lib/components/src/label.rs index d6747361aaae..31ced759bbf0 100644 --- a/ide/src/rust/ensogl/lib/components/src/label.rs +++ b/ide/src/rust/ensogl/lib/components/src/label.rs @@ -28,15 +28,14 @@ mod background { let width = Var::::from("input_size.x"); let height = Var::::from("input_size.y"); - let padding = style.get_number_or(theme::padding, 0.0); + let padding = style.get_number_or(theme::padding_outer, 0.0); let width = width - padding.px() * 2.0; let height = height - padding.px() * 2.0; let radius = &height / 2.0; let base_shape = Rect((&width,&height)).corners_radius(&radius); let shape = base_shape.fill(Var::::from(bg_color.clone())); - - let alpha = Var::::from(format!("({0}.w)",bg_color)); - let shadow = shadow::from_shape_with_alpha(base_shape.into(),&alpha,style); + let alpha = Var::::from(format!("({0}.w)",bg_color)); + let shadow = shadow::from_shape_with_alpha(base_shape.into(),&alpha,style); (shadow+shape).into() } @@ -103,14 +102,19 @@ impl Model { } fn set_width(&self, width:f32) -> Vector2 { - let padding = self.style.get_number_or(theme::padding,0.0); - let text_size = self.style.get_number_or(theme::text::size,0.0); - let text_offset = self.style.get_number_or(theme::text::offset,0.0); - let height = self.height(); - let size = Vector2(width * 1.25,height); - let padded_size = size + Vector2(padding,padding) * 2.0; + let padding_outer = self.style.get_number_or(theme::padding_outer,0.0); + let padding_inner_x = self.style.get_number_or(theme::padding_inner_x,0.0); + let padding_inner_y = self.style.get_number_or(theme::padding_inner_y,0.0); + let padding_x = padding_outer + padding_inner_x; + let padding_y = padding_outer + padding_inner_y; + let padding = Vector2(padding_x,padding_y); + let text_size = self.style.get_number_or(theme::text::size,0.0); + let text_offset = self.style.get_number_or(theme::text::offset,0.0); + let height = self.height(); + let size = Vector2(width,height); + let padded_size = size + padding * 2.0; self.background.size.set(padded_size); - let text_origin = Vector2(padding / 2.0 + text_offset - size.x/2.0, text_size /2.0); + let text_origin = Vector2(text_offset - size.x/2.0, text_size /2.0); self.label.set_position_xy(text_origin); padded_size } @@ -149,7 +153,7 @@ pub struct Label { impl Label { /// Constructor. - pub fn new(app:Application) -> Self { + pub fn new(app:&Application) -> Self { let frp = Rc::new(Frp::new()); let model = Rc::new(Model::new(app.clone_ref())); Label {frp,model}.init() diff --git a/ide/src/rust/ensogl/lib/core/src/animation/frp/animation.rs b/ide/src/rust/ensogl/lib/core/src/animation/frp/animation.rs index 34177f5d95cd..d3d20403308c 100644 --- a/ide/src/rust/ensogl/lib/core/src/animation/frp/animation.rs +++ b/ide/src/rust/ensogl/lib/core/src/animation/frp/animation.rs @@ -1,6 +1,7 @@ //! FRP bindings to the animation engine. pub mod hysteretic; +pub mod delayed; use crate::prelude::*; diff --git a/ide/src/rust/ensogl/lib/core/src/animation/frp/animation/delayed.rs b/ide/src/rust/ensogl/lib/core/src/animation/frp/animation/delayed.rs new file mode 100644 index 000000000000..4d43a233ee3a --- /dev/null +++ b/ide/src/rust/ensogl/lib/core/src/animation/frp/animation/delayed.rs @@ -0,0 +1,84 @@ +//! Animation that has a delayed onset. +use crate::prelude::*; + +use crate::Easing; + +use enso_frp as frp; + + + +// =========== +// === Frp === +// =========== + +crate::define_endpoints! { + Input { + /// Start the onset of the animation. After the specified delay, the animation will run + /// with the specified duration. + start(), + /// Reset animation immediately to 0.0. + reset(), + /// Set the onset delay of the animation. + set_delay(f32), + /// Set the duration of the animation. + set_duration(f32), + } + Output { + /// Represents the numeric state of the animation in the range 0...1. + value(f32), + /// Triggered when the state reaches 1.0. + on_end(), + /// Triggered when the state reaches 0.0. + on_reset(), + } +} + + + +// ========================= +// === DelayedAnimation === +// ========================= + +/// Animation that has a delayed onset. +#[derive(CloneRef,Debug,Shrinkwrap)] +pub struct DelayedAnimation { + #[allow(missing_docs)] + pub frp : FrpEndpoints, +} + +impl DelayedAnimation { + #[allow(missing_docs)] + pub fn new(network:&frp::Network) -> Self { + let frp = Frp::extend(network); + let delay = Easing::new(network); + let transition = Easing::new(network); + + frp::extend! { network + // Set delay duration. + delay.set_duration <+ frp.set_delay; + transition.set_duration <+ frp.set_duration; + + // Start the delay. + delay.target <+ frp.start.constant(1.0); + + // End delay if 1.0 reached. + delay_end <- delay.value.map(|t| (t - 1.0).abs() < std::f32::EPSILON).on_true(); + + // Start animation. + transition.target <+ delay_end.constant(1.0); + + // Reset the animation and delay. + delay.stop_and_rewind <+ frp.reset.constant(0.0); + transition.stop_and_rewind <+ frp.reset.constant(0.0); + + // Output bindings. + frp.source.value <+ transition.value; + frp.source.on_reset <+ transition.value.map(|t| + (t - 0.0).abs() < std::f32::EPSILON).on_true(); + frp.source.on_end <+ transition.value.map(|t| + (t - 1.0).abs() < std::f32::EPSILON).on_true(); + } + + Self{frp} + } +} diff --git a/ide/src/rust/ensogl/lib/core/src/display/scene.rs b/ide/src/rust/ensogl/lib/core/src/display/scene.rs index 6d515e0be5f1..42aac6150568 100644 --- a/ide/src/rust/ensogl/lib/core/src/display/scene.rs +++ b/ide/src/rust/ensogl/lib/core/src/display/scene.rs @@ -484,7 +484,7 @@ impl DomLayers { canvas.set_style_or_warn("z-index" , "2" , &logger); canvas.set_style_or_warn("pointer-events", "none" , &logger); front.dom.set_class_name("front"); - front.dom.set_style_or_warn("z-index", "1", &logger); + front.dom.set_style_or_warn("z-index", "3", &logger); back.dom.set_class_name("back"); back.dom.set_style_or_warn("pointer-events", "auto", &logger); back.dom.set_style_or_warn("z-index" , "0" , &logger); diff --git a/ide/src/rust/ensogl/lib/theme/src/lib.rs b/ide/src/rust/ensogl/lib/theme/src/lib.rs index dda5d46913a6..ee50a03f8280 100644 --- a/ide/src/rust/ensogl/lib/theme/src/lib.rs +++ b/ide/src/rust/ensogl/lib/theme/src/lib.rs @@ -286,11 +286,13 @@ define_themes! { [light:0, dark:1] background = Lcha(0.98,0.014,0.18,1.0) , Lcha(0.2,0.014,0.18,1.0); text = Lcha(0.0,0.0,0.0,0.7) , Lcha(1.0,0.0,0.0,0.7); text { - offset = 10.0, 10.0; + offset = 00.0, 00.0; size = 12.0, 12.0; } - padding = 15.0, 15.0; - height = 36.0, 36.0; + padding_outer = 10.0, 10.0; + padding_inner_x = 10.0, 10.0; + padding_inner_y = 2.0, 10.0; + height = 30.0, 30.0; } } diff --git a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/error.rs b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/error.rs index 76dc4b627c85..06e2a5644246 100644 --- a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/error.rs +++ b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/error.rs @@ -75,8 +75,8 @@ pub struct Input { #[derive(Clone,CloneRef,Debug)] #[allow(missing_docs)] pub struct Error { + pub frp : visualization::instance::Frp, model : Model, - frp : visualization::instance::Frp, network : frp::Network, } @@ -103,7 +103,7 @@ impl Error { pub fn new(scene:&Scene) -> Self { let network = frp::Network::new("js_visualization_raw_text"); let frp = visualization::instance::Frp::new(&network); - let model = Model::new(scene); + let model = Model::new(scene.clone_ref()); Self {model,frp,network} . init() } @@ -113,11 +113,12 @@ impl Error { let frp = self.frp.clone_ref(); frp::extend! { network eval frp.set_size ((size) model.set_size(*size)); - eval frp.send_data ([frp](data) { + eval frp.send_data ([frp,model](data) { if let Err(e) = model.receive_data(data) { frp.data_receive_error.emit(Some(e)); } }); + eval frp.set_layer ((layer) model.set_layer(*layer)); } frp.preprocessor_change.emit(preprocessor()); @@ -152,11 +153,12 @@ pub struct Model { // when payload changes. displayed : Rc>, messages : SharedHashMap, + scene : Scene, } impl Model { /// Constructor. - fn new(scene:&Scene) -> Self { + fn new(scene:Scene) -> Self { let logger = Logger::new("RawText"); let div = web::create_div(); let dom = DomSymbol::new(&div); @@ -177,7 +179,7 @@ impl Model { dom.dom().set_style_or_warn("pointer-events","auto" ,&logger); scene.dom.layers.back.manage(&dom); - Model{dom,logger,size,styles,displayed,messages}.init() + Model{dom,logger,size,styles,displayed,messages,scene}.init() } fn init(self) -> Self { @@ -242,6 +244,10 @@ impl Model { let text_color = format!("rgba({},{},{},{})",red,green,blue,text_color.alpha); self.dom.dom().set_style_or_warn("color",text_color,&self.logger); } + + fn set_layer(&self, layer:Layer) { + layer.apply_for_html_component(&self.scene,&self.dom) + } } impl From for Instance { diff --git a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/raw_text.rs b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/raw_text.rs index 68a058986d25..f4196d457a86 100644 --- a/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/raw_text.rs +++ b/ide/src/rust/ide/view/graph-editor/src/builtin/visualization/native/raw_text.rs @@ -45,12 +45,12 @@ impl RawText { let path = Path::builtin("JSON"); Definition::new( Signature::new_for_any_type(path,Format::Json), - |scene| { Ok(Self::new(scene).into()) } + |scene| { Ok(Self::new(scene.clone_ref()).into()) } ) } /// Constructor. - pub fn new(scene:&Scene) -> Self { + pub fn new(scene:Scene) -> Self { let network = frp::Network::new("js_visualization_raw_text"); let frp = visualization::instance::Frp::new(&network); let model = RawTextModel::new(scene); @@ -63,11 +63,12 @@ impl RawText { let frp = self.frp.clone_ref(); frp::extend! { network eval frp.set_size ((size) model.set_size(*size)); - eval frp.send_data ([frp](data) { + eval frp.send_data ([frp,model](data) { if let Err(e) = model.receive_data(data) { frp.data_receive_error.emit(Some(e)); } }); + eval frp.set_layer ((layer) model.set_layer(*layer)); } self } @@ -79,11 +80,12 @@ pub struct RawTextModel { logger : Logger, dom : DomSymbol, size : Rc>, + scene : Scene, } impl RawTextModel { /// Constructor. - fn new(scene:&Scene) -> Self { + fn new(scene:Scene) -> Self { let logger = Logger::new("RawText"); let div = web::create_div(); let dom = DomSymbol::new(&div); @@ -111,7 +113,7 @@ impl RawTextModel { dom.dom().set_style_or_warn("pointer-events","auto" ,&logger); scene.dom.layers.back.manage(&dom); - RawTextModel{dom,logger,size}.init() + RawTextModel{dom,logger,size,scene}.init() } fn init(self) -> Self { @@ -141,6 +143,10 @@ impl RawTextModel { fn reload_style(&self) { self.dom.set_size(self.size.get()); } + + fn set_layer(&self, layer:Layer) { + layer.apply_for_html_component(&self.scene,&self.dom) + } } impl From for Instance { diff --git a/ide/src/rust/ide/view/graph-editor/src/component/node.rs b/ide/src/rust/ide/view/graph-editor/src/component/node.rs index 5edaf47aa7ef..1d6061371311 100644 --- a/ide/src/rust/ide/view/graph-editor/src/component/node.rs +++ b/ide/src/rust/ide/view/graph-editor/src/component/node.rs @@ -26,6 +26,7 @@ use crate::tooltip; use enso_frp as frp; use enso_frp; use ensogl::Animation; +use ensogl::animation::delayed::DelayedAnimation; use ensogl::application::Application; use ensogl::data::color; use ensogl::display::shape::*; @@ -55,6 +56,8 @@ const ERROR_VISUALIZATION_SIZE : (f32,f32) = visualization::container::DEF const VISUALIZATION_OFFSET_Y : f32 = -120.0; +const VIS_PREVIEW_ONSET_MS : f32 = 3000.0; +const ERROR_PREVIEW_ONSET_MS : f32 = 0000.0; /// A type of unresolved methods. We filter them out, because we don't want to treat them as types /// for ports and edges coloring (due to bad UX otherwise). const UNRESOLVED_SYMBOL_TYPE : &str = "Builtins.Main.Unresolved_Symbol"; @@ -257,6 +260,8 @@ ensogl::define_endpoints! { set_expression_usage_type (Crumbs,Option), set_output_expression_visibility (bool), set_vcs_status (Option), + /// Indicate whether preview visualisations should be delayed or immediate. + quick_preview_vis (bool), } Output { /// Press event. Emitted when user clicks on non-active part of the node, like its @@ -414,7 +419,7 @@ impl NodeModel { display_object.add_child(&visualization); display_object.add_child(&input); - let error_visualization = builtin_visualization::Error::new(&scene); + let error_visualization = builtin_visualization::Error::new(scene); let (x,y) = ERROR_VISUALIZATION_SIZE; error_visualization.set_size.emit(Vector2(x,y)); @@ -588,34 +593,85 @@ impl Node { out.source.skip <+ action_bar.action_skip; out.source.freeze <+ action_bar.action_freeze; eval out.hover ((t) action_bar.set_visibility(t)); + } - // === Errors on Node === + // === Visualizations & Errors === - is_error_set <- frp.error.map(|err| err.is_some()); + let hover_onset_delay = DelayedAnimation::new(network); + hover_onset_delay.set_delay(VIS_PREVIEW_ONSET_MS); + hover_onset_delay.set_duration(0.0); - frp.source.error <+ frp.set_error.map(f!([model](error) { - model.set_error(error.as_ref()); - error.clone() - })); + frp::extend! { network + frp.source.error <+ frp.set_error; + is_error_set <- frp.error.map(|err| err.is_some()); + no_error_set <- not(&is_error_set); error_color_anim.target <+ frp.error.map(f!([style](error) Self::error_color(error,&style)) ); - eval error_color_anim.value ((value) model.set_error_color(value)); - - - // === Visualization === - eval frp.set_visualization ((t) model.visualization.frp.set_visualization.emit(t)); visualization_enabled_frp <- bool(&frp.disable_visualization,&frp.enable_visualization); - eval visualization_enabled_frp ((enabled) model.action_bar.set_action_visibility_state(enabled)); - no_error_set <- not(&is_error_set); + eval visualization_enabled_frp ((enabled) + model.action_bar.set_action_visibility_state(enabled) + ); + + // 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 + // expression. + has_tooltip <- model.output.frp.tooltip.map(|tt| tt.has_content()); + has_expression <- frp.set_expression.map(|expr| *expr != Expression::default()); + + preview_show_delay <- all(&frp.quick_preview_vis,&is_error_set); + preview_show_delay <- preview_show_delay.map(|(quick_preview,is_error)| { + match(is_error,quick_preview) { + (true,_) => ERROR_PREVIEW_ONSET_MS, + (false,false) => VIS_PREVIEW_ONSET_MS, + (false,true) => 0.0 + } + }); + hover_onset_delay.set_delay <+ preview_show_delay; + hide_tooltip <- preview_show_delay.map(|&delay| delay <= EPSILON); + + outout_hover <- model.output.on_port_hover.map(|s| s.is_on()); + hover_onset_delay.start <+ outout_hover.on_true(); + hover_onset_delay.reset <+ outout_hover.on_false(); + preview_visible <- bool(&hover_onset_delay.on_reset,&hover_onset_delay.on_end); + preview_visible <- preview_visible && has_expression; + preview_visible <- preview_visible.on_change(); + visualization_visible <- visualization_enabled && no_error_set; - frp.source.visualization_enabled <+ visualization_enabled; - eval visualization_visible ((is_visible) model.visualization.frp.set_visibility(is_visible)); + visualization_visible <- visualization_visible || preview_visible; + visualization_visible <- visualization_visible.on_change(); + frp.source.visualization_enabled <+ visualization_enabled || preview_visible; + eval visualization_visible ((is_visible) + model.visualization.frp.set_visibility(is_visible) + ); + // Ensure the preview is visible above all other elements, but the normal visualisation + // is below nodes. + layer_on_hover <- preview_visible.on_false().map(|_| visualization::Layer::Default); + layer_on_not_hover <- preview_visible.on_true().map(|_| visualization::Layer::Front); + layer <- any(layer_on_hover,layer_on_not_hover); + model.visualization.frp.set_layer <+ layer; + eval layer ((l) model.error_visualization.frp.set_layer.emit(l)); + + + update_error <- all(frp.set_error,visualization_visible); + eval update_error([model]((error,visible)){ + if *visible { + model.set_error(error.as_ref()); + } else { + model.set_error(None); + } + }); + + eval error_color_anim.value ((value) model.set_error_color(value)); + + } + + frp::extend! { network // === Color Handling === @@ -638,7 +694,15 @@ impl Node { model.background.bg_color.set(color::Rgba::from(c).into()) ); - frp.source.tooltip <+ model.output.frp.tooltip; + + // === Tooltip === + + // Hide tooltip if we show the preview vis. + frp.source.tooltip <+ preview_visible.on_true().constant(tooltip::Style::unset_label()); + // Propagate output tooltip. Only if it is not hidden, or to disable it. + block_tooltip <- hide_tooltip && has_tooltip; + frp.source.tooltip <+ model.output.frp.tooltip.gate_not(&block_tooltip); + // === VCS Handling === model.vcs_indicator.frp.set_status <+ frp.set_vcs_status; diff --git a/ide/src/rust/ide/view/graph-editor/src/component/node/output/port.rs b/ide/src/rust/ide/view/graph-editor/src/component/node/output/port.rs index 215930f59ad1..c45d175b199b 100644 --- a/ide/src/rust/ide/view/graph-editor/src/component/node/output/port.rs +++ b/ide/src/rust/ide/view/graph-editor/src/component/node/output/port.rs @@ -2,6 +2,7 @@ use crate::prelude::*; use crate::tooltip; +use crate::tooltip::Placement; use crate::Type; use crate::component::node; use crate::component::type_coloring; @@ -34,6 +35,8 @@ const SEGMENT_GAP_WIDTH : f32 = 2.0; const HOVER_AREA_PADDING : f32 = 20.0; const INFINITE : f32 = 99999.0; +const TOOLTIP_LOCATION : Placement = Placement::Bottom; + // ===================== @@ -449,7 +452,7 @@ impl Model { on_hover <- frp.on_hover.on_true(); non_hover <- frp.on_hover.on_false(); frp.source.tooltip <+ frp.tp.sample(&on_hover).unwrap().map(|tp| { - tooltip::Style::set_label(tp.to_string()) + tooltip::Style::set_label(tp.to_string()).with_placement(TOOLTIP_LOCATION) }); frp.source.tooltip <+ non_hover.constant(tooltip::Style::unset_label()); } diff --git a/ide/src/rust/ide/view/graph-editor/src/component/tooltip.rs b/ide/src/rust/ide/view/graph-editor/src/component/tooltip.rs index a744a7759efb..33f4365979d6 100644 --- a/ide/src/rust/ide/view/graph-editor/src/component/tooltip.rs +++ b/ide/src/rust/ide/view/graph-editor/src/component/tooltip.rs @@ -14,30 +14,37 @@ use ensogl::display::shape::StyleWatch; +// ================= +// === Constants === +// ================= + +const PLACEMENT_OFFSET:f32 = 5.0; + + + // ============= // === Style === // ============= define_style! { - /// Host defines an object which the cursor position is bound to. It is used to implement - /// label selection. After setting the host to the label, cursor will not follow mouse anymore, - /// it will inherit its position from the label instead. - text : Option, + text : Option, + placement : Placement } impl Style { /// Create a `TooltipUpdate` that sets the label of the tooltip. pub fn set_label(text:String) -> Self { let text = Some(StyleValue::new(Some(text))); - Self{text} + Self{text, ..default()} } /// Create a `TooltipUpdate` that unsets the label of the tooltip. pub fn unset_label() -> Self { let text = Some(StyleValue::new(None)); - Self{text} + Self{text, ..default()} } - fn has_text(&self) -> bool { + /// Indicate whether the `Style` has content to display. + pub fn has_content(&self) -> bool { if let Some(style_value) = self.text.as_ref() { if let Some(inner) = style_value.value.as_ref() { return inner.is_some() @@ -45,6 +52,18 @@ impl Style { } false } + + /// Create a `TooltipUpdate` that sets the placement of the tooltip. + pub fn set_placement(placement:Placement) -> Self { + let placement = Some(StyleValue::new(placement)); + Self{placement, ..default()} + } + + /// Sets the placement of the tooltip. + pub fn with_placement(mut self, placement:Placement) -> Self { + self.placement = Some(StyleValue::new(placement)); + self + } } @@ -53,7 +72,7 @@ impl Style { // === Offset === // ============== -#[derive(Clone,Copy,Debug)] +#[derive(Clone,Copy,Debug,Eq,PartialEq)] #[allow(missing_docs)] /// Indicates the placement of the tooltip relative to the base position location. pub enum Placement { @@ -83,25 +102,27 @@ ensogl::define_endpoints! { #[derive(Clone,Debug)] struct Model { - tooltip : Label, - root : display::object::Instance, + tooltip : Label, + root : display::object::Instance, + placement : Cell, } impl Model { fn new(app:&Application) -> Self { let logger = Logger::new("TooltipModel"); - let tooltip = Label::new(app.clone_ref()); + let tooltip = Label::new(app); let root = display::object::Instance::new(&logger); root.add_child(&tooltip); - Self{tooltip,root} + let placement = default(); + Self{tooltip,root,placement} } - fn set_location(&self, position:Vector2, size:Vector2, layout: Placement) { - let layout_offset = match layout { - Placement::Top => Vector2::new(0.0, size.y * 0.5), - Placement::Bottom => Vector2::new(0.0, -size.y * 0.5), - Placement::Left => Vector2::new(-size.x / 2.0, 0.0), - Placement::Right => Vector2::new(size.x / 2.0, 0.0), + fn set_location(&self, position:Vector2, size:Vector2) { + let layout_offset = match self.placement.get() { + Placement::Top => Vector2::new(0.0, size.y * 0.5 + PLACEMENT_OFFSET), + Placement::Bottom => Vector2::new(0.0, -size.y * 0.5 - PLACEMENT_OFFSET), + Placement::Left => Vector2::new(-size.x / 2.0 - PLACEMENT_OFFSET, 0.0), + Placement::Right => Vector2::new(size.x / 2.0 + PLACEMENT_OFFSET, 0.0), }; let base_positions = position.xy(); @@ -115,6 +136,11 @@ impl Model { self.tooltip.frp.set_content(text) } } + if let Some(style) = update.placement.as_ref() { + if let Some(placement) = style.value { + self.placement.set(placement) + } + } } fn set_visibility(&self, visible:bool) { @@ -174,7 +200,7 @@ impl Tooltip { // === Style === eval frp.set_style ((t) model.set_style(t)); - show_text <- frp.set_style.map(|c| c.has_text()); + show_text <- frp.set_style.map(|c| c.has_content()); on_has_content <- show_text.on_true(); on_has_no_content <- show_text.on_false(); @@ -183,11 +209,10 @@ impl Tooltip { location_update <- all(frp.set_location, model.tooltip.frp.size, - frp.set_offset, - frp.set_placement); - eval location_update ([model]((pos,size,offset,placement)) { + frp.set_offset); + eval location_update ([model]((pos,size,offset)) { let base_position = pos+offset; - model.set_location(base_position,*size,*placement) + model.set_location(base_position,*size) }); diff --git a/ide/src/rust/ide/view/graph-editor/src/component/visualization.rs b/ide/src/rust/ide/view/graph-editor/src/component/visualization.rs index 0fd233cebd68..7ead7b8ef070 100644 --- a/ide/src/rust/ide/view/graph-editor/src/component/visualization.rs +++ b/ide/src/rust/ide/view/graph-editor/src/component/visualization.rs @@ -28,6 +28,7 @@ pub mod data; pub mod definition; pub mod foreign; pub mod instance; +pub mod layer; pub mod metadata; pub mod path; pub mod registry; @@ -37,6 +38,7 @@ pub use data::*; pub use definition::*; pub use foreign::*; pub use instance::Instance; +pub use layer::*; pub use metadata::*; pub use path::*; -pub use registry::*; +pub use registry::*; \ No newline at end of file diff --git a/ide/src/rust/ide/view/graph-editor/src/component/visualization/container.rs b/ide/src/rust/ide/view/graph-editor/src/component/visualization/container.rs index 7e978f9e1593..6206f553a123 100644 --- a/ide/src/rust/ide/view/graph-editor/src/component/visualization/container.rs +++ b/ide/src/rust/ide/view/graph-editor/src/component/visualization/container.rs @@ -88,6 +88,7 @@ ensogl::define_endpoints! { enable_fullscreen (), disable_fullscreen (), set_vis_input_type (Option), + set_layer (visualization::Layer), } Output { @@ -117,12 +118,13 @@ pub struct View { // This should be further investigated while fixing rust visualization displaying. (#526) // background : background::View, overlay : overlay::View, - background_dom : DomSymbol + background_dom : DomSymbol, + scene : Scene, } impl View { /// Constructor. - pub fn new(logger:&Logger, scene:&Scene) -> Self { + pub fn new(logger:&Logger, scene:Scene) -> Self { let logger = Logger::sub(logger,"view"); let display_object = display::object::Instance::new(&logger); let overlay = overlay::View::new(&logger); @@ -149,13 +151,16 @@ impl View { shadow::add_to_dom_element(&background_dom,&styles,&logger); display_object.add_child(&background_dom); - scene.dom.layers.back.manage(&background_dom); + Self {logger,display_object,overlay,background_dom,scene}.init() + } - Self {logger,display_object,overlay,background_dom} . init(scene) + fn set_layer(&self, layer:visualization::Layer) { + layer.apply_for_html_component(&self.scene, &self.background_dom); } - fn init(self, scene:&Scene) -> Self { - scene.layers.viz.add_exclusive(&self); + fn init(self) -> Self { + self.set_layer(visualization::Layer::Default); + self.scene.layers.viz.add_exclusive(&self); self } } @@ -206,7 +211,7 @@ impl ContainerModel { let drag_root = display::object::Instance::new(&logger); let visualization = default(); let vis_frp_connection = default(); - let view = View::new(&logger,scene); + let view = View::new(&logger,scene.clone_ref()); let fullscreen_view = fullscreen::Panel::new(&logger,scene); let scene = scene.clone_ref(); let is_fullscreen = default(); @@ -433,6 +438,12 @@ impl Container { 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); + }); } diff --git a/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs b/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs index 48edf95a171a..e19b78e87cd7 100644 --- a/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs +++ b/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs @@ -96,6 +96,7 @@ pub struct InstanceModel { object : Rc, #[derivative(Debug="ignore")] preprocessor_change : PreprocessorCallbackCell, + scene : Scene, } impl InstanceModel { @@ -167,7 +168,9 @@ impl InstanceModel { let set_size = get_method(&object.as_ref(),method::SET_SIZE).ok(); let set_size = Rc::new(set_size); let object = Rc::new(object); - Ok(InstanceModel{object,on_data_received,set_size,root_node,logger,preprocessor_change}) + let scene = scene.clone_ref(); + Ok(InstanceModel{object,on_data_received,set_size,root_node,logger,preprocessor_change, + scene}) } /// Hooks the root node into the given scene. @@ -214,6 +217,10 @@ impl InstanceModel { } Ok(()) } + + fn set_layer(&self, layer:Layer) { + layer.apply_for_html_component(&self.scene,&self.root_node) + } } @@ -248,11 +255,12 @@ impl Instance { let frp = self.frp.clone_ref(); frp::extend! { network eval frp.set_size ((size) model.set_size(*size)); - eval frp.send_data ([frp](data) { + eval frp.send_data ([frp,model](data) { if let Err(e) = model.receive_data(data) { frp.data_receive_error.emit(Some(e)); } }); + eval frp.set_layer ((layer) model.set_layer(*layer)); } frp.pass_events_to_dom_if_active(scene,network); self diff --git a/ide/src/rust/ide/view/graph-editor/src/component/visualization/instance.rs b/ide/src/rust/ide/view/graph-editor/src/component/visualization/instance.rs index b660a8eb3a6c..fb5ce8f93c07 100644 --- a/ide/src/rust/ide/view/graph-editor/src/component/visualization/instance.rs +++ b/ide/src/rust/ide/view/graph-editor/src/component/visualization/instance.rs @@ -118,6 +118,7 @@ pub struct FrpInputs { pub send_data : frp::Source, pub activate : frp::Source, pub deactivate : frp::Source, + pub set_layer : frp::Source } /// Visualization FRP network. @@ -149,8 +150,9 @@ impl FrpInputs { send_data <- source(); activate <- source(); deactivate <- source(); + set_layer <- source(); }; - Self {set_size,send_data,activate,deactivate} + Self {set_size,send_data,activate,deactivate,set_layer} } } diff --git a/ide/src/rust/ide/view/graph-editor/src/component/visualization/layer.rs b/ide/src/rust/ide/view/graph-editor/src/component/visualization/layer.rs new file mode 100644 index 000000000000..e7b38daa5d68 --- /dev/null +++ b/ide/src/rust/ide/view/graph-editor/src/component/visualization/layer.rs @@ -0,0 +1,34 @@ +//! Functionality that allows one to manage occlusion/layers of visualisations in the scene. +use ensogl::display::DomSymbol; +use ensogl::display::Scene; + + + +// ============= +// === Layer === +// ============= + +/// Indicates where the visualisation should be displayed. +#[derive(Clone,Copy,Debug,Eq,PartialEq)] +pub enum Layer { + /// Display the visualisation as part of the scene. + Default, + /// Display the visualisation over the scene. + Front +} + +impl Layer { + /// Apply the layer setting to the given `DomSymbol`. + pub fn apply_for_html_component(self, scene:&Scene, dom:&DomSymbol){ + match self { + Layer::Default => scene.dom.layers.back.manage(dom), + Layer::Front => scene.dom.layers.front.manage(dom), + } + } +} + +impl Default for Layer { + fn default() -> Self{ + Layer::Default + } +} diff --git a/ide/src/rust/ide/view/graph-editor/src/lib.rs b/ide/src/rust/ide/view/graph-editor/src/lib.rs index fb2bf4d04e96..d1cab5d5b2a7 100644 --- a/ide/src/rust/ide/view/graph-editor/src/lib.rs +++ b/ide/src/rust/ide/view/graph-editor/src/lib.rs @@ -35,7 +35,6 @@ pub mod builtin; pub mod data; use crate::component::node; -use crate::component::tooltip::Placement; use crate::component::tooltip::Tooltip; use crate::component::visualization::instance::PreprocessorConfiguration; use crate::component::tooltip; @@ -457,6 +456,10 @@ ensogl::define_endpoints! { reset_visualization_registry (), /// Reload visualization registry reload_visualization_registry(), + /// Show visualisation previews on nodes without delay. + enable_quick_visualization_preview(), + /// Show visualisation previews on nodes with delay. + disable_quick_visualization_preview(), } Output { @@ -866,6 +869,12 @@ impl Nodes { pub fn check_grid_magnet(&self, position:Vector2) -> Vector2> { self.grid.borrow().close_to(position,SNAP_DISTANCE_THRESHOLD) } + + pub fn set_quick_preview(&self, quick:bool) { + self.all.raw.borrow().values().for_each(|node|{ + node.view.frp.quick_preview_vis.emit(quick) + }) + } } @@ -1005,6 +1014,16 @@ impl Deref for GraphEditorModelWithNetwork { } } +/// Context data required to create a new node. +#[derive(Debug)] +struct NodeCreationContext<'a> { + pointer_style : &'a frp::Source, + tooltip_update : &'a frp::Source, + output_press : &'a frp::Source, + input_press : &'a frp::Source, + output : &'a FrpEndpoints, +} + impl GraphEditorModelWithNetwork { pub fn new(app:&Application, cursor:cursor::Cursor, frp:&Frp) -> Self { let network = frp.network.clone_ref(); // FIXME make weak @@ -1012,15 +1031,7 @@ impl GraphEditorModelWithNetwork { Self {model,network} } - #[allow(clippy::too_many_arguments)] - fn new_node - ( &self - , pointer_style : &frp::Source - , tooltip_update : &frp::Source - , output_press : &frp::Source - , input_press : &frp::Source - , output : &FrpEndpoints - ) -> NodeId { + fn new_node(&self, ctx:&NodeCreationContext) -> NodeId { let view = component::Node::new(&self.app,self.vis_registry.clone_ref()); let node = Node::new(view); let node_id = node.id(); @@ -1029,6 +1040,8 @@ impl GraphEditorModelWithNetwork { let touch = &self.touch_state; let model = &self.model; + let NodeCreationContext {pointer_style,tooltip_update,output_press,input_press,output} = ctx; + frp::new_bridge_network! { [self.network, node.frp.network] graph_node_bridge eval_ node.frp.background_press(touch.nodes.down.emit(node_id)); @@ -1263,8 +1276,6 @@ impl GraphEditorModel { else { MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET }; self.breadcrumbs.set_position_x(x_offset); self.breadcrumbs.set_position_y(-5.0); - - self.tooltip.frp.set_placement(Placement::Bottom); self.scene().add_child(&self.tooltip); self } @@ -1887,8 +1898,11 @@ impl application::View for GraphEditor { , (Release , "!node_editing" , "space" , "release_visualization_visibility" ) , (Press , "" , "cmd i" , "reload_visualization_registry" ) , (Press , "is_fs_visualization_displayed" , "space" , "close_fullscreen_visualization" ) + , (Press , "" , "cmd" , "enable_quick_visualization_preview") + , (Release , "" , "cmd" , "disable_quick_visualization_preview") + - // === Selection === + // === Selection === , (Press , "" , "shift" , "enable_node_multi_select") , (Press , "" , "shift left-mouse-button" , "enable_node_multi_select") , (Release , "" , "shift" , "disable_node_multi_select") @@ -2386,9 +2400,14 @@ fn new_graph_editor(app:&Application) -> GraphEditor { let add_node_at_cursor = inputs.add_node_at_cursor.clone_ref(); add_node <- any (inputs.add_node,add_node_at_cursor); new_node <- add_node.map(f_!([model,node_pointer_style,node_tooltip,out] { - model.new_node(&node_pointer_style, - &node_tooltip, - &node_output_touch.down,&node_input_touch.down,&out) + let ctx = NodeCreationContext { + pointer_style : &node_pointer_style, + tooltip_update : &node_tooltip, + output_press : &node_output_touch.down, + input_press : &node_input_touch.down, + output : &out, + }; + model.new_node(&ctx) })); out.source.node_added <+ new_node; @@ -3004,6 +3023,10 @@ fn new_graph_editor(app:&Application) -> GraphEditor { frp::extend! { network eval cursor.frp.scene_position ((pos) model.tooltip.frp.set_location(pos.xy()) ); eval node_tooltip ((tooltip_update) model.tooltip.frp.set_style(tooltip_update) ); + + quick_visualization_preview <- bool(&frp.disable_quick_visualization_preview, + &frp.enable_quick_visualization_preview); + eval quick_visualization_preview((value) model.nodes.set_quick_preview(*value)); } GraphEditor {model,frp} diff --git a/ide/src/rust/lib/frp/src/nodes.rs b/ide/src/rust/lib/frp/src/nodes.rs index 556848997d62..78c2857d159a 100644 --- a/ide/src/rust/lib/frp/src/nodes.rs +++ b/ide/src/rust/lib/frp/src/nodes.rs @@ -128,6 +128,15 @@ impl Network { self.register(OwnedGet2::new(label,event)) } + /// Only if the input event has changed, emit the input event. This will hide multiple + /// consecutive events with the same value. + pub fn on_change(&self, label:Label, t:&T) -> Stream + where T:EventOutput, V:Data+PartialEq { + let prev = self.previous(label,t); + let changed = self.map2(label,t,&prev,|t1,t2| t1!=t2); + self.gate(label,t,&changed) + } + // === Bool Utils ===