Implement segmented output ports. (https://github.com/enso-org/ide/pull/525)

* Implement segmented output ports
* Delayed appearance of multi ports (https://github.com/enso-org/ide/pull/543)
* Implement delay to show output ports.

Original commit: ea0badb2e2
This commit is contained in:
Michael Mauderer 2020-06-18 22:55:48 +02:00 committed by GitHub
parent 790d668731
commit f6f99934b4
10 changed files with 822 additions and 84 deletions

View File

@ -127,7 +127,7 @@ commands.build.rust = async function(argv) {
console.log('Checking the resulting WASM size.')
let stats = fss.statSync(paths.dist.wasm.mainOptGz)
let limit = 3.29
let limit = 3.31
let size = Math.round(100 * stats.size / 1024 / 1024) / 100
if (size > limit) {
throw(`Output file size exceeds the limit (${size}MB > ${limit}MB).`)

View File

@ -147,6 +147,16 @@ pub trait ShapeOps : Sized where for<'t> &'t Self : IntoOwned<Owned=Self> {
fn pixel_snap(&self) -> PixelSnap<Self> {
PixelSnap(self)
}
/// Grows the shape by the given amount.
fn grow<T:Into<Var<Distance<Pixels>>>>(&self, value:T) -> Grow<Self> {
Grow(self, value.into())
}
/// Shrinks the shape by the given amount.
fn shrink<T:Into<Var<Distance<Pixels>>>>(&self, value:T) -> Shrink<Self> {
Shrink(self, value.into())
}
}
macro_rules! define_shape_operator {

View File

@ -123,4 +123,6 @@ define_modifiers! {
Intersection intersection (child1,child2) ()
Fill fill (child) (color:Rgba)
PixelSnap pixel_snap (child) ()
Grow grow (child) (value:f32)
Shrink shrink (child) (value:f32)
}

View File

@ -6,7 +6,9 @@ use crate::data::color;
use crate::display::shape::primitive::def::unit::PixelDistance;
use crate::math::algebra::Acos;
use crate::math::algebra::Asin;
use crate::math::algebra::Clamp;
use crate::math::algebra::Cos;
use crate::math::algebra::Signum;
use crate::math::algebra::Sin;
use crate::math::algebra::Sqrt;
use crate::math::topology::unit::Angle;
@ -22,7 +24,6 @@ use nalgebra::Scalar;
use std::ops::*;
// ======================
// === VarInitializer ===
// ======================
@ -514,6 +515,48 @@ where T: Sqrt<Output=T> {
// =============
// === Clamp ===
// =============
impl<T> Clamp for Var<T>
where T: Clamp<Output=T>+Into<Glsl> {
type Output = Var<T>;
fn clamp(self, lower:Var<T>, upper:Var<T>) -> Var<T> {
use Var::Static;
use Var::Dynamic;
match (self, lower, upper) {
(Static(value),Static(lower),Static(upper)) => Static(value.clamp(lower, upper)),
(value, lower, upper) => {
let value:Glsl = value.into();
let lower:Glsl = lower.into();
let upper:Glsl = upper.into();
Dynamic(format!("clamp({},{},{})", value.glsl(), lower.glsl(), upper.glsl()).into())
}
}
}
}
// ==============
// === Signum ===
// ==============
impl<T> Signum for Var<T>
where T: Signum<Output=T> {
type Output = Var<T>;
fn signum(self) -> Self {
match self {
Self::Static (t) => Var::Static(t.signum()),
Self::Dynamic (t) => Var::Dynamic(format!("sign({})",t).into())
}
}
}
// ============================
// === Conversion Functions ===
// ============================

View File

@ -84,7 +84,9 @@ Sdf difference (Sdf a, Sdf b) {
return intersection(a,inverse(b));
}
Sdf grow (Sdf a, float size) {
return Sdf(a.distance - size);
}
// ================
// === BoundSdf ===
@ -136,6 +138,12 @@ BoundSdf pixel_snap (BoundSdf a) {
return a;
}
BoundSdf grow (BoundSdf a, float size) {
a.distance = a.distance - size;
a.bounds = grow(a.bounds,size);
return a;
}
BoundSdf inverse (BoundSdf a) {
return bound_sdf(inverse(sdf(a)),inverse(a.bounds));
}
@ -244,6 +252,14 @@ Shape pixel_snap (Shape s) {
return shape(id,sdf,color);
}
Shape grow (Shape s, float value) {
Id id = s.id;
BoundSdf sdf = grow(s.sdf,value);
Srgba color = unpremultiply(s.color);
color.raw.a /= s.alpha;
return shape(id,sdf,color);
}
Shape inverse (Shape s1) {
return shape(s1.id,inverse(s1.sdf),s1.color);
}

View File

@ -263,7 +263,7 @@ impl Canvas {
let color:Glsl = color.into().glsl();
this.add_current_function_code_line(iformat!("Shape shape = {s.getter()};"));
this.add_current_function_code_line(iformat!("Srgba color = srgba({color});"));
let expr = iformat!("return set_color(shape,color);");
let expr = iformat!("return set_color(shape,color);");
let mut shape = this.new_shape_from_expr(num,&expr);
shape.add_ids(&s.ids);
shape
@ -274,12 +274,31 @@ impl Canvas {
pub fn pixel_snap
(&mut self, num:usize, s:Shape) -> Shape {
self.if_not_defined(num, |this| {
let expr = iformat!("return pixel_snap({s.getter()});");
let expr = iformat!("return pixel_snap({s.getter()});");
let mut shape = this.new_shape_from_expr(num,&expr);
shape.add_ids(&s.ids);
shape
})
}
/// Grow the shape by the given value.
pub fn grow<T:Into<Var<f32>>>
(&mut self, num:usize, s:Shape, value:T) -> Shape {
self.if_not_defined(num, |this| {
let value:Glsl = value.into().glsl();
let expr = iformat!("return grow({s.getter()},{value});");
let mut shape = this.new_shape_from_expr(num,&expr);
shape.add_ids(&s.ids);
shape
})
}
/// Shrink the shape by the given value.
pub fn shrink<T:Into<Var<f32>>>
(&mut self, num:usize, s:Shape, value:T) -> Shape {
let value = value.into();
self.grow(num, s, -value)
}
}

View File

@ -9,8 +9,8 @@ pub use port::Expression;
use crate::prelude::*;
use enso_frp;
use enso_frp as frp;
use enso_frp;
use ensogl::data::color;
use ensogl::display::Attribute;
use ensogl::display::Buffer;
@ -21,13 +21,21 @@ use ensogl::display::traits::*;
use ensogl::display;
use ensogl::gui::component::Animation;
use ensogl::gui::component;
use std::num::NonZeroU32;
use super::edge;
use crate::component::visualization;
use crate::component::node::port::output::OutputPorts;
const NODE_SHAPE_PADDING : f32 = 40.0;
// =================
// === Constants ===
// =================
pub const NODE_SHAPE_PADDING : f32 = 40.0;
pub const NODE_SHAPE_RADIUS : f32 = 14.0;
// ============
@ -50,7 +58,7 @@ pub mod shape {
let height : Var<Distance<Pixels>> = "input_size.y".into();
let width = width - NODE_SHAPE_PADDING.px() * 2.0;
let height = height - NODE_SHAPE_PADDING.px() * 2.0;
let radius = 14.px();
let radius = NODE_SHAPE_RADIUS.px();
let shape = Rect((&width,&height)).corners_radius(radius);
let shape = shape.fill(color::Rgba::from(bg_color));
@ -92,48 +100,6 @@ pub mod shape {
}
}
/// Canvas node shape definition.
pub mod output_area {
use super::*;
ensogl::define_shape_system! {
(style:Style, grow:f32) {
let width : Var<Distance<Pixels>> = "input_size.x".into();
let height : Var<Distance<Pixels>> = "input_size.y".into();
let width = width - NODE_SHAPE_PADDING.px() * 2.0;
let height = height - NODE_SHAPE_PADDING.px() * 2.0;
let hover_area_size = 20.0.px();
let hover_area_width = &width + &hover_area_size * 2.0;
let hover_area_height = &height / 2.0 + &hover_area_size;
let hover_area = Rect((&hover_area_width,&hover_area_height));
let hover_area = hover_area.translate_y(-hover_area_height/2.0);
let hover_area = hover_area.fill(color::Rgba::new(0.0,0.0,0.0,0.000_001));
let shrink = 1.px() - 1.px() * &grow;
let radius = 14.px();
let port_area_size = 4.0.px() * &grow;
let port_area_width = &width + (&port_area_size - &shrink) * 2.0;
let port_area_height = &height + (&port_area_size - &shrink) * 2.0;
let bottom_radius = &radius + &port_area_size;
let port_area = Rect((&port_area_width,&port_area_height));
let port_area = port_area.corners_radius(&bottom_radius);
let port_area = port_area - BottomHalfPlane();
let corner_radius = &port_area_size / 2.0;
let corner_offset = &port_area_width / 2.0 - &corner_radius;
let corner = Circle(&corner_radius);
let left_corner = corner.translate_x(-&corner_offset);
let right_corner = corner.translate_x(&corner_offset);
let port_area = port_area + left_corner + right_corner;
let port_area = port_area.fill(color::Rgba::from(color::Lcha::new(0.6,0.5,0.76,1.0)));
let out = hover_area + port_area;
out.into()
}
}
}
/// Canvas node shape definition.
pub mod drag_area {
use super::*;
@ -186,18 +152,10 @@ impl InputEvents {
}
#[derive(Clone,CloneRef,Debug,Deref)]
#[allow(missing_docs)]
pub struct OutputPortsEvents {
pub shape_view_events : component::ShapeViewEvents,
}
#[derive(Clone,CloneRef,Debug)]
#[allow(missing_docs)]
pub struct Frp {
pub input : InputEvents,
pub output_ports : OutputPortsEvents
}
impl Deref for Frp {
@ -246,9 +204,9 @@ pub struct NodeModel {
pub frp : Frp,
pub main_area : component::ShapeView<shape::Shape>,
pub drag_area : component::ShapeView<drag_area::Shape>,
pub output_area : component::ShapeView<output_area::Shape>,
pub ports : port::Manager,
pub visualization : visualization::Container,
pub output_ports : OutputPorts,
}
pub const CORNER_RADIUS : f32 = 14.0;
@ -264,19 +222,17 @@ impl NodeModel {
let logger = Logger::new("node");
edge::sort_hack_1(scene);
let output_logger = Logger::sub(&logger,"output_area");
let main_logger = Logger::sub(&logger,"main_area");
let drag_logger = Logger::sub(&logger,"drag_area");
let output_area = component::ShapeView::<output_area::Shape>::new(&output_logger,scene);
let main_area = component::ShapeView::<shape ::Shape>::new(&main_logger ,scene);
let drag_area = component::ShapeView::<drag_area ::Shape>::new(&drag_logger ,scene);
OutputPorts::order_hack(&scene);
let main_logger = Logger::sub(&logger,"main_area");
let drag_logger = Logger::sub(&logger,"drag_area");
let main_area = component::ShapeView::<shape ::Shape>::new(&main_logger ,scene);
let drag_area = component::ShapeView::<drag_area ::Shape>::new(&drag_logger ,scene);
edge::sort_hack_2(scene);
port::sort_hack(scene); // FIXME hack for sorting
let display_object = display::object::Instance::new(&logger);
display_object.add_child(&drag_area);
display_object.add_child(&output_area);
display_object.add_child(&main_area);
// FIXME: maybe we can expose shape system from shape?
@ -301,14 +257,15 @@ impl NodeModel {
});
display_object.add_child(&ports);
let output_ports = OutputPortsEvents { shape_view_events:output_area.events.clone_ref() };
let frp = Frp{input,output_ports};
let frp = Frp{input};
// TODO: Determine number of output ports based on node semantics.
let output_ports = OutputPorts::new(&scene, NonZeroU32::new(10).unwrap());
display_object.add_child(&output_ports);
Self {scene,display_object,logger,frp,main_area,drag_area,output_area,ports
Self {scene,display_object,logger,frp,main_area,drag_area,output_ports,ports
,visualization} . init()
}
@ -330,18 +287,19 @@ impl NodeModel {
self.ports.set_expression(expr);
let width = self.width();
let height = 28.0;
let height = self.height();
let size = Vector2::new(width+NODE_SHAPE_PADDING*2.0, height+NODE_SHAPE_PADDING*2.0);
self.main_area.shape.sprite.size.set(size);
self.drag_area.shape.sprite.size.set(size);
self.output_area.shape.sprite.size.set(size);
self.main_area.mod_position(|t| t.x = width/2.0);
self.main_area.mod_position(|t| t.y = height/2.0);
self.drag_area.mod_position(|t| t.x = width/2.0);
self.drag_area.mod_position(|t| t.y = height/2.0);
self.output_area.mod_position(|t| t.x = width/2.0);
self.output_area.mod_position(|t| t.y = height/2.0);
self.output_ports.frp.set_size.emit(size);
self.output_ports.mod_position(|t| t.x = width/2.0);
self.output_ports.mod_position(|t| t.y = height/2.0);
}
pub fn visualization(&self) -> &visualization::Container {
@ -355,7 +313,7 @@ impl Node {
let model = Rc::new(NodeModel::new(scene,&frp_network));
let inputs = &model.frp.input;
let selection = Animation::<f32>::new(&frp_network);
let output_area_size = Animation::<f32>::new(&frp_network);
frp::extend! { frp_network
eval selection.value ((v) model.main_area.shape.selection.set(*v));
@ -364,11 +322,6 @@ impl Node {
eval inputs.set_expression ((expr) model.set_expression(expr));
eval output_area_size.value ((size) model.output_area.shape.grow.set(*size));
eval_ model.output_area.events.mouse_over (output_area_size.set_target_value(1.0));
eval_ model.output_area.events.mouse_out (output_area_size.set_target_value(0.0));
eval inputs.set_visualization ((content)
model.visualization.frp.set_visualization.emit(content)
);

View File

@ -1,5 +1,8 @@
//! Definition of the Port component.
#[warn(missing_docs)]
pub mod output;
use crate::prelude::*;
//use crate::component::node::port::Registry;

View File

@ -0,0 +1,691 @@
//! Implements the segmented output port area.
use crate::prelude::*;
use ensogl::display::traits::*;
use enso_frp as frp;
use enso_frp;
use ensogl::data::color;
use ensogl::display::Attribute;
use ensogl::display::Buffer;
use ensogl::display::Sprite;
use ensogl::display::scene::Scene;
use ensogl::display::shape::AnyShape;
use ensogl::display::shape::BottomHalfPlane;
use ensogl::display::shape::Circle;
use ensogl::display::shape::Distance;
use ensogl::display::shape::PixelDistance;
use ensogl::display::shape::Pixels;
use ensogl::display::shape::Rect;
use ensogl::display::shape::Var;
use ensogl::display::shape::primitive::def::class::ShapeOps;
use ensogl::display;
use ensogl::gui::component::Animation;
use ensogl::gui::component::Tween;
use ensogl::gui::component;
use ensogl::math::algebra::Clamp;
use ensogl::math::algebra::Signum;
use std::num::NonZeroU32;
use crate::node;
// =================
// === Constants ===
// =================
// TODO: These values should be in some IDE configuration.
const BASE_SIZE : f32 = 0.5;
const HIGHLIGHT_SIZE : f32 = 1.0;
const SEGMENT_GAP_WIDTH : f32 = 2.0;
const SHOW_DELAY_DURATION : f32 = 150.0;
const HIDE_DELAY_DURATION : f32 = 150.0;
const SHAPE_HOVER_AREA_SIZE : f32 = 20.0;
const SHAPE_MAX_WIDTH : f32 = 4.0;
// ==================
// === Base Shape ===
// ==================
/// The base port shape for the output port shape. This shape is later on used to create a
/// simple single output port shape and a more complex multi-output port shape.
///
/// The base shape looks roughly like this:
/// ```text
/// . .
/// . .
/// . .
/// . .
/// """""""""""""
/// | r | | r |
/// | width |
/// ```
/// where r is the radius of the left and right quarter circle shapes.
///
/// The struct that contains the port base shapes, and some information about the shapes that is
/// required for the computation of segments.
struct BaseShapeData {
port_area : AnyShape,
hover_area : AnyShape,
radius : Var<Distance<Pixels>>,
width : Var<Distance<Pixels>>,
}
impl BaseShapeData {
fn new
(width:&Var<Distance<Pixels>>, height:&Var<Distance<Pixels>>, grow:&Var<f32>) -> Self {
let width = width - node::NODE_SHAPE_PADDING.px() * 2.0;
let height = height - node::NODE_SHAPE_PADDING.px() * 2.0;
let hover_area_width = &width + &SHAPE_HOVER_AREA_SIZE.px() * 2.0;
let hover_area_height = &height / 2.0 + &SHAPE_HOVER_AREA_SIZE.px();
let hover_area = Rect((&hover_area_width,&hover_area_height));
let hover_area = hover_area.translate_y(-hover_area_height/2.0);
let shrink = 1.px() - 1.px() * grow;
let radius = node::NODE_SHAPE_RADIUS.px();
let port_area_size = SHAPE_MAX_WIDTH.px() * grow;
let port_area_width = &width + (&port_area_size - &shrink) * 2.0;
let port_area_height = &height + (&port_area_size - &shrink) * 2.0;
let bottom_radius = &radius + &port_area_size;
let port_area = Rect((&port_area_width,&port_area_height));
let port_area = port_area.corners_radius(&bottom_radius);
let port_area = port_area - BottomHalfPlane();
let corner_radius = &port_area_size / 2.0;
let corner_offset = &port_area_width / 2.0 - &corner_radius;
let corner = Circle(&corner_radius);
let left_corner = corner.translate_x(-&corner_offset);
let right_corner = corner.translate_x(&corner_offset);
let port_area = port_area + left_corner + right_corner;
let port_area = port_area.into();
let hover_area = hover_area.into();
BaseShapeData{port_area,hover_area,radius,width}
}
}
/// Trait that allows us to abstract the API of the `multi_port_area::Shape` and the
/// `single_port_area::Shape`. This is needed to avoid code duplication for functionality that can
/// work with either shape.
#[allow(missing_docs)]
trait PortShape {
fn set_grow(&self, grow_value:f32);
fn set_opacity(&self, opacity:f32);
}
// ========================
// === Multi Port Shape ===
// ========================
/// Implements the shape for a segment of the OutputPort with multiple output ports.
pub mod multi_port_area {
use super::*;
use ensogl::display::shape::*;
use std::f32::consts::PI;
/// Compute the angle perpendicular to the shape border.
fn compute_border_perpendicular_angle
(full_shape_border_length:&Var<f32>, corner_segment_length:&Var<f32>, position:&Var<f32>)
-> Var<f32> {
// TODO implement proper abstraction for non-branching "if/then/else" or "case" in
// shaderland
// Here we use a trick to use a pseudo-boolean float that is either 0 or 1 to multiply a
// value that should be returned, iff it's case is true. That way we can add return values
// of different "branches" of which exactly one will be non-zero.
// Transform position to be centered in the shape.
// The `distance`s here describe the distance along the shape border, so not straight line
// x coordinate, but the length of the path along the shape.
let center_distance = position - full_shape_border_length / 2.0;
let center_distance_absolute = center_distance.abs();
let center_distance_sign = center_distance.signum();
let end = full_shape_border_length / 2.0;
let center_segment_end = &end - corner_segment_length;
let default_rotation = Var::<f32>::from(90.0_f32.to_radians());
// Case 1: The center segment, always zero, so not needed, due to clamping.
// Case 2: The right circle segment.
let relative_position = (center_distance_absolute - center_segment_end)
/ corner_segment_length;
let relative_position = relative_position.clamp(0.0.into(),1.0.into());
let corner_base_rotation = (-90.0_f32).to_radians();
let corner_rotation_delta = relative_position * corner_base_rotation;
corner_rotation_delta * center_distance_sign + default_rotation
}
/// Returns the x position of the crop plane as a fraction of the base shapes center segment.
/// To get the actual x position of the plane, multiply this value by the length of the center
/// segment and apply the appropriate x-offset.
///
/// * `full_shape_border_length` should be the length of the shapes border path.
/// * `corner_segment_length` should be the quarter circumference of the circles on the
/// sides of base shape.
/// * `position_on_path` should be the position along the shape border
/// (not the pure x-coordinate).
fn calculate_crop_plane_position_relative_to_center_segment
(full_shape_border_length:&Var<f32>, corner_segment_length:&Var<f32>, position_on_path:&Var<f32>)
-> Var<f32> {
let middle_segment_start_point = corner_segment_length;
let middle_segment_end_point = full_shape_border_length - corner_segment_length;
// Case 1: The left circle, always 0, achieved through clamping.
// Case 2: The middle segment.
let middle_segment_plane_position_x = (position_on_path - middle_segment_start_point)
/ (&middle_segment_end_point - middle_segment_start_point);
// Case 3: The right circle, always 1, achieved through clamping.
middle_segment_plane_position_x.clamp(0.0.into(), 1.0.into())
}
/// Compute the crop plane at the location of the given port index. Also takes into account an
/// `position_offset` that is given as an offset along the shape boundary.
///
/// The crop plane is a `HalfPlane` that is perpendicular to the border of the shape and can be
/// used to crop the shape at the specified port index.
fn compute_crop_plane
(index:&Var<f32>, port_num: &Var<f32>, width: &Var<f32>, corner_radius:&Var<f32>,
position_offset:&Var<f32>) -> AnyShape {
let corner_circumference = corner_radius * 2.0 * PI;
let corner_segment_length = &corner_circumference * 0.25;
let center_segment_length = width - corner_radius * 2.0;
let full_shape_border_length = &center_segment_length + &corner_segment_length * 2.0;
let position_relative = index / port_num;
let crop_segment_pos = &position_relative * &full_shape_border_length + position_offset;
let crop_plane_pos_relative = calculate_crop_plane_position_relative_to_center_segment
(&full_shape_border_length,&corner_segment_length,&crop_segment_pos);
let crop_plane_pos = crop_plane_pos_relative * &center_segment_length;
let crop_plane_pos = crop_plane_pos + corner_radius;
let plane_rotation_angle = compute_border_perpendicular_angle
(&full_shape_border_length,&corner_segment_length,&crop_segment_pos);
let plane_shape_offset = Var::<Distance<Pixels>>::from(&crop_plane_pos - width * 0.5);
let crop_shape = HalfPlane();
let crop_shape = crop_shape.rotate(plane_rotation_angle);
let crop_shape = crop_shape.translate_x(plane_shape_offset);
crop_shape.into()
}
ensogl::define_shape_system! {
(style:Style, grow:f32, index:f32, port_num:f32, opacity:f32, padding_left:f32,
padding_right:f32) {
let overall_width : Var<Distance<Pixels>> = "input_size.x".into();
let overall_height : Var<Distance<Pixels>> = "input_size.y".into();
let base_shape_data = BaseShapeData::new(&overall_width,&overall_height,&grow);
let BaseShapeData{ port_area,hover_area,radius,width } = base_shape_data;
let radius:Var::<f32> = radius.into();
let width:Var::<f32> = width.into();
let left_shape_crop = compute_crop_plane
(&index,&port_num,&width,&radius,&0.0.into());
let right_shape_crop = compute_crop_plane
(&(Var::<f32>::from(1.0) + &index),&port_num,&width,&radius,&0.0.into());
let hover_area = hover_area.difference(&left_shape_crop);
let hover_area = hover_area.intersection(&right_shape_crop);
let hover_area = hover_area.fill(color::Rgba::new(0.0,0.0,0.0,0.000_001));
let padding_left = Var::<Distance<Pixels>>::from(padding_left);
let padding_right = Var::<Distance<Pixels>>::from(padding_right);
let left_shape_crop = left_shape_crop.grow(padding_left);
let right_shape_crop = right_shape_crop.grow(padding_right);
let port_area = port_area.difference(&left_shape_crop);
let port_area = port_area.intersection(&right_shape_crop);
// FIXME: Use color from style and apply transparency there.
let color = Var::<color::Rgba>::from("srgba(0.25,0.58,0.91,input_opacity)");
let port_area = port_area.fill(color);
(port_area + hover_area).into()
}
}
impl PortShape for Shape {
fn set_grow(&self, grow_value:f32) {
self.grow.set(grow_value)
}
fn set_opacity(&self, opacity:f32) {
self.opacity.set(opacity)
}
}
}
// =========================
// === Single Port Shape ===
// =========================
/// Implements a simplified version of the multi_port_area::Shape shape for the case where there is
/// only a single output port.
pub mod single_port_area {
use super::*;
use ensogl::display::shape::*;
ensogl::define_shape_system! {
(style:Style, grow:f32, opacity:f32) {
let overall_width : Var<Distance<Pixels>> = "input_size.x".into();
let overall_height : Var<Distance<Pixels>> = "input_size.y".into();
let base_shape_data = BaseShapeData::new(&overall_width,&overall_height,&grow);
let BaseShapeData{ port_area,hover_area, .. } = base_shape_data;
// FIXME: Use color from style and apply transparency there.
let color = Var::<color::Rgba>::from("srgba(0.25,0.58,0.91,input_opacity)");
let port_area = port_area.fill(color);
let hover_area = hover_area.fill(color::Rgba::new(0.0,0.0,0.0,0.000_001));
(port_area + hover_area).into()
}
}
impl PortShape for Shape {
fn set_grow(&self, grow_value:f32) {
self.grow.set(grow_value)
}
fn set_opacity(&self, opacity:f32) {
self.opacity.set(opacity)
}
}
}
// ==================
// === Shape View ===
// ==================
/// Wrapper that handles the distinction between a single ShapeView<single_port_area::Shape>
/// and collection of component::ShapeView<multi_port_area::Shape>.
#[derive(Clone,Debug)]
enum ShapeView {
Single { view : component::ShapeView<single_port_area::Shape> },
Multi {
display_object: display::object::Instance,
views : Vec<component::ShapeView<multi_port_area::Shape>>,
},
}
impl ShapeView {
/// Constructor.
fn new(number_of_ports:NonZeroU32, logger:&Logger, scene:&Scene) -> Self {
let number_of_ports = number_of_ports.get();
if number_of_ports == 1 {
ShapeView::Single { view: component::ShapeView::new(&logger,&scene) }
} else {
let display_object = display::object::Instance::new(logger);
let mut views = Vec::default();
let number_of_ports = number_of_ports as usize;
views.resize_with(number_of_ports,|| component::ShapeView::new(&logger,&scene));
views.iter().for_each(|view| view.display_object().set_parent(&display_object));
ShapeView::Multi {display_object,views}
}
}
/// Set up the frp for all ports.
fn init_frp(&self, network:&frp::Network, port_frp:PortFrp) {
match self {
ShapeView::Single {view} => init_port_frp(&view,PortId::new(0),port_frp,network),
ShapeView::Multi {views, ..} => {
views.iter().enumerate().for_each(|(index,view)| {
init_port_frp(&view,PortId::new(index),port_frp.clone_ref(),network)
})
}
}
}
/// Resize all the port output shapes to fit the new layout requirements for thr given
/// parameters.
fn update_shape_layout_based_on_size_and_gap(&self, size:Vector2<f32>, gap_width:f32) {
match self {
ShapeView::Single { view } => {
let shape = &view.shape;
shape.sprite.size.set(size);
}
ShapeView::Multi { views, .. } => {
let port_num = views.len() as f32;
for (index,view) in views.iter().enumerate(){
let shape = &view.shape;
shape.sprite.size.set(size);
shape.index.set(index as f32);
shape.port_num.set(port_num);
shape.padding_left.set(gap_width * 0.5);
shape.padding_right.set(-gap_width * 0.5);
}
views[0] .shape.padding_left .set(0.0);
views[views.len() - 1].shape.padding_right.set(0.0);
}
}
}
}
impl display::Object for ShapeView {
fn display_object(&self) -> &display::object::Instance {
match self {
ShapeView::Single { view } => view.display_object(),
ShapeView::Multi { display_object, .. } => display_object,
}
}
}
// ===============
// === PortId ===
// ===============
/// Id of a specific port inside of `OutPutPortsData`.
#[derive(Clone,Copy,Default,Debug,Eq,PartialEq)]
pub struct PortId {
index: usize,
}
impl PortId {
fn new(index:usize) -> Self {
Self{index}
}
}
// =================
// === Port Frp ===
// =================
/// Struct that contains all required FRP endpoints to set up the FRP of a port shape view.
#[derive(Clone,CloneRef,Debug)]
struct PortFrp {
mouse_over : frp::Source<PortId>,
mouse_out : frp::Source<PortId>,
mouse_down : frp::Source<PortId>,
hide : frp::Stream<()>,
/// Activate the port and if it has the given PortId, show it highlighted.
activate_and_highlight_selected : frp::Stream<PortId>
}
/// Set up the FRP system for a ShapeView of a shape that implements the PortShapeApi.
///
/// This allows us to use the same setup code for bot the `multi_port_area::Shape` and the
/// `single_port_area::Shape`.
fn init_port_frp<Shape: PortShape + CloneRef + 'static>
(view:&component::ShapeView<Shape>, port_id:PortId, frp:PortFrp, network:&frp::Network) {
let PortFrp { mouse_over,mouse_out,mouse_down,
hide,activate_and_highlight_selected} = frp;
let shape = &view.shape;
let port_size = Animation::<f32>::new(&network);
let port_opacity = Animation::<f32>::new(&network);
frp::extend! { network
// === Mouse Event Handling == ///
eval_ view.events.mouse_over(mouse_over.emit(port_id));
eval_ view.events.mouse_out(mouse_out.emit(port_id));
eval_ view.events.mouse_down(mouse_down.emit(port_id));
// === Animation Handling == ///
eval port_size.value ((size) shape.set_grow(*size));
eval port_opacity.value ((size) shape.set_opacity(*size));
// === Visibility and Highlight Handling == ///
eval_ hide ([port_size,port_opacity]{
port_size.set_target_value(0.0);
port_opacity.set_target_value(0.0);
});
// Through the provided ID we can infer whether this port should be highlighted.
is_selected <- activate_and_highlight_selected.map(move |id| *id == port_id);
show_normal <- activate_and_highlight_selected.gate_not(&is_selected);
show_highlighted <- activate_and_highlight_selected.gate(&is_selected);
eval_ show_highlighted ([port_opacity,port_size]{
port_opacity.set_target_value(1.0);
port_size.set_target_value(HIGHLIGHT_SIZE);
});
eval_ show_normal ([port_opacity,port_size] {
port_opacity.set_target_value(0.5);
port_size.set_target_value(BASE_SIZE);
});
}
}
// ===========
// === Frp ===
// ===========
/// Frp API of the `OutPutPorts`.
#[derive(Clone,CloneRef,Debug)]
pub struct Frp {
/// Update the size of the `OutPutPorts`. Should match the size of the parent node for visual
/// correctness.
pub set_size : frp::Source<Vector2<f32>>,
/// Emitted whenever one of the ports receives a `MouseDown` event. The `PortId` indicates the
/// source port.
pub port_mouse_down : frp::Stream<PortId>,
/// Emitted whenever one of the ports receives a `MouseOver` event. The `PortId` indicates the
/// source port.
pub port_mouse_over : frp::Stream<PortId>,
/// Emitted whenever one of the ports receives a `MouseOut` event. The `PortId` indicates the
/// source port.
pub port_mouse_out : frp::Stream<PortId>,
on_port_mouse_down : frp::Source<PortId>,
on_port_mouse_over : frp::Source<PortId>,
on_port_mouse_out : frp::Source<PortId>,
}
impl Frp {
fn new(network: &frp::Network) -> Self {
frp::extend! { network
def set_size = source();
def on_port_mouse_down = source();
def on_port_mouse_over = source();
def on_port_mouse_out = source();
let port_mouse_down = on_port_mouse_down.clone_ref().into();
let port_mouse_over = on_port_mouse_over.clone_ref().into();
let port_mouse_out = on_port_mouse_out.clone_ref().into();
}
Self{set_size,on_port_mouse_down,on_port_mouse_over,on_port_mouse_out,
port_mouse_down,port_mouse_over,port_mouse_out}
}
}
// =======================
// === OutputPortsData ===
// =======================
/// Internal data of the `OutputPorts`.
#[derive(Debug)]
pub struct OutputPortsData {
display_object : display::object::Instance,
logger : Logger,
size : Cell<Vector2<f32>>,
gap_width : Cell<f32>,
ports : RefCell<ShapeView>,
}
impl OutputPortsData {
fn new(scene:&Scene, number_of_ports:NonZeroU32) -> Self {
let logger = Logger::new("OutputPorts");
let display_object = display::object::Instance::new(&logger);
let size = Cell::new(Vector2::zero());
let gap_width = Cell::new(SEGMENT_GAP_WIDTH);
let ports = ShapeView::new(number_of_ports,&logger,scene);
let ports = RefCell::new(ports);
OutputPortsData {display_object,logger,size,ports,gap_width}.init()
}
fn init(self) -> Self {
self.ports.borrow().display_object().set_parent(&self.display_object);
self.update_shape_layout_based_on_size_and_gap();
self
}
fn update_shape_layout_based_on_size_and_gap(&self) {
let size = self.size.get();
let gap_width = self.gap_width.get();
self.ports.borrow().update_shape_layout_based_on_size_and_gap(size,gap_width);
}
fn set_size(&self, size:Vector2<f32>) {
self.size.set(size);
self.update_shape_layout_based_on_size_and_gap();
}
}
// ===================
// === OutputPorts ===
// ===================
/// Implements the segmented output port area. Provides shapes that can be attached to a `Node` to
/// add an interactive area with output ports.
///
/// The `OutputPorts` facilitate the falling behaviour:
/// * when one of the output ports is hovered, after a set time, all ports are show and the hovered
/// port is highlighted.
/// * when a different port is hovered, it is highlighted immediately.
/// * when none of the ports is hovered all of the `OutputPorts` disappear. Note: there is a very
/// small delay for disappearing to allow for smooth switching between ports.
///
#[derive(Debug,Clone,CloneRef)]
pub struct OutputPorts {
/// The FRP api of the `OutPutPorts`.
pub frp : Frp,
network : frp::Network,
data : Rc<OutputPortsData>,
}
impl OutputPorts {
/// Constructor.
pub fn new(scene:&Scene, number_of_ports:NonZeroU32) -> Self {
let network = default();
let frp = Frp::new(&network);
let data = OutputPortsData::new(scene,number_of_ports);
let data = Rc::new(data);
OutputPorts {data,network,frp}.init()
}
fn init(self) -> Self {
let network = &self.network;
let frp = &self.frp;
let data = &self.data;
// Used to set and detect the end of the tweens. The actual value is irrelevant, only the
// duration of the tween matters and that this value is reached after that time.
const TWEEN_END_VALUE:f32 = 1.0;
// Timer used to measure whether the hover has been long enough to show the ports.
let delay_show = Tween::new(&network);
delay_show.set_duration(SHOW_DELAY_DURATION);
// Timer used to measure whether the mouse has been gone long enough to hide all ports.
let delay_hide = Tween::new(&network);
delay_hide.set_duration(HIDE_DELAY_DURATION);
let mouse_down = frp.on_port_mouse_down.clone_ref();
let mouse_over = frp.on_port_mouse_over.clone_ref();
let mouse_out = frp.on_port_mouse_out.clone_ref();
frp::extend! { network
// === Size Change Handling == ///
eval frp.set_size ((size) data.set_size(*size));
// === Hover Event Handling == ///
delay_show_finished <- delay_show.value.map(|t| *t>=TWEEN_END_VALUE );
delay_hide_finished <- delay_hide.value.map(|t| *t>=TWEEN_END_VALUE );
on_delay_show_finished <- delay_show_finished.gate(&delay_show_finished).constant(());
on_delay_hide_finished <- delay_hide_finished.gate(&delay_hide_finished).constant(());
visible <- delay_show_finished.map(|v| *v);
mouse_over_while_inactive <- mouse_over.gate_not(&visible).constant(());
mouse_over_while_active <- mouse_over.gate(&visible).constant(());
eval_ mouse_over_while_inactive ([delay_show,delay_hide]{
delay_hide.stop();
delay_show.reset();
delay_show.set_target_value(TWEEN_END_VALUE);
});
eval_ mouse_out ([delay_hide,delay_show]{
delay_show.stop();
delay_hide.reset();
delay_hide.set_target_value(TWEEN_END_VALUE);
});
activate_ports <- any(mouse_over_while_active,on_delay_show_finished);
eval_ activate_ports (delay_hide.stop_and_rewind());
activate_and_highlight_selected <- mouse_over.sample(&activate_ports);
hide_all <- on_delay_hide_finished.map(f_!(delay_show.stop_and_rewind()));
}
let port_frp = PortFrp {mouse_down,mouse_over,mouse_out,
hide: hide_all,
activate_and_highlight_selected
};
data.ports.borrow().init_frp(&network,port_frp);
// // FIXME this is a hack to ensure the ports are invisible at startup.
// // Right now we get some of FRP mouse events on startup that leave the
// // ports visible by default.
// // Once that is fixed, remove this line.
delay_hide.from_now_to(TWEEN_END_VALUE);
self
}
// TODO: Implement proper sorting and remove.
/// Hack function used to register the elements for the sorting purposes. To be removed.
pub(crate) fn order_hack(scene:&Scene) {
let logger = Logger::new("output shape order hack");
component::ShapeView::<multi_port_area::Shape>::new(&logger,scene);
component::ShapeView::<single_port_area::Shape>::new(&logger,scene);
}
}
impl display::Object for OutputPorts {
fn display_object(&self) -> &display::object::Instance {
&self.data.display_object
}
}

View File

@ -966,7 +966,8 @@ impl GraphEditorModelWithNetwork {
frp::new_bridge_network! { [self.network, node.main_area.events.network]
eval_ node.drag_area.events.mouse_down(touch.nodes.down.emit(node_id));
eval node.ports.frp.cursor_style ((style) cursor_style.emit(style));
eval_ node.frp.output_ports.mouse_down (output_press.emit(node_id));
eval_ node.view.output_ports.frp.port_mouse_down (output_press.emit(node_id));
eval_ node.view.output_ports.frp.port_mouse_down (output_press.emit(node_id));
eval node.ports.frp.press ([input_press](crumbs)
let target = EdgeTarget::new(node_id,crumbs.clone());
input_press.emit(target);
@ -977,12 +978,12 @@ impl GraphEditorModelWithNetwork {
model.frp.hover_node_input.emit(target);
});
eval_ node.frp.output_ports.mouse_over ([model] {
eval_ node.view.output_ports.frp.port_mouse_over ([model] {
let target = EdgeTarget::new(node_id,default());
model.frp.hover_node_output.emit(Some(target));
});
eval_ node.frp.output_ports.mouse_out ( model.frp.hover_node_output.emit(None));
eval_ node.view.output_ports.frp.port_mouse_out ( model.frp.hover_node_output.emit(None));
}
// self.visualizations.push(node.visualization().clone_ref());