Skip to content

Commit

Permalink
Creating a new node with the (+) button (#3278)
Browse files Browse the repository at this point in the history
[The Task](https://www.pivotaltracker.com/story/show/180887253)

A new (+) button on the left-bottom corner appeared. It may be clicked to open searcher in the middle of the scene, as an alternative to tab key.

https://user-images.githubusercontent.com/3919101/154514279-7972ed6a-0203-47cb-9a09-82dba948cf2f.mp4

# Important Notes
* The window_control_buttons::common was extracted to separate crate `ensogl-component-button` almost without change.
* This includes a severe refactoring of adding nodes in general in the Graph Editor. The whole responsibility of adding new nodes (and starting their editing) was moved to Graph Editor - the Project View only reacts for GE events to show searcher properly.
* The status bar was moved from the bottom-left corner to the middle-top of the scene. It does not collide with (+) button, and plays "notification" role anyway.
* The `interface` debug scene was buggy. The problem was with one expression's span-tree. When I replaced it, the scene works.
* I've removed "new searcher" API, as it is completely outdated.
* I've changed code owners of integration tests to GUI team, as it is the team writing mostly the integration tests (int rust)
  • Loading branch information
farmaazon authored Feb 24, 2022
1 parent 3858ae7 commit 0836ce7
Show file tree
Hide file tree
Showing 27 changed files with 1,181 additions and 654 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Cargo.lock @MichaelMauderer @4e6 @mwu-tow @farmaazon
Cargo.toml @MichaelMauderer @4e6 @mwu-tow @farmaazon
/lib/rust/ @MichaelMauderer @4e6 @mwu-tow @farmaazon
/lib/rust/ensogl/ @MichaelMauderer @wdanilo @farmaazon
/integration-test/ @MichaelMauderer @wdanilo @farmaazon

# Scala Libraries
/lib/scala/ @4e6
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

#### Visual Environment

- [Nodes can be added to the graph by clicking (+) button on the screen][3278].
The button is in the bottom-left corner. Node is added at the center or pushed
down if the center is already occupied by nodes.
- [Maximum zoom factor is limited to 1.0x if IDE is not in Debug Mode.][3273]
- [Debug Mode for Graph Editor can be activated/deactivated using a
shortcut.][3264] It allows access to a set of restricted features. See
Expand Down Expand Up @@ -76,6 +79,7 @@
[3259]: https://github.com/enso-org/enso/pull/3259
[3273]: https://github.com/enso-org/enso/pull/3273
[3276]: https://github.com/enso-org/enso/pull/3276
[3278]: https://github.com/enso-org/enso/pull/3278
[3283]: https://github.com/enso-org/enso/pull/3283
[3282]: https://github.com/enso-org/enso/pull/3282
[3285]: https://github.com/enso-org/enso/pull/3285
Expand Down
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/gui/src/presenter/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ impl Graph {
view.disable_visualization <+ disable_vis;

view.add_node <+ update_data.map(|update| update.count_nodes_to_add()).repeat();
added_node_update <- view.node_added.filter_map(f!((view_id)
added_node_update <- view.node_added.filter_map(f!(((view_id,_))
model.state.assign_node_view(*view_id)
));
init_node_expression <- added_node_update.filter_map(|update| Some((update.view_id?, update.expression.clone())));
Expand Down
13 changes: 8 additions & 5 deletions app/gui/src/presenter/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use crate::presenter::graph::ViewNodeId;

use enso_frp as frp;
use ide_view as view;
use ide_view::project::ComponentBrowserOpenReason;
use ide_view::project::SearcherParams;



// =============
Expand Down Expand Up @@ -65,15 +66,15 @@ impl Model {
}
}

fn setup_searcher_presenter(&self, way_of_opening_searcher: ComponentBrowserOpenReason) {
fn setup_searcher_presenter(&self, params: SearcherParams) {
let new_presenter = presenter::Searcher::setup_controller(
&self.logger,
self.ide_controller.clone_ref(),
self.controller.clone_ref(),
self.graph_controller.clone_ref(),
&self.graph,
self.view.clone_ref(),
way_of_opening_searcher,
params,
);
match new_presenter {
Ok(searcher) => {
Expand Down Expand Up @@ -185,8 +186,10 @@ impl Project {
let graph_view = &model.view.graph().frp;

frp::extend! { network
eval view.searcher_opened ((way_of_opening_searcher) {
model.setup_searcher_presenter(*way_of_opening_searcher)
eval view.searcher ([model](params) {
if let Some(params) = params {
model.setup_searcher_presenter(*params)
}
});

graph_view.remove_node <+ view.editing_committed.filter_map(f!([model]((node_view, entry)) {
Expand Down
34 changes: 7 additions & 27 deletions app/gui/src/presenter/searcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ use crate::presenter::graph::ViewNodeId;
use enso_frp as frp;
use ide_view as view;
use ide_view::graph_editor::component::node as node_view;
use ide_view::project::ComponentBrowserOpenReason;

use ide_view::project::SearcherParams;


// =============
Expand Down Expand Up @@ -153,18 +152,18 @@ impl Searcher {
graph_controller: controller::ExecutedGraph,
graph_presenter: &presenter::Graph,
view: view::project::View,
way_of_opening_searcher: ComponentBrowserOpenReason,
parameters: SearcherParams,
) -> FallibleResult<Self> {
let id = way_of_opening_searcher.node();
let ast_node = graph_presenter.ast_node_of_view(id);
let SearcherParams { input, source_node } = parameters;
let ast_node = graph_presenter.ast_node_of_view(input);
let mode = match ast_node {
Some(node_id) => controller::searcher::Mode::EditNode { node_id },
None => {
let view_data = view.graph().model.nodes.get_cloned_ref(&id);
let view_data = view.graph().model.nodes.get_cloned_ref(&input);
let position = view_data.map(|node| node.position().xy());
let position = position.map(|vector| model::module::Position { vector });
let source_node =
Self::source_node_ast_id(&view, graph_presenter, &way_of_opening_searcher);
source_node.and_then(|id| graph_presenter.ast_node_of_view(id.node));
controller::searcher::Mode::NewNode { position, source_node }
}
};
Expand All @@ -175,7 +174,7 @@ impl Searcher {
graph_controller,
mode,
)?;
Ok(Self::new(parent, searcher_controller, view, id))
Ok(Self::new(parent, searcher_controller, view, input))
}

/// Commit editing.
Expand All @@ -202,23 +201,4 @@ impl Searcher {
let entry = controller.actions().list().and_then(|l| l.get_cloned(entry));
entry.map_or(false, |e| matches!(e.action, Example(_)))
}

/// Return the AST id of the source node. Source node is either:
/// 1. The source node of the connection that was dropped to create a node.
/// 2. The first of the selected nodes on the scene.
fn source_node_ast_id(
view: &view::project::View,
graph_presenter: &presenter::Graph,
way_of_opening_searcher: &ComponentBrowserOpenReason,
) -> Option<Uuid> {
if let Some(edge_id) = way_of_opening_searcher.edge() {
let edge = view.graph().model.edges.get_cloned_ref(&edge_id);
let edge_source = edge.and_then(|edge| edge.source());
let source_node_id = edge_source.map(|source| source.node_id);
source_node_id.and_then(|id| graph_presenter.ast_node_of_view(id))
} else {
let selected_views = view.graph().model.nodes.all_selected();
selected_views.iter().find_map(|view| graph_presenter.ast_node_of_view(*view))
}
}
}
21 changes: 11 additions & 10 deletions app/gui/view/debug_scene/interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ fn init(app: &Application) {

// === Nodes ===

let node1_id = graph_editor.add_node();
let node2_id = graph_editor.add_node();
let node3_id = graph_editor.add_node();
let node1_id = graph_editor.model.add_node();
let node2_id = graph_editor.model.add_node();
let node3_id = graph_editor.model.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)));
Expand All @@ -144,18 +144,18 @@ fn init(app: &Application) {
let expression_2 = expression_mock3();
graph_editor.frp.set_node_expression.emit((node2_id, expression_2.clone()));

let expression_3 = expression_mock2();
let expression_3 = expression_mock3();
graph_editor.frp.set_node_expression.emit((node3_id, expression_3));
let kind = Immutable(graph_editor::component::node::error::Kind::Panic);
let message = Rc::new(Some("Runtime Error".to_owned()));
let propagated = Immutable(false);
let error = graph_editor::component::node::Error { kind, message, propagated };
graph_editor.frp.set_node_error_status.emit((node3_id, Some(error)));

let foo_node = graph_editor.add_node_below(node3_id);
let foo_node = graph_editor.model.add_node_below(node3_id);
graph_editor.set_node_expression.emit((foo_node, Expression::new_plain("foo")));

let baz_node = graph_editor.add_node_below(node3_id);
let baz_node = graph_editor.model.add_node_below(node3_id);
graph_editor.set_node_expression.emit((baz_node, Expression::new_plain("baz")));
let (_, baz_position) = graph_editor.node_position_set.value();
let styles = StyleWatch::new(&scene.style_sheet);
Expand All @@ -164,7 +164,7 @@ fn init(app: &Application) {
let gap_for_bar_node = min_spacing + gap_between_nodes + f32::EPSILON;
graph_editor.set_node_position((baz_node, baz_position + Vector2(gap_for_bar_node, 0.0)));

let bar_node = graph_editor.add_node_below(node3_id);
let bar_node = graph_editor.model.add_node_below(node3_id);
graph_editor.set_node_expression.emit((bar_node, Expression::new_plain("bar")));


Expand All @@ -178,9 +178,9 @@ fn init(app: &Application) {

// === VCS ===

let dummy_node_added_id = graph_editor.add_node();
let dummy_node_edited_id = graph_editor.add_node();
let dummy_node_unchanged_id = graph_editor.add_node();
let dummy_node_added_id = graph_editor.model.add_node();
let dummy_node_edited_id = graph_editor.model.add_node();
let dummy_node_unchanged_id = graph_editor.model.add_node();

graph_editor.frp.set_node_position.emit((dummy_node_added_id, Vector2(-450.0, 50.0)));
graph_editor.frp.set_node_position.emit((dummy_node_edited_id, Vector2(-450.0, 125.0)));
Expand Down Expand Up @@ -338,6 +338,7 @@ pub fn expression_mock() -> Expression {
Expression { pattern, code, whole_expression_id, input_span_tree, output_span_tree }
}

// TODO[ao] This expression mocks results in panic. If you want to use it, please fix it first.
pub fn expression_mock2() -> Expression {
let pattern = Some("var1".to_string());
let pattern_cr = vec![Seq { right: false }, Or, Or, Build];
Expand Down
1 change: 1 addition & 0 deletions app/gui/view/graph-editor/src/component.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Root module for graph component definitions.
pub mod add_node_button;
pub mod breadcrumbs;
pub mod edge;
pub mod node;
Expand Down
140 changes: 140 additions & 0 deletions app/gui/view/graph-editor/src/component/add_node_button.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//! A module containing definition of (+) button for adding nodes.
use ensogl_component::button::prelude::*;

use crate::display::camera::Camera2d;

use enso_frp as frp;
use ensogl::application::Application;
use ensogl::display;
use ensogl_hardcoded_theme::graph_editor::add_node_button as theme;


// =============
// === Shape ===
// =============

mod shape {
use super::*;

ensogl::define_shape_system! {
(style: Style, background_color:Vector4<f32>, icon_color:Vector4<f32>) {
let size = Var::canvas_size();
let shadow_size = style.get_number(ensogl_hardcoded_theme::shadow::size);
let radius = Min::min(size.x(),size.y()) / 2.0 - shadow_size.px();

let angle = Radians::from(90.0.degrees());
let bar_length = &radius * 4.0 / 3.0;
let bar_width = &bar_length / 10.0;
#[allow(clippy::blacklisted_name)] // The `bar` name here is totally legit.
let bar = Rect((bar_length, &bar_width));
let plus = (bar.rotate(angle) + bar).into();
let shape = shape(background_color, icon_color, plus, radius);
let shadow = ensogl_component::shadow::from_shape(shape.clone(), style);
(shadow + shape).into()
}
}
}

impl ButtonShape for shape::DynamicShape {
fn debug_name() -> &'static str {
"AddNodeButton"
}

fn background_color_path(state: State) -> StaticPath {
match state {
State::Unconcerned => theme::background,
State::Hovered => theme::hover::background,
State::Pressed => theme::click::background,
}
}

fn icon_color_path(state: State) -> StaticPath {
match state {
State::Unconcerned => theme::color,
State::Hovered => theme::hover::color,
State::Pressed => theme::click::color,
}
}

fn background_color(&self) -> &DynamicParam<Attribute<Vector4<f32>>> {
&self.background_color
}

fn icon_color(&self) -> &DynamicParam<Attribute<Vector4<f32>>> {
&self.icon_color
}
}



// =====================
// === AddNodeButton ===
// =====================

type View = ensogl_component::button::View<shape::DynamicShape>;

/// Add Node Button Component.
///
/// This is a button with + icon, which sticks to the left-bottom corner of the scene. It exposes
/// the FRP of EnsoGL Button Component, including the main "click" event.
#[derive(Clone, CloneRef, Debug)]
pub struct AddNodeButton {
network: frp::Network,
view: View,
style_watch: StyleWatchFrp,
}

impl Deref for AddNodeButton {
type Target = ensogl_component::button::Frp;
fn deref(&self) -> &Self::Target {
self.view.deref()
}
}

impl AddNodeButton {
/// Create new component.
pub fn new(app: &Application) -> Self {
let view = ensogl_component::button::View::new(app);
let network = frp::Network::new("AddNodeButton");
let scene = app.display.scene();
let camera = scene.camera();
let style_watch = StyleWatchFrp::new(&scene.style_sheet);

let size = style_watch.get_number(theme::size);
let shadow = style_watch.get_number(ensogl_hardcoded_theme::shadow::size);
let margin = style_watch.get_number(theme::margin);
frp::extend! { network
init <- source();
let camera_changed = scene.frp.camera_changed.clone_ref();
update_position <- all(init, camera_changed, size, margin);
eval update_position ([view, camera] (&((), (), size, margin)) {
Self::update_position(&view, &camera, size, margin);
});
update_size <- all(init, size, shadow);
view.set_size <+ update_size.map(|&((), size, shadow)| {
let view_size_1d = size + shadow * 2.0;
Vector2(view_size_1d, view_size_1d)
});
}

scene.layers.panel.add_exclusive(&view);
init.emit(());

Self { network, view, style_watch }
}

fn update_position(view: &View, camera: &Camera2d, size: f32, margin: f32) {
let screen = camera.screen();
let x = -screen.width / 2.0 + margin + size / 2.0;
let y = -screen.height / 2.0 + margin + size / 2.0;
view.set_position_x(x.round());
view.set_position_y(y.round());
}
}

impl display::Object for AddNodeButton {
fn display_object(&self) -> &display::object::Instance {
self.view.display_object()
}
}
Loading

0 comments on commit 0836ce7

Please sign in to comment.