Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Node searcher zoom & edited node growth/shrink animation #3327

Merged
merged 18 commits into from
Mar 17, 2022

Conversation

vitvakatu
Copy link
Contributor

@vitvakatu vitvakatu commented Mar 8, 2022

Pull Request Description

In this PR two things are implemented:

  1. Node Searcher zoom factor (and therefore its size) is fixed no matter how you move the main camera. The node searcher is also positioned directly below currently edited node at all times.
  2. Node growth/shrink animation when you start/finish node editing. After animation end the edited node zoom factor is also fixed and matches the zoom factor of the node searcher.

See attached video with different ways of editing/creating nodes:

2022-03-09.03-23-24.mp4

Technical details

  1. Added several additional scene layers for separate rendering: node_searcher, node_searcher_text, edited_node, edited_node_text. Searcher is always rendered by node_searcher camera, edited node moves between its usual layers and edited_node layer. Because text rendering uses different API, all node components were modified to support change of the layer.
  2. Also added node_searcher DOM layer, because documentation is implemented as a DOM object.
  3. Added two FRP endpoints for ensogl::Animation: on_end and set_value. These endpoints are useful while implementing growth/shrink animation.
  4. Added FRP endpoints for the Camera2d: position and zoom outputs. This allows to synchronize cameras easily using FRP networks.
  5. Growth/shrink animation implemented in GraphEditor by blending two animations, similar to Node Snapping implementation. However, shrinking animation is a bit tricky to implement correctly, as we must always return node back to the main scene layer after editing is done.

Important Notes

Checklist

Please include the following checklist in your PR:

  • The documentation has been updated if necessary.
  • All code conforms to the Scala, Java, and Rust style guides.
  • All code has been tested:
    • Unit tests have been written where possible.
    • If GUI codebase was changed: Enso GUI was tested when built using BOTH ./run dist and ./run watch.

@vitvakatu vitvakatu self-assigned this Mar 8, 2022
@vitvakatu vitvakatu force-pushed the wip/vitvakatu/searcher-zoom-181181221 branch from 86ef3f5 to 5c41a50 Compare March 8, 2022 10:35
@vitvakatu vitvakatu force-pushed the wip/vitvakatu/searcher-zoom-181181221 branch from 5c41a50 to 43c2920 Compare March 8, 2022 23:50
@vitvakatu vitvakatu marked this pull request as ready for review March 9, 2022 12:07
@@ -2596,6 +2612,59 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
}


// === Edited node growth/shrink animation ===
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic should be outsourced to a separate file to avoid further growing the graph editor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, moved this FRP code to a separate module in component/node hierarchy (where I feel it belongs).

@@ -1428,6 +1430,20 @@ impl GraphEditorModelWithNetwork {
node_id
}

/// Move node to the `edited_node` scene layer, so that it is rendered by the separate camera.
pub fn move_node_to_edited_node_layer(&self, node_id: NodeId) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this and the following method operate only on the nodes, they might be better suited to live in the Nodes struct directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These methods were pure helpers to avoid code repetition in FRP definition! We need access to GraphEditorModel because we need to access Nodes struct, and we can't implement growth/shrink animation inside component/node module, because it is implemented by moving a single edited_node camera, so this behavior can't be shared by all Nodes.

Now these functions are moved to a separate module.

app/gui/view/src/project.rs Outdated Show resolved Hide resolved
@wdanilo
Copy link
Member

wdanilo commented Mar 11, 2022

Very nice results!!!!

This PR contains some heavy code, I need more time to review it – will be reviewed tomorrow.

CHANGELOG.md Outdated
@@ -2,6 +2,7 @@

#### Visual Environment

- [Node Searcher preserves its zoom factor.][3327]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs additional explanation. Thunk about changelog as something read by users. They should understand what happened after reading these notes. The link to the task is "additional technical explanation", but without seeing it, it should already be clear.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added additional explanation, please take a look.

Comment on lines 541 to 571
fn set_layers(&self, layer: &Layer, text_layer: &Layer, action_bar_layer: &Layer) {
layer.add_exclusive(&self.display_object);
action_bar_layer.add_exclusive(&self.action_bar);
self.output.set_label_layer(text_layer);
self.input.set_label_layer(text_layer);
self.profiling_label.set_label_layer(text_layer);
self.comment.add_to_scene_layer(text_layer);
}

/// Move all sub-components to `edited_node` layer.
///
/// A simple [`Layer::add_exclusive`] wouldn't work because text rendering in ensogl uses a
/// separate layer management API.
pub fn move_to_edited_node_layer(&self) {
let scene = &self.app.display.default_scene;
let layer = &scene.layers.edited_node;
let text_layer = &scene.layers.edited_node_text;
let action_bar_layer = &scene.layers.edited_node;
self.set_layers(layer, text_layer, action_bar_layer);
}

/// Move all sub-components to `main` layer.
///
/// A simple [`Layer::add_exclusive`] wouldn't work because text rendering in ensogl uses a
/// separate layer management API.
pub fn move_to_main_layer(&self) {
let scene = &self.app.display.default_scene;
let layer = &scene.layers.main;
let text_layer = &scene.layers.label;
let action_bar_layer = &scene.layers.above_nodes;
self.set_layers(layer, text_layer, action_bar_layer);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs additional explanation in the documentation why action_bar_layer is handled in such a special way. This is not obvious from reading the code, so I'd be very thankful for making the docs a little bit more expanded here with clear explanation of which layer handles what in every scenario.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

action_bar is the only node component that uses a separate scene layer (above_nodes). I'm not aware of the exact reasons behind that. I added a note to the docs about that.

let main_cam_frp = main_cam.frp();


// === Starting node editing. ===
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double space + we are not using dots in section titles - in English, section titles are dotless sentences. Which is kind of stupid, but it is this way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!

Comment on lines 42 to 45
_eval <- all_with(&out.node_editing_started, &previous_edited_node, f!([model] (current, previous) {
move_node_to_main_layer(&model, *previous);
move_node_to_edited_node_layer(&model, *current);
}));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like these functions that take model as argument, as they clearly belong to model. This can be rewritten to:

_eval <- all_with(&previous_edited_node, &out.node_editing_started, f!((previous, current) {
    model.move_node_to_main_layer(previous);
    model.move_node_to_edited_node_layer(current);
}));

I know that model is in another module, but you can just extend it with a local trait in this module.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was written that way, but after moving to a separate module I changed methods to functions. Fixed it back now.

let weight = Vector3::from_element(*weight);
let inv_weight = Vector3::from_element(1.0) - weight;
target.component_mul(&weight) + animation.component_mul(&inv_weight)
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we format such lines this way instead, please?


edited_node_cam_position <- all_with3
    (&edited_node_cam_target, &growth_animation.value, &animation_blending.value, |target,animation,weight| {
        let weight = Vector3::from_element(*weight);
        let inv_weight = Vector3::from_element(1.0) - weight;
        target.component_mul(&weight) + animation.component_mul(&inv_weight)
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, done

let move_to_edited_node = searcher_pos * (1.0 - zoom);
preserve_zoom + move_to_edited_node
});
eval searcher_cam_pos ([searcher_cam] (pos) searcher_cam.set_position_xy(*pos));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need this part here [searcher_cam]. If the expression starts with foo.<something>, then foo will automatically be threaded as clone able thing by the f! macro (and the eval construct uses f! under the hood).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know, fixed!

Comment on lines 32 to 34
// on_end (inertia::EndStatus),
// on_end (),
// }
// }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an edit in a commented code. Is this on purpose? Why this code is commented in the first place?

Copy link
Contributor Author

@vitvakatu vitvakatu Mar 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this code. I suppose it lives here since prehistoric times.

Comment on lines 39 to 41
/// Default animation precision.
pub const DEFAULT_PRECISION: f32 = 0.001;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doc does not explain what it is at all. After reading it, I still dont understand what it does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added additional docs

Comment on lines 76 to 80
set_value <- any_mut::<T>();
precision <- any_mut::<f32>();
skip <- any_mut::<()>();
eval target ((t) simulator.set_target_value(mix::into_space(t.clone())));
eval set_value ((t) simulator.set_value(mix::into_space(t.clone())));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the idea for the ability to set the value of the physics-based animator. It can make the animations jumpy. It was not exposed on purpose. Can we have a fast call why this is needed and if there is a way not to expose it, please?

Copy link
Contributor Author

@vitvakatu vitvakatu Mar 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh well, actually it turned out that I'm not using it anywhere :) It was used in some previous versions of the code. Removed it

lib/rust/ensogl/core/src/display/scene.rs Outdated Show resolved Hide resolved
@vitvakatu vitvakatu requested a review from wdanilo March 14, 2022 13:56
@vitvakatu vitvakatu requested review from wdanilo and removed request for MichaelMauderer and wdanilo March 16, 2022 15:08
app/gui/view/src/project.rs Outdated Show resolved Hide resolved
lib/rust/ensogl/core/src/animation/frp/animation.rs Outdated Show resolved Hide resolved
@4e6 4e6 added the CI: Ready to merge This PR is eligible for automatic merge label Mar 17, 2022
@mergify mergify bot merged commit cdcc852 into develop Mar 17, 2022
@mergify mergify bot deleted the wip/vitvakatu/searcher-zoom-181181221 branch March 17, 2022 10:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI: Ready to merge This PR is eligible for automatic merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants