diff --git a/CHANGELOG.md b/CHANGELOG.md
index a9766a5428..d9751dccf7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,8 @@
unnecessary library imports when selecting hints from node searcher. This
makes the generated textual code easier to read and reduces likelihood of
accidental name collision.
+- [Hovering over an output port shows a pop-up with the result type of a node]
+ [1312].
#### EnsoGL (rendering engine)
@@ -165,6 +167,7 @@ you can find their release notes
https://www.youtube.com/watch?v=BYUAL4ksEgY&ab_channel=Enso
[podcast-future-of-enso]:
https://www.youtube.com/watch?v=rF8DuJPOfTs&t=1863s&ab_channel=Enso
+[1312]: https://github.com/enso-org/ide/pull/1312
diff --git a/src/rust/ensogl/lib/components/src/lib.rs b/src/rust/ensogl/lib/components/src/lib.rs
index ddca4ec4d4..4c572d82e2 100644
--- a/src/rust/ensogl/lib/components/src/lib.rs
+++ b/src/rust/ensogl/lib/components/src/lib.rs
@@ -18,6 +18,7 @@
pub mod drop_down_menu;
pub mod list_view;
+pub mod text_label;
pub mod toggle_button;
/// Commonly used types and functions.
diff --git a/src/rust/ensogl/lib/components/src/text_label.rs b/src/rust/ensogl/lib/components/src/text_label.rs
new file mode 100644
index 0000000000..57b508e15f
--- /dev/null
+++ b/src/rust/ensogl/lib/components/src/text_label.rs
@@ -0,0 +1,198 @@
+//! Text label component. Appears as text with background.
+
+use crate::prelude::*;
+
+use enso_frp as frp;
+use enso_frp;
+use ensogl_core::application::Application;
+use ensogl_core::data::color;
+use ensogl_core::display::shape::*;
+use ensogl_core::display::traits::*;
+use ensogl_core::display;
+use ensogl_text as text;
+
+
+// ==========================
+// === Shapes Definitions ===
+// ==========================
+
+
+// === Constants ===
+
+const TEXT_OFFSET : f32 = 10.0;
+const TEXT_SIZE : f32 = 12.0;
+const HEIGHT : f32 = TEXT_SIZE * 3.0;
+const PADDING : f32 = 15.0;
+const SHADOW_SIZE : f32 = 10.0;
+
+
+// === Background ===
+
+mod background {
+ use super::*;
+
+ ensogl_core::define_shape_system! {
+ (style:Style,bg_color:Vector4) {
+ use ensogl_theme::graph_editor::node as node_theme;
+
+ let width = Var::::from("input_size.x");
+ let height = Var::::from("input_size.y");
+ let width = width - PADDING.px() * 2.0;
+ let height = height - PADDING.px() * 2.0;
+ let radius = &height / 2.0;
+ let shape = Rect((&width,&height)).corners_radius(&radius);
+ let shape = shape.fill(Var::::from(bg_color.clone()));
+
+
+ // === Shadow ===
+ let alpha = Var::::from(format!("({0}.w)",bg_color));
+ let border_size_f = 16.0;
+ let shadow_size = SHADOW_SIZE.px();
+ let shadow_width = &width + &shadow_size * 2.0;
+ let shadow_height = &height + &shadow_size * 2.0;
+ let shadow_radius = &shadow_height / 2.0;
+ let shadow = Rect((shadow_width,shadow_height)).corners_radius(shadow_radius);
+ let base_color = color::Rgba::from(style.get_color(node_theme::shadow));
+ let base_color = Var::::from(base_color);
+ let base_color = base_color.multiply_alpha(&alpha);
+ let fading_color = color::Rgba::from(style.get_color(node_theme::shadow::fading));
+ let fading_color = Var::::from(fading_color);
+ let fading_color = fading_color.multiply_alpha(&alpha);
+ let exponent = style.get_number_or(node_theme::shadow::exponent,2.0);
+ let shadow_color = color::LinearGradient::new()
+ .add(0.0,fading_color.into_linear())
+ .add(1.0,base_color.into_linear());
+ let shadow_color = color::SdfSampler::new(shadow_color)
+ .max_distance(border_size_f)
+ .slope(color::Slope::Exponent(exponent));
+ let shadow = shadow.fill(shadow_color);
+
+ (shadow+shape).into()
+ }
+ }
+}
+
+
+
+// ===========
+// === FRP ===
+// ===========
+
+ensogl_core::define_endpoints! {
+ Input {
+ set_content(String),
+ set_opacity(f32)
+ }
+ Output {
+ size (Vector2)
+ }
+}
+
+
+
+// =============
+// === Model ===
+// =============
+
+#[derive(Clone,Debug)]
+struct Model {
+ background : background::View,
+ label : text::Area,
+ display_object : display::object::Instance,
+ app : Application
+}
+
+impl Model {
+ fn new(app: Application) -> Self {
+ let app = app.clone_ref();
+ let scene = app.display.scene();
+ let logger = Logger::new("TextLabel");
+ let display_object = display::object::Instance::new(&logger);
+ let label = app.new_view::();
+ let background = background::View::new(&logger);
+ display_object.add_child(&background);
+ display_object.add_child(&label);
+
+ // Depth sorting of labels to in front of the background.
+ label.remove_from_scene_layer_DEPRECATED(&scene.layers.main);
+ label.add_to_scene_layer_DEPRECATED(&scene.layers.label);
+
+ Model { label, display_object, background, app }
+ }
+
+ pub fn height(&self) -> f32 {
+ HEIGHT
+ }
+
+ fn set_width(&self, width:f32) -> Vector2 {
+ let height = self.height();
+ let size = Vector2(width*1.25,height);
+ let padded_size = size + Vector2(PADDING,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);
+ self.label.set_position_xy(text_origin);
+ padded_size
+ }
+
+ fn set_content(&self, t:&str) -> Vector2 {
+ self.label.set_content(t);
+ self.set_width(self.label.width.value())
+ }
+
+ fn set_opacity(&self, value:f32) {
+ let style = StyleWatch::new(&self.app.display.scene().style_sheet);
+ let text_color_path = ensogl_theme::graph_editor::node::text;
+ let text_color = style.get_color_dim(text_color_path).multiply_alpha(value);
+ let text_color = color::Rgba::from(text_color);
+ self.label.frp.set_color_all.emit(text_color);
+ self.label.frp.set_default_color.emit(text_color);
+
+ let bg_color_path = ensogl_theme::graph_editor::node::background;
+ let bg_color = style.get_color_dim(bg_color_path).multiply_alpha(value);
+ let bg_color = color::Rgba::from(bg_color);
+ self.background.bg_color.set(bg_color.into())
+ }
+}
+
+
+
+// ============================
+// === Text Label Component ===
+// ============================
+
+#[allow(missing_docs)]
+#[derive(Clone,CloneRef,Debug)]
+pub struct TextLabel {
+ model : Rc,
+ pub frp : Rc,
+}
+
+
+impl TextLabel {
+ /// Constructor.
+ pub fn new(app:Application) -> Self {
+ let frp = Rc::new(Frp::new());
+ let model = Rc::new(Model::new(app.clone_ref()));
+ TextLabel {frp,model}.init()
+ }
+
+ fn init(self) -> Self {
+ let frp = &self.frp;
+ let network = &frp.network;
+ let model = &self.model;
+
+ frp::extend! { network
+ frp.source.size <+ frp.set_content.map(f!((t)
+ model.set_content(t)
+ ));
+
+ eval frp.set_opacity((value) model.set_opacity(*value));
+ }
+
+ self
+ }
+}
+
+impl display::Object for TextLabel {
+ fn display_object(&self) -> &display::object::Instance { &self.model.display_object }
+}
diff --git a/src/rust/ensogl/lib/core/src/animation/frp/animation.rs b/src/rust/ensogl/lib/core/src/animation/frp/animation.rs
index c6afec2393..34177f5d95 100644
--- a/src/rust/ensogl/lib/core/src/animation/frp/animation.rs
+++ b/src/rust/ensogl/lib/core/src/animation/frp/animation.rs
@@ -1,5 +1,7 @@
//! FRP bindings to the animation engine.
+pub mod hysteretic;
+
use crate::prelude::*;
use crate::animation::physics::inertia;
diff --git a/src/rust/ensogl/lib/core/src/animation/frp/animation/hysteretic.rs b/src/rust/ensogl/lib/core/src/animation/frp/animation/hysteretic.rs
new file mode 100644
index 0000000000..5a9298f600
--- /dev/null
+++ b/src/rust/ensogl/lib/core/src/animation/frp/animation/hysteretic.rs
@@ -0,0 +1,116 @@
+//! Animation that has a delayed onset.\
+
+use crate::prelude::*;
+
+use crate::Animation;
+use crate::Easing;
+
+use enso_frp as frp;
+
+
+
+// ===========
+// === Frp ===
+// ===========
+
+crate::define_endpoints! {
+ Input {
+ /// Trigger start of animation towards start state (0.0). Will be delayed by onset time.
+ to_start(),
+ /// Trigger start of animation towards end state (1.0). Will be delayed by onset time.
+ to_end(),
+ }
+ 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_start(),
+ }
+}
+
+
+
+// ===========================
+// === HystereticAnimation ===
+// ===========================
+
+/// Animation that has a delayed onset.
+///
+/// The basic idea behind this animation is, that it changes between two states: of (0.0) and on
+/// (1.0), but only after a delay (`st`/`et` start delay, end delay). If the animation gets
+/// canceled before the delay has passed, nothing happens, and the delay resets.
+///
+///
+/// This can be used to hide state changes that are only happening for a short duration. For example
+/// consider a UI element that is shown when hovering ofer an icon. When only moving the mouse
+/// cursor past the icon we want to avoid the pop-up appearing and disappearing right away. The
+/// `HystereticAnimation` allows this by setting a `start_delay_duration` long enough to avoid
+/// triggering during the time it takes to move past the icon. Thus, when the cursor moves past the
+/// icon nothing is visible to the user, but if the mosque cursor stays on the icon longer than
+/// `start_delay_duration`, the animation starts, and the pop-up becomes visible. In reverse, when
+/// moving the cursor between multiple icons, the pop-up should not permanently start and disappear
+/// and re-appear. Thus setting a `end_delay_duration` will avoid the pop-up from disappearing, if
+/// the time the cursor is between icons is less than the `end_delay_duration`. Instead, the hiding
+/// will only start iof the cursos has left any icon triggering the pop-up for longer than the
+/// `end_delay_duration`.
+///
+#[derive(CloneRef,Debug,Shrinkwrap)]
+pub struct HystereticAnimation {
+ /// Public FRP Api.
+ pub frp : FrpEndpoints,
+}
+
+impl HystereticAnimation {
+ /// Constructor.
+ pub fn new(network:&frp::Network, start_delay_duration:f32, end_delay_duration:f32) -> Self {
+ let frp = Frp::extend(network);
+
+ let start_delay = Easing::new(network);
+ let end_delay = Easing::new(network);
+ start_delay.set_duration(start_delay_duration);
+ end_delay.set_duration(end_delay_duration);
+
+ let transition = Animation::::new(network);
+
+ frp::extend! { network
+
+ during_transition <- any_mut();
+
+ on_end <- frp.to_end.constant(());
+ on_end_while_active <- on_end.gate(&during_transition);
+ on_end_while_inactive <- on_end.gate_not(&during_transition);
+
+ on_start <- frp.to_start.constant(());
+ on_start_while_active <- on_start.gate(&during_transition);
+ on_start_while_inactive <- on_start.gate_not(&during_transition);
+
+ start_delay.target <+ on_start_while_inactive.constant(1.0);
+ end_delay.target <+ on_end_while_inactive.constant(1.0);
+
+ start_delay.stop_and_rewind <+ on_end.constant(0.0);
+ end_delay.stop_and_rewind <+ on_start.constant(0.0);
+
+ offset_start <- end_delay.on_end.map(|t| t.is_normal()).on_true();
+ onset_start <- start_delay.on_end.map(|t| t.is_normal()).on_true();
+
+ onset_end <- transition.value.map(|t| (t - 1.0).abs() < std::f32::EPSILON).on_true();
+ offset_end <- transition.value.map(|t| t.abs() < std::f32::EPSILON).on_true();
+
+ transition.target <+ onset_start.constant(1.0);
+ transition.target <+ offset_start.constant(0.0);
+ transition.target <+ on_end_while_active.constant(0.0);
+ transition.target <+ on_start_while_active.constant(1.0);
+
+ during_transition <+ bool(&onset_end,&onset_start);
+ during_transition <+ bool(&offset_end,&offset_start);
+
+ frp.source.value <+ transition.value;
+ frp.source.on_end <+ onset_end;
+ frp.source.on_start <+ offset_end;
+ }
+
+ HystereticAnimation{frp}
+ }
+}
diff --git a/src/rust/ensogl/lib/core/src/data/color/data.rs b/src/rust/ensogl/lib/core/src/data/color/data.rs
index 766f78688f..3fd20c967e 100644
--- a/src/rust/ensogl/lib/core/src/data/color/data.rs
+++ b/src/rust/ensogl/lib/core/src/data/color/data.rs
@@ -291,3 +291,11 @@ impl Default for Alpha {
Self {alpha,opaque}
}
}
+
+impl Alpha {
+ /// Return the color with a multiplied alpha channel.
+ pub fn multiply_alpha(self, alpha:f32) -> Color {
+ let alpha = self.alpha * alpha;
+ Color(Alpha{alpha, opaque: self.opaque })
+ }
+}
diff --git a/src/rust/ensogl/lib/core/src/data/color/gradient.rs b/src/rust/ensogl/lib/core/src/data/color/gradient.rs
index 28237f0bc1..a688e76ec2 100644
--- a/src/rust/ensogl/lib/core/src/data/color/gradient.rs
+++ b/src/rust/ensogl/lib/core/src/data/color/gradient.rs
@@ -56,7 +56,7 @@ impl LinearGradient {
}
impls! { [Color] From<&LinearGradient> for Glsl
-where [Color:Copy + RefInto] {
+where [Color:RefInto] {
|t| {
let args = t.control_points.iter().map(|control_point| {
let offset = control_point.offset.glsl();
@@ -82,7 +82,7 @@ pub const DEFAULT_DISTANCE_GRADIENT_MAX_DISTANCE : f32 = 10.0;
/// A gradient which transforms a linear gradient to a gradient along the signed distance field.
/// The slope parameter modifies how fast the gradient values are changed, allowing for nice,
/// smooth transitions.
-#[derive(Copy,Clone,Debug)]
+#[derive(Clone,Debug)]
pub struct SdfSampler {
/// The distance from the shape border at which the gradient should start.
pub min_distance : f32,
diff --git a/src/rust/ensogl/lib/core/src/display/shape/primitive/def/var.rs b/src/rust/ensogl/lib/core/src/display/shape/primitive/def/var.rs
index ac370a5179..cd5543127b 100644
--- a/src/rust/ensogl/lib/core/src/display/shape/primitive/def/var.rs
+++ b/src/rust/ensogl/lib/core/src/display/shape/primitive/def/var.rs
@@ -640,3 +640,38 @@ impl From>> for Var {
}
}
}
+
+impl Var {
+ /// Return the color with the given alpha value.
+ pub fn with_alpha(self, alpha:&Var) -> Self {
+ match (self, alpha) {
+ (Var::Static (t), Var::Static(alpha)) => {
+ Var::Static(color::Rgba::new(t.data.red,t.data.green,t.data.blue,*alpha))
+ },
+ (t, alpha) => {
+ Var::Dynamic(format!("srgba({0}.raw.x,{0}.raw.y,{0}.raw.z,{1})",t.glsl(),alpha.glsl()).into())
+ },
+ }
+ }
+
+ /// Return the color with the alpha value replaced by multiplying the colors' alpha value with
+ /// the given alpha value.
+ pub fn multiply_alpha(self, alpha:&Var) -> Self {
+ match (self, alpha) {
+ (Var::Static (t), Var::Static(alpha)) => {
+ Var::Static(color::Rgba::new(t.data.red,t.data.green,t.data.blue,*alpha*t.data.alpha))
+ },
+ (t, alpha) => {
+ Var::Dynamic(format!("srgba({0}.raw.x,{0}.raw.y,{0}.raw.z,{0}.raw.w*{1})",t.glsl(),alpha.glsl()).into())
+ },
+ }
+ }
+
+ /// Transform to LinearRgba.
+ pub fn into_linear(self) -> Var {
+ match self {
+ Var::Static(c) => Var::Static(c.into()),
+ Var::Dynamic(c) => Var::Dynamic(format!("rgba({0})",c.glsl()).into())
+ }
+ }
+}
diff --git a/src/rust/ensogl/lib/core/src/gui.rs b/src/rust/ensogl/lib/core/src/gui.rs
index 0e4a325817..1894a034a3 100644
--- a/src/rust/ensogl/lib/core/src/gui.rs
+++ b/src/rust/ensogl/lib/core/src/gui.rs
@@ -2,3 +2,4 @@
pub mod component;
pub mod cursor;
+#[macro_use] pub mod style;
diff --git a/src/rust/ensogl/lib/core/src/gui/cursor.rs b/src/rust/ensogl/lib/core/src/gui/cursor.rs
index 76b0deb2a5..05eb5d0cfc 100644
--- a/src/rust/ensogl/lib/core/src/gui/cursor.rs
+++ b/src/rust/ensogl/lib/core/src/gui/cursor.rs
@@ -6,10 +6,12 @@ use crate::Animation;
use crate::DEPRECATED_Animation;
use crate::DEPRECATED_Tween;
use crate::data::color;
+use crate::define_style;
use crate::display::scene::Scene;
use crate::display::shape::*;
use crate::display;
use crate::frp;
+use crate::gui::style::*;
@@ -30,105 +32,10 @@ fn DEFAULT_SIZE() -> Vector2 { Vector2(16.0,16.0) }
-// ==================
-// === StyleValue ===
-// ==================
-
-/// Defines a value of the cursor style.
-#[derive(Debug,Clone,Eq,PartialEq)]
-#[allow(missing_docs)]
-pub struct StyleValue {
- /// Defines the value of the style. In case it is set to `None`, the default value will be used.
- /// Please note that setting it to `None` has a different effect than not providing the value
- /// in the `Style` at all. If the value is provided it can override the existing values when
- /// used in a semigroup operation.
- pub value : Option,
-
- /// Defines if the state transition should be used. Sometimes disabling animation is required.
- /// A good example is the implementation of a selection box. When drawing selection box with the
- /// mouse, the user wants to see it in real-time, without it growing over time.
- pub animate : bool,
-}
-
-impl Default for StyleValue {
- fn default() -> Self {
- let value = default();
- let animate = true;
- Self {value,animate}
- }
-}
-
-impl StyleValue {
- /// Constructor.
- pub fn new(value:T) -> Self {
- let value = Some(value);
- let animate = true;
- Self {value,animate}
- }
-
- /// Constructor for a default value setter. Please note that this is not made a `Default` impl
- /// on purpose. This method creates a non-empty value setter which sets the target to its
- /// default value. Read `Style` docs to learn more.
- pub fn new_default() -> Self {
- let value = None;
- let animate = true;
- Self {value,animate}
- }
-
- /// Constructor with disabled animation.
- pub fn new_no_animation(value:T) -> Self {
- let value = Some(value);
- let animate = false;
- Self {value,animate}
- }
-}
-
-
-
// =============
// === Style ===
// =============
-macro_rules! define_style {( $( $(#$meta:tt)* $field:ident : $field_type:ty),* $(,)? ) => {
- /// Set of cursor style parameters. You can construct this object in FRP network, merge it using
- /// its `Semigroup` instance, and finally pass to the cursor to apply the style. Please note
- /// that cursor does not implement any complex style management (like pushing or popping a style
- /// from a style stack) on purpose, as it is stateful, while it is straightforward to implement
- /// it in FRP.
- #[derive(Debug,Clone,Default,PartialEq)]
- pub struct Style {
- $($(#$meta)? $field : Option>),*
- }
-
- impl Style {
- /// Create a new style with all fields set to default value. Please note that it is
- /// different than empty style, as this one overrides fields with default values when
- /// used in a semigroup operation.
- pub fn new_with_all_fields_default() -> Self {
- $(let $field = Some(StyleValue::new_default());)*
- Self {$($field),*}
- }
-
- /// Check whether the style is a default, empty one.
- pub fn is_default(&self) -> bool {
- $(self.$field.is_none())&&*
- }
- }
-
- impl PartialSemigroup<&Style> for Style {
- #[allow(clippy::clone_on_copy)]
- fn concat_mut(&mut self, other:&Self) {
- $(if self.$field . is_none() { self.$field = other.$field . clone() })*
- }
- }
-
- impl PartialSemigroup