Skip to content

Commit

Permalink
Implement tooltips that show the type of an output port on hover. (en…
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelMauderer authored Mar 17, 2021
1 parent 421c788 commit 93f8503
Show file tree
Hide file tree
Showing 20 changed files with 820 additions and 165 deletions.
4 changes: 4 additions & 0 deletions ide/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
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]. This allows discovering the result type of a node which can help with
debugging and development.
- [Visualizations can define context for preprocessor evaluation][1291]. Users
can now decide what module's context should be used for visualization
preprocessor. This allows providing visualization with standard library
Expand Down Expand Up @@ -195,6 +198,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

<br/>

Expand Down
198 changes: 198 additions & 0 deletions ide/src/rust/ensogl/lib/components/src/label.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
//! 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;

use ensogl_theme::component::label as theme;



// ==========================
// === Shapes Definitions ===
// ==========================

mod background {
use super::*;

ensogl_core::define_shape_system! {
(style:Style,bg_color:Vector4) {

let width = Var::<Pixels>::from("input_size.x");
let height = Var::<Pixels>::from("input_size.y");
let padding = style.get_number_or(theme::padding, 0.0);
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::<color::Rgba>::from(bg_color.clone()));


// === Shadow ===
let alpha = Var::<f32>::from(format!("({0}.w)",bg_color));
let border_size_f = 16.0;
let shaow_size = style.get_number_or(theme::shadow::size,0.0);
let shadow_size = shaow_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(theme::shadow));
let base_color = Var::<color::Rgba>::from(base_color);
let base_color = base_color.multiply_alpha(&alpha);
let fading_color = color::Rgba::from(style.get_color(theme::shadow::fading));
let fading_color = Var::<color::Rgba>::from(fading_color);
let fading_color = fading_color.multiply_alpha(&alpha);
let exponent = style.get_number_or(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,
style : StyleWatch,
}

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::<text::Area>();
let background = background::View::new(&logger);

// FIXME[MM/WD]: Depth sorting of labels to in front of everything else in the scene.
// Temporary solution. The depth management needs to allow defining relative position of
// the text and background and let the whole component to be set to am an arbitrary layer.
label.remove_from_scene_layer_DEPRECATED(&scene.layers.main);
label.add_to_scene_layer_DEPRECATED(&scene.layers.tooltip_text);
scene.layers.tooltip_background.add_exclusive(&background);

display_object.add_child(&background);
display_object.add_child(&label);

let style = StyleWatch::new(&app.display.scene().style_sheet);

Model { label, display_object, background, app, style }
}

pub fn height(&self) -> f32 {
self.style.get_number_or(theme::height, 0.0)
}

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;
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 text_color_path = theme::text;
let text_color = self.style.get_color(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 = theme::background;
let bg_color = self.style.get_color(bg_color_path).multiply_alpha(value);
let bg_color = color::Rgba::from(bg_color);
self.background.bg_color.set(bg_color.into())
}
}



// =======================
// === Label Component ===
// =======================

#[allow(missing_docs)]
#[derive(Clone,CloneRef,Debug)]
pub struct Label {
model : Rc<Model>,
pub frp : Rc<Frp>,
}

impl Label {
/// Constructor.
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()
}

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 Label {
fn display_object(&self) -> &display::object::Instance { &self.model.display_object }
}
1 change: 1 addition & 0 deletions ide/src/rust/ensogl/lib/components/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

pub mod drop_down_menu;
pub mod list_view;
pub mod label;
pub mod toggle_button;

/// Commonly used types and functions.
Expand Down
2 changes: 2 additions & 0 deletions ide/src/rust/ensogl/lib/core/src/animation/frp/animation.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! FRP bindings to the animation engine.

pub mod hysteretic;

use crate::prelude::*;

use crate::animation::physics::inertia;
Expand Down
113 changes: 113 additions & 0 deletions ide/src/rust/ensogl/lib/core/src/animation/frp/animation/hysteretic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//! Animation that has a delayed onset and offset.
//!
//! 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`.
//!
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 and offset.
#[derive(CloneRef,Debug,Shrinkwrap)]
pub struct HystereticAnimation {
#[allow(missing_docs)]
pub frp : FrpEndpoints,
}

impl HystereticAnimation {
#[allow(missing_docs)]
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::<f32>::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}
}
}
13 changes: 13 additions & 0 deletions ide/src/rust/ensogl/lib/core/src/data/color/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,16 @@ impl<C:Default> Default for Alpha<C> {
Self {alpha,opaque}
}
}

impl<C> Alpha<C> {
/// Return the color with a multiplied alpha channel.
pub fn multiply_alpha(self, alpha:f32) -> Color<Self> {
let alpha = self.alpha * alpha;
Color(Alpha{alpha, opaque: self.opaque })
}

/// Modify the color's alpha channel.
pub fn mod_alpha<F:FnOnce(&mut f32)>(&mut self, f:F) {
f(&mut self.alpha)
}
}
4 changes: 2 additions & 2 deletions ide/src/rust/ensogl/lib/core/src/data/color/gradient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl<Color> LinearGradient<Color> {
}

impls! { [Color] From<&LinearGradient<Color>> for Glsl
where [Color:Copy + RefInto<Glsl>] {
where [Color:RefInto<Glsl>] {
|t| {
let args = t.control_points.iter().map(|control_point| {
let offset = control_point.offset.glsl();
Expand All @@ -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<Gradient> {
/// The distance from the shape border at which the gradient should start.
pub min_distance : f32,
Expand Down
Loading

0 comments on commit 93f8503

Please sign in to comment.