Fix hiding and collapsing layers (#1481)

* Hide and collapse layers

* Reorder imports

* Fix Ctrl+H shortcut advertized action and hotkey tooltip; improve graph top right of options bar

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2023-11-27 02:27:11 +00:00
committed by GitHub
parent 6d9dd5fc27
commit 5ee79031ab
22 changed files with 164 additions and 123 deletions

View File

@@ -1,4 +1,4 @@
use crate::document_metadata::DocumentMetadata;
use crate::document_metadata::{is_artboard, DocumentMetadata, LayerNodeIdentifier};
use crate::intersection::Quad;
use crate::layers::folder_layer::FolderLayer;
use crate::layers::layer_info::{Layer, LayerData, LayerDataType, LayerDataTypeDiscriminant};
@@ -6,10 +6,13 @@ use crate::layers::layer_layer::{CachedOutputData, LayerLayer};
use crate::layers::shape_layer::ShapeLayer;
use crate::layers::style::RenderData;
use crate::{DocumentError, DocumentResponse, Operation};
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeNetwork, NodeOutput};
use graphene_core::renderer::ClickTarget;
use graphene_core::transform::Footprint;
use graphene_core::{concrete, generic, NodeIdentifier};
use graphene_std::wasm_application_io::WasmEditorApi;
use glam::{DAffine2, DVec2};
use graphene_core::transform::Footprint;
use graphene_std::wasm_application_io::WasmEditorApi;
use serde::{Deserialize, Serialize};
use std::cmp::max;
use std::collections::hash_map::DefaultHasher;
@@ -17,10 +20,6 @@ use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::vec;
use graph_craft::document::{DocumentNode, NodeOutput};
use graph_craft::document::{DocumentNodeImplementation, NodeId};
use graphene_core::{concrete, generic, NodeIdentifier};
/// A number that identifies a layer.
/// This does not technically need to be unique globally, only within a folder.
pub type LayerId = u64;
@@ -34,7 +33,9 @@ pub struct Document {
#[serde(skip)]
pub state_identifier: DefaultHasher,
#[serde(default)]
pub document_network: graph_craft::document::NodeNetwork,
pub document_network: NodeNetwork,
#[serde(default)]
pub collapsed_folders: Vec<LayerNodeIdentifier>,
#[serde(skip)]
pub metadata: DocumentMetadata,
#[serde(default)]
@@ -53,7 +54,7 @@ impl Default for Document {
root: Layer::new(LayerDataType::Folder(FolderLayer::default()), DAffine2::IDENTITY.to_cols_array()),
state_identifier: DefaultHasher::new(),
document_network: {
use graph_craft::document::{value::TaggedValue, NodeInput, NodeNetwork};
use graph_craft::document::{value::TaggedValue, NodeInput};
let mut network = NodeNetwork::default();
let node = graph_craft::document::DocumentNode {
name: "Output".into(),
@@ -106,12 +107,61 @@ impl Default for Document {
network
},
metadata: Default::default(),
collapsed_folders: Vec::new(),
commit_hash: String::new(),
}
}
}
impl Document {
pub fn layer_visible(&self, layer: LayerNodeIdentifier) -> bool {
!layer.ancestors(&self.metadata).any(|layer| self.document_network.disabled.contains(&layer.to_node()))
}
pub fn selected_visible_layers(&self) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.metadata.selected_layers().filter(|&layer| self.layer_visible(layer))
}
pub fn load_network_structure(&mut self) {
self.metadata.load_structure(&self.document_network);
self.collapsed_folders.retain(|&layer| self.metadata.layer_exists(layer));
}
/// Runs an intersection test with all layers and a viewport space quad
pub fn intersect_quad<'a>(&'a self, viewport_quad: graphene_core::renderer::Quad, network: &'a NodeNetwork) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
let document_quad = self.metadata.document_to_viewport.inverse() * viewport_quad;
self.metadata
.root()
.decendants(&self.metadata)
.filter(|&layer| self.layer_visible(layer))
.filter(|&layer| !is_artboard(layer, network))
.filter_map(|layer| self.metadata.click_target(layer).map(|targets| (layer, targets)))
.filter(move |(layer, target)| target.iter().any(move |target| target.intersect_rectangle(document_quad, self.metadata.transform_to_document(*layer))))
.map(|(layer, _)| layer)
}
/// Find all of the layers that were clicked on from a viewport space location
pub fn click_xray(&self, viewport_location: DVec2) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
let point = self.metadata.document_to_viewport.inverse().transform_point2(viewport_location);
self.metadata
.root()
.decendants(&self.metadata)
.filter(|&layer| self.layer_visible(layer))
.filter_map(|layer| self.metadata.click_target(layer).map(|targets| (layer, targets)))
.filter(move |(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.metadata.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, network: &NodeNetwork) -> Option<LayerNodeIdentifier> {
self.click_xray(viewport_location).find(|&layer| !is_artboard(layer, network))
}
pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.selected_visible_layers()
.filter_map(|layer| self.metadata.bounding_box_viewport(layer))
.reduce(graphene_core::renderer::Quad::combine_bounds)
}
/// Wrapper around render, that returns the whole document as a Response.
pub fn render_root(&mut self, render_data: &RenderData) -> String {
// Render and append to the defs section

View File

@@ -55,10 +55,6 @@ impl DocumentMetadata {
self.selected_layers().any(|selected| selected == layer)
}
pub fn selected_visible_layers(&self) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.selected_layers()
}
pub fn selected_nodes(&self) -> core::slice::Iter<'_, NodeId> {
self.selected_nodes.iter()
}
@@ -71,6 +67,14 @@ impl DocumentMetadata {
!self.selected_nodes.is_empty()
}
pub fn layer_exists(&self, layer: LayerNodeIdentifier) -> bool {
self.structure.contains_key(&layer)
}
pub fn click_target(&self, layer: LayerNodeIdentifier) -> Option<&Vec<ClickTarget>> {
self.click_targets.get(&layer)
}
/// Access the [`NodeRelations`] of a layer.
fn get_relations(&self, node_identifier: LayerNodeIdentifier) -> Option<&NodeRelations> {
self.structure.get(&node_identifier)
@@ -97,13 +101,13 @@ impl DocumentMetadata {
}
/// Ancestor that is shared by all layers and that is deepest (more nested). Default may be the root.
pub fn deepest_common_ancestor(&self, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Option<LayerNodeIdentifier> {
pub fn deepest_common_ancestor(&self, layers: impl Iterator<Item = LayerNodeIdentifier>, include_self: bool) -> Option<LayerNodeIdentifier> {
layers
.map(|layer| {
let mut layer_path = layer.ancestors(self).collect::<Vec<_>>();
layer_path.reverse();
if !self.folders.contains(&layer) {
if include_self || !self.folders.contains(&layer) {
layer_path.pop();
}
@@ -200,6 +204,11 @@ impl DocumentMetadata {
current = sibling_below(graph, current_node);
}
}
self.selected_nodes.retain(|node| graph.nodes.contains_key(node));
self.upstream_transforms.retain(|node, _| graph.nodes.contains_key(node));
self.transforms.retain(|layer, _| self.structure.contains_key(layer));
self.click_targets.retain(|layer, _| self.structure.contains_key(layer));
}
}
@@ -235,11 +244,11 @@ impl DocumentMetadata {
}
}
fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
pub fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
network.primary_flow_from_node(Some(layer.to_node())).any(|(node, _)| node.name == "Artboard")
}
fn is_folder(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
pub fn is_folder(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
network.nodes.get(&layer.to_node()).and_then(|node| node.inputs.first()).is_some_and(|input| input.as_node().is_none())
|| network
.primary_flow_from_node(Some(layer.to_node()))
@@ -254,32 +263,6 @@ impl DocumentMetadata {
self.click_targets = new_click_targets;
}
/// Runs an intersection test with all layers and a viewport space quad
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)))
.filter(move |(layer, target)| target.iter().any(move |target| target.intersect_rectangle(document_quad, self.transform_to_document(*layer))))
.map(|(layer, _)| layer)
}
/// Find all of the layers that were clicked on from a viewport space location
pub fn click_xray(&self, viewport_location: DVec2) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
let point = self.document_to_viewport.inverse().transform_point2(viewport_location);
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_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, network: &NodeNetwork) -> Option<LayerNodeIdentifier> {
self.click_xray(viewport_location).find(|&layer| !is_artboard(layer, network))
}
/// Get the bounding box of the click target of the specified layer in the specified transform space
pub fn bounding_box_with_transform(&self, layer: LayerNodeIdentifier, transform: DAffine2) -> Option<[DVec2; 2]> {
self.click_targets
@@ -316,10 +299,6 @@ impl DocumentMetadata {
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)