Migrate the Select tool to the document graph (#1433)

* function for accessing document metadata

* Better select tool

* Fix render

* Fix transforms

* Fix loading saved documents

* Populate graph UI when loading autosave

* Multiple transform nodes

* Fix deep select

* Graph tooltips

* Fix flip axis icon

* Show disabled widgets

* Stop select tool from selecting artboards

* Disable (not hide) the pivot widget; remove Deep/Shallow select for now

* Code review changes

* Fix pivot position with select tool

* Fix incorrectly selected layers when shift clicking

---------

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2023-10-17 11:09:32 -07:00
committed by Keavon Chambers
co-authored by Dennis Kobert Keavon Chambers
parent e1cdb2242d
commit 5827e989dc
46 changed files with 1041 additions and 1215 deletions
+181 -28
View File
@@ -3,15 +3,17 @@ use graphene_core::renderer::ClickTarget;
use std::collections::HashMap;
use std::num::NonZeroU64;
use graph_craft::document::{NodeId, NodeNetwork};
use graph_craft::document::{DocumentNode, NodeId, NodeNetwork};
use graphene_core::renderer::Quad;
#[derive(Debug, Clone)]
pub struct DocumentMetadata {
transforms: HashMap<LayerNodeIdentifier, DAffine2>,
upstream_transforms: HashMap<NodeId, DAffine2>,
structure: HashMap<LayerNodeIdentifier, NodeRelations>,
click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
selected_nodes: Vec<NodeId>,
/// Transform from document space to viewport space.
pub document_to_viewport: DAffine2,
}
@@ -20,13 +22,17 @@ impl Default for DocumentMetadata {
fn default() -> Self {
Self {
transforms: HashMap::new(),
upstream_transforms: HashMap::new(),
click_targets: HashMap::new(),
structure: HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]),
selected_nodes: Vec::new(),
document_to_viewport: DAffine2::IDENTITY,
}
}
}
pub struct SelectionChanged;
// layer iters
impl DocumentMetadata {
/// Get the root layer from the document
pub const fn root(&self) -> LayerNodeIdentifier {
@@ -38,7 +44,7 @@ impl DocumentMetadata {
}
pub fn selected_layers(&self) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.all_layers()
self.all_layers().filter(|layer| self.selected_nodes.contains(&layer.to_node()))
}
pub fn selected_layers_contains(&self, layer: LayerNodeIdentifier) -> bool {
@@ -46,7 +52,19 @@ impl DocumentMetadata {
}
pub fn selected_visible_layers(&self) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.all_layers()
self.selected_layers()
}
pub fn selected_nodes(&self) -> core::slice::Iter<'_, NodeId> {
self.selected_nodes.iter()
}
pub fn selected_nodes_ref(&self) -> &Vec<NodeId> {
&self.selected_nodes
}
pub fn has_selected_nodes(&self) -> bool {
!self.selected_nodes.is_empty()
}
/// Access the [`NodeRelations`] of a layer
@@ -59,35 +77,125 @@ impl DocumentMetadata {
self.structure.entry(node_identifier).or_default()
}
/// Update the cached transforms of the layers
pub fn update_transforms(&mut self, new_transforms: HashMap<LayerNodeIdentifier, DAffine2>) {
self.transforms = new_transforms;
pub fn shallowest_unique_layers<'a>(&self, layers: impl Iterator<Item = &'a LayerNodeIdentifier>) -> Vec<Vec<LayerNodeIdentifier>> {
let mut sorted_layers = layers
.map(|layer| {
let mut layer_path = layer.ancestors(self).collect::<Vec<_>>();
layer_path.reverse();
layer_path
})
.collect::<Vec<_>>();
sorted_layers.sort();
// Sorting here creates groups of similar UUID paths
sorted_layers.dedup_by(|a, b| a.starts_with(b));
sorted_layers
}
}
// selected layer modifications
impl DocumentMetadata {
#[must_use]
pub fn retain_selected_nodes(&mut self, f: impl FnMut(&NodeId) -> bool) -> SelectionChanged {
self.selected_nodes.retain(f);
SelectionChanged
}
#[must_use]
pub fn set_selected_nodes(&mut self, new: Vec<NodeId>) -> SelectionChanged {
self.selected_nodes = new;
SelectionChanged
}
#[must_use]
pub fn add_selected_nodes(&mut self, iter: impl IntoIterator<Item = NodeId>) -> SelectionChanged {
self.selected_nodes.extend(iter);
SelectionChanged
}
#[must_use]
pub fn clear_selected_nodes(&mut self) -> SelectionChanged {
self.set_selected_nodes(Vec::new())
}
/// Loads the structure of layer nodes from a node graph.
pub fn load_structure(&mut self, graph: &NodeNetwork) {
self.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]);
let id = graph.outputs[0].node_id;
let Some(output_node) = graph.nodes.get(&id) else {
return;
};
let Some((layer_node, node_id)) = first_child_layer(graph, output_node, id) else {
return;
};
let parent = LayerNodeIdentifier::ROOT;
let mut stack = vec![(layer_node, node_id, parent)];
while let Some((node, id, parent)) = stack.pop() {
let mut current = Some((node, id));
while let Some(&(current_node, current_id)) = current.as_ref() {
let current_identifier = LayerNodeIdentifier::new_unchecked(current_id);
if !self.structure.contains_key(&current_identifier) {
parent.push_child(self, current_identifier);
if let Some((child_node, child_id)) = first_child_layer(graph, current_node, current_id) {
stack.push((child_node, child_id, current_identifier));
}
}
current = sibling_below(graph, current_node, current_id);
}
}
}
}
fn first_child_layer<'a>(graph: &'a NodeNetwork, node: &DocumentNode, id: NodeId) -> Option<(&'a DocumentNode, NodeId)> {
graph.primary_flow_from_opt(Some(node.inputs[0].as_node()?)).find(|(node, _)| node.name == "Layer")
}
fn sibling_below<'a>(graph: &'a NodeNetwork, node: &DocumentNode, id: NodeId) -> Option<(&'a DocumentNode, NodeId)> {
node.inputs[7].as_node().and_then(|id| graph.nodes.get(&id).filter(|node| node.name == "Layer").map(|node| (node, id)))
}
// transforms
impl DocumentMetadata {
/// Update the cached transforms of the layers
pub fn update_transforms(&mut self, new_transforms: HashMap<LayerNodeIdentifier, DAffine2>, new_upstream_transforms: HashMap<NodeId, DAffine2>) {
self.transforms = new_transforms;
self.upstream_transforms = new_upstream_transforms;
}
/// Access the cached transformation to document space from layer space
pub fn transform_to_document(&self, layer: LayerNodeIdentifier) -> DAffine2 {
self.transforms.get(&layer).copied().unwrap_or_else(|| {
warn!("Tried to access transform of bad layer {layer:?}");
DAffine2::IDENTITY
})
}
pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
self.document_to_viewport * self.transform_to_document(layer)
}
pub fn upstream_transform(&self, node_id: NodeId) -> DAffine2 {
self.upstream_transforms.get(&node_id).copied().unwrap_or(DAffine2::IDENTITY)
}
}
fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
network.primary_flow_from_opt(Some(layer.to_node())).any(|(node, _)| node.name == "Artboard")
}
// click targets
impl DocumentMetadata {
/// Update the cached click targets of the layers
pub fn update_click_targets(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>) {
self.click_targets = new_click_targets;
}
/// Access the cached transformation from document space to layer space
pub fn transform_from_document(&self, layer: LayerNodeIdentifier) -> DAffine2 {
self.transforms.get(&layer).copied().unwrap_or_else(|| {
warn!("Tried to access transform of bad layer");
DAffine2::IDENTITY
})
}
pub fn transform_from_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
self.document_to_viewport * self.transform_from_document(layer)
}
/// Runs an intersection test with all layers and a viewport space quad
pub fn intersect_quad(&self, viewport_quad: Quad) -> Option<LayerNodeIdentifier> {
pub fn intersect_quad<'a>(&'a self, viewport_quad: Quad, network: &'a NodeNetwork) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
let document_quad = self.document_to_viewport.inverse() * viewport_quad;
self.root()
.decendants(self)
.filter(|&layer| !is_artboard(layer, network))
.filter_map(|layer| self.click_targets.get(&layer).map(|targets| (layer, targets)))
.find(|(layer, target)| target.iter().any(|target| target.intersect_rectangle(document_quad, self.transform_from_document(*layer))))
.filter(move |(layer, target)| target.iter().any(move |target| target.intersect_rectangle(document_quad, self.transform_to_document(*layer))))
.map(|(layer, _)| layer)
}
@@ -97,13 +205,13 @@ impl DocumentMetadata {
self.root()
.decendants(self)
.filter_map(|layer| self.click_targets.get(&layer).map(|targets| (layer, targets)))
.filter(move |(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.transform_from_document(*layer))))
.filter(move |(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.transform_to_document(*layer))))
.map(|(layer, _)| layer)
}
/// Find the layer that has been clicked on from a viewport space location
pub fn click(&self, viewport_location: DVec2) -> Option<LayerNodeIdentifier> {
self.click_xray(viewport_location).next()
pub fn click(&self, viewport_location: DVec2, network: &NodeNetwork) -> Option<LayerNodeIdentifier> {
self.click_xray(viewport_location).filter(|&layer| !is_artboard(layer, network)).next()
}
/// Get the bounding box of the click target of the specified layer in the specified transform space
@@ -115,14 +223,47 @@ impl DocumentMetadata {
.reduce(Quad::combine_bounds)
}
/// Calculate the corners of the bounding box but with a nonzero size.
///
/// If the layer bounds are `0` in either axis then they are changed to be `1`.
pub fn nonzero_bounding_box(&self, layer: LayerNodeIdentifier) -> [DVec2; 2] {
let [bounds_min, mut bounds_max] = self.bounding_box_with_transform(layer, DAffine2::IDENTITY).unwrap_or_default();
let bounds_size = bounds_max - bounds_min;
if bounds_size.x < 1e-10 {
bounds_max.x = bounds_min.x + 1.;
}
if bounds_size.y < 1e-10 {
bounds_max.y = bounds_min.y + 1.;
}
[bounds_min, bounds_max]
}
/// Get the bounding box of the click target of the specified layer in document space
pub fn bounding_box_document(&self, layer: LayerNodeIdentifier) -> Option<[DVec2; 2]> {
self.bounding_box_with_transform(layer, self.transform_from_document(layer))
self.bounding_box_with_transform(layer, self.transform_to_document(layer))
}
/// Get the bounding box of the click target of the specified layer in viewport space
pub fn bounding_box_viewport(&self, layer: LayerNodeIdentifier) -> Option<[DVec2; 2]> {
self.bounding_box_with_transform(layer, self.transform_from_viewport(layer))
self.bounding_box_with_transform(layer, self.transform_to_viewport(layer))
}
pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.selected_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds)
}
/// Calculates the document bounds used for scrolling and centring (the layer bounds or the artboard (if applicable))
pub fn document_bounds(&self) -> Option<[DVec2; 2]> {
self.all_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds)
}
pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> graphene_core::vector::Subpath {
let Some(click_targets) = self.click_targets.get(&layer) else {
return graphene_core::vector::Subpath::new();
};
graphene_core::vector::Subpath::from_bezier_rs(click_targets.iter().map(|click_target| &click_target.subpath))
}
}
@@ -242,7 +383,7 @@ impl LayerNodeIdentifier {
pub fn decendants(self, document_metadata: &DocumentMetadata) -> DecendantsIter {
DecendantsIter {
front: self.first_child(document_metadata),
back: self.last_child(document_metadata),
back: self.last_child(document_metadata).and_then(|child| child.last_children(document_metadata).last()),
document_metadata,
}
}
@@ -339,6 +480,17 @@ impl LayerNodeIdentifier {
pub fn exists(&self, document_metadata: &DocumentMetadata) -> bool {
document_metadata.get_relations(*self).is_some()
}
pub fn starts_with(&self, other: Self, document_metadata: &DocumentMetadata) -> bool {
self.ancestors(document_metadata).any(|parent| parent == other)
}
pub fn child_of_root(&self, document_metadata: &DocumentMetadata) -> Self {
self.ancestors(document_metadata)
.filter(|&layer| layer != LayerNodeIdentifier::ROOT)
.last()
.expect("There should be a layer before the root")
}
}
impl From<NodeId> for LayerNodeIdentifier {
@@ -457,7 +609,8 @@ fn test_tree() {
assert!(root.children(document_metadata).all(|child| child.parent(document_metadata) == Some(root)));
LayerNodeIdentifier::new_unchecked(6).delete(document_metadata);
LayerNodeIdentifier::new_unchecked(1).delete(document_metadata);
LayerNodeIdentifier::new_unchecked(9).push_child(document_metadata, LayerNodeIdentifier::new_unchecked(10));
assert_eq!(root.children(document_metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(), vec![2, 3, 4, 5, 9]);
assert_eq!(root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(), vec![2, 3, 4, 5, 9]);
assert_eq!(root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).rev().collect::<Vec<_>>(), vec![9, 5, 4, 3, 2]);
assert_eq!(root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(), vec![2, 3, 4, 5, 9, 10]);
assert_eq!(root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).rev().collect::<Vec<_>>(), vec![10, 9, 5, 4, 3, 2]);
}
+3 -2
View File
@@ -32,11 +32,12 @@ impl LayerData for ShapeLayer {
let layer_bounds = subpath.bounding_box().unwrap_or_default();
let transform = self.transform(transforms, render_data.view_mode);
let inverse = transform.inverse();
if !inverse.is_finite() {
if !transform.is_finite() || transform.matrix2.determinant() == 0. {
let _ = write!(svg, "<!-- SVG shape has an invalid transform -->");
return false;
}
let inverse = transform.inverse();
subpath.apply_affine(transform);
let transformed_bounds = subpath.bounding_box().unwrap_or_default();
-3
View File
@@ -3,13 +3,10 @@
extern crate log;
pub mod boolean_ops;
/// Contains constant values used by this crate.
pub mod consts;
pub mod document;
pub mod document_metadata;
/// Defines errors that can occur when using this crate.
pub mod error;
/// Utilities for computing intersections.
pub mod intersection;
pub mod layers;
pub mod operation;
-4
View File
@@ -11,9 +11,6 @@ pub enum DocumentResponse {
FolderChanged {
path: Vec<LayerId>,
},
AddSelectedLayer {
additional_layers: Vec<Vec<LayerId>>,
},
CreatedLayer {
path: Vec<LayerId>,
is_selected: bool,
@@ -38,7 +35,6 @@ impl fmt::Display for DocumentResponse {
match self {
DocumentResponse::DocumentChanged { .. } => write!(f, "DocumentChanged"),
DocumentResponse::FolderChanged { .. } => write!(f, "FolderChanged"),
DocumentResponse::AddSelectedLayer { .. } => write!(f, "AddSelectedLayer"),
DocumentResponse::CreatedLayer { .. } => write!(f, "CreatedLayer"),
DocumentResponse::LayerChanged { .. } => write!(f, "LayerChanged"),
DocumentResponse::DeletedLayer { .. } => write!(f, "DeleteLayer"),