diff --git a/gui/src/rust/ensogl/src/data/color/data.rs b/gui/src/rust/ensogl/src/data/color/data.rs index fb46706e8e3..91cd8f6802d 100644 --- a/gui/src/rust/ensogl/src/data/color/data.rs +++ b/gui/src/rust/ensogl/src/data/color/data.rs @@ -108,14 +108,14 @@ where Color : HasComponents { } impl Into> for &Color - where Color : Copy + HasComponents { +where Color : Copy + HasComponents { fn into(self) -> Vector3 { Into::>::into((*self).into_components()) } } impl Into> for &Color - where Color : Copy + HasComponents { +where Color : Copy + HasComponents { fn into(self) -> Vector4 { Into::>::into((*self).into_components()) } diff --git a/gui/src/rust/ensogl/src/math/algebra.rs b/gui/src/rust/ensogl/src/math/algebra.rs index 92eda071c7c..0a3ed8dfefa 100644 --- a/gui/src/rust/ensogl/src/math/algebra.rs +++ b/gui/src/rust/ensogl/src/math/algebra.rs @@ -432,7 +432,7 @@ macro_rules! define_vector { /// Smart constructor. #[allow(non_snake_case)] - pub fn $name($($field:T),*) -> $name { + pub const fn $name($($field:T),*) -> $name { $name {$($field),*} } diff --git a/gui/src/rust/lib/graph-editor/src/component/cursor.rs b/gui/src/rust/lib/graph-editor/src/component/cursor.rs index 1003e552900..3df2071f219 100644 --- a/gui/src/rust/lib/graph-editor/src/component/cursor.rs +++ b/gui/src/rust/lib/graph-editor/src/component/cursor.rs @@ -15,6 +15,17 @@ use ensogl::gui::component; +// ================= +// === Constants === +// ================= + +const DEFAULT_SIZE : V2 = V2(16.0,16.0); +const DEFAULT_RADIUS : f32 = 8.0; +const DEFAULT_COLOR_LAB : V3 = V3(1.0,0.0,0.0); +const DEFAULT_COLOR_ALPHA : f32 = 0.2; + + + // ================== // === StyleValue === // ================== @@ -58,7 +69,7 @@ impl StyleValue { // === Style === // ============= -macro_rules! define_style {( $($field:ident : $field_type:ty),* $(,)? ) => { +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 @@ -66,7 +77,7 @@ macro_rules! define_style {( $($field:ident : $field_type:ty),* $(,)? ) => { /// it in FRP. #[derive(Debug,Clone,Default)] pub struct Style { - $($field : $field_type),* + $($(#$meta)? $field : Option<$field_type>),* } impl PartialSemigroup<&Style> for Style { @@ -84,12 +95,15 @@ macro_rules! define_style {( $($field:ident : $field_type:ty),* $(,)? ) => { };} define_style! { - host : Option, - size : Option>>, - offset : Option>>, - color : Option>, - radius : Option>, - press : Option>, + /// 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. + host : display::object::Instance, + size : StyleValue>, + offset : StyleValue>, + color : StyleValue, + radius : StyleValue, + press : StyleValue, } @@ -117,8 +131,9 @@ impl Style { } pub fn new_box_selection(size:Vector2) -> Self { - let offset = Some(StyleValue::new_no_animation(-size / 2.0)); - let size = Some(StyleValue::new_no_animation(size.abs() + Vector2::new(16.0,16.0))); + let def_size = Vector2::new(DEFAULT_SIZE.x,DEFAULT_SIZE.y); + let offset = Some(StyleValue::new_no_animation(-size / 2.0)); + let size = Some(StyleValue::new_no_animation(size.abs() + def_size)); Self {size,offset,..default()} } @@ -166,12 +181,13 @@ pub mod shape { ) { let width : Var> = "input_size.x".into(); let height : Var> = "input_size.y".into(); - let press_diff = 2.px() * &press; - let radius = 1.px() * radius - &press_diff; - let selection_width = 1.px() * &selection_size.x(); - let selection_height = 1.px() * &selection_size.y(); - let width = (&width - &press_diff * 2.0) + selection_width.abs(); - let height = (&height - &press_diff * 2.0) + selection_height.abs(); + let press_side_shrink = 2.px(); + let press_diff = press_side_shrink * &press; + let radius = 1.px() * radius - &press_diff; + let selection_width = 1.px() * &selection_size.x(); + let selection_height = 1.px() * &selection_size.y(); + let width = (&width - &press_diff * 2.0) + selection_width.abs(); + let height = (&height - &press_diff * 2.0) + selection_height.abs(); let cursor = Rect((width,height)) .corners_radius(radius) .fill("srgba(input_color)"); @@ -243,7 +259,7 @@ impl CursorModel { let logger = Logger::new("cursor"); let frp = FrpInputs::new(network); let view = component::ShapeView::::new(&logger,&scene); - let style = Rc::new(RefCell::new(Style::default())); + let style = default(); let shape_system = scene.shapes.shape_system(PhantomData::); shape_system.shape_system.set_pointer_events(false); @@ -279,16 +295,16 @@ impl Cursor { // === Animations === // - // The following animators are used for smooth cursor transitions. There are two components + // The following animators are used for smooth cursor transitions. There are two of them // with a non-obvious behavior, namely the `host_follow_weight` and `host_attached_weight`. - // The mouse position can be in three stages: + // The mouse position is driven by three factors: // // - Real-time cursor mode. // Cursor follows the system mouse position. // // - Host-follow mode. - // Cursor follows the host using dynamic simulator. The `host_follow_weight` variable is - // a weight between real-time mode and this one. + // Cursor follows the host using dynamic inertia simulator. The `host_follow_weight` + // variable is a weight between real-time mode and this one. // // - Host-attached mode. // Cursor follows the host without any delay. The `host_attached_weight` variable is a @@ -307,17 +323,11 @@ impl Cursor { let host_follow_weight = Animation :: :: new(&network); let host_attached_weight = Tween :: new(&network); - let default_size = V2(16.0,16.0); - let default_radius = 8.0; - let default_lab = V3(1.0,0.0,0.0); - let default_alpha = 0.2; - host_attached_weight.set_duration(300.0); - color_lab.set_target_value(default_lab); - color_alpha.set_target_value(default_alpha); - radius.set_target_value(default_radius); - size.set_target_value(default_size); - + color_lab.set_target_value(DEFAULT_COLOR_LAB); + color_alpha.set_target_value(DEFAULT_COLOR_ALPHA); + radius.set_target_value(DEFAULT_RADIUS); + size.set_target_value(DEFAULT_SIZE); frp::extend! { network eval press.value ((v) model.view.shape.press.set(*v)); @@ -344,8 +354,8 @@ impl Cursor { match &new_style.color { None => { - color_lab.set_target_value(default_lab); - color_alpha.set_target_value(default_alpha); + color_lab.set_target_value(DEFAULT_COLOR_LAB); + color_alpha.set_target_value(DEFAULT_COLOR_ALPHA); } Some(new_color) => { let lab = color::Laba::from(new_color.value); @@ -359,7 +369,7 @@ impl Cursor { } match &new_style.size { - None => size.set_target_value(default_size), + None => size.set_target_value(DEFAULT_SIZE), Some(new_size) => { size.set_target_value(V2::new(new_size.value.x,new_size.value.y)); if !new_size.animate { size.skip() } @@ -375,7 +385,7 @@ impl Cursor { } match &new_style.radius { - None => radius.set_target_value(default_radius), + None => radius.set_target_value(DEFAULT_RADIUS), Some(new_radius) => { radius.set_target_value(new_radius.value); if !new_radius.animate { radius.skip() } diff --git a/gui/src/rust/lib/graph-editor/src/lib.rs b/gui/src/rust/lib/graph-editor/src/lib.rs index 2efdecb3002..282ff2ed83e 100644 --- a/gui/src/rust/lib/graph-editor/src/lib.rs +++ b/gui/src/rust/lib/graph-editor/src/lib.rs @@ -211,9 +211,6 @@ impl SharedHashMap { - - - #[derive(Debug,Clone,CloneRef)] pub struct Frp { pub inputs : FrpInputs, @@ -478,19 +475,20 @@ macro_rules! generate_frp_outputs { generate_frp_outputs! { - node_added : NodeId, - node_removed : NodeId, - node_selected : NodeId, - node_deselected : NodeId, - node_position_set : (NodeId,Position), - node_expression_set : (NodeId,node::Expression), + node_added : NodeId, + node_removed : NodeId, + node_selected : NodeId, + node_deselected : NodeId, + node_position_set : (NodeId,Position), + node_position_set_batched : (NodeId,Position), + node_expression_set : (NodeId,node::Expression), - edge_added : EdgeId, - edge_removed : EdgeId, - edge_source_set : (EdgeId,EdgeTarget), - edge_target_set : (EdgeId,EdgeTarget), - edge_source_unset : EdgeId, - edge_target_unset : EdgeId, + edge_added : EdgeId, + edge_removed : EdgeId, + edge_source_set : (EdgeId,EdgeTarget), + edge_target_set : (EdgeId,EdgeTarget), + edge_source_unset : EdgeId, + edge_target_unset : EdgeId, some_edge_targets_detached : (), all_edge_targets_attached : (), @@ -1042,6 +1040,14 @@ impl GraphEditorModel { } } + pub fn node_position(&self, node_id:impl Into) -> Position { + let node_id = node_id.into(); + self.nodes.get_cloned_ref(&node_id).map(|node| { + let v_pos = node.position(); + frp::Position::new(v_pos.x, v_pos.y) + }).unwrap_or_default() + } + pub fn node_pos_mod (&self, node_id:impl Into, pos_diff:impl Into) -> (NodeId,Position) { let node_id = node_id.into(); @@ -1397,7 +1403,8 @@ fn new_graph_editor(world:&World) -> GraphEditor { outputs.node_added <+ new_node; node_with_position <- add_node_at_cursor.map3(&new_node,&mouse.position,|_,id,pos| (*id,*pos)); - outputs.node_position_set <+ node_with_position; + outputs.node_position_set <+ node_with_position; + outputs.node_position_set_batched <+ node_with_position; cursor_style <- all [ cursor_selection @@ -1481,16 +1488,26 @@ fn new_graph_editor(world:&World) -> GraphEditor { node_drag <- mouse.translation.gate(&touch.nodes.is_down); was_selected <- touch.nodes.down.map(f!((id) model.nodes.selected.contains(id))); tx_sel_nodes <- any (node_drag, inputs.translate_selected_nodes); - non_selected_drag <- tx_sel_nodes.map2(&touch.nodes.down,|_,id|*id).gate_not(&was_selected); - selected_drag <= tx_sel_nodes.map(f_!(model.nodes.selected.keys())).gate(&was_selected); + non_selected_drag <- tx_sel_nodes.map2(&touch.nodes.down,|_,id|vec![*id]).gate_not(&was_selected); + selected_drag <- tx_sel_nodes.map(f_!(model.nodes.selected.keys())).gate(&was_selected); nodes_to_drag <- any (non_selected_drag, selected_drag); - nodes_new_pos <- nodes_to_drag.map2(&tx_sel_nodes,f!((id,tx) model.node_pos_mod(id,tx))); - outputs.node_position_set <+ nodes_new_pos; + node_to_drag <= nodes_to_drag; + node_new_pos <- node_to_drag.map2(&tx_sel_nodes,f!((id,tx) model.node_pos_mod(id,tx))); + outputs.node_position_set <+ node_new_pos; + + was_drag_false <- touch.nodes.down.constant(false); + was_drag_true <- node_drag.constant(true); + was_drag <- any (was_drag_false,was_drag_true); + drag_finish <- touch.nodes.up.gate(&was_drag); + dragged_node <= nodes_to_drag.sample(&drag_finish); + dragged_node_pos <- dragged_node.map(f!([model] (id) (*id,model.node_position(id)))); + outputs.node_position_set_batched <+ dragged_node_pos; // === Set Node Position === - outputs.node_position_set <+ inputs.set_node_position; + outputs.node_position_set <+ inputs.set_node_position; + outputs.node_position_set_batched <+ inputs.set_node_position; eval outputs.node_position_set (((id,pos)) model.set_node_position(id,pos));