mirror of
https://github.com/enso-org/enso.git
synced 2024-11-30 17:44:58 +03:00
Errors on nodes (https://github.com/enso-org/ide/pull/1079)
Original commit: 570d3759fb
This commit is contained in:
parent
063c028b4c
commit
f7ea27b0cd
@ -136,7 +136,7 @@ commands.build.rust = async function(argv) {
|
||||
|
||||
console.log('Checking the resulting WASM size.')
|
||||
let stats = fss.statSync(paths.dist.wasm.mainOptGz)
|
||||
let limit = 4.27
|
||||
let limit = 4.28
|
||||
let size = Math.round(100 * stats.size / 1024 / 1024) / 100
|
||||
if (size > limit) {
|
||||
throw(`Output file size exceeds the limit (${size}MB > ${limit}MB).`)
|
||||
|
@ -157,6 +157,11 @@ pub trait ShapeOps : Sized where for<'t> &'t Self : IntoOwned<Owned=Self> {
|
||||
fn shrink<T:Into<Var<Pixels>>>(&self, value:T) -> Shrink<Self> {
|
||||
Shrink(self, value.into())
|
||||
}
|
||||
|
||||
/// Repeats the shape with the given tile size.
|
||||
fn repeat<T:Into<Var<Vector2<Pixels>>>>(&self, value:T) -> Repeat<Self> {
|
||||
Repeat(self, value)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! define_shape_operator {
|
||||
|
@ -124,4 +124,5 @@ define_modifiers! {
|
||||
PixelSnap pixel_snap (child) ()
|
||||
Grow grow (child) (value:f32)
|
||||
Shrink shrink (child) (value:f32)
|
||||
Repeat repeat (child) (tile_size:Vector2<Pixels>)
|
||||
}
|
||||
|
@ -30,6 +30,10 @@ BoundingBox inverse (BoundingBox a) {
|
||||
return BoundingBox(0.0,0.0,0.0,0.0);
|
||||
}
|
||||
|
||||
BoundingBox infinite () {
|
||||
return BoundingBox(0.0,0.0,0.0,0.0);
|
||||
}
|
||||
|
||||
BoundingBox unify (BoundingBox a, BoundingBox b) {
|
||||
float min_x = min(a.min_x,b.min_x);
|
||||
float max_x = max(a.max_x,b.max_x);
|
||||
@ -95,6 +99,8 @@ Sdf grow (Sdf a, float size) {
|
||||
return Sdf(a.distance - size);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ================
|
||||
// === BoundSdf ===
|
||||
// ================
|
||||
@ -290,6 +296,14 @@ Shape set_color(Shape shape, Srgba t) {
|
||||
return shape;
|
||||
}
|
||||
|
||||
Shape withInfiniteBounds (Shape s) {
|
||||
Id id = s.id;
|
||||
Color color = s.color;
|
||||
BoundSdf sdf = s.sdf;
|
||||
sdf.bounds = infinite();
|
||||
return shape(id, sdf, color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===========
|
||||
@ -322,3 +336,7 @@ vec2 scale (vec2 position, float value) {
|
||||
vec2 cartesian2polar (vec2 position) {
|
||||
return vec2(length(position), atan(position.y, position.x));
|
||||
}
|
||||
|
||||
vec2 repeat (vec2 position, vec2 tile_size) {
|
||||
return mod(position-tile_size/2.0, tile_size);
|
||||
}
|
||||
|
@ -298,6 +298,20 @@ impl Canvas {
|
||||
let value = value.into();
|
||||
self.grow(num, s, -value)
|
||||
}
|
||||
|
||||
/// Repeat the shape with the given tile size.
|
||||
pub fn repeat<T:Into<Var<Vector2<Pixels>>>>
|
||||
(&mut self, num:usize, s:Shape, tile_size:T) -> Shape {
|
||||
self.if_not_defined(num, |this| {
|
||||
let value:Glsl = tile_size.into().glsl();
|
||||
let repeat = iformat!("position = mod(position,{value});");
|
||||
let expr = iformat!("return withInfiniteBounds({s.getter()});");
|
||||
this.add_current_function_code_line(repeat);
|
||||
let mut shape = this.new_shape_from_expr(num,&expr);
|
||||
shape.add_ids(&s.ids);
|
||||
shape
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -8,21 +8,26 @@ pub mod action_bar;
|
||||
pub mod expression;
|
||||
pub mod input;
|
||||
pub mod output;
|
||||
#[deny(missing_docs)]
|
||||
pub mod error;
|
||||
|
||||
pub use expression::Expression;
|
||||
pub use error::Error;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use enso_frp as frp;
|
||||
use enso_frp;
|
||||
use ensogl::Animation;
|
||||
use ensogl::application::Application;
|
||||
use ensogl::data::color;
|
||||
use ensogl::display::shape::*;
|
||||
use ensogl::display::traits::*;
|
||||
use ensogl::display;
|
||||
use ensogl::Animation;
|
||||
use ensogl::gui::component;
|
||||
use ensogl::gui::component::ShapeView;
|
||||
use ensogl::gui::text;
|
||||
use ensogl_text::Text;
|
||||
use ensogl_text::style::Size as TextSize;
|
||||
use ensogl_theme;
|
||||
|
||||
use crate::Type;
|
||||
@ -44,6 +49,14 @@ pub const PADDING : f32 = 40.0;
|
||||
pub const RADIUS : f32 = 14.0;
|
||||
pub const SHADOW_SIZE : f32 = 10.0;
|
||||
|
||||
const INFINITE : f32 = 99999.0;
|
||||
const ERROR_PATTERN_STRIPE_WIDTH : f32 = 10.0;
|
||||
const ERROR_PATTERN_STRIPE_ANGLE : f32 = 45.0;
|
||||
const ERROR_PATTERN_REPEAT_TILE_SIZE : (f32,f32) = (15.0,15.0);
|
||||
const ERROR_BORDER_WIDTH : f32 = 10.0;
|
||||
|
||||
const TEXT_SIZE : f32 = 12.0;
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
@ -55,7 +68,7 @@ pub mod shape {
|
||||
use super::*;
|
||||
|
||||
ensogl::define_shape_system! {
|
||||
(style:Style, selection:f32, bg_color:Vector4 ) {
|
||||
(style:Style, selection:f32, bg_color:Vector4) {
|
||||
use ensogl_theme::graph_editor::node as node_theme;
|
||||
|
||||
let bg_color = Var::<color::Rgba>::from(bg_color);
|
||||
@ -66,7 +79,7 @@ pub mod shape {
|
||||
let width = width - PADDING.px() * 2.0;
|
||||
let height = height - PADDING.px() * 2.0;
|
||||
let radius = RADIUS.px();
|
||||
let shape = Rect((&width,&height)).corners_radius(radius);
|
||||
let shape = Rect((&width,&height)).corners_radius(&radius);
|
||||
let shape = shape.fill(bg_color);
|
||||
|
||||
|
||||
@ -110,6 +123,18 @@ pub mod shape {
|
||||
let select = select.fill(color::Rgba::from(sel_color));
|
||||
|
||||
|
||||
// === Error Pattern Alternative ===
|
||||
// TODO: Remove once the error indicator design is finalised.
|
||||
// let repeat = Var::<Vector2<Pixels>>::from((10.px(), 10.px()));
|
||||
// let error_width = Var::<Pixels>::from(5.px());
|
||||
//
|
||||
// let stripe_red = Rect((error_width, 99999.px()));
|
||||
// let pattern = stripe_red.repeat(repeat).rotate(45.0.radians());
|
||||
// let mask = Rect((&width,&height)).corners_radius(&radius);
|
||||
// let pattern1 = mask.intersection(pattern).fill(color::Rgba::red());
|
||||
|
||||
// let out = select + shadow + shape + pattern1;
|
||||
|
||||
// === Final Shape ===
|
||||
|
||||
let out = select + shadow + shape;
|
||||
@ -137,7 +162,43 @@ pub mod drag_area {
|
||||
}
|
||||
}
|
||||
|
||||
// =======================
|
||||
// === Error Indicator ===
|
||||
// =======================
|
||||
|
||||
pub mod error_shape {
|
||||
use super::*;
|
||||
|
||||
ensogl::define_shape_system! {
|
||||
(style:Style,color_rgba:Vector4<f32>) {
|
||||
let width = Var::<Pixels>::from("input_size.x");
|
||||
let height = Var::<Pixels>::from("input_size.y");
|
||||
let zoom = Var::<f32>::from("1.0/zoom()");
|
||||
let width = width - PADDING.px() * 2.0;
|
||||
let height = height - PADDING.px() * 2.0;
|
||||
let radius = RADIUS.px();
|
||||
|
||||
let (repeat_x,repeat_y) = ERROR_PATTERN_REPEAT_TILE_SIZE;
|
||||
let repeat = Var::<Vector2<Pixels>>::from((repeat_x.px(),repeat_y.px()));
|
||||
let error_width = Var::<Pixels>::from(zoom * ERROR_PATTERN_STRIPE_WIDTH);
|
||||
let stripe_red = Rect((error_width,INFINITE.px()));
|
||||
let stripe_angle_rad = ERROR_PATTERN_STRIPE_ANGLE.radians();
|
||||
let pattern = stripe_red.repeat(repeat).rotate(stripe_angle_rad);
|
||||
let pattern_width = ERROR_BORDER_WIDTH.px();
|
||||
let mask = Rect((&width,&height)).corners_radius(&radius);
|
||||
let mask = mask.grow(pattern_width);
|
||||
let pattern = mask.intersection(pattern).fill(color_rgba);
|
||||
|
||||
pattern.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Crumbs ===
|
||||
// ==============
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub enum Endpoint { Input, Output }
|
||||
@ -180,6 +241,7 @@ ensogl::define_endpoints! {
|
||||
set_disabled (bool),
|
||||
set_input_connected (span_tree::Crumbs,Option<Type>,bool),
|
||||
set_expression (Expression),
|
||||
set_error (Option<Error>),
|
||||
/// Set the expression USAGE type. This is not the definition type, which can be set with
|
||||
/// `set_expression` instead. In case the usage type is set to None, ports still may be
|
||||
/// colored if the definition type was present.
|
||||
@ -273,15 +335,20 @@ impl Deref for Node {
|
||||
#[derive(Clone,CloneRef,Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct NodeModel {
|
||||
pub app : Application,
|
||||
pub display_object : display::object::Instance,
|
||||
pub logger : Logger,
|
||||
pub main_area : component::ShapeView<shape::Shape>,
|
||||
pub drag_area : component::ShapeView<drag_area::Shape>,
|
||||
pub input : input::Area,
|
||||
pub output : output::Area,
|
||||
pub visualization : visualization::Container,
|
||||
pub action_bar : action_bar::ActionBar,
|
||||
pub app : Application,
|
||||
pub display_object : display::object::Instance,
|
||||
pub logger : Logger,
|
||||
pub main_area : ShapeView<shape::Shape>,
|
||||
pub drag_area : ShapeView<drag_area::Shape>,
|
||||
pub error_indicator : ShapeView<error_shape::Shape>,
|
||||
// TODO: This extra text field should not be required after #1026 has been finished.
|
||||
// Instead we should get the error content as normal node output that is visible in the
|
||||
// visualisation. Alternatively it might be extended to use a preview of the new information.
|
||||
pub error_text : text::Area,
|
||||
pub input : input::Area,
|
||||
pub output : output::Area,
|
||||
pub visualization : visualization::Container,
|
||||
pub action_bar : action_bar::ActionBar,
|
||||
}
|
||||
|
||||
impl NodeModel {
|
||||
@ -292,10 +359,14 @@ impl NodeModel {
|
||||
edge::depth_sort_hack_1(scene);
|
||||
|
||||
output::area::depth_sort_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);
|
||||
let main_logger = Logger::sub(&logger,"main_area");
|
||||
let drag_logger = Logger::sub(&logger,"drag_area");
|
||||
let error_indicator_logger = Logger::sub(&logger,"error_indicator");
|
||||
|
||||
let error_indicator = ShapeView::<error_shape::Shape>::new(&error_indicator_logger,scene);
|
||||
let main_area = ShapeView::<shape::Shape>::new(&main_logger,scene);
|
||||
let drag_area = ShapeView::<drag_area::Shape>::new(&drag_logger,scene);
|
||||
let error_text = app.new_view::<text::Area>();
|
||||
edge::depth_sort_hack_2(scene);
|
||||
|
||||
input::area::depth_sort_hack(scene); // FIXME hack for sorting
|
||||
@ -303,6 +374,12 @@ impl NodeModel {
|
||||
let display_object = display::object::Instance::new(&logger);
|
||||
display_object.add_child(&drag_area);
|
||||
display_object.add_child(&main_area);
|
||||
display_object.add_child(&error_indicator);
|
||||
display_object.add_child(&error_text);
|
||||
|
||||
error_text.set_default_color.emit(color::Rgba::red());
|
||||
error_text.set_default_text_size(TextSize::from(TEXT_SIZE));
|
||||
|
||||
|
||||
// Disable shadows to allow interaction with the output port.
|
||||
let shape_system = scene.shapes.shape_system(PhantomData::<shape::Shape>);
|
||||
@ -325,8 +402,8 @@ impl NodeModel {
|
||||
display_object.add_child(&output);
|
||||
|
||||
let app = app.clone_ref();
|
||||
Self {app,display_object,logger,main_area,drag_area,output,input,visualization,action_bar}
|
||||
.init()
|
||||
Self {app,display_object,logger,main_area,drag_area,output,input,visualization,action_bar,
|
||||
error_indicator,error_text}.init()
|
||||
}
|
||||
|
||||
pub fn get_crumbs_by_id(&self, id:ast::Id) -> Option<Crumbs> {
|
||||
@ -366,9 +443,14 @@ impl NodeModel {
|
||||
let padded_size = size + Vector2(PADDING,PADDING) * 2.0;
|
||||
self.main_area.shape.sprite.size.set(padded_size);
|
||||
self.drag_area.shape.sprite.size.set(padded_size);
|
||||
self.error_indicator.shape.sprite.size.set(padded_size);
|
||||
self.main_area.mod_position(|t| t.x = width/2.0);
|
||||
self.drag_area.mod_position(|t| t.x = width/2.0);
|
||||
|
||||
self.error_indicator.set_position_x(width/2.0);
|
||||
self.error_text.set_position_y(height + TEXT_SIZE);
|
||||
self.error_text.set_position_x(CORNER_RADIUS);
|
||||
|
||||
let action_bar_width = ACTION_BAR_WIDTH;
|
||||
self.action_bar.mod_position(|t| {
|
||||
t.x = width + CORNER_RADIUS + action_bar_width / 2.0;
|
||||
@ -380,6 +462,12 @@ impl NodeModel {
|
||||
pub fn visualization(&self) -> &visualization::Container {
|
||||
&self.visualization
|
||||
}
|
||||
|
||||
fn set_error(&self, error:Option<&Error>) {
|
||||
let message = error.map(|t| t.message.clone()).unwrap_or_default();
|
||||
self.error_text.set_content.emit(message);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
@ -390,9 +478,10 @@ impl Node {
|
||||
let model = Rc::new(NodeModel::new(app,registry));
|
||||
let selection = Animation::<f32>::new(network);
|
||||
|
||||
let bg_color_anim = color::Animation::new(network);
|
||||
let style = StyleWatch::new(&app.display.scene().style_sheet);
|
||||
let action_bar = &model.action_bar.frp;
|
||||
let bg_color_anim = color::Animation::new(network);
|
||||
let error_color_anim = color::Animation::new(network);
|
||||
let style = StyleWatch::new(&app.display.scene().style_sheet);
|
||||
let action_bar = &model.action_bar.frp;
|
||||
|
||||
frp::extend! { network
|
||||
|
||||
@ -450,6 +539,25 @@ impl Node {
|
||||
eval out.hover ((t) action_bar.set_visibility(t));
|
||||
|
||||
|
||||
// === Set Node Error ===
|
||||
|
||||
error_color_anim.target <+ frp.set_error.map(f!((error) {
|
||||
model.set_error(error.as_ref());
|
||||
match error.as_ref() {
|
||||
Some(error) => {
|
||||
let error_data:visualization::Data = error.clone().into();
|
||||
model.visualization.frp.set_data.emit(error_data);
|
||||
color::Lcha::from(color::Rgba::red())
|
||||
},
|
||||
None => color::Lcha::transparent(),
|
||||
}
|
||||
}));
|
||||
|
||||
eval error_color_anim.value((value)
|
||||
model.error_indicator.shape.color_rgba.set(color::Rgba::from(value).into());
|
||||
);
|
||||
|
||||
|
||||
// === Color Handling ===
|
||||
|
||||
bg_color <- frp.set_disabled.map(f!([model,style](disabled) {
|
||||
@ -464,6 +572,7 @@ impl Node {
|
||||
);
|
||||
}
|
||||
|
||||
frp.set_error.emit(None);
|
||||
frp.set_disabled.emit(false);
|
||||
Self {frp,model}
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
//! Contains a struct definition for error information on nodes.
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::component::node::visualization;
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Error ===
|
||||
// =============
|
||||
|
||||
/// Error information to be displayed on a node.
|
||||
///
|
||||
/// Note:
|
||||
/// Tis is a dummy implementation that can and should be extended once we have the proper
|
||||
/// error information from the language server.
|
||||
/// See [issue #1026](https://github.com/enso-org/ide/issues/1026) for more information.
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct Error {
|
||||
/// The error message to show on the node.
|
||||
pub message:String,
|
||||
}
|
||||
|
||||
impl From<Error> for visualization::Data {
|
||||
fn from(error:Error) -> visualization::Data {
|
||||
let content = serde_json::Value::String(error.message).into();
|
||||
visualization::Data::Json {content}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(message:String) -> Error {
|
||||
Self{message}
|
||||
}
|
||||
}
|
@ -362,6 +362,8 @@ ensogl::define_endpoints! {
|
||||
stop_editing(),
|
||||
/// Remove all nodes from the graph.
|
||||
collapse_selected_nodes(),
|
||||
/// Indicate whether this node had an error or not.
|
||||
set_node_error_status(NodeId,Option<node::error::Error>),
|
||||
|
||||
|
||||
// === Visualization ===
|
||||
@ -2424,11 +2426,26 @@ fn new_graph_editor(app:&Application) -> GraphEditor {
|
||||
set_node_expression_string <- inputs.set_node_expression.map(|(id,expr)| (*id,expr.code.clone()));
|
||||
out.source.node_expression_set <+ set_node_expression_string;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// === Set Node Error ===
|
||||
frp::extend! { network
|
||||
|
||||
eval inputs.set_node_error_status([model]((node_id, error)) {
|
||||
if let Some(node) = model.nodes.get_cloned_ref(node_id) {
|
||||
node.set_error.emit(error)
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === Move Nodes ===
|
||||
// ==================
|
||||
frp::extend! { network
|
||||
|
||||
mouse_pos <- mouse.position.map(|p| Vector2(p.x,p.y));
|
||||
|
||||
|
@ -105,15 +105,23 @@ fn init(app:&Application) {
|
||||
|
||||
let node1_id = graph_editor.add_node();
|
||||
let node2_id = graph_editor.add_node();
|
||||
let node3_id = graph_editor.add_node();
|
||||
|
||||
graph_editor.frp.set_node_position.emit((node1_id,Vector2(-150.0,50.0)));
|
||||
graph_editor.frp.set_node_position.emit((node2_id,Vector2(50.0,50.0)));
|
||||
graph_editor.frp.set_node_position.emit((node3_id,Vector2(150.0,250.0)));
|
||||
|
||||
|
||||
let expression_1 = expression_mock();
|
||||
graph_editor.frp.set_node_expression.emit((node1_id,expression_1.clone()));
|
||||
let expression_2 = expression_mock3();
|
||||
graph_editor.frp.set_node_expression.emit((node2_id,expression_2.clone()));
|
||||
|
||||
let expression_3 = expression_mock2();
|
||||
graph_editor.frp.set_node_expression.emit((node3_id,expression_3));
|
||||
let error = "Runtime Error".to_string().into();
|
||||
graph_editor.frp.set_node_error_status.emit((node3_id,Some(error)));
|
||||
|
||||
|
||||
// === Connections ===
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user