Initial work migrating vector layers to document graph

* Fix pen tool (except overlays)
* Thumbnail of only the layer and not the composite
* Fix occasional transform breakages
* Constrain size of thumbnail
* Insert new layers at the top
* Broken layer tree
* Fix crash when drawing
* Reduce calls to send graph
* Reduce calls to updating properties
* Store cached transforms upon the document
* Fix missing node UI updates
* Fix fill tool and clean up imports and indentation
* Error on overide existing layer
* Fix pen tool (partially)
* Fix some lints
This commit is contained in:
0hypercube
2023-10-17 11:02:06 -07:00
committed by Keavon Chambers
parent fc6cee372a
commit 4cd72edb64
50 changed files with 3585 additions and 3053 deletions
+4
View File
@@ -1,3 +1,4 @@
use crate::document_metadata::DocumentMetadata;
use crate::intersection::Quad; use crate::intersection::Quad;
use crate::layers::folder_layer::FolderLayer; use crate::layers::folder_layer::FolderLayer;
use crate::layers::layer_info::{Layer, LayerData, LayerDataType, LayerDataTypeDiscriminant}; use crate::layers::layer_info::{Layer, LayerData, LayerDataType, LayerDataTypeDiscriminant};
@@ -28,6 +29,8 @@ pub struct Document {
pub state_identifier: DefaultHasher, pub state_identifier: DefaultHasher,
#[serde(default)] #[serde(default)]
pub document_network: graph_craft::document::NodeNetwork, pub document_network: graph_craft::document::NodeNetwork,
#[serde(skip)]
pub metadata: DocumentMetadata,
#[serde(default)] #[serde(default)]
pub commit_hash: String, pub commit_hash: String,
} }
@@ -56,6 +59,7 @@ impl Default for Document {
network.push_node(node, false); network.push_node(node, false);
network network
}, },
metadata: Default::default(),
commit_hash: String::new(), commit_hash: String::new(),
} }
} }
+407
View File
@@ -0,0 +1,407 @@
use glam::{DAffine2, DVec2};
use graphene_core::renderer::ClickTarget;
use std::collections::HashMap;
use std::num::NonZeroU64;
use graph_craft::document::{NodeId, NodeNetwork};
use graphene_core::renderer::Quad;
#[derive(Debug, Clone)]
pub struct DocumentMetadata {
transforms: HashMap<LayerNodeIdentifier, DAffine2>,
structure: HashMap<LayerNodeIdentifier, NodeRelations>,
click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
/// Transform from document space to viewport space.
pub document_to_viewport: DAffine2,
}
impl Default for DocumentMetadata {
fn default() -> Self {
Self {
transforms: HashMap::new(),
click_targets: HashMap::new(),
structure: HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]),
document_to_viewport: DAffine2::IDENTITY,
}
}
}
impl DocumentMetadata {
/// Get the root layer from the document
pub const fn root(&self) -> LayerNodeIdentifier {
LayerNodeIdentifier::ROOT
}
pub fn all_layers(&self) -> DecendantsIter<'_> {
self.root().decendants(self)
}
pub fn selected_layers(&self) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.all_layers()
}
/// Access the [`NodeRelations`] of a layer
fn get_relations(&self, node_identifier: LayerNodeIdentifier) -> Option<&NodeRelations> {
self.structure.get(&node_identifier)
}
/// Mutably access the [`NodeRelations`] of a layer
fn get_structure_mut(&mut self, node_identifier: LayerNodeIdentifier) -> &mut NodeRelations {
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;
}
/// 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 document space quad
pub fn intersect_quad(&self, viewport_quad: Quad) -> Option<(LayerNodeIdentifier, &Vec<ClickTarget>)> {
let document_quad = self.document_to_viewport.inverse() * viewport_quad;
self.root()
.decendants(self)
.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))))
}
/// Find the layer that has been clicked on from a document space location
pub fn click(&self, viewport_location: DVec2) -> Option<(LayerNodeIdentifier, &Vec<ClickTarget>)> {
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)))
.find(|(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.transform_from_document(*layer))))
}
}
/// Id of a layer node
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LayerNodeIdentifier(NonZeroU64);
impl LayerNodeIdentifier {
const ROOT: Self = LayerNodeIdentifier::new_unchecked(0);
/// Construct a [`LayerNodeIdentifier`] without checking if it is a layer node
pub const fn new_unchecked(node_id: NodeId) -> Self {
// Safety: will always be >=1
Self(unsafe { NonZeroU64::new_unchecked(node_id + 1) })
}
/// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node
pub fn new(node_id: NodeId, network: &NodeNetwork) -> Self {
debug_assert!(
is_layer_node(node_id, network),
"Layer identifer constructed from non layer node {node_id}: {:#?}",
network.nodes.get(&node_id)
);
Self::new_unchecked(node_id)
}
pub fn from_path(path: &[u64], network: &NodeNetwork) -> Self {
Self::new(*path.last().unwrap(), network)
}
/// Access the node id of this layer
pub fn to_node(self) -> NodeId {
u64::from(self.0) - 1
}
/// Convert layer to layer path
pub fn to_path(self) -> Vec<NodeId> {
vec![self.to_node()]
}
/// Access the parent layer if possible
pub fn parent(self, document_metadata: &DocumentMetadata) -> Option<LayerNodeIdentifier> {
document_metadata.get_relations(self).and_then(|relations| relations.parent)
}
/// Access the previous sibling of this layer (up the layer tree)
pub fn previous_sibling(self, document_metadata: &DocumentMetadata) -> Option<LayerNodeIdentifier> {
document_metadata.get_relations(self).and_then(|relations| relations.previous_sibling)
}
/// Access the next sibling of this layer (down the layer tree)
pub fn next_sibling(self, document_metadata: &DocumentMetadata) -> Option<LayerNodeIdentifier> {
document_metadata.get_relations(self).and_then(|relations| relations.next_sibling)
}
/// Access the first child of this layer (top most in layer tree)
pub fn first_child(self, document_metadata: &DocumentMetadata) -> Option<LayerNodeIdentifier> {
document_metadata.get_relations(self).and_then(|relations| relations.first_child)
}
/// Access the last child of this layer (bottom most in layer tree)
pub fn last_child(self, document_metadata: &DocumentMetadata) -> Option<LayerNodeIdentifier> {
document_metadata.get_relations(self).and_then(|relations| relations.last_child)
}
/// Does the layer have children?
pub fn has_children(self, document_metadata: &DocumentMetadata) -> bool {
self.first_child(document_metadata).is_some()
}
/// Iterator over all direct children (excluding self and recursive children)
pub fn children(self, document_metadata: &DocumentMetadata) -> AxisIter {
AxisIter {
layer_node: self.first_child(document_metadata),
next_node: Self::next_sibling,
document_metadata,
}
}
/// All ancestors of this layer, including self, going to the document root
pub fn ancestors(self, document_metadata: &DocumentMetadata) -> AxisIter {
AxisIter {
layer_node: Some(self),
next_node: Self::parent,
document_metadata,
}
}
/// Iterator through all the last children, starting from self
pub fn last_children(self, document_metadata: &DocumentMetadata) -> AxisIter {
AxisIter {
layer_node: Some(self),
next_node: Self::last_child,
document_metadata,
}
}
/// Iterator through all decendants, including recursive children (not including self)
pub fn decendants(self, document_metadata: &DocumentMetadata) -> DecendantsIter {
DecendantsIter {
front: self.first_child(document_metadata),
back: self.last_child(document_metadata),
document_metadata,
}
}
/// Add a child towards the top of the layer tree
pub fn push_front_child(self, document_metadata: &mut DocumentMetadata, new: LayerNodeIdentifier) {
assert!(!document_metadata.structure.contains_key(&new), "Cannot add already existing layer");
let parent = document_metadata.get_structure_mut(self);
let old_first_child = parent.first_child.replace(new);
parent.last_child.get_or_insert(new);
if let Some(old_first_child) = old_first_child {
document_metadata.get_structure_mut(old_first_child).previous_sibling = Some(new);
}
document_metadata.get_structure_mut(new).next_sibling = old_first_child;
document_metadata.get_structure_mut(new).parent = Some(self);
}
/// Add a child towards the bottom of the layer tree
pub fn push_child(self, document_metadata: &mut DocumentMetadata, new: LayerNodeIdentifier) {
assert!(!document_metadata.structure.contains_key(&new), "Cannot add already existing layer");
let parent = document_metadata.get_structure_mut(self);
let old_last_child = parent.last_child.replace(new);
parent.first_child.get_or_insert(new);
if let Some(old_last_child) = old_last_child {
document_metadata.get_structure_mut(old_last_child).next_sibling = Some(new);
}
document_metadata.get_structure_mut(new).previous_sibling = old_last_child;
document_metadata.get_structure_mut(new).parent = Some(self);
}
/// Add sibling above in the layer tree
pub fn add_before(self, document_metadata: &mut DocumentMetadata, new: LayerNodeIdentifier) {
assert!(!document_metadata.structure.contains_key(&new), "Cannot add already existing layer");
document_metadata.get_structure_mut(new).next_sibling = Some(self);
document_metadata.get_structure_mut(new).parent = self.parent(document_metadata);
let old_previous_sibling = document_metadata.get_structure_mut(self).previous_sibling.replace(new);
if let Some(old_previous_sibling) = old_previous_sibling {
document_metadata.get_structure_mut(old_previous_sibling).next_sibling = Some(new);
document_metadata.get_structure_mut(new).previous_sibling = Some(old_previous_sibling);
} else if let Some(structure) = self
.parent(document_metadata)
.map(|parent| document_metadata.get_structure_mut(parent))
.filter(|structure| structure.first_child == Some(self))
{
structure.first_child = Some(new);
}
}
/// Add sibling below in the layer tree
pub fn add_after(self, document_metadata: &mut DocumentMetadata, new: LayerNodeIdentifier) {
assert!(!document_metadata.structure.contains_key(&new), "Cannot add already existing layer");
document_metadata.get_structure_mut(new).previous_sibling = Some(self);
document_metadata.get_structure_mut(new).parent = self.parent(document_metadata);
let old_next_sibling = document_metadata.get_structure_mut(self).next_sibling.replace(new);
if let Some(old_next_sibling) = old_next_sibling {
document_metadata.get_structure_mut(old_next_sibling).previous_sibling = Some(new);
document_metadata.get_structure_mut(new).next_sibling = Some(old_next_sibling);
} else if let Some(structure) = self
.parent(document_metadata)
.map(|parent| document_metadata.get_structure_mut(parent))
.filter(|structure| structure.last_child == Some(self))
{
structure.last_child = Some(new);
}
}
/// Delete layer and all children
pub fn delete(self, document_metadata: &mut DocumentMetadata) {
let previous_sibling = self.previous_sibling(document_metadata);
let next_sibling = self.next_sibling(document_metadata);
if let Some(previous_sibling) = previous_sibling.map(|node| document_metadata.get_structure_mut(node)) {
previous_sibling.next_sibling = next_sibling;
}
if let Some(next_sibling) = next_sibling.map(|node| document_metadata.get_structure_mut(node)) {
next_sibling.previous_sibling = previous_sibling;
}
let mut parent = self.parent(document_metadata).map(|parent| document_metadata.get_structure_mut(parent));
if let Some(structure) = parent.as_mut().filter(|structure| structure.first_child == Some(self)) {
structure.first_child = next_sibling;
}
if let Some(structure) = parent.as_mut().filter(|structure| structure.last_child == Some(self)) {
structure.last_child = previous_sibling;
}
let mut delete = vec![self];
delete.extend(self.decendants(document_metadata));
for node in delete {
document_metadata.structure.remove(&node);
}
}
}
impl From<NodeId> for LayerNodeIdentifier {
fn from(node_id: NodeId) -> Self {
Self::new_unchecked(node_id)
}
}
impl From<LayerNodeIdentifier> for NodeId {
fn from(identifer: LayerNodeIdentifier) -> Self {
identifer.to_node()
}
}
/// Iterator over specified axis.
#[derive(Clone)]
pub struct AxisIter<'a> {
layer_node: Option<LayerNodeIdentifier>,
next_node: fn(LayerNodeIdentifier, &DocumentMetadata) -> Option<LayerNodeIdentifier>,
document_metadata: &'a DocumentMetadata,
}
impl<'a> Iterator for AxisIter<'a> {
type Item = LayerNodeIdentifier;
fn next(&mut self) -> Option<Self::Item> {
let layer_node = self.layer_node.take();
self.layer_node = layer_node.and_then(|node| (self.next_node)(node, self.document_metadata));
layer_node
}
}
#[derive(Clone)]
pub struct DecendantsIter<'a> {
front: Option<LayerNodeIdentifier>,
back: Option<LayerNodeIdentifier>,
document_metadata: &'a DocumentMetadata,
}
impl<'a> Iterator for DecendantsIter<'a> {
type Item = LayerNodeIdentifier;
fn next(&mut self) -> Option<Self::Item> {
if self.front == self.back {
self.back = None;
self.front.take()
} else {
let layer_node = self.front.take();
if let Some(layer_node) = layer_node {
self.front = layer_node
.first_child(self.document_metadata)
.or_else(|| layer_node.ancestors(self.document_metadata).find_map(|ancestor| ancestor.next_sibling(self.document_metadata)));
}
layer_node
}
}
}
impl<'a> DoubleEndedIterator for DecendantsIter<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.front == self.back {
self.front = None;
self.back.take()
} else {
let layer_node = self.back.take();
if let Some(layer_node) = layer_node {
self.back = layer_node
.previous_sibling(self.document_metadata)
.and_then(|sibling| sibling.last_children(self.document_metadata).last())
.or_else(|| layer_node.parent(self.document_metadata));
}
layer_node
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NodeRelations {
parent: Option<LayerNodeIdentifier>,
previous_sibling: Option<LayerNodeIdentifier>,
next_sibling: Option<LayerNodeIdentifier>,
first_child: Option<LayerNodeIdentifier>,
last_child: Option<LayerNodeIdentifier>,
}
fn is_layer_node(node: NodeId, network: &NodeNetwork) -> bool {
node == LayerNodeIdentifier::ROOT.to_node() || network.nodes.get(&node).is_some_and(|node| node.name == "Layer")
}
#[test]
fn test_tree() {
let mut document_metadata = DocumentMetadata::default();
let root = document_metadata.root();
let document_metadata = &mut document_metadata;
root.push_child(document_metadata, LayerNodeIdentifier::new_unchecked(3));
assert_eq!(root.children(document_metadata).collect::<Vec<_>>(), vec![LayerNodeIdentifier::new_unchecked(3)]);
root.push_child(document_metadata, LayerNodeIdentifier::new_unchecked(6));
assert_eq!(root.children(document_metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(), vec![3, 6]);
assert_eq!(root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(), vec![3, 6]);
LayerNodeIdentifier::new_unchecked(3).add_after(document_metadata, LayerNodeIdentifier::new_unchecked(4));
LayerNodeIdentifier::new_unchecked(3).add_before(document_metadata, LayerNodeIdentifier::new_unchecked(2));
LayerNodeIdentifier::new_unchecked(6).add_before(document_metadata, LayerNodeIdentifier::new_unchecked(5));
LayerNodeIdentifier::new_unchecked(6).add_after(document_metadata, LayerNodeIdentifier::new_unchecked(9));
LayerNodeIdentifier::new_unchecked(6).push_child(document_metadata, LayerNodeIdentifier::new_unchecked(8));
LayerNodeIdentifier::new_unchecked(6).push_front_child(document_metadata, LayerNodeIdentifier::new_unchecked(7));
root.push_front_child(document_metadata, LayerNodeIdentifier::new_unchecked(1));
assert_eq!(root.children(document_metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(), vec![1, 2, 3, 4, 5, 6, 9]);
assert_eq!(
root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(),
vec![1, 2, 3, 4, 5, 6, 7, 8, 9]
);
assert_eq!(
root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).rev().collect::<Vec<_>>(),
vec![9, 8, 7, 6, 5, 4, 3, 2, 1]
);
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);
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]);
}
+1
View File
@@ -6,6 +6,7 @@ pub mod boolean_ops;
/// Contains constant values used by this crate. /// Contains constant values used by this crate.
pub mod consts; pub mod consts;
pub mod document; pub mod document;
pub mod document_metadata;
/// Defines errors that can occur when using this crate. /// Defines errors that can occur when using this crate.
pub mod error; pub mod error;
/// Utilities for computing intersections. /// Utilities for computing intersections.
+4
View File
@@ -36,6 +36,10 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Artboard( MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Artboard(
ArtboardMessageDiscriminant::RenderArtboards, ArtboardMessageDiscriminant::RenderArtboards,
))), ))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(NodeGraphMessageDiscriminant::SendGraph))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
PropertiesPanelMessageDiscriminant::ResendActiveProperties,
))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::FolderChanged)), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::FolderChanged)),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerTreeStructure), MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerTreeStructure),
@@ -232,6 +232,10 @@ pub enum FrontendMessage {
UpdateNodeGraphSelection { UpdateNodeGraphSelection {
selected: Vec<NodeId>, selected: Vec<NodeId>,
}, },
UpdateNodeThumbnail {
id: NodeId,
value: String,
},
UpdateNodeTypes { UpdateNodeTypes {
#[serde(rename = "nodeTypes")] #[serde(rename = "nodeTypes")]
node_types: Vec<FrontendNodeType>, node_types: Vec<FrontendNodeType>,
@@ -195,6 +195,9 @@ pub enum DocumentMessage {
folder_path: Vec<LayerId>, folder_path: Vec<LayerId>,
}, },
UngroupSelectedLayers, UngroupSelectedLayers,
UpdateDocumentTransform {
transform: glam::DAffine2,
},
UpdateLayerMetadata { UpdateLayerMetadata {
layer_path: Vec<LayerId>, layer_path: Vec<LayerId>,
layer_metadata: LayerMetadata, layer_metadata: LayerMetadata,
@@ -18,8 +18,9 @@ use crate::messages::tool::utility_types::ToolType;
use crate::node_graph_executor::NodeGraphExecutor; use crate::node_graph_executor::NodeGraphExecutor;
use document_legacy::document::Document as DocumentLegacy; use document_legacy::document::Document as DocumentLegacy;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::layers::blend_mode::BlendMode; use document_legacy::layers::blend_mode::BlendMode;
use document_legacy::layers::folder_layer::FolderLayer;
use document_legacy::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant}; use document_legacy::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant};
use document_legacy::layers::layer_layer::CachedOutputData; use document_legacy::layers::layer_layer::CachedOutputData;
use document_legacy::layers::style::{RenderData, ViewMode}; use document_legacy::layers::style::{RenderData, ViewMode};
@@ -211,7 +212,6 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
responses, responses,
NodeGraphHandlerData { NodeGraphHandlerData {
document: &mut self.document_legacy, document: &mut self.document_legacy,
executor,
document_id, document_id,
document_name: self.name.as_str(), document_name: self.name.as_str(),
input: ipp, input: ipp,
@@ -315,6 +315,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
} }
DeleteLayer { layer_path } => { DeleteLayer { layer_path } => {
responses.add_front(DocumentOperation::DeleteLayer { path: layer_path.clone() }); responses.add_front(DocumentOperation::DeleteLayer { path: layer_path.clone() });
responses.add(GraphOperationMessage::DeleteLayer { id: layer_path[0] });
responses.add_front(BroadcastEvent::ToolAbort); responses.add_front(BroadcastEvent::ToolAbort);
responses.add(PropertiesPanelMessage::CheckSelectedWasDeleted { path: layer_path }); responses.add(PropertiesPanelMessage::CheckSelectedWasDeleted { path: layer_path });
} }
@@ -938,6 +939,11 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
} }
responses.add(DocumentMessage::CommitTransaction); responses.add(DocumentMessage::CommitTransaction);
} }
UpdateDocumentTransform { transform } => {
self.document_legacy.metadata.document_to_viewport = transform;
let transform = graphene_core::renderer::format_transform_matrix(transform);
responses.add(FrontendMessage::UpdateDocumentTransform { transform });
}
UpdateLayerMetadata { layer_path, layer_metadata } => { UpdateLayerMetadata { layer_path, layer_metadata } => {
self.layer_metadata.insert(layer_path, layer_metadata); self.layer_metadata.insert(layer_path, layer_metadata);
} }
@@ -997,6 +1003,9 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
} }
impl DocumentMessageHandler { impl DocumentMessageHandler {
pub fn network(&self) -> &NodeNetwork {
&self.document_legacy.document_network
}
pub fn rasterize_region_below_layer(&mut self, document_id: u64, layer_path: Vec<LayerId>, _preferences: &PreferencesMessageHandler, persistent_data: &PersistentData) -> Option<Message> { pub fn rasterize_region_below_layer(&mut self, document_id: u64, layer_path: Vec<LayerId>, _preferences: &PreferencesMessageHandler, persistent_data: &PersistentData) -> Option<Message> {
// Prepare the node graph input image // Prepare the node graph input image
@@ -1117,7 +1126,7 @@ impl DocumentMessageHandler {
pub fn with_name(name: String, ipp: &InputPreprocessorMessageHandler) -> Self { pub fn with_name(name: String, ipp: &InputPreprocessorMessageHandler) -> Self {
let mut document = Self { name, ..Self::default() }; let mut document = Self { name, ..Self::default() };
let starting_root_transform = document.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.); let starting_root_transform = document.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.);
document.document_legacy.root.transform = starting_root_transform; document.document_legacy.metadata.document_to_viewport = starting_root_transform;
document.artboard_message_handler.artboards_document.root.transform = starting_root_transform; document.artboard_message_handler.artboards_document.root.transform = starting_root_transform;
document document
@@ -1221,13 +1230,14 @@ impl DocumentMessageHandler {
) )
} }
fn serialize_structure(&self, folder: &FolderLayer, structure: &mut Vec<u64>, data: &mut Vec<LayerId>, path: &mut Vec<LayerId>) { fn serialize_structure(&self, folder: LayerNodeIdentifier, structure: &mut Vec<u64>, data: &mut Vec<LayerId>, path: &mut Vec<LayerId>) {
let mut space = 0; let mut space = 0;
for (id, layer) in folder.layer_ids.iter().zip(folder.layers()).rev() { for layer_node in folder.children(&self.document_legacy.metadata) {
data.push(*id); data.push(layer_node.to_node());
info!("Pushed child");
space += 1; space += 1;
if let LayerDataType::Folder(ref folder) = layer.data { if layer_node.has_children(&self.document_legacy.metadata) {
path.push(*id); path.push(layer_node.to_node());
if self.layer_metadata(path).expanded { if self.layer_metadata(path).expanded {
structure.push(space); structure.push(space);
self.serialize_structure(folder, structure, data, path); self.serialize_structure(folder, structure, data, path);
@@ -1273,7 +1283,7 @@ impl DocumentMessageHandler {
/// ``` /// ```
pub fn serialize_root(&self) -> Vec<u64> { pub fn serialize_root(&self) -> Vec<u64> {
let (mut structure, mut data) = (vec![0], Vec::new()); let (mut structure, mut data) = (vec![0], Vec::new());
self.serialize_structure(self.document_legacy.root.as_folder().unwrap(), &mut structure, &mut data, &mut vec![]); self.serialize_structure(self.document_legacy.metadata.root(), &mut structure, &mut data, &mut vec![]);
structure[0] = structure.len() as u64 - 1; structure[0] = structure.len() as u64 - 1;
structure.extend(data); structure.extend(data);
@@ -9,8 +9,6 @@ use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::document::Document; use document_legacy::document::Document;
use document_legacy::Operation as DocumentOperation;
use graphene_core::renderer::format_transform_matrix;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -407,21 +405,9 @@ impl NavigationMessageHandler {
fn create_document_transform(&self, viewport_bounds: &ViewportBounds, responses: &mut VecDeque<Message>) { fn create_document_transform(&self, viewport_bounds: &ViewportBounds, responses: &mut VecDeque<Message>) {
let half_viewport = viewport_bounds.size() / 2.; let half_viewport = viewport_bounds.size() / 2.;
let scaled_half_viewport = half_viewport / self.snapped_scale(); let scaled_half_viewport = half_viewport / self.snapped_scale();
responses.add(DocumentOperation::SetLayerTransform {
path: vec![],
transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(),
});
responses.add(ArtboardMessage::DispatchOperation( let transform = self.calculate_offset_transform(scaled_half_viewport);
DocumentOperation::SetLayerTransform { responses.add(DocumentMessage::UpdateDocumentTransform { transform });
path: vec![],
transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(),
}
.into(),
));
let transform = format_transform_matrix(self.calculate_offset_transform(scaled_half_viewport));
responses.add(FrontendMessage::UpdateDocumentTransform { transform });
// TODO: Artboard pos
} }
pub fn center_zoom(&self, viewport_bounds: DVec2, zoom_factor: f64, mouse: DVec2) -> Message { pub fn center_zoom(&self, viewport_bounds: DVec2, zoom_factor: f64, mouse: DVec2) -> Message {
@@ -1,5 +1,6 @@
use crate::messages::prelude::*; use crate::messages::prelude::*;
use bezier_rs::Subpath;
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graphene_core::uuid::ManipulatorGroupId; use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::brush_stroke::BrushStroke; use graphene_core::vector::brush_stroke::BrushStroke;
@@ -58,12 +59,16 @@ pub enum GraphOperationMessage {
id: NodeId, id: NodeId,
artboard: Artboard, artboard: Artboard,
}, },
NewVectorLayer {
id: NodeId,
subpaths: Vec<Subpath<ManipulatorGroupId>>,
},
ResizeArtboard { ResizeArtboard {
id: NodeId, id: NodeId,
location: IVec2, location: IVec2,
dimensions: IVec2, dimensions: IVec2,
}, },
DeleteArtboard { DeleteLayer {
id: NodeId, id: NodeId,
}, },
ClearArtboards, ClearArtboards,
@@ -1,10 +1,13 @@
use super::{resolve_document_node_type, VectorDataModification}; use super::{resolve_document_node_type, VectorDataModification};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use bezier_rs::Subpath;
use document_legacy::document::Document; use document_legacy::document::Document;
use document_legacy::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use document_legacy::{LayerId, Operation}; use document_legacy::{LayerId, Operation};
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, DocumentNode, DocumentNodeMetadata, NodeId, NodeInput, NodeNetwork, NodeOutput}; use graph_craft::document::{generate_uuid, DocumentNode, NodeId, NodeInput, NodeNetwork, NodeOutput};
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::brush_stroke::BrushStroke; use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::style::{Fill, FillType, Stroke}; use graphene_core::vector::style::{Fill, FillType, Stroke};
use graphene_core::Artboard; use graphene_core::Artboard;
@@ -24,22 +27,11 @@ struct ModifyInputsContext<'a> {
layer: &'a [LayerId], layer: &'a [LayerId],
outwards_links: HashMap<NodeId, Vec<NodeId>>, outwards_links: HashMap<NodeId, Vec<NodeId>>,
layer_node: Option<NodeId>, layer_node: Option<NodeId>,
document_metadata: &'a mut DocumentMetadata,
} }
impl<'a> ModifyInputsContext<'a> { impl<'a> ModifyInputsContext<'a> {
/// Get the node network from the document /// Get the node network from the document
fn new(layer: &'a [LayerId], document: &'a mut Document, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque<Message>) -> Option<Self> { fn new(document: &'a mut Document, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque<Message>) -> Self {
document.layer_mut(layer).ok().and_then(|layer| layer.as_layer_network_mut().ok()).map(|network| Self {
outwards_links: network.collect_outwards_links(),
network,
node_graph,
responses,
layer,
layer_node: None,
})
}
/// Get the node network from the document
fn new_doc(document: &'a mut Document, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque<Message>) -> Self {
Self { Self {
outwards_links: document.document_network.collect_outwards_links(), outwards_links: document.document_network.collect_outwards_links(),
network: &mut document.document_network, network: &mut document.document_network,
@@ -47,15 +39,22 @@ impl<'a> ModifyInputsContext<'a> {
responses, responses,
layer: &[], layer: &[],
layer_node: None, layer_node: None,
document_metadata: &mut document.metadata,
} }
} }
fn locate_layer(&mut self, mut id: NodeId) -> Option<NodeId> { /// Get the node network from the document
while self.network.nodes.get(&id)?.name != "Layer" { fn new_layer(layer: &'a [LayerId], document: &'a mut Document, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque<Message>) -> Option<Self> {
id = self.outwards_links.get(&id)?.first().copied()?; let mut document = Self::new(document, node_graph, responses);
let Some(mut id) = layer.last().copied() else {
error!("Tried to modify root layer");
return None;
};
while document.network.nodes.get(&id)?.name != "Layer" {
id = document.outwards_links.get(&id)?.first().copied()?;
} }
self.layer_node = Some(id); document.layer_node = Some(id);
Some(id) Some(document)
} }
/// Updates the input of an existing node /// Updates the input of an existing node
@@ -64,8 +63,8 @@ impl<'a> ModifyInputsContext<'a> {
update_input(&mut document_node.inputs); update_input(&mut document_node.inputs);
} }
pub fn insert_between(&mut self, pre: NodeOutput, post: NodeOutput, mut node: DocumentNode, input: usize, output: usize) -> Option<NodeId> { pub fn insert_between(&mut self, id: NodeId, pre: NodeOutput, post: NodeOutput, mut node: DocumentNode, input: usize, output: usize, shift_upstream: IVec2) -> Option<NodeId> {
let id = generate_uuid(); assert!(!self.network.nodes.contains_key(&id), "Creating already existing node");
let pre_node = self.network.nodes.get_mut(&pre.node_id)?; let pre_node = self.network.nodes.get_mut(&pre.node_id)?;
node.metadata.position = pre_node.metadata.position; node.metadata.position = pre_node.metadata.position;
@@ -75,12 +74,13 @@ impl<'a> ModifyInputsContext<'a> {
self.network.nodes.insert(id, node); self.network.nodes.insert(id, node);
self.shift_upstream(id, IVec2::new(-8, 0)); self.shift_upstream(id, shift_upstream);
Some(id) Some(id)
} }
pub fn insert_node_before(&mut self, new_id: NodeId, node_id: NodeId, input_index: usize, mut document_node: DocumentNode, offset: IVec2) -> Option<NodeId> { pub fn insert_node_before(&mut self, new_id: NodeId, node_id: NodeId, input_index: usize, mut document_node: DocumentNode, offset: IVec2) -> Option<NodeId> {
assert!(!self.network.nodes.contains_key(&new_id), "Creating already existing node");
let post_node = self.network.nodes.get_mut(&node_id)?; let post_node = self.network.nodes.get_mut(&node_id)?;
post_node.inputs[input_index] = NodeInput::node(new_id, 0); post_node.inputs[input_index] = NodeInput::node(new_id, 0);
@@ -90,32 +90,39 @@ impl<'a> ModifyInputsContext<'a> {
Some(new_id) Some(new_id)
} }
pub fn create_layer(&mut self, new_id: NodeId, output_node_id: NodeId) -> Option<NodeId> { pub fn create_layer(&mut self, new_id: NodeId, output_node_id: NodeId, input_index: usize) -> Option<NodeId> {
let mut current_node = output_node_id; assert!(!self.network.nodes.contains_key(&new_id), "Creating already existing layer");
let mut input_index = 0;
let mut current_input = &self.network.nodes.get(&current_node)?.inputs[input_index];
while let NodeInput::Node { node_id, output_index, .. } = current_input { let output = NodeOutput::new(output_node_id, input_index);
// Locate the node output of the first sibling layer to the new layer
let new_id = if let NodeInput::Node { node_id, output_index, .. } = &self.network.nodes.get(&output_node_id)?.inputs[input_index] {
let sibling_node = &self.network.nodes.get(node_id)?; let sibling_node = &self.network.nodes.get(node_id)?;
if sibling_node.name == "Layer" { let sibling_layer = if sibling_node.name == "Layer" {
current_node = *node_id; // There is already a layer node
input_index = 7; NodeOutput::new(*node_id, 0)
current_input = &self.network.nodes.get(&current_node)?.inputs[input_index];
} else { } else {
// Insert a layer node between the output and the new // The user has connected another node to the output. Insert a layer node between the output and the node.
let layer_node = resolve_document_node_type("Layer").expect("Layer node"); let node = resolve_document_node_type("Layer").expect("Layer node").default_document_node();
let node = layer_node.to_document_node_default_inputs([], DocumentNodeMetadata::default()); let node_id = self.insert_between(generate_uuid(), NodeOutput::new(*node_id, *output_index), output, node, 0, 0, IVec2::new(-8, 0))?;
let node_id = self.insert_between(NodeOutput::new(*node_id, *output_index), NodeOutput::new(current_node, input_index), node, 0, 0)?; NodeOutput::new(node_id, 0)
current_node = node_id; };
input_index = 7;
current_input = &self.network.nodes.get(&current_node)?.inputs[input_index]; let node = resolve_document_node_type("Layer").expect("Layer node").default_document_node();
} self.insert_between(new_id, sibling_layer, output, node, 7, 0, IVec2::new(-4, 3))
} else {
let layer_node = resolve_document_node_type("Layer").expect("Node").default_document_node();
self.insert_node_before(new_id, output_node_id, input_index, layer_node, IVec2::new(-4, 3))
};
// Update the document metadata structure
if let Some(new_id) = new_id {
let parent = LayerNodeIdentifier::new(output_node_id, self.network);
let new_child = LayerNodeIdentifier::new(new_id, self.network);
parent.push_front_child(self.document_metadata, new_child);
self.responses.add(DocumentMessage::DocumentStructureChanged);
} }
let layer_node = resolve_document_node_type("Layer").expect("Node").to_document_node_default_inputs([], Default::default()); new_id
let layer_node = self.insert_node_before(new_id, current_node, input_index, layer_node, IVec2::new(-4, 3))?;
Some(layer_node)
} }
fn insert_artboard(&mut self, artboard: Artboard, layer: NodeId) -> Option<NodeId> { fn insert_artboard(&mut self, artboard: Artboard, layer: NodeId) -> Option<NodeId> {
@@ -129,9 +136,30 @@ impl<'a> ModifyInputsContext<'a> {
], ],
Default::default(), Default::default(),
); );
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
self.insert_node_before(generate_uuid(), layer, 0, artboard_node, IVec2::new(-8, 0)) self.insert_node_before(generate_uuid(), layer, 0, artboard_node, IVec2::new(-8, 0))
} }
fn insert_vector_data(&mut self, subpaths: Vec<Subpath<ManipulatorGroupId>>, layer: NodeId) {
let shape = {
let node_type = resolve_document_node_type("Shape").expect("Shape node does not exist");
node_type.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], Default::default())
};
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_document_node();
let fill = resolve_document_node_type("Fill").expect("Fill node does not exist").default_document_node();
let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist").default_document_node();
let stroke_id = generate_uuid();
self.insert_node_before(stroke_id, layer, 0, stroke, IVec2::new(-8, 0));
let fill_id = generate_uuid();
self.insert_node_before(fill_id, stroke_id, 0, fill, IVec2::new(-8, 0));
let transform_id = generate_uuid();
self.insert_node_before(transform_id, fill_id, 0, transform, IVec2::new(-8, 0));
let shape_id = generate_uuid();
self.insert_node_before(shape_id, transform_id, 0, shape, IVec2::new(-8, 0));
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
}
fn shift_upstream(&mut self, node_id: NodeId, shift: IVec2) { fn shift_upstream(&mut self, node_id: NodeId, shift: IVec2) {
let mut shift_nodes = HashSet::new(); let mut shift_nodes = HashSet::new();
let mut stack = vec![node_id]; let mut stack = vec![node_id];
@@ -184,7 +212,6 @@ impl<'a> ModifyInputsContext<'a> {
} else { } else {
self.modify_new_node(name, update_input); self.modify_new_node(name, update_input);
} }
self.node_graph.update_layer_path(Some(self.layer.to_vec()), self.responses);
self.node_graph.nested_path.clear(); self.node_graph.nested_path.clear();
self.responses.add(PropertiesPanelMessage::ResendActiveProperties); self.responses.add(PropertiesPanelMessage::ResendActiveProperties);
let layer_path = self.layer.to_vec(); let layer_path = self.layer.to_vec();
@@ -354,13 +381,19 @@ impl<'a> ModifyInputsContext<'a> {
let mut new_input = None; let mut new_input = None;
let post_node = self.outwards_links.get(&id).and_then(|links| links.first().copied()); let post_node = self.outwards_links.get(&id).and_then(|links| links.first().copied());
let mut delete_nodes = vec![id]; let mut delete_nodes = vec![id];
let mut is_artboard = false;
for (node, id) in self.network.primary_flow_from_opt(Some(id)) { for (node, id) in self.network.primary_flow_from_opt(Some(id)) {
delete_nodes.push(id); delete_nodes.push(id);
if node.name == "Artboard" { if node.name == "Artboard" {
new_input = Some(node.inputs[0].clone()); new_input = Some(node.inputs[0].clone());
is_artboard = true;
break; break;
} }
} }
if !is_artboard {
LayerNodeIdentifier::new(id, self.network).delete(self.document_metadata);
}
self.responses.add(DocumentMessage::DocumentStructureChanged);
for node_id in delete_nodes { for node_id in delete_nodes {
self.network.nodes.remove(&node_id); self.network.nodes.remove(&node_id);
@@ -378,19 +411,19 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, (document, node_graph): (&mut Document, &mut NodeGraphMessageHandler)) { fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, (document, node_graph): (&mut Document, &mut NodeGraphMessageHandler)) {
match message { match message {
GraphOperationMessage::FillSet { layer, fill } => { GraphOperationMessage::FillSet { layer, fill } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) {
modify_inputs.fill_set(fill); modify_inputs.fill_set(fill);
} else { } else {
responses.add(Operation::SetLayerFill { path: layer, fill }); responses.add(Operation::SetLayerFill { path: layer, fill });
} }
} }
GraphOperationMessage::UpdateBounds { layer, old_bounds, new_bounds } => { GraphOperationMessage::UpdateBounds { layer, old_bounds, new_bounds } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) {
modify_inputs.update_bounds(old_bounds, new_bounds); modify_inputs.update_bounds(old_bounds, new_bounds);
} }
} }
GraphOperationMessage::StrokeSet { layer, stroke } => { GraphOperationMessage::StrokeSet { layer, stroke } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) {
modify_inputs.stroke_set(stroke); modify_inputs.stroke_set(stroke);
} else { } else {
responses.add(Operation::SetLayerStroke { path: layer, stroke }); responses.add(Operation::SetLayerStroke { path: layer, stroke });
@@ -402,9 +435,9 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
transform_in, transform_in,
skip_rerender, skip_rerender,
} => { } => {
let parent_transform = document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default(); let parent_transform = document.metadata.document_to_viewport * document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default();
let bounds = LayerBounds::new(document, &layer); let bounds = LayerBounds::new(document, &layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) {
modify_inputs.transform_change(transform, transform_in, parent_transform, bounds, skip_rerender); modify_inputs.transform_change(transform, transform_in, parent_transform, bounds, skip_rerender);
} }
@@ -424,10 +457,10 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
transform_in, transform_in,
skip_rerender, skip_rerender,
} => { } => {
let parent_transform = document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default(); let parent_transform = document.metadata.document_to_viewport * document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default();
let current_transform = document.layer(&layer).ok().map(|layer| layer.transform); let current_transform = document.layer(&layer).ok().map(|layer| layer.transform);
let bounds = LayerBounds::new(document, &layer); let bounds = LayerBounds::new(document, &layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) {
modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, bounds, skip_rerender); modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, bounds, skip_rerender);
} }
let transform = transform.to_cols_array(); let transform = transform.to_cols_array();
@@ -442,7 +475,7 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
} }
GraphOperationMessage::TransformSetPivot { layer, pivot } => { GraphOperationMessage::TransformSetPivot { layer, pivot } => {
let bounds = LayerBounds::new(document, &layer); let bounds = LayerBounds::new(document, &layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) {
modify_inputs.pivot_set(pivot, bounds); modify_inputs.pivot_set(pivot, bounds);
} }
@@ -450,33 +483,38 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
responses.add(Operation::SetPivot { layer_path: layer, pivot }); responses.add(Operation::SetPivot { layer_path: layer, pivot });
} }
GraphOperationMessage::Vector { layer, modification } => { GraphOperationMessage::Vector { layer, modification } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) {
modify_inputs.vector_modify(modification); modify_inputs.vector_modify(modification);
} }
} }
GraphOperationMessage::Brush { layer, strokes } => { GraphOperationMessage::Brush { layer, strokes } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) {
modify_inputs.brush_modify(strokes); modify_inputs.brush_modify(strokes);
} }
} }
GraphOperationMessage::NewArtboard { id, artboard } => { GraphOperationMessage::NewArtboard { id, artboard } => {
let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses); let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id) { if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id, 0) {
modify_inputs.insert_artboard(artboard, layer); modify_inputs.insert_artboard(artboard, layer);
} }
} }
GraphOperationMessage::NewVectorLayer { id, subpaths } => {
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id, 0) {
modify_inputs.insert_vector_data(subpaths, layer);
}
}
GraphOperationMessage::ResizeArtboard { id, location, dimensions } => { GraphOperationMessage::ResizeArtboard { id, location, dimensions } => {
let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses); if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&[id], document, node_graph, responses) {
if modify_inputs.locate_layer(id).is_some() {
modify_inputs.resize_artboard(location, dimensions); modify_inputs.resize_artboard(location, dimensions);
} }
} }
GraphOperationMessage::DeleteArtboard { id } => { GraphOperationMessage::DeleteLayer { id } => {
let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses); let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
modify_inputs.delete_layer(id); modify_inputs.delete_layer(id);
} }
GraphOperationMessage::ClearArtboards => { GraphOperationMessage::ClearArtboards => {
let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses); let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
let artboard_nodes = modify_inputs.network.nodes.iter().filter(|(_, node)| node.name == "Artboard").map(|(id, _)| *id).collect::<Vec<_>>(); let artboard_nodes = modify_inputs.network.nodes.iter().filter(|(_, node)| node.name == "Artboard").map(|(id, _)| *id).collect::<Vec<_>>();
for id in artboard_nodes { for id in artboard_nodes {
modify_inputs.delete_layer(id); modify_inputs.delete_layer(id);
@@ -2,7 +2,7 @@ pub use self::document_node_types::*;
use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::node_graph_executor::{GraphIdentifier, NodeGraphExecutor}; use crate::node_graph_executor::GraphIdentifier;
use document_legacy::document::Document; use document_legacy::document::Document;
use document_legacy::LayerId; use document_legacy::LayerId;
@@ -87,8 +87,6 @@ pub struct FrontendNode {
pub position: (i32, i32), pub position: (i32, i32),
pub disabled: bool, pub disabled: bool,
pub previewed: bool, pub previewed: bool,
#[serde(rename = "thumbnailSvg")]
pub thumbnail_svg: Option<String>,
} }
// (link_start, link_end, link_end_input_index) // (link_start, link_end, link_end_input_index)
@@ -128,11 +126,6 @@ pub struct NodeGraphMessageHandler {
} }
impl NodeGraphMessageHandler { impl NodeGraphMessageHandler {
pub fn update_layer_path(&mut self, layer_path: Option<Vec<LayerId>>, responses: &mut VecDeque<Message>) {
self.layer_path = layer_path;
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
}
fn get_root_network<'a>(&self, document: &'a Document) -> &'a graph_craft::document::NodeNetwork { fn get_root_network<'a>(&self, document: &'a Document) -> &'a graph_craft::document::NodeNetwork {
self.layer_path self.layer_path
.as_ref() .as_ref()
@@ -284,7 +277,7 @@ impl NodeGraphMessageHandler {
} }
} }
fn send_graph(network: &NodeNetwork, executor: &NodeGraphExecutor, layer_path: &Option<Vec<LayerId>>, responses: &mut VecDeque<Message>) { fn send_graph(network: &NodeNetwork, layer_path: &Option<Vec<LayerId>>, responses: &mut VecDeque<Message>) {
responses.add(PropertiesPanelMessage::ResendActiveProperties); responses.add(PropertiesPanelMessage::ResendActiveProperties);
let layer_id = layer_path.as_ref().and_then(|path| path.last().copied()); let layer_id = layer_path.as_ref().and_then(|path| path.last().copied());
@@ -345,8 +338,7 @@ impl NodeGraphMessageHandler {
}); });
let primary_output = outputs.next(); let primary_output = outputs.next();
let graph_identifier = GraphIdentifier::new(layer_id); let _graph_identifier = GraphIdentifier::new(layer_id);
let thumbnail_svg = executor.thumbnails.get(&graph_identifier).and_then(|thumbnails| thumbnails.get(id)).map(|svg| svg.to_string());
nodes.push(FrontendNode { nodes.push(FrontendNode {
id: *id, id: *id,
@@ -358,7 +350,6 @@ impl NodeGraphMessageHandler {
position: node.metadata.position.into(), position: node.metadata.position.into(),
previewed: network.outputs_contain(*id), previewed: network.outputs_contain(*id),
disabled: network.disabled.contains(id), disabled: network.disabled.contains(id),
thumbnail_svg,
}) })
} }
responses.add(FrontendMessage::UpdateNodeGraph { nodes, links }); responses.add(FrontendMessage::UpdateNodeGraph { nodes, links });
@@ -467,7 +458,6 @@ impl NodeGraphMessageHandler {
#[derive(Debug)] #[derive(Debug)]
pub struct NodeGraphHandlerData<'a> { pub struct NodeGraphHandlerData<'a> {
pub document: &'a mut Document, pub document: &'a mut Document,
pub executor: &'a NodeGraphExecutor,
pub document_id: u64, pub document_id: u64,
pub document_name: &'a str, pub document_name: &'a str,
pub input: &'a InputPreprocessorMessageHandler, pub input: &'a InputPreprocessorMessageHandler,
@@ -478,9 +468,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, data: NodeGraphHandlerData<'a>) { fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, data: NodeGraphHandlerData<'a>) {
#[remain::sorted] #[remain::sorted]
match message { match message {
NodeGraphMessage::CloseNodeGraph => { NodeGraphMessage::CloseNodeGraph => {}
self.update_layer_path(None, responses);
}
NodeGraphMessage::ConnectNodesByLink { NodeGraphMessage::ConnectNodesByLink {
output_node, output_node,
output_node_connector_index, output_node_connector_index,
@@ -571,6 +559,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id)) { if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id)) {
if let Some(layer_path) = self.layer_path.clone() { if let Some(layer_path) = self.layer_path.clone() {
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
} else {
responses.add(NodeGraphMessage::RunDocumentGraph);
} }
} }
} }
@@ -610,7 +600,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
} }
} }
if let Some(network) = self.get_active_network(data.document) { if let Some(network) = self.get_active_network(data.document) {
Self::send_graph(network, data.executor, &self.layer_path, responses); Self::send_graph(network, &self.layer_path, responses);
} }
self.collect_nested_addresses(data.document, data.document_name, responses); self.collect_nested_addresses(data.document, data.document_name, responses);
self.update_selected(data.document, responses); self.update_selected(data.document, responses);
@@ -635,7 +625,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(NodeGraphMessage::InsertNode { node_id, document_node }); responses.add(NodeGraphMessage::InsertNode { node_id, document_node });
} }
Self::send_graph(network, data.executor, &self.layer_path, responses); Self::send_graph(network, &self.layer_path, responses);
self.update_selected(data.document, responses); self.update_selected(data.document, responses);
responses.add(NodeGraphMessage::SendGraph { should_rerender: false }); responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
} }
@@ -646,7 +636,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
self.nested_path.pop(); self.nested_path.pop();
} }
if let Some(network) = self.get_active_network(data.document) { if let Some(network) = self.get_active_network(data.document) {
Self::send_graph(network, data.executor, &self.layer_path, responses); Self::send_graph(network, &self.layer_path, responses);
} }
self.collect_nested_addresses(data.document, data.document_name, responses); self.collect_nested_addresses(data.document, data.document_name, responses);
self.update_selected(data.document, responses); self.update_selected(data.document, responses);
@@ -697,7 +687,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
node.metadata.position += IVec2::new(displacement_x, displacement_y) node.metadata.position += IVec2::new(displacement_x, displacement_y)
} }
} }
Self::send_graph(network, data.executor, &self.layer_path, responses); Self::send_graph(network, &self.layer_path, responses);
} }
NodeGraphMessage::OpenNodeGraph { layer_path } => { NodeGraphMessage::OpenNodeGraph { layer_path } => {
self.layer_path = Some(layer_path); self.layer_path = Some(layer_path);
@@ -705,7 +695,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
if let Some(network) = self.get_active_network(data.document) { if let Some(network) = self.get_active_network(data.document) {
self.selected_nodes.clear(); self.selected_nodes.clear();
Self::send_graph(network, data.executor, &self.layer_path, responses); Self::send_graph(network, &self.layer_path, responses);
let node_types = document_node_types::collect_node_types(); let node_types = document_node_types::collect_node_types();
responses.add(FrontendMessage::UpdateNodeTypes { node_types }); responses.add(FrontendMessage::UpdateNodeTypes { node_types });
@@ -774,7 +764,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
} }
NodeGraphMessage::SendGraph { should_rerender } => { NodeGraphMessage::SendGraph { should_rerender } => {
if let Some(network) = self.get_active_network(data.document) { if let Some(network) = self.get_active_network(data.document) {
Self::send_graph(network, data.executor, &self.layer_path, responses); Self::send_graph(network, &self.layer_path, responses);
if should_rerender { if should_rerender {
if let Some(layer_path) = self.layer_path.clone() { if let Some(layer_path) = self.layer_path.clone() {
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
@@ -901,12 +891,14 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
.disabled .disabled
.extend(self.selected_nodes.iter().filter(|&id| !network.inputs.contains(id) && !original_outputs.contains(id))); .extend(self.selected_nodes.iter().filter(|&id| !network.inputs.contains(id) && !original_outputs.contains(id)));
} }
Self::send_graph(network, data.executor, &self.layer_path, responses); Self::send_graph(network, &self.layer_path, responses);
// Only generate node graph if one of the selected nodes is connected to the output // Only generate node graph if one of the selected nodes is connected to the output
if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id)) { if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id)) {
if let Some(layer_path) = self.layer_path.clone() { if let Some(layer_path) = self.layer_path.clone() {
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
} else {
responses.add(NodeGraphMessage::RunDocumentGraph);
} }
} }
} }
@@ -927,18 +919,20 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
} else { } else {
return; return;
} }
Self::send_graph(network, data.executor, &self.layer_path, responses); Self::send_graph(network, &self.layer_path, responses);
} }
self.update_selection_action_buttons(data.document, responses); self.update_selection_action_buttons(data.document, responses);
if let Some(layer_path) = self.layer_path.clone() { if let Some(layer_path) = self.layer_path.clone() {
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
} else {
responses.add(NodeGraphMessage::RunDocumentGraph);
} }
} }
NodeGraphMessage::UpdateNewNodeGraph => { NodeGraphMessage::UpdateNewNodeGraph => {
if let Some(network) = self.get_active_network(data.document) { if let Some(network) = self.get_active_network(data.document) {
self.selected_nodes.clear(); self.selected_nodes.clear();
Self::send_graph(network, data.executor, &self.layer_path, responses); Self::send_graph(network, &self.layer_path, responses);
let node_types = document_node_types::collect_node_types(); let node_types = document_node_types::collect_node_types();
responses.add(FrontendMessage::UpdateNodeTypes { node_types }); responses.add(FrontendMessage::UpdateNodeTypes { node_types });
@@ -181,23 +181,14 @@ fn static_nodes() -> Vec<DocumentNodeType> {
name: "Layer", name: "Layer",
category: "General", category: "General",
identifier: NodeImplementation::DocumentNode(NodeNetwork { identifier: NodeImplementation::DocumentNode(NodeNetwork {
inputs: vec![0; 8], inputs: vec![0, 2, 2, 2, 2, 2, 2, 2],
outputs: vec![NodeOutput::new(1, 0)], outputs: vec![NodeOutput::new(2, 0)],
nodes: [ nodes: [
( (
0, 0,
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![NodeInput::Network(concrete!(graphene_core::vector::VectorData))],
NodeInput::Network(concrete!(graphene_core::vector::VectorData)), implementation: DocumentNodeImplementation::proto("graphene_core::ToGraphicElementData"),
NodeInput::Network(concrete!(String)),
NodeInput::Network(concrete!(BlendMode)),
NodeInput::Network(concrete!(f32)),
NodeInput::Network(concrete!(bool)),
NodeInput::Network(concrete!(bool)),
NodeInput::Network(concrete!(bool)),
NodeInput::Network(concrete!(graphene_core::GraphicGroup)),
],
implementation: DocumentNodeImplementation::proto("graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>"),
..Default::default() ..Default::default()
}, },
), ),
@@ -211,6 +202,23 @@ fn static_nodes() -> Vec<DocumentNodeType> {
..Default::default() ..Default::default()
}, },
), ),
(
2,
DocumentNode {
inputs: vec![
NodeInput::node(1, 0),
NodeInput::Network(concrete!(String)),
NodeInput::Network(concrete!(BlendMode)),
NodeInput::Network(concrete!(f32)),
NodeInput::Network(concrete!(bool)),
NodeInput::Network(concrete!(bool)),
NodeInput::Network(concrete!(bool)),
NodeInput::Network(concrete!(graphene_core::GraphicGroup)),
],
implementation: DocumentNodeImplementation::proto("graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>"),
..Default::default()
},
),
] ]
.into(), .into(),
..Default::default() ..Default::default()
@@ -2390,6 +2398,11 @@ impl DocumentNodeType {
let inputs = self.inputs.iter().map(|default| input_override.next().unwrap_or_default().unwrap_or_else(|| default.default.clone())); let inputs = self.inputs.iter().map(|default| input_override.next().unwrap_or_default().unwrap_or_else(|| default.default.clone()));
self.to_document_node(inputs, metadata) self.to_document_node(inputs, metadata)
} }
/// Converts the [DocumentNodeType] type to a [DocumentNode], completly default
pub fn default_document_node(&self) -> DocumentNode {
self.to_document_node(self.inputs.iter().map(|input| input.default.clone()), DocumentNodeMetadata::default())
}
} }
pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork { pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork {
@@ -17,7 +17,6 @@ use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput}; use graph_craft::document::{NodeId, NodeInput};
use graphene_core::text::Font; use graphene_core::text::Font;
use glam::DAffine2;
use std::sync::Arc; use std::sync::Arc;
#[derive(Debug, Default)] #[derive(Debug, Default)]
@@ -515,7 +514,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
resolution, resolution,
} => { } => {
if let Some(node_id) = node_id { if let Some(node_id) = node_id {
self.executor.insert_thumbnail_blob_url(blob_url, layer_path.last().copied(), node_id, responses); self.executor.insert_thumbnail_blob_url(blob_url, node_id, responses);
return; return;
} }
let message = DocumentMessage::SetImageBlobUrl { let message = DocumentMessage::SetImageBlobUrl {
@@ -713,8 +712,12 @@ impl PortfolioMessageHandler {
} }
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) { pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) {
let transform = self.active_document().map(|document| document.document_legacy.root.transform).unwrap_or(DAffine2::IDENTITY); let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get_mut(&id)) else {
self.executor.poll_node_graph_evaluation(transform, responses).unwrap_or_else(|e| { warn!("Polling node graph with no document");
return;
};
self.executor.poll_node_graph_evaluation(&mut active_document.document_legacy, responses).unwrap_or_else(|e| {
log::error!("Error while evaluating node graph: {}", e); log::error!("Error while evaluating node graph: {}", e);
}); });
} }
@@ -1,10 +1,9 @@
use crate::messages::portfolio::document::node_graph;
use crate::messages::portfolio::document::node_graph::VectorDataModification; use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use bezier_rs::{ManipulatorGroup, Subpath}; use bezier_rs::{ManipulatorGroup, Subpath};
use document_legacy::{LayerId, Operation}; use document_legacy::{document_metadata::LayerNodeIdentifier, LayerId, Operation};
use graph_craft::document::NodeNetwork; use graph_craft::document::{value::TaggedValue, DocumentNode, NodeId, NodeInput, NodeNetwork};
use graphene_core::uuid::ManipulatorGroupId; use graphene_core::uuid::ManipulatorGroupId;
use glam::DAffine2; use glam::DAffine2;
@@ -12,8 +11,10 @@ use std::collections::VecDeque;
/// Create a new vector layer from a vector of [`bezier_rs::Subpath`]. /// Create a new vector layer from a vector of [`bezier_rs::Subpath`].
pub fn new_vector_layer(subpaths: Vec<Subpath<ManipulatorGroupId>>, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) { pub fn new_vector_layer(subpaths: Vec<Subpath<ManipulatorGroupId>>, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
let network = node_graph::new_vector_network(subpaths); responses.add(GraphOperationMessage::NewVectorLayer {
new_custom_layer(network, layer_path, responses); id: *layer_path.last().unwrap(),
subpaths,
});
} }
pub fn new_custom_layer(network: NodeNetwork, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) { pub fn new_custom_layer(network: NodeNetwork, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
@@ -38,3 +39,63 @@ pub fn set_manipulator_mirror_angle(manipulator_groups: &Vec<ManipulatorGroup<Ma
}); });
} }
} }
/// An immutable reference to a layer within the document node graph for easy access.
pub struct NodeGraphLayer<'a> {
node_graph: &'a NodeNetwork,
outwards_links: HashMap<NodeId, Vec<NodeId>>,
layer_node: NodeId,
}
impl<'a> NodeGraphLayer<'a> {
/// Get the layer node from the document
pub fn new(layer: LayerNodeIdentifier, document: &'a document_legacy::document::Document) -> Option<Self> {
let node_graph = &document.document_network;
let outwards_links = document.document_network.collect_outwards_links();
Some(Self {
node_graph,
outwards_links,
layer_node: layer.to_node(),
})
}
/// Get the nearest layer node from the path and the document
pub fn new_from_path(layer: &[LayerId], document: &'a document_legacy::document::Document) -> Option<Self> {
let node_graph = &document.document_network;
let outwards_links = document.document_network.collect_outwards_links();
let Some(mut layer_node) = layer.last().copied() else {
error!("Tried to modify root layer");
return None;
};
while node_graph.nodes.get(&layer_node)?.name != "Layer" {
layer_node = outwards_links.get(&layer_node)?.first().copied()?;
}
Some(Self {
node_graph,
outwards_links,
layer_node,
})
}
/// Return an iterator up the primary flow of the layer
pub fn primary_layer_flow(&self) -> impl Iterator<Item = (&'a DocumentNode, u64)> {
self.node_graph.primary_flow_from_opt(Some(self.layer_node))
}
/// Find a specific input of a node within the layer's primary flow
pub fn find_input(&self, node_name: &str, index: usize) -> Option<&'a TaggedValue> {
for (node, _id) in self.primary_layer_flow() {
if node.name == node_name {
let subpaths_input = node.inputs.get(index)?;
let NodeInput::Value { tagged_value, .. } = subpaths_input else {
continue;
};
return Some(tagged_value);
}
}
None
}
}
@@ -3,9 +3,11 @@ use crate::application::generate_uuid;
use crate::consts::VIEWPORT_GRID_ROUNDING_BIAS; use crate::consts::VIEWPORT_GRID_ROUNDING_BIAS;
use crate::consts::{COLOR_ACCENT, HIDE_HANDLE_DISTANCE, MANIPULATOR_GROUP_MARKER_SIZE, PATH_OUTLINE_WEIGHT}; use crate::consts::{COLOR_ACCENT, HIDE_HANDLE_DISTANCE, MANIPULATOR_GROUP_MARKER_SIZE, PATH_OUTLINE_WEIGHT};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::tool_messages::pen_tool::{get_manipulator_groups, get_subpaths};
use bezier_rs::ManipulatorGroup; use bezier_rs::ManipulatorGroup;
use document_legacy::document::Document; use document_legacy::document::Document;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::layers::style::{self, Fill, Stroke}; use document_legacy::layers::style::{self, Fill, Stroke};
use document_legacy::{LayerId, Operation}; use document_legacy::{LayerId, Operation};
use graphene_core::raster::color::Color; use graphene_core::raster::color::Color;
@@ -35,8 +37,8 @@ const POINT_STROKE_WEIGHT: f64 = 2.;
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct OverlayRenderer { pub struct OverlayRenderer {
shape_overlay_cache: HashMap<LayerId, Vec<LayerId>>, shape_overlay_cache: HashMap<LayerNodeIdentifier, Vec<LayerId>>,
manipulator_group_overlay_cache: HashMap<LayerId, HashMap<ManipulatorGroupId, ManipulatorGroupOverlays>>, manipulator_group_overlay_cache: HashMap<LayerNodeIdentifier, HashMap<ManipulatorGroupId, ManipulatorGroupOverlays>>,
} }
impl OverlayRenderer { impl OverlayRenderer {
@@ -44,116 +46,116 @@ impl OverlayRenderer {
Self::default() Self::default()
} }
pub fn render_subpath_overlays(&mut self, selected_shape_state: &SelectedShapeState, document: &Document, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) { pub fn query_cache(&self, layer: &LayerNodeIdentifier) -> Option<&Vec<LayerId>> {
let transform = document.generate_transform_relative_to_viewport(&layer_path).ok().unwrap(); self.shape_overlay_cache.get(layer)
if let Ok(layer) = document.layer(&layer_path) { }
let layer_id = layer_path.last().unwrap();
self.layer_overlay_visibility(document, layer_path.clone(), true, responses);
if let Some(vector_data) = layer.as_vector_data() { pub fn render_subpath_overlays(&mut self, selected_shape_state: &SelectedShapeState, document: &Document, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
let outline_cache = self.shape_overlay_cache.get(layer_id); let transform = document.metadata.transform_from_viewport(layer);
trace!("Overlay: Outline cache {:?}", &outline_cache);
// Create an outline if we do not have a cached one let Some(subpaths) = get_subpaths(layer, document) else {
if outline_cache.is_none() { return;
let outline_path = self.create_shape_outline_overlay(graphene_core::vector::Subpath::from_bezier_rs(&vector_data.subpaths), responses); };
self.shape_overlay_cache.insert(*layer_id, outline_path.clone());
Self::place_outline_overlays(outline_path.clone(), &transform, responses);
trace!("Overlay: Creating new outline {:?}", &outline_path);
} else if let Some(outline_path) = outline_cache {
trace!("Overlay: Updating overlays for {:?} owning layer: {:?}", outline_path, layer_id);
Self::modify_outline_overlays(outline_path.clone(), graphene_core::vector::Subpath::from_bezier_rs(&vector_data.subpaths), responses);
Self::place_outline_overlays(outline_path.clone(), &transform, responses);
}
// Create, place, and style the manipulator overlays self.layer_overlay_visibility(document, layer, true, responses);
for manipulator_group in vector_data.manipulator_groups() {
let manipulator_group_cache = self.manipulator_group_overlay_cache.entry(*layer_id).or_default().entry(manipulator_group.id).or_default();
// Only view in and out handles if they are not on top of the anchor let outline_cache = self.shape_overlay_cache.get(&layer);
let [in_handle, out_handle] = { trace!("Overlay: Outline cache {:?}", &outline_cache);
let anchor = manipulator_group.anchor;
let anchor_position = transform.transform_point2(anchor); // Create an outline if we do not have a cached one
let not_under_anchor = |&position: &DVec2| transform.transform_point2(position).distance_squared(anchor_position) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; if outline_cache.is_none() {
let filter_handle = |manipulator: Option<DVec2>| manipulator.filter(not_under_anchor); let outline_path = self.create_shape_outline_overlay(graphene_core::vector::Subpath::from_bezier_rs(subpaths), responses);
[filter_handle(manipulator_group.in_handle), filter_handle(manipulator_group.out_handle)] self.shape_overlay_cache.insert(layer, outline_path.clone());
}; Self::place_outline_overlays(outline_path.clone(), &transform, responses);
trace!("Overlay: Creating new outline {:?}", &outline_path);
} else if let Some(outline_path) = outline_cache {
trace!("Overlay: Updating overlays for {:?} owning layer: {:?}", outline_path, layer);
Self::modify_outline_overlays(outline_path.clone(), graphene_core::vector::Subpath::from_bezier_rs(subpaths), responses);
Self::place_outline_overlays(outline_path.clone(), &transform, responses);
}
// Create anchor // Create, place, and style the manipulator overlays
manipulator_group_cache.anchor = manipulator_group_cache.anchor.take().or_else(|| Some(Self::create_anchor_overlay(responses))); for manipulator_group in get_manipulator_groups(subpaths) {
// Create or delete in handle let manipulator_group_cache = self.manipulator_group_overlay_cache.entry(layer).or_default().entry(manipulator_group.id).or_default();
if in_handle.is_none() {
Self::remove_overlay(manipulator_group_cache.in_handle.take(), responses); // Only view in and out handles if they are not on top of the anchor
Self::remove_overlay(manipulator_group_cache.in_line.take(), responses); let [in_handle, out_handle] = {
let anchor = manipulator_group.anchor;
let anchor_position = transform.transform_point2(anchor);
let not_under_anchor = |&position: &DVec2| transform.transform_point2(position).distance_squared(anchor_position) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
let filter_handle = |manipulator: Option<DVec2>| manipulator.filter(not_under_anchor);
[filter_handle(manipulator_group.in_handle), filter_handle(manipulator_group.out_handle)]
};
// Create anchor
manipulator_group_cache.anchor = manipulator_group_cache.anchor.take().or_else(|| Some(Self::create_anchor_overlay(responses)));
// Create or delete in handle
if in_handle.is_none() {
Self::remove_overlay(manipulator_group_cache.in_handle.take(), responses);
Self::remove_overlay(manipulator_group_cache.in_line.take(), responses);
} else {
manipulator_group_cache.in_handle = manipulator_group_cache.in_handle.take().or_else(|| Self::create_handle_overlay_if_exists(in_handle, responses));
manipulator_group_cache.in_line = manipulator_group_cache.in_line.take().or_else(|| Self::create_handle_line_overlay_if_exists(in_handle, responses));
}
// Create or delete out handle
if out_handle.is_none() {
Self::remove_overlay(manipulator_group_cache.out_handle.take(), responses);
Self::remove_overlay(manipulator_group_cache.out_line.take(), responses);
} else {
manipulator_group_cache.out_handle = manipulator_group_cache.out_handle.take().or_else(|| Self::create_handle_overlay_if_exists(out_handle, responses));
manipulator_group_cache.out_line = manipulator_group_cache.out_line.take().or_else(|| Self::create_handle_line_overlay_if_exists(out_handle, responses));
}
// Update placement and style
Self::place_manipulator_group_overlays(manipulator_group, manipulator_group_cache, &transform, responses);
Self::style_overlays(selected_shape_state, layer, manipulator_group, manipulator_group_cache, responses);
}
if let Some(layer_overlays) = self.manipulator_group_overlay_cache.get_mut(&layer) {
if layer_overlays.len() > subpaths.iter().map(|subpath| subpath.len()).sum() {
layer_overlays.retain(|manipulator, manipulator_group_overlays| {
if get_manipulator_groups(subpaths).any(|current_manipulator| current_manipulator.id == *manipulator) {
true
} else { } else {
manipulator_group_cache.in_handle = manipulator_group_cache.in_handle.take().or_else(|| Self::create_handle_overlay_if_exists(in_handle, responses)); Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses);
manipulator_group_cache.in_line = manipulator_group_cache.in_line.take().or_else(|| Self::create_handle_line_overlay_if_exists(in_handle, responses)); false
} }
// Create or delete out handle });
if out_handle.is_none() {
Self::remove_overlay(manipulator_group_cache.out_handle.take(), responses);
Self::remove_overlay(manipulator_group_cache.out_line.take(), responses);
} else {
manipulator_group_cache.out_handle = manipulator_group_cache.out_handle.take().or_else(|| Self::create_handle_overlay_if_exists(out_handle, responses));
manipulator_group_cache.out_line = manipulator_group_cache.out_line.take().or_else(|| Self::create_handle_line_overlay_if_exists(out_handle, responses));
}
// Update placement and style
Self::place_manipulator_group_overlays(manipulator_group, manipulator_group_cache, &transform, responses);
Self::style_overlays(selected_shape_state, &layer_path, manipulator_group, manipulator_group_cache, responses);
}
if let Some(layer_overlays) = self.manipulator_group_overlay_cache.get_mut(layer_id) {
if layer_overlays.len() > vector_data.manipulator_groups().count() {
layer_overlays.retain(|manipulator, manipulator_group_overlays| {
if vector_data.manipulator_groups().any(|current_manipulator| current_manipulator.id == *manipulator) {
true
} else {
Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses);
false
}
});
}
}
// TODO Handle removing shapes from cache so we don't memory leak
// Eventually will get replaced with am immediate mode renderer for overlays
} }
} }
// TODO Handle removing shapes from cache so we don't memory leak
// Eventually will get replaced with am immediate mode renderer for overlays
responses.add(OverlaysMessage::Rerender); responses.add(OverlaysMessage::Rerender);
} }
pub fn clear_subpath_overlays(&mut self, _document: &Document, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) { pub fn clear_subpath_overlays(&mut self, _document: &Document, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
let layer_id = layer_path.last().unwrap();
// Remove the shape outline overlays // Remove the shape outline overlays
if let Some(overlay_path) = self.shape_overlay_cache.get(layer_id) { if let Some(overlay_path) = self.shape_overlay_cache.get(&layer) {
Self::remove_outline_overlays(overlay_path.clone(), responses) Self::remove_outline_overlays(overlay_path.clone(), responses)
} }
self.shape_overlay_cache.remove(layer_id); self.shape_overlay_cache.remove(&layer);
// Remove the ManipulatorGroup overlays // Remove the ManipulatorGroup overlays
let Some(layer_cache) = self.manipulator_group_overlay_cache.remove(layer_id) else { return }; let Some(layer_cache) = self.manipulator_group_overlay_cache.remove(&layer) else { return };
for manipulator_group_overlays in layer_cache.values() { for manipulator_group_overlays in layer_cache.values() {
Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses); Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses);
} }
} }
pub fn layer_overlay_visibility(&mut self, document: &Document, layer_path: Vec<LayerId>, visibility: bool, responses: &mut VecDeque<Message>) { pub fn layer_overlay_visibility(&mut self, document: &Document, layer: LayerNodeIdentifier, visibility: bool, responses: &mut VecDeque<Message>) {
let layer_id = layer_path.last().unwrap();
// Hide the shape outline overlays // Hide the shape outline overlays
if let Some(overlay_path) = self.shape_overlay_cache.get(layer_id) { if let Some(overlay_path) = self.shape_overlay_cache.get(&layer) {
Self::set_outline_overlay_visibility(overlay_path.clone(), visibility, responses); Self::set_outline_overlay_visibility(overlay_path.clone(), visibility, responses);
} }
// Hide the manipulator group overlays // Hide the manipulator group overlays
let Some(manipulator_groups) = self.manipulator_group_overlay_cache.get(layer_id) else { return }; let Some(manipulator_groups) = self.manipulator_group_overlay_cache.get(&layer) else { return };
if visibility { if visibility {
let Ok(layer) = document.layer(&layer_path) else { return }; let Some(subpaths) = get_subpaths(layer, document) else { return };
let Some(vector_data) = layer.as_vector_data() else { return }; for manipulator_group in get_manipulator_groups(subpaths) {
for manipulator_group in vector_data.manipulator_groups() {
let id = manipulator_group.id; let id = manipulator_group.id;
if let Some(manipulator_group_overlays) = manipulator_groups.get(&id) { if let Some(manipulator_group_overlays) = manipulator_groups.get(&id) {
Self::set_manipulator_group_overlay_visibility(manipulator_group_overlays, visibility, responses); Self::set_manipulator_group_overlay_visibility(manipulator_group_overlays, visibility, responses);
@@ -338,11 +340,11 @@ impl OverlayRenderer {
} }
/// Sets the overlay style for this point. /// Sets the overlay style for this point.
fn style_overlays(state: &SelectedShapeState, layer_path: &[LayerId], manipulator_group: &GraphiteManipulatorGroup, overlays: &ManipulatorGroupOverlays, responses: &mut VecDeque<Message>) { fn style_overlays(state: &SelectedShapeState, layer: LayerNodeIdentifier, manipulator_group: &GraphiteManipulatorGroup, overlays: &ManipulatorGroupOverlays, responses: &mut VecDeque<Message>) {
// TODO Move the style definitions out of the Subpath, should be looked up from a stylesheet or similar // TODO Move the style definitions out of the Subpath, should be looked up from a stylesheet or similar
let selected_style = style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), POINT_STROKE_WEIGHT + 1.0)), Fill::solid(COLOR_ACCENT)); let selected_style = style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), POINT_STROKE_WEIGHT + 1.0)), Fill::solid(COLOR_ACCENT));
let deselected_style = style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), POINT_STROKE_WEIGHT)), Fill::solid(Color::WHITE)); let deselected_style = style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), POINT_STROKE_WEIGHT)), Fill::solid(Color::WHITE));
let selected_shape_state = state.get(layer_path); let selected_shape_state = state.get(&layer);
// Update if the manipulator points are shown as selected // Update if the manipulator points are shown as selected
// Here the index is important, even though overlays[..] has five elements we only care about the first three // Here the index is important, even though overlays[..] has five elements we only care about the first three
for (index, overlay) in [&overlays.in_handle, &overlays.out_handle, &overlays.anchor].into_iter().enumerate() { for (index, overlay) in [&overlays.in_handle, &overlays.out_handle, &overlays.anchor].into_iter().enumerate() {
@@ -20,7 +20,7 @@ impl Resize {
pub fn start(&mut self, responses: &mut VecDeque<Message>, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, render_data: &RenderData) { pub fn start(&mut self, responses: &mut VecDeque<Message>, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, render_data: &RenderData) {
self.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); self.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
let root_transform = document.document_legacy.root.transform; let root_transform = document.document_legacy.metadata.document_to_viewport;
self.drag_start = root_transform.inverse().transform_point2(self.snap_manager.snap_position(responses, document, input.mouse.position)); self.drag_start = root_transform.inverse().transform_point2(self.snap_manager.snap_position(responses, document, input.mouse.position));
} }
@@ -32,7 +32,7 @@ impl Resize {
/// Calculate the drag start position in viewport space. /// Calculate the drag start position in viewport space.
pub fn viewport_drag_start(&self, document: &DocumentMessageHandler) -> DVec2 { pub fn viewport_drag_start(&self, document: &DocumentMessageHandler) -> DVec2 {
let root_transform = document.document_legacy.root.transform; let root_transform = document.document_legacy.metadata.document_to_viewport;
root_transform.transform_point2(self.drag_start) root_transform.transform_point2(self.drag_start)
} }
@@ -1,12 +1,13 @@
use crate::consts::DRAG_THRESHOLD; use crate::consts::DRAG_THRESHOLD;
use crate::messages::portfolio::document::node_graph::VectorDataModification; use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::tool_messages::pen_tool::{get_manipulator_from_id, get_manipulator_groups, get_mirror_handles, get_subpaths};
use bezier_rs::{Bezier, ManipulatorGroup, TValue}; use bezier_rs::{Bezier, ManipulatorGroup, TValue};
use document_legacy::document::Document; use document_legacy::document::Document;
use document_legacy::LayerId; use document_legacy::document_metadata::LayerNodeIdentifier;
use graphene_core::uuid::ManipulatorGroupId; use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::{ManipulatorPointId, SelectedType, VectorData}; use graphene_core::vector::{ManipulatorPointId, SelectedType};
use glam::DVec2; use glam::DVec2;
@@ -40,25 +41,25 @@ impl SelectedLayerState {
} }
} }
pub type SelectedShapeState = HashMap<Vec<LayerId>, SelectedLayerState>; pub type SelectedShapeState = HashMap<LayerNodeIdentifier, SelectedLayerState>;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct ShapeState { pub struct ShapeState {
// The layers we can select and edit manipulators (anchors and handles) from // The layers we can select and edit manipulators (anchors and handles) from
pub selected_shape_state: SelectedShapeState, pub selected_shape_state: SelectedShapeState,
} }
pub struct SelectedPointsInfo<'a> { pub struct SelectedPointsInfo {
pub points: Vec<ManipulatorPointInfo<'a>>, pub points: Vec<ManipulatorPointInfo>,
pub offset: DVec2, pub offset: DVec2,
} }
#[derive(Clone, Copy, Eq, PartialEq)] #[derive(Clone, Copy, Eq, PartialEq)]
pub struct ManipulatorPointInfo<'a> { pub struct ManipulatorPointInfo {
pub shape_layer_path: &'a [LayerId], pub layer: LayerNodeIdentifier,
pub point_id: ManipulatorPointId, pub point_id: ManipulatorPointId,
} }
pub type OpposingHandleLengths = HashMap<Vec<LayerId>, HashMap<ManipulatorGroupId, Option<f64>>>; pub type OpposingHandleLengths = HashMap<LayerNodeIdentifier, HashMap<ManipulatorGroupId, Option<f64>>>;
// TODO Consider keeping a list of selected manipulators to minimize traversals of the layers // TODO Consider keeping a list of selected manipulators to minimize traversals of the layers
impl ShapeState { impl ShapeState {
@@ -69,14 +70,14 @@ impl ShapeState {
return None; return None;
} }
if let Some((shape_layer_path, manipulator_point_id)) = self.find_nearest_point_indices(document, mouse_position, select_threshold) { if let Some((layer, manipulator_point_id)) = self.find_nearest_point_indices(document, mouse_position, select_threshold) {
trace!("Selecting... manipulator point: {:?}", manipulator_point_id); trace!("Selecting... manipulator point: {:?}", manipulator_point_id);
let vector_data = document.layer(&shape_layer_path).ok()?.as_vector_data()?; let subpaths = get_subpaths(layer, document)?;
let manipulator_group = vector_data.manipulator_groups().find(|group| group.id == manipulator_point_id.group)?; let manipulator_group = get_manipulator_groups(subpaths).find(|group| group.id == manipulator_point_id.group)?;
let point_position = manipulator_point_id.manipulator_type.get_position(manipulator_group)?; let point_position = manipulator_point_id.manipulator_type.get_position(manipulator_group)?;
let selected_shape_state = self.selected_shape_state.get(&shape_layer_path)?; let selected_shape_state = self.selected_shape_state.get(&layer)?;
let already_selected = selected_shape_state.is_selected(manipulator_point_id); let already_selected = selected_shape_state.is_selected(manipulator_point_id);
// Should we select or deselect the point? // Should we select or deselect the point?
@@ -90,24 +91,21 @@ impl ShapeState {
} }
// Add to the selected points // Add to the selected points
let selected_shape_state = self.selected_shape_state.get_mut(&shape_layer_path)?; let selected_shape_state = self.selected_shape_state.get_mut(&layer)?;
selected_shape_state.select_point(manipulator_point_id); selected_shape_state.select_point(manipulator_point_id);
// Offset to snap the selected point to the cursor // Offset to snap the selected point to the cursor
let offset = document let offset = mouse_position - document.metadata.transform_from_viewport(layer).transform_point2(point_position);
.generate_transform_relative_to_viewport(&shape_layer_path)
.map(|viewspace| mouse_position - viewspace.transform_point2(point_position))
.unwrap_or_default();
let points = self let points = self
.selected_shape_state .selected_shape_state
.iter() .iter()
.flat_map(|(shape_layer_path, state)| state.selected_points.iter().map(|&point_id| ManipulatorPointInfo { shape_layer_path, point_id })) .flat_map(|(layer, state)| state.selected_points.iter().map(|&point_id| ManipulatorPointInfo { layer: *layer, point_id }))
.collect(); .collect();
return Some(SelectedPointsInfo { points, offset }); return Some(SelectedPointsInfo { points, offset });
} else { } else {
let selected_shape_state = self.selected_shape_state.get_mut(&shape_layer_path)?; let selected_shape_state = self.selected_shape_state.get_mut(&layer)?;
selected_shape_state.deselect_point(manipulator_point_id); selected_shape_state.deselect_point(manipulator_point_id);
return None; return None;
@@ -117,14 +115,12 @@ impl ShapeState {
} }
pub fn select_all_points(&mut self, document: &Document) { pub fn select_all_points(&mut self, document: &Document) {
for (layer_path, selected_layer_state) in self.selected_shape_state.iter_mut() { for (layer, state) in self.selected_shape_state.iter_mut() {
let Ok(layer) = document.layer(layer_path) else { continue }; let Some(subpaths) = get_subpaths(*layer, document) else { return };
let Some(vector_data) = layer.as_vector_data() else { continue }; for manipulator in get_manipulator_groups(subpaths) {
state.select_point(ManipulatorPointId::new(manipulator.id, SelectedType::Anchor));
for group in vector_data.manipulator_groups() {
selected_layer_state.select_point(ManipulatorPointId::new(group.id, SelectedType::Anchor));
for selected_type in &[SelectedType::InHandle, SelectedType::OutHandle] { for selected_type in &[SelectedType::InHandle, SelectedType::OutHandle] {
selected_layer_state.deselect_point(ManipulatorPointId::new(group.id, *selected_type)); state.deselect_point(ManipulatorPointId::new(manipulator.id, *selected_type));
} }
} }
} }
@@ -135,14 +131,14 @@ impl ShapeState {
} }
/// Set the shapes we consider for selection, we will choose draggable manipulators from these shapes. /// Set the shapes we consider for selection, we will choose draggable manipulators from these shapes.
pub fn set_selected_layers(&mut self, target_layers: Vec<Vec<LayerId>>) { pub fn set_selected_layers(&mut self, target_layers: Vec<LayerNodeIdentifier>) {
self.selected_shape_state.retain(|layer_path, _| target_layers.contains(layer_path)); self.selected_shape_state.retain(|layer_path, _| target_layers.contains(layer_path));
for layer in target_layers { for layer in target_layers {
self.selected_shape_state.entry(layer).or_insert_with(SelectedLayerState::default); self.selected_shape_state.entry(layer).or_insert_with(SelectedLayerState::default);
} }
} }
pub fn selected_layers(&self) -> impl Iterator<Item = &Vec<LayerId>> { pub fn selected_layers(&self) -> impl Iterator<Item = &LayerNodeIdentifier> {
self.selected_shape_state.keys() self.selected_shape_state.keys()
} }
@@ -157,15 +153,14 @@ impl ShapeState {
/// A mutable iterator of all the manipulators, regardless of selection. /// A mutable iterator of all the manipulators, regardless of selection.
pub fn manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a ManipulatorGroup<ManipulatorGroupId>> { pub fn manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a ManipulatorGroup<ManipulatorGroupId>> {
self.iter(document).flat_map(|shape| shape.manipulator_groups()) self.iter(document).flat_map(|subpaths| get_manipulator_groups(subpaths))
} }
// Sets the selected points to all points for the corresponding intersection // Sets the selected points to all points for the corresponding intersection
pub fn select_all_anchors(&mut self, document: &Document, layer_path: &[LayerId]) { pub fn select_all_anchors(&mut self, document: &Document, layer: LayerNodeIdentifier) {
let Ok(layer) = document.layer(layer_path) else { return }; let Some(subpaths) = get_subpaths(layer, document) else { return };
let Some(vector_data) = layer.as_vector_data() else { return }; let Some(state) = self.selected_shape_state.get_mut(&layer) else { return };
let Some(state) = self.selected_shape_state.get_mut(layer_path) else { return }; for manipulator in get_manipulator_groups(subpaths) {
for manipulator in vector_data.manipulator_groups() {
state.select_point(ManipulatorPointId::new(manipulator.id, SelectedType::Anchor)) state.select_point(ManipulatorPointId::new(manipulator.id, SelectedType::Anchor))
} }
} }
@@ -219,7 +214,7 @@ impl ShapeState {
.selected_shape_state .selected_shape_state
.iter() .iter()
.filter_map(|(layer_id, selection_state)| { .filter_map(|(layer_id, selection_state)| {
let layer = document.layer(layer_id).ok()?; let layer = document.layer(&layer_id.to_path()).ok()?;
let vector_data = layer.as_vector_data()?; let vector_data = layer.as_vector_data()?;
Some((vector_data, selection_state)) Some((vector_data, selection_state))
}) })
@@ -236,7 +231,7 @@ impl ShapeState {
} }
} }
pub fn smooth_manipulator_group(&self, subpath: &bezier_rs::Subpath<ManipulatorGroupId>, index: usize, responses: &mut VecDeque<Message>, layer_path: &[u64]) { pub fn smooth_manipulator_group(&self, subpath: &bezier_rs::Subpath<ManipulatorGroupId>, index: usize, responses: &mut VecDeque<Message>, layer: &LayerNodeIdentifier) {
let manipulator_groups = subpath.manipulator_groups(); let manipulator_groups = subpath.manipulator_groups();
let manipulator = manipulator_groups[index]; let manipulator = manipulator_groups[index];
@@ -269,7 +264,7 @@ impl ShapeState {
// Mirror the angle but not the distance // Mirror the angle but not the distance
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(), layer: layer.to_path(),
modification: VectorDataModification::SetManipulatorHandleMirroring { modification: VectorDataModification::SetManipulatorHandleMirroring {
id: manipulator.id, id: manipulator.id,
mirror_angle: true, mirror_angle: true,
@@ -290,7 +285,7 @@ impl ShapeState {
if let Some(in_handle) = length_previous.map(|length| anchor_position + handle_vector * length) { if let Some(in_handle) = length_previous.map(|length| anchor_position + handle_vector * length) {
let point = ManipulatorPointId::new(manipulator.id, SelectedType::InHandle); let point = ManipulatorPointId::new(manipulator.id, SelectedType::InHandle);
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(), layer: layer.to_path(),
modification: VectorDataModification::SetManipulatorPosition { point, position: in_handle }, modification: VectorDataModification::SetManipulatorPosition { point, position: in_handle },
}); });
} }
@@ -298,7 +293,7 @@ impl ShapeState {
if let Some(out_handle) = length_next.map(|length| anchor_position - handle_vector * length) { if let Some(out_handle) = length_next.map(|length| anchor_position - handle_vector * length) {
let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle); let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle);
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(), layer: layer.to_path(),
modification: VectorDataModification::SetManipulatorPosition { point, position: out_handle }, modification: VectorDataModification::SetManipulatorPosition { point, position: out_handle },
}); });
} }
@@ -309,7 +304,7 @@ impl ShapeState {
let mut skip_set = HashSet::new(); let mut skip_set = HashSet::new();
for (layer_id, layer_state) in self.selected_shape_state.iter() { for (layer_id, layer_state) in self.selected_shape_state.iter() {
let layer = document.layer(layer_id).ok()?; let layer = document.layer(&layer_id.to_path()).ok()?;
let vector_data = layer.as_vector_data()?; let vector_data = layer.as_vector_data()?;
for point in layer_state.selected_points.iter() { for point in layer_state.selected_points.iter() {
@@ -338,7 +333,7 @@ impl ShapeState {
let out_handle = ManipulatorPointId::new(point.group, SelectedType::OutHandle); let out_handle = ManipulatorPointId::new(point.group, SelectedType::OutHandle);
if let Some(position) = group.out_handle { if let Some(position) = group.out_handle {
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer_id.to_vec(), layer: layer_id.to_path(),
modification: VectorDataModification::SetManipulatorPosition { point: out_handle, position }, modification: VectorDataModification::SetManipulatorPosition { point: out_handle, position },
}); });
} }
@@ -347,7 +342,7 @@ impl ShapeState {
let in_handle = ManipulatorPointId::new(point.group, SelectedType::InHandle); let in_handle = ManipulatorPointId::new(point.group, SelectedType::InHandle);
if let Some(position) = group.in_handle { if let Some(position) = group.in_handle {
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer_id.to_vec(), layer: layer_id.to_path(),
modification: VectorDataModification::SetManipulatorPosition { point: in_handle, position }, modification: VectorDataModification::SetManipulatorPosition { point: in_handle, position },
}); });
} }
@@ -373,11 +368,11 @@ impl ShapeState {
/// Move the selected points by dragging the mouse. /// Move the selected points by dragging the mouse.
pub fn move_selected_points(&self, document: &Document, delta: DVec2, mirror_distance: bool, responses: &mut VecDeque<Message>) { pub fn move_selected_points(&self, document: &Document, delta: DVec2, mirror_distance: bool, responses: &mut VecDeque<Message>) {
for (layer_path, state) in &self.selected_shape_state { for (&layer, state) in &self.selected_shape_state {
let Ok(layer) = document.layer(layer_path) else { continue }; let Some(subpaths) = get_subpaths(layer, document) else { continue };
let Some(vector_data) = layer.as_vector_data() else { continue }; let Some(mirror_angle) = get_mirror_handles(layer, document) else { continue };
let transform = document.multiply_transforms(layer_path).unwrap_or_default(); let transform = document.metadata.transform_from_viewport(layer);
let delta = transform.inverse().transform_vector2(delta); let delta = transform.inverse().transform_vector2(delta);
for &point in state.selected_points.iter() { for &point in state.selected_points.iter() {
@@ -385,13 +380,13 @@ impl ShapeState {
continue; continue;
} }
let Some(group) = vector_data.manipulator_from_id(point.group) else { continue }; let Some(group) =get_manipulator_from_id(subpaths,point.group) else { continue };
let mut move_point = |point: ManipulatorPointId| { let mut move_point = |point: ManipulatorPointId| {
let Some(previous_position) = point.manipulator_type.get_position(group) else { return }; let Some(previous_position) = point.manipulator_type.get_position(group) else { return };
let position = previous_position + delta; let position = previous_position + delta;
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer_path.clone(), layer: layer.to_path(),
modification: VectorDataModification::SetManipulatorPosition { point, position }, modification: VectorDataModification::SetManipulatorPosition { point, position },
}); });
}; };
@@ -404,13 +399,13 @@ impl ShapeState {
} }
if mirror_distance && point.manipulator_type != SelectedType::Anchor { if mirror_distance && point.manipulator_type != SelectedType::Anchor {
let mut mirror = vector_data.mirror_angle.contains(&point.group); let mut mirror = mirror_angle.contains(&point.group);
// If there is no opposing handle, we mirror even if mirror_angle doesn't contain the group // If there is no opposing handle, we mirror even if mirror_angle doesn't contain the group
// and set angle mirroring to true. // and set angle mirroring to true.
if !mirror && point.manipulator_type.opposite().get_position(group).is_none() { if !mirror && point.manipulator_type.opposite().get_position(group).is_none() {
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer_path.clone(), layer: layer.to_path(),
modification: VectorDataModification::SetManipulatorHandleMirroring { id: group.id, mirror_angle: true }, modification: VectorDataModification::SetManipulatorHandleMirroring { id: group.id, mirror_angle: true },
}); });
mirror = true; mirror = true;
@@ -428,7 +423,7 @@ impl ShapeState {
} }
let position = group.anchor - (original_handle_position - group.anchor); let position = group.anchor - (original_handle_position - group.anchor);
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer_path.clone(), layer: layer.to_path(),
modification: VectorDataModification::SetManipulatorPosition { point, position }, modification: VectorDataModification::SetManipulatorPosition { point, position },
}); });
} }
@@ -439,13 +434,13 @@ impl ShapeState {
/// Delete selected and mirrored handles with zero length when the drag stops. /// Delete selected and mirrored handles with zero length when the drag stops.
pub fn delete_selected_handles_with_zero_length(&self, document: &Document, opposing_handle_lengths: &Option<OpposingHandleLengths>, responses: &mut VecDeque<Message>) { pub fn delete_selected_handles_with_zero_length(&self, document: &Document, opposing_handle_lengths: &Option<OpposingHandleLengths>, responses: &mut VecDeque<Message>) {
for (layer_path, state) in &self.selected_shape_state { for (&layer, state) in &self.selected_shape_state {
let Ok(layer) = document.layer(layer_path) else { continue }; let Some(subpaths) = get_subpaths(layer, document) else { continue };
let Some(vector_data) = layer.as_vector_data() else { continue }; let Some(mirror_angle) = get_mirror_handles(layer, document) else { continue };
let opposing_handle_lengths = opposing_handle_lengths.as_ref().and_then(|lengths| lengths.get(layer_path)); let opposing_handle_lengths = opposing_handle_lengths.as_ref().and_then(|lengths| lengths.get(&layer));
let transform = document.multiply_transforms(layer_path).unwrap_or(glam::DAffine2::IDENTITY); let transform = document.metadata.transform_from_viewport(layer);
for &point in state.selected_points.iter() { for &point in state.selected_points.iter() {
let anchor = ManipulatorPointId::new(point.group, SelectedType::Anchor); let anchor = ManipulatorPointId::new(point.group, SelectedType::Anchor);
@@ -453,7 +448,7 @@ impl ShapeState {
continue; continue;
} }
let Some(group) = vector_data.manipulator_from_id(point.group) else { continue }; let Some(group) = get_manipulator_from_id(subpaths,point.group) else { continue };
let anchor_position = transform.transform_point2(group.anchor); let anchor_position = transform.transform_point2(group.anchor);
@@ -465,17 +460,17 @@ impl ShapeState {
if (anchor_position - point_position).length() < DRAG_THRESHOLD { if (anchor_position - point_position).length() < DRAG_THRESHOLD {
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer_path.clone(), layer: layer.to_path(),
modification: VectorDataModification::RemoveManipulatorPoint { point }, modification: VectorDataModification::RemoveManipulatorPoint { point },
}); });
// Remove opposing handle if it is not selected and is mirrored. // Remove opposing handle if it is not selected and is mirrored.
let opposite_point = ManipulatorPointId::new(point.group, point.manipulator_type.opposite()); let opposite_point = ManipulatorPointId::new(point.group, point.manipulator_type.opposite());
if !state.is_selected(opposite_point) && vector_data.mirror_angle.contains(&point.group) { if !state.is_selected(opposite_point) && mirror_angle.contains(&point.group) {
if let Some(lengths) = opposing_handle_lengths { if let Some(lengths) = opposing_handle_lengths {
if lengths.contains_key(&point.group) { if lengths.contains_key(&point.group) {
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer_path.clone(), layer: layer.to_path(),
modification: VectorDataModification::RemoveManipulatorPoint { point: opposite_point }, modification: VectorDataModification::RemoveManipulatorPoint { point: opposite_point },
}); });
} }
@@ -490,11 +485,9 @@ impl ShapeState {
pub fn opposing_handle_lengths(&self, document: &Document) -> OpposingHandleLengths { pub fn opposing_handle_lengths(&self, document: &Document) -> OpposingHandleLengths {
self.selected_shape_state self.selected_shape_state
.iter() .iter()
.filter_map(|(path, state)| { .filter_map(|(&layer, state)| {
let layer = document.layer(path).ok()?; let subpaths = get_subpaths(layer, document)?;
let vector_data = layer.as_vector_data()?; let opposing_handle_lengths = subpaths
let opposing_handle_lengths = vector_data
.subpaths
.iter() .iter()
.flat_map(|subpath| { .flat_map(|subpath| {
subpath.manipulator_groups().iter().filter_map(|manipulator_group| { subpath.manipulator_groups().iter().filter_map(|manipulator_group| {
@@ -525,21 +518,21 @@ impl ShapeState {
}) })
}) })
.collect::<HashMap<_, _>>(); .collect::<HashMap<_, _>>();
Some((path.clone(), opposing_handle_lengths)) Some((layer, opposing_handle_lengths))
}) })
.collect::<HashMap<_, _>>() .collect::<HashMap<_, _>>()
} }
/// Reset the opposing handle lengths. /// Reset the opposing handle lengths.
pub fn reset_opposing_handle_lengths(&self, document: &Document, opposing_handle_lengths: &OpposingHandleLengths, responses: &mut VecDeque<Message>) { pub fn reset_opposing_handle_lengths(&self, document: &Document, opposing_handle_lengths: &OpposingHandleLengths, responses: &mut VecDeque<Message>) {
for (path, state) in &self.selected_shape_state { for (&layer, state) in &self.selected_shape_state {
let Ok(layer) = document.layer(path) else { continue }; let Some(subpaths) = get_subpaths(layer, document) else { continue };
let Some(vector_data) = layer.as_vector_data() else { continue }; let Some(mirror_angle) = get_mirror_handles(layer, document) else { continue };
let Some(opposing_handle_lengths) = opposing_handle_lengths.get(path) else { continue }; let Some(opposing_handle_lengths) = opposing_handle_lengths.get(&layer) else { continue };
for subpath in &vector_data.subpaths { for subpath in subpaths {
for manipulator_group in subpath.manipulator_groups() { for manipulator_group in subpath.manipulator_groups() {
if !vector_data.mirror_angle.contains(&manipulator_group.id) { if !mirror_angle.contains(&manipulator_group.id) {
continue; continue;
} }
@@ -563,7 +556,7 @@ impl ShapeState {
let Some(opposing_handle_length) = opposing_handle_length else { let Some(opposing_handle_length) = opposing_handle_length else {
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: path.to_vec(), layer: layer.to_path(),
modification: VectorDataModification::RemoveManipulatorPoint { modification: VectorDataModification::RemoveManipulatorPoint {
point: ManipulatorPointId::new(manipulator_group.id, single_selected_handle.opposite()), point: ManipulatorPointId::new(manipulator_group.id, single_selected_handle.opposite()),
}, },
@@ -582,7 +575,7 @@ impl ShapeState {
assert!(position.is_finite(), "Opposing handle not finite!"); assert!(position.is_finite(), "Opposing handle not finite!");
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: path.to_vec(), layer: layer.to_path(),
modification: VectorDataModification::SetManipulatorPosition { point, position }, modification: VectorDataModification::SetManipulatorPosition { point, position },
}); });
} }
@@ -595,7 +588,7 @@ impl ShapeState {
for (layer, state) in &self.selected_shape_state { for (layer, state) in &self.selected_shape_state {
for &point in &state.selected_points { for &point in &state.selected_points {
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer.to_vec(), layer: layer.to_path(),
modification: VectorDataModification::RemoveManipulatorPoint { point }, modification: VectorDataModification::RemoveManipulatorPoint { point },
}) })
} }
@@ -607,7 +600,7 @@ impl ShapeState {
for (layer, state) in &self.selected_shape_state { for (layer, state) in &self.selected_shape_state {
for point in &state.selected_points { for point in &state.selected_points {
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer.to_vec(), layer: layer.to_path(),
modification: VectorDataModification::ToggleManipulatorHandleMirroring { id: point.group }, modification: VectorDataModification::ToggleManipulatorHandleMirroring { id: point.group },
}) })
} }
@@ -619,7 +612,7 @@ impl ShapeState {
for (layer, state) in &self.selected_shape_state { for (layer, state) in &self.selected_shape_state {
for point in &state.selected_points { for point in &state.selected_points {
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer.to_vec(), layer: layer.to_path(),
modification: VectorDataModification::SetManipulatorHandleMirroring { id: point.group, mirror_angle }, modification: VectorDataModification::SetManipulatorHandleMirroring { id: point.group, mirror_angle },
}); });
} }
@@ -627,27 +620,24 @@ impl ShapeState {
} }
/// Iterate over the shapes. /// Iterate over the shapes.
pub fn iter<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a VectorData> + 'a { pub fn iter<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a Vec<bezier_rs::Subpath<ManipulatorGroupId>>> + 'a {
self.selected_shape_state self.selected_shape_state.keys().filter_map(|&layer| get_subpaths(layer, document))
.keys()
.flat_map(|layer_id| document.layer(layer_id))
.filter_map(|shape| shape.as_vector_data())
} }
/// Find a [ManipulatorPoint] that is within the selection threshold and return the layer path, an index to the [ManipulatorGroup], and an enum index for [ManipulatorPoint]. /// Find a [ManipulatorPoint] that is within the selection threshold and return the layer path, an index to the [ManipulatorGroup], and an enum index for [ManipulatorPoint].
pub fn find_nearest_point_indices(&mut self, document: &Document, mouse_position: DVec2, select_threshold: f64) -> Option<(Vec<LayerId>, ManipulatorPointId)> { pub fn find_nearest_point_indices(&mut self, document: &Document, mouse_position: DVec2, select_threshold: f64) -> Option<(LayerNodeIdentifier, ManipulatorPointId)> {
if self.selected_shape_state.is_empty() { if self.selected_shape_state.is_empty() {
return None; return None;
} }
let select_threshold_squared = select_threshold * select_threshold; let select_threshold_squared = select_threshold * select_threshold;
// Find the closest control point among all elements of shapes_to_modify // Find the closest control point among all elements of shapes_to_modify
for layer in self.selected_shape_state.keys() { for &layer in self.selected_shape_state.keys() {
if let Some((manipulator_point_id, distance_squared)) = Self::closest_point_in_layer(document, layer, mouse_position) { if let Some((manipulator_point_id, distance_squared)) = Self::closest_point_in_layer(document, layer, mouse_position) {
// Choose the first point under the threshold // Choose the first point under the threshold
if distance_squared < select_threshold_squared { if distance_squared < select_threshold_squared {
trace!("Selecting... manipulator point: {:?}", manipulator_point_id); trace!("Selecting... manipulator point: {:?}", manipulator_point_id);
return Some((layer.clone(), manipulator_point_id)); return Some((layer, manipulator_point_id));
} }
} }
} }
@@ -659,20 +649,18 @@ impl ShapeState {
/// Find the closest manipulator, manipulator point, and distance so we can select path elements. /// Find the closest manipulator, manipulator point, and distance so we can select path elements.
/// Brute force comparison to determine which manipulator (handle or anchor) we want to select taking O(n) time. /// Brute force comparison to determine which manipulator (handle or anchor) we want to select taking O(n) time.
/// Return value is an `Option` of the tuple representing `(ManipulatorPointId, distance squared)`. /// Return value is an `Option` of the tuple representing `(ManipulatorPointId, distance squared)`.
fn closest_point_in_layer(document: &Document, layer_path: &[LayerId], pos: glam::DVec2) -> Option<(ManipulatorPointId, f64)> { fn closest_point_in_layer(document: &Document, layer: LayerNodeIdentifier, pos: glam::DVec2) -> Option<(ManipulatorPointId, f64)> {
let mut closest_distance_squared: f64 = f64::MAX; let mut closest_distance_squared: f64 = f64::MAX;
let mut result = None; let mut result = None;
let vector_data = document.layer(layer_path).ok()?.as_vector_data()?; let subpaths = get_subpaths(layer, document)?;
let viewspace = document.generate_transform_relative_to_viewport(layer_path).ok()?; let viewspace = document.metadata.transform_from_viewport(layer);
for subpath in &vector_data.subpaths { for manipulator in get_manipulator_groups(subpaths) {
for manipulator in subpath.manipulator_groups() { let (selected, distance_squared) = SelectedType::closest_widget(manipulator, viewspace, pos, crate::consts::HIDE_HANDLE_DISTANCE);
let (selected, distance_squared) = SelectedType::closest_widget(manipulator, viewspace, pos, crate::consts::HIDE_HANDLE_DISTANCE);
if distance_squared < closest_distance_squared { if distance_squared < closest_distance_squared {
closest_distance_squared = distance_squared; closest_distance_squared = distance_squared;
result = Some((ManipulatorPointId::new(manipulator.id, selected), distance_squared)); result = Some((ManipulatorPointId::new(manipulator.id, selected), distance_squared));
}
} }
} }
@@ -680,17 +668,17 @@ impl ShapeState {
} }
/// Find the `t` value along the path segment we have clicked upon, together with that segment ID. /// Find the `t` value along the path segment we have clicked upon, together with that segment ID.
fn closest_segment(&self, document: &Document, layer_path: &[LayerId], position: glam::DVec2, tolerance: f64) -> Option<(ManipulatorGroupId, ManipulatorGroupId, Bezier, f64)> { fn closest_segment(&self, document: &Document, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<(ManipulatorGroupId, ManipulatorGroupId, Bezier, f64)> {
let transform = document.generate_transform_relative_to_viewport(layer_path).ok()?; let transform = document.metadata.transform_from_viewport(layer);
let layer_pos = transform.inverse().transform_point2(position); let layer_pos = transform.inverse().transform_point2(position);
let projection_options = bezier_rs::ProjectionOptions { lut_size: 5, ..Default::default() }; let projection_options = bezier_rs::ProjectionOptions { lut_size: 5, ..Default::default() };
let mut result = None; let mut result = None;
let mut closest_distance_squared: f64 = tolerance * tolerance; let mut closest_distance_squared: f64 = tolerance * tolerance;
let vector_data = document.layer(layer_path).ok()?.as_vector_data()?; let subpaths = get_subpaths(layer, document)?;
for subpath in &vector_data.subpaths { for subpath in subpaths {
for (manipulator_index, bezier) in subpath.iter().enumerate() { for (manipulator_index, bezier) in subpath.iter().enumerate() {
let t = bezier.project(layer_pos, Some(projection_options)); let t = bezier.project(layer_pos, Some(projection_options));
let layerspace = bezier.evaluate(TValue::Parametric(t)); let layerspace = bezier.evaluate(TValue::Parametric(t));
@@ -712,15 +700,15 @@ impl ShapeState {
/// Handles the splitting of a curve to insert new points (which can be activated by double clicking on a curve with the Path tool). /// Handles the splitting of a curve to insert new points (which can be activated by double clicking on a curve with the Path tool).
pub fn split(&self, document: &Document, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) { pub fn split(&self, document: &Document, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) {
for layer_path in self.selected_layers() { for &layer in self.selected_layers() {
if let Some((start, end, bezier, t)) = self.closest_segment(document, layer_path, position, tolerance) { if let Some((start, end, bezier, t)) = self.closest_segment(document, layer, position, tolerance) {
let [first, second] = bezier.split(TValue::Parametric(t)); let [first, second] = bezier.split(TValue::Parametric(t));
// Adjust the first manipulator group's out handle // Adjust the first manipulator group's out handle
let point = ManipulatorPointId::new(start, SelectedType::OutHandle); let point = ManipulatorPointId::new(start, SelectedType::OutHandle);
let position = first.handle_start().unwrap_or(first.start()); let position = first.handle_start().unwrap_or(first.start());
let out_handle = GraphOperationMessage::Vector { let out_handle = GraphOperationMessage::Vector {
layer: layer_path.clone(), layer: layer.to_path(),
modification: VectorDataModification::SetManipulatorPosition { point, position }, modification: VectorDataModification::SetManipulatorPosition { point, position },
}; };
responses.add(out_handle); responses.add(out_handle);
@@ -728,7 +716,7 @@ impl ShapeState {
// Insert a new manipulator group between the existing ones // Insert a new manipulator group between the existing ones
let manipulator_group = ManipulatorGroup::new(first.end(), first.handle_end(), second.handle_start()); let manipulator_group = ManipulatorGroup::new(first.end(), first.handle_end(), second.handle_start());
let insert = GraphOperationMessage::Vector { let insert = GraphOperationMessage::Vector {
layer: layer_path.clone(), layer: layer.to_path(),
modification: VectorDataModification::AddManipulatorGroup { manipulator_group, after_id: start }, modification: VectorDataModification::AddManipulatorGroup { manipulator_group, after_id: start },
}; };
responses.add(insert); responses.add(insert);
@@ -737,7 +725,7 @@ impl ShapeState {
let point = ManipulatorPointId::new(end, SelectedType::InHandle); let point = ManipulatorPointId::new(end, SelectedType::InHandle);
let position = second.handle_end().unwrap_or(second.end()); let position = second.handle_end().unwrap_or(second.end());
let in_handle = GraphOperationMessage::Vector { let in_handle = GraphOperationMessage::Vector {
layer: layer_path.clone(), layer: layer.to_path(),
modification: VectorDataModification::SetManipulatorPosition { point, position }, modification: VectorDataModification::SetManipulatorPosition { point, position },
}; };
responses.add(in_handle); responses.add(in_handle);
@@ -749,15 +737,15 @@ impl ShapeState {
/// Handles the flipping between sharp corner and smooth (which can be activated by double clicking on an anchor with the Path tool). /// Handles the flipping between sharp corner and smooth (which can be activated by double clicking on an anchor with the Path tool).
pub fn flip_sharp(&self, document: &Document, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool { pub fn flip_sharp(&self, document: &Document, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
let mut process_layer = |layer_path| { let mut process_layer = |layer| {
let vector_data = document.layer(layer_path).ok()?.as_vector_data()?; let subpaths = get_subpaths(layer, document)?;
let transform_to_screenspace = document.generate_transform_relative_to_viewport(layer_path).ok()?; let transform_to_screenspace = document.metadata.transform_from_viewport(layer);
let mut result = None; let mut result = None;
let mut closest_distance_squared = tolerance * tolerance; let mut closest_distance_squared = tolerance * tolerance;
// Find the closest anchor point on the current layer // Find the closest anchor point on the current layer
for (subpath_index, subpath) in vector_data.subpaths.iter().enumerate() { for (subpath_index, subpath) in subpaths.iter().enumerate() {
for (manipulator_index, manipulator) in subpath.manipulator_groups().iter().enumerate() { for (manipulator_index, manipulator) in subpath.manipulator_groups().iter().enumerate() {
let screenspace = transform_to_screenspace.transform_point2(manipulator.anchor); let screenspace = transform_to_screenspace.transform_point2(manipulator.anchor);
let distance_squared = screenspace.distance_squared(position); let distance_squared = screenspace.distance_squared(position);
@@ -771,7 +759,7 @@ impl ShapeState {
let (subpath_index, index, manipulator) = result?; let (subpath_index, index, manipulator) = result?;
let anchor_position = manipulator.anchor; let anchor_position = manipulator.anchor;
let subpath = &vector_data.subpaths[subpath_index]; let subpath = &subpaths[subpath_index];
// Check by comparing the handle positions to the anchor if this maniuplator group is a point // Check by comparing the handle positions to the anchor if this maniuplator group is a point
let already_sharp = match (manipulator.in_handle, manipulator.out_handle) { let already_sharp = match (manipulator.in_handle, manipulator.out_handle) {
@@ -781,20 +769,20 @@ impl ShapeState {
}; };
if already_sharp { if already_sharp {
self.smooth_manipulator_group(subpath, index, responses, layer_path); self.smooth_manipulator_group(subpath, index, responses, &layer);
} else { } else {
let point = ManipulatorPointId::new(manipulator.id, SelectedType::InHandle); let point = ManipulatorPointId::new(manipulator.id, SelectedType::InHandle);
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(), layer: layer.to_path(),
modification: VectorDataModification::SetManipulatorPosition { point, position: anchor_position }, modification: VectorDataModification::SetManipulatorPosition { point, position: anchor_position },
}); });
let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle); let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle);
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(), layer: layer.to_path(),
modification: VectorDataModification::SetManipulatorPosition { point, position: anchor_position }, modification: VectorDataModification::SetManipulatorPosition { point, position: anchor_position },
}); });
responses.add(GraphOperationMessage::Vector { responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(), layer: layer.to_path(),
modification: VectorDataModification::SetManipulatorHandleMirroring { modification: VectorDataModification::SetManipulatorHandleMirroring {
id: manipulator.id, id: manipulator.id,
mirror_angle: false, mirror_angle: false,
@@ -804,8 +792,8 @@ impl ShapeState {
Some(true) Some(true)
}; };
for layer_path in self.selected_shape_state.keys() { for &layer in self.selected_shape_state.keys() {
if let Some(result) = process_layer(layer_path) { if let Some(result) = process_layer(layer) {
return result; return result;
} }
} }
@@ -813,17 +801,16 @@ impl ShapeState {
} }
pub fn select_all_in_quad(&mut self, document: &Document, quad: [DVec2; 2], clear_selection: bool) { pub fn select_all_in_quad(&mut self, document: &Document, quad: [DVec2; 2], clear_selection: bool) {
for (layer_path, state) in &mut self.selected_shape_state { for (&layer, state) in &mut self.selected_shape_state {
if clear_selection { if clear_selection {
state.clear_points() state.clear_points()
} }
let Ok(layer) = document.layer(layer_path) else { continue }; let Some(subpaths) = get_subpaths(layer, document) else { continue };
let Some(vector_data) = layer.as_vector_data() else { continue };
let transform = document.multiply_transforms(layer_path).unwrap_or_default(); let transform = document.metadata.transform_from_viewport(layer);
for manipulator_group in vector_data.manipulator_groups() { for manipulator_group in get_manipulator_groups(subpaths) {
for selected_type in [SelectedType::Anchor, SelectedType::InHandle, SelectedType::OutHandle] { for selected_type in [SelectedType::Anchor, SelectedType::InHandle, SelectedType::OutHandle] {
let Some(position) = selected_type.get_position(manipulator_group) else { continue }; let Some(position) = selected_type.get_position(manipulator_group) else { continue };
let transformed_position = transform.transform_point2(position); let transformed_position = transform.transform_point2(position);
@@ -6,6 +6,7 @@ use crate::consts::{
}; };
use crate::messages::prelude::*; use crate::messages::prelude::*;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::layers::layer_info::Layer; use document_legacy::layers::layer_info::Layer;
use document_legacy::layers::style::{self, Stroke}; use document_legacy::layers::style::{self, Stroke};
use document_legacy::{LayerId, Operation}; use document_legacy::{LayerId, Operation};
@@ -287,7 +288,12 @@ impl SnapManager {
} }
}) })
.flatten() .flatten()
.filter(|&(point_id, _)| !ignore_points.contains(&ManipulatorPointInfo { shape_layer_path: path, point_id })) .filter(|&(point_id, _)| {
!ignore_points.contains(&ManipulatorPointInfo {
layer: LayerNodeIdentifier::from_path(path, document_message_handler.network()),
point_id,
})
})
.map(|(_, pos)| transform.transform_point2(pos)); .map(|(_, pos)| transform.transform_point2(pos));
self.add_snap_points(document_message_handler, input, snap_points); self.add_snap_points(document_message_handler, input, snap_points);
} }
@@ -1,20 +1,15 @@
use super::tool_prelude::*;
use crate::application::generate_uuid; use crate::application::generate_uuid;
use crate::consts::SELECTION_TOLERANCE; use crate::consts::SELECTION_TOLERANCE;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::misc::TargetDocument; use crate::messages::portfolio::document::utility_types::misc::TargetDocument;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::common_functionality::transformation_cage::*; use crate::messages::tool::common_functionality::transformation_cage::*;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::intersection::Quad; use document_legacy::intersection::Quad;
use document_legacy::LayerId; use document_legacy::LayerId;
use glam::{DVec2, IVec2, Vec2Swizzles}; use document_legacy::layers::RenderData;
use serde::{Deserialize, Serialize}; use glam::{IVec2, Vec2Swizzles};
#[derive(Default)] #[derive(Default)]
pub struct ArtboardTool { pub struct ArtboardTool {
@@ -108,306 +103,317 @@ struct ArtboardToolData {
drag_current: DVec2, drag_current: DVec2,
} }
impl ArtboardToolData {
fn refresh_overlays(&mut self, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque<Message>) {
let current_artboard = self.selected_artboard.and_then(|path| document.artboard_bounding_box_and_transform(&[path], render_data));
match (current_artboard, self.bounding_box_overlays.take()) {
(None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses),
(Some((bounds, transform)), paths) => {
let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses));
bounding_box_overlays.bounds = bounds;
bounding_box_overlays.transform = transform;
bounding_box_overlays.transform(responses);
self.bounding_box_overlays = Some(bounding_box_overlays);
responses.add(OverlaysMessage::Rerender);
}
_ => {}
};
}
fn check_dragging_bounds(&mut self, cursor: DVec2) -> Option<(bool, bool, bool, bool)> {
let bounding_box = self.bounding_box_overlays.as_mut()?;
let edges = bounding_box.check_selected_edges(cursor)?;
let (top, bottom, left, right) = edges;
let selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds);
bounding_box.opposite_pivot = selected_edges.calculate_pivot();
Some(edges)
}
fn start_resizing(&mut self, selected_edges: (bool, bool, bool, bool), document: &DocumentMessageHandler, render_data: &RenderData, input: &InputPreprocessorMessageHandler) {
let snap_x = selected_edges.2 || selected_edges.3;
let snap_y = selected_edges.0 || selected_edges.1;
let artboard = self.selected_artboard.unwrap();
self.snap_manager
.start_snap(document, input, document.bounding_boxes(None, Some(artboard), render_data), snap_x, snap_y);
self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
if let Some(bounds) = &mut self.bounding_box_overlays {
let pivot = document.artboard_message_handler.artboards_document.pivot(&[artboard], render_data).unwrap_or_default();
let root = document.document_legacy.metadata.document_to_viewport;
let pivot = root.inverse().transform_point2(pivot);
bounds.center_of_transformation = pivot;
}
}
fn select_artboard(&mut self, document: &DocumentMessageHandler, render_data: &RenderData, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> bool {
responses.add(DocumentMessage::StartTransaction);
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]);
let intersection = document.artboard_message_handler.artboards_document.intersects_quad_root(quad, render_data);
responses.add(BroadcastEvent::DocumentIsDirty);
if let Some(intersection) = intersection.last() {
self.selected_artboard = Some(intersection[0]);
self.snap_manager
.start_snap(document, input, document.bounding_boxes(None, Some(intersection[0]), render_data), true, true);
self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
true
} else {
self.selected_artboard = None;
responses.add(PropertiesPanelMessage::ClearSelection);
false
}
}
fn resize_artboard(&mut self, responses: &mut VecDeque<Message>, document: &DocumentMessageHandler, mouse_position: DVec2, from_center: bool, constrain_square: bool) {
let Some(bounds) = &self.bounding_box_overlays else {
return;
};
let Some(movement) = &bounds.selected_edges else {
return;
};
let snapped_mouse_position: DVec2 = self.snap_manager.snap_position(responses, document, mouse_position);
let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, from_center, bounds.center_of_transformation, constrain_square);
responses.add(ArtboardMessage::ResizeArtboard {
artboard: self.selected_artboard.unwrap(),
position: position.round().into(),
size: size.round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: self.selected_artboard.unwrap(),
location: position.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
responses.add(BroadcastEvent::DocumentIsDirty);
}
}
impl Fsm for ArtboardToolFsmState { impl Fsm for ArtboardToolFsmState {
type ToolData = ArtboardToolData; type ToolData = ArtboardToolData;
type ToolOptions = (); type ToolOptions = ();
fn transition( fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
self, let ToolActionHandlerData { document, input, render_data, .. } = tool_action_data;
event: ToolMessage,
tool_data: &mut Self::ToolData,
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
if let ToolMessage::Artboard(event) = event {
match (self, event) {
(state, ArtboardToolMessage::DocumentIsDirty) if state != ArtboardToolFsmState::Drawing => {
let current_artboard = tool_data.selected_artboard.and_then(|path| document.artboard_bounding_box_and_transform(&[path], render_data));
match (current_artboard, tool_data.bounding_box_overlays.take()) {
(None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses),
(Some((bounds, transform)), paths) => {
let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses));
bounding_box_overlays.bounds = bounds; let ToolMessage::Artboard(event) = event else {
bounding_box_overlays.transform = transform; return self;
};
bounding_box_overlays.transform(responses); match (self, event) {
(state, ArtboardToolMessage::DocumentIsDirty) if state != ArtboardToolFsmState::Drawing => {
tool_data.refresh_overlays(document, render_data, responses);
tool_data.bounding_box_overlays = Some(bounding_box_overlays); self
}
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerDown) => {
tool_data.drag_start = input.mouse.position;
tool_data.drag_current = input.mouse.position;
responses.add(OverlaysMessage::Rerender); if let Some(selected_edges) = tool_data.check_dragging_bounds(input.mouse.position) {
} responses.add(DocumentMessage::StartTransaction);
_ => {} tool_data.start_resizing(selected_edges, document, render_data, input);
};
self
}
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerDown) => {
tool_data.drag_start = input.mouse.position;
tool_data.drag_current = input.mouse.position;
let dragging_bounds = if let Some(bounding_box) = &mut tool_data.bounding_box_overlays {
let edges = bounding_box.check_selected_edges(input.mouse.position);
bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| {
let edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds);
bounding_box.opposite_pivot = edges.calculate_pivot();
edges
});
edges
} else {
None
};
if let Some(selected_edges) = dragging_bounds {
responses.add(DocumentMessage::StartTransaction);
let snap_x = selected_edges.2 || selected_edges.3;
let snap_y = selected_edges.0 || selected_edges.1;
let artboard = tool_data.selected_artboard.unwrap();
tool_data
.snap_manager
.start_snap(document, input, document.bounding_boxes(None, Some(artboard), render_data), snap_x, snap_y);
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
let pivot = document.artboard_message_handler.artboards_document.pivot(&[artboard], render_data).unwrap_or_default();
let root = document.document_legacy.root.transform;
let pivot = root.inverse().transform_point2(pivot);
bounds.center_of_transformation = pivot;
}
ArtboardToolFsmState::ResizingBounds
} else {
responses.add(DocumentMessage::StartTransaction);
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]);
let intersection = document.artboard_message_handler.artboards_document.intersects_quad_root(quad, render_data);
responses.add(BroadcastEvent::DocumentIsDirty);
if let Some(intersection) = intersection.last() {
tool_data.selected_artboard = Some(intersection[0]);
tool_data
.snap_manager
.start_snap(document, input, document.bounding_boxes(None, Some(intersection[0]), render_data), true, true);
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
ArtboardToolFsmState::Dragging
} else {
tool_data.selected_artboard = None;
responses.add(PropertiesPanelMessage::ClearSelection);
ArtboardToolFsmState::Drawing
}
}
}
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
if let Some(bounds) = &tool_data.bounding_box_overlays {
if let Some(movement) = &bounds.selected_edges {
let from_center = input.keyboard.get(center as usize);
let constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize);
let mouse_position = input.mouse.position;
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, from_center, bounds.center_of_transformation, constrain_square);
responses.add(ArtboardMessage::ResizeArtboard {
artboard: tool_data.selected_artboard.unwrap(),
position: position.round().into(),
size: size.round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap(),
location: position.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
responses.add(BroadcastEvent::DocumentIsDirty);
}
}
ArtboardToolFsmState::ResizingBounds ArtboardToolFsmState::ResizingBounds
} } else if tool_data.select_artboard(document, render_data, input, responses) {
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, .. }) => {
if let Some(bounds) = &tool_data.bounding_box_overlays {
let axis_align = input.keyboard.get(constrain_axis_or_aspect as usize);
let mouse_position = axis_align_drag(axis_align, input.mouse.position, tool_data.drag_start);
let mouse_delta = mouse_position - tool_data.drag_current;
let snap = bounds.evaluate_transform_handle_positions().into_iter().collect();
let closest_move = tool_data.snap_manager.snap_layers(responses, document, snap, mouse_delta);
let size = bounds.bounds[1] - bounds.bounds[0];
let position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_position - tool_data.drag_current + closest_move);
responses.add(ArtboardMessage::ResizeArtboard {
artboard: tool_data.selected_artboard.unwrap(),
position: position.round().into(),
size: size.round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap(),
location: position.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
responses.add(BroadcastEvent::DocumentIsDirty);
tool_data.drag_current = mouse_position + closest_move;
}
ArtboardToolFsmState::Dragging ArtboardToolFsmState::Dragging
} } else {
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
let mouse_position = input.mouse.position;
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
let root_transform = document.document_legacy.root.transform.inverse();
let mut start = tool_data.drag_start;
let mut size = snapped_mouse_position - start;
// Constrain axis
if input.keyboard.get(constrain_axis_or_aspect as usize) {
size = size.abs().max(size.abs().yx()) * size.signum();
}
// From center
if input.keyboard.get(center as usize) {
start -= size;
size *= 2.;
}
let start = root_transform.transform_point2(start);
let size = root_transform.transform_vector2(size);
if let Some(artboard) = tool_data.selected_artboard {
responses.add(ArtboardMessage::ResizeArtboard {
artboard,
position: start.round().into(),
size: size.round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap(),
location: start.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
} else {
let id = generate_uuid();
tool_data.selected_artboard = Some(id);
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, Some(id), render_data), true, true);
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
responses.add(ArtboardMessage::AddArtboard {
id: Some(id),
position: start.round().into(),
size: (1., 1.),
});
responses.add(GraphOperationMessage::NewArtboard {
id,
artboard: graphene_core::Artboard {
graphic_group: graphene_core::GraphicGroup::EMPTY,
location: start.round().as_ivec2(),
dimensions: IVec2::splat(1),
background: graphene_core::Color::WHITE,
clip: false,
},
})
}
// Have to put message here instead of when Artboard is created
// This might result in a few more calls but it is not reliant on the order of messages
responses.add(BroadcastEvent::DocumentIsDirty);
ArtboardToolFsmState::Drawing ArtboardToolFsmState::Drawing
} }
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerMove { .. }) => { }
let cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false)); (ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
let from_center = input.keyboard.get(center as usize);
let constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize);
let mouse_position = input.mouse.position;
tool_data.resize_artboard(responses, document, mouse_position, from_center, constrain_square);
if tool_data.cursor != cursor { ArtboardToolFsmState::ResizingBounds
tool_data.cursor = cursor; }
responses.add(FrontendMessage::UpdateMouseCursor { cursor }); (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, .. }) => {
} if let Some(bounds) = &tool_data.bounding_box_overlays {
let axis_align = input.keyboard.get(constrain_axis_or_aspect as usize);
ArtboardToolFsmState::Ready let mouse_position = axis_align_drag(axis_align, input.mouse.position, tool_data.drag_start);
} let mouse_delta = mouse_position - tool_data.drag_current;
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => {
tool_data.snap_manager.cleanup(responses);
if let Some(bounds) = &mut tool_data.bounding_box_overlays { let snap = bounds.evaluate_transform_handle_positions().into_iter().collect();
bounds.original_transforms.clear(); let closest_move = tool_data.snap_manager.snap_layers(responses, document, snap, mouse_delta);
}
ArtboardToolFsmState::Ready let size = bounds.bounds[1] - bounds.bounds[0];
}
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => {
tool_data.snap_manager.cleanup(responses);
if let Some(bounds) = &mut tool_data.bounding_box_overlays { let position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_position - tool_data.drag_current + closest_move);
bounds.original_transforms.clear();
} responses.add(ArtboardMessage::ResizeArtboard {
artboard: tool_data.selected_artboard.unwrap(),
position: position.round().into(),
size: size.round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap(),
location: position.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
ArtboardToolFsmState::Ready tool_data.drag_current = mouse_position + closest_move;
} }
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => { ArtboardToolFsmState::Dragging
tool_data.snap_manager.cleanup(responses);
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
bounds.original_transforms.clear();
}
ArtboardToolFsmState::Ready
}
(_, ArtboardToolMessage::DeleteSelected) => {
if let Some(artboard) = tool_data.selected_artboard.take() {
responses.add(ArtboardMessage::DeleteArtboard { artboard });
responses.add(GraphOperationMessage::DeleteArtboard { id: artboard });
responses.add(BroadcastEvent::DocumentIsDirty);
}
ArtboardToolFsmState::Ready
}
(_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => {
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
responses.add(ArtboardMessage::ResizeArtboard {
artboard: tool_data.selected_artboard.unwrap(),
position: (bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y),
size: (bounds.bounds[1] - bounds.bounds[0]).round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap(),
location: DVec2::new(bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y).round().as_ivec2(),
dimensions: (bounds.bounds[1] - bounds.bounds[0]).round().as_ivec2(),
});
}
ArtboardToolFsmState::Ready
}
(_, ArtboardToolMessage::Abort) => {
if let Some(bounding_box_overlays) = tool_data.bounding_box_overlays.take() {
bounding_box_overlays.delete(responses);
}
// Register properties when switching back to other tools
responses.add(PropertiesPanelMessage::SetActiveLayers {
paths: document.selected_layers().map(|path| path.to_vec()).collect(),
document: TargetDocument::Artwork,
});
tool_data.snap_manager.cleanup(responses);
ArtboardToolFsmState::Ready
}
_ => self,
} }
} else { (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
self let mouse_position = input.mouse.position;
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
let root_transform = document.document_legacy.metadata.document_to_viewport.inverse();
let mut start = tool_data.drag_start;
let mut size = snapped_mouse_position - start;
// Constrain axis
if input.keyboard.get(constrain_axis_or_aspect as usize) {
size = size.abs().max(size.abs().yx()) * size.signum();
}
// From center
if input.keyboard.get(center as usize) {
start -= size;
size *= 2.;
}
let start = root_transform.transform_point2(start);
let size = root_transform.transform_vector2(size);
if let Some(artboard) = tool_data.selected_artboard {
responses.add(ArtboardMessage::ResizeArtboard {
artboard,
position: start.round().into(),
size: size.round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap(),
location: start.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
} else {
let id = generate_uuid();
tool_data.selected_artboard = Some(id);
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, Some(id), render_data), true, true);
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
responses.add(ArtboardMessage::AddArtboard {
id: Some(id),
position: start.round().into(),
size: (1., 1.),
});
responses.add(GraphOperationMessage::NewArtboard {
id,
artboard: graphene_core::Artboard {
graphic_group: graphene_core::GraphicGroup::EMPTY,
location: start.round().as_ivec2(),
dimensions: IVec2::splat(1),
background: graphene_core::Color::WHITE,
clip: false,
},
})
}
// Have to put message here instead of when Artboard is created
// This might result in a few more calls but it is not reliant on the order of messages
responses.add(BroadcastEvent::DocumentIsDirty);
ArtboardToolFsmState::Drawing
}
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerMove { .. }) => {
let cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false));
if tool_data.cursor != cursor {
tool_data.cursor = cursor;
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
}
ArtboardToolFsmState::Ready
}
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => {
tool_data.snap_manager.cleanup(responses);
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
bounds.original_transforms.clear();
}
ArtboardToolFsmState::Ready
}
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => {
tool_data.snap_manager.cleanup(responses);
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
bounds.original_transforms.clear();
}
responses.add(BroadcastEvent::DocumentIsDirty);
ArtboardToolFsmState::Ready
}
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => {
tool_data.snap_manager.cleanup(responses);
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
bounds.original_transforms.clear();
}
ArtboardToolFsmState::Ready
}
(_, ArtboardToolMessage::DeleteSelected) => {
if let Some(artboard) = tool_data.selected_artboard.take() {
responses.add(ArtboardMessage::DeleteArtboard { artboard });
responses.add(GraphOperationMessage::DeleteLayer { id: artboard });
responses.add(BroadcastEvent::DocumentIsDirty);
}
ArtboardToolFsmState::Ready
}
(_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => {
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
responses.add(ArtboardMessage::ResizeArtboard {
artboard: tool_data.selected_artboard.unwrap(),
position: (bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y),
size: (bounds.bounds[1] - bounds.bounds[0]).round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap(),
location: DVec2::new(bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y).round().as_ivec2(),
dimensions: (bounds.bounds[1] - bounds.bounds[0]).round().as_ivec2(),
});
}
ArtboardToolFsmState::Ready
}
(_, ArtboardToolMessage::Abort) => {
if let Some(bounding_box_overlays) = tool_data.bounding_box_overlays.take() {
bounding_box_overlays.delete(responses);
}
// Register properties when switching back to other tools
responses.add(PropertiesPanelMessage::SetActiveLayers {
paths: document.selected_layers().map(|path| path.to_vec()).collect(),
document: TargetDocument::Artwork,
});
tool_data.snap_manager.cleanup(responses);
ArtboardToolFsmState::Ready
}
_ => self,
} }
} }
@@ -1,12 +1,7 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; use super::tool_prelude::*;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::transform_utils::get_current_transform; use crate::messages::portfolio::document::node_graph::transform_utils::get_current_transform;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::layers::layer_layer::CachedOutputData; use document_legacy::layers::layer_layer::CachedOutputData;
use document_legacy::LayerId; use document_legacy::LayerId;
@@ -16,9 +11,6 @@ use graphene_core::raster::{BlendMode, ImageFrame};
use graphene_core::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle}; use graphene_core::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
use graphene_core::Color; use graphene_core::Color;
use glam::DAffine2;
use serde::{Deserialize, Serialize};
const EXPOSED_BLEND_MODES: &[&[BlendMode]] = { const EXPOSED_BLEND_MODES: &[&[BlendMode]] = {
use BlendMode::*; use BlendMode::*;
&[ &[
@@ -227,43 +219,41 @@ impl LayoutHolder for BrushTool {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message { let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else{
match action { self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true);
BrushToolMessageOptionsUpdate::BlendMode(blend_mode) => self.options.blend_mode = blend_mode,
BrushToolMessageOptionsUpdate::ChangeDiameter(change) => {
let needs_rounding = ((self.options.diameter + change.abs() / 2.) % change.abs() - change.abs() / 2.).abs() > 0.5;
if needs_rounding && change > 0. {
self.options.diameter = (self.options.diameter / change.abs()).ceil() * change.abs();
} else if needs_rounding && change < 0. {
self.options.diameter = (self.options.diameter / change.abs()).floor() * change.abs();
} else {
self.options.diameter = (self.options.diameter / change.abs()).round() * change.abs() + change;
}
self.options.diameter = self.options.diameter.max(1.);
self.send_layout(responses, LayoutTarget::ToolOptions);
}
BrushToolMessageOptionsUpdate::Diameter(diameter) => self.options.diameter = diameter,
BrushToolMessageOptionsUpdate::DrawMode(draw_mode) => self.options.draw_mode = draw_mode,
BrushToolMessageOptionsUpdate::Hardness(hardness) => self.options.hardness = hardness,
BrushToolMessageOptionsUpdate::Flow(flow) => self.options.flow = flow,
BrushToolMessageOptionsUpdate::Spacing(spacing) => self.options.spacing = spacing,
BrushToolMessageOptionsUpdate::Color(color) => {
self.options.color.custom_color = color;
self.options.color.color_type = ToolColorType::Custom;
}
BrushToolMessageOptionsUpdate::ColorType(color_type) => self.options.color.color_type = color_type,
BrushToolMessageOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.color.primary_working_color = primary;
self.options.color.secondary_working_color = secondary;
}
}
self.send_layout(responses, LayoutTarget::ToolOptions);
return; return;
};
match action {
BrushToolMessageOptionsUpdate::BlendMode(blend_mode) => self.options.blend_mode = blend_mode,
BrushToolMessageOptionsUpdate::ChangeDiameter(change) => {
let needs_rounding = ((self.options.diameter + change.abs() / 2.) % change.abs() - change.abs() / 2.).abs() > 0.5;
if needs_rounding && change > 0. {
self.options.diameter = (self.options.diameter / change.abs()).ceil() * change.abs();
} else if needs_rounding && change < 0. {
self.options.diameter = (self.options.diameter / change.abs()).floor() * change.abs();
} else {
self.options.diameter = (self.options.diameter / change.abs()).round() * change.abs() + change;
}
self.options.diameter = self.options.diameter.max(1.);
self.send_layout(responses, LayoutTarget::ToolOptions);
}
BrushToolMessageOptionsUpdate::Diameter(diameter) => self.options.diameter = diameter,
BrushToolMessageOptionsUpdate::DrawMode(draw_mode) => self.options.draw_mode = draw_mode,
BrushToolMessageOptionsUpdate::Hardness(hardness) => self.options.hardness = hardness,
BrushToolMessageOptionsUpdate::Flow(flow) => self.options.flow = flow,
BrushToolMessageOptionsUpdate::Spacing(spacing) => self.options.spacing = spacing,
BrushToolMessageOptionsUpdate::Color(color) => {
self.options.color.custom_color = color;
self.options.color.color_type = ToolColorType::Custom;
}
BrushToolMessageOptionsUpdate::ColorType(color_type) => self.options.color.color_type = color_type,
BrushToolMessageOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.color.primary_working_color = primary;
self.options.color.secondary_working_color = secondary;
}
} }
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true); self.send_layout(responses, LayoutTarget::ToolOptions);
} }
fn actions(&self) -> ActionList { fn actions(&self) -> ActionList {
@@ -346,94 +336,88 @@ impl Fsm for BrushToolFsmState {
type ToolData = BrushToolData; type ToolData = BrushToolData;
type ToolOptions = BrushOptions; type ToolOptions = BrushOptions;
fn transition( fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
self, let ToolActionHandlerData {
event: ToolMessage,
tool_data: &mut Self::ToolData,
ToolActionHandlerData {
document, global_tool_data, input, .. document, global_tool_data, input, ..
}: &mut ToolActionHandlerData, } = tool_action_data;
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>, let document_position = document.document_legacy.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position);
) -> Self {
let document_position = (document.document_legacy.root.transform).inverse().transform_point2(input.mouse.position);
let layer_position = tool_data.transform.inverse().transform_point2(document_position); let layer_position = tool_data.transform.inverse().transform_point2(document_position);
if let ToolMessage::Brush(event) = event { let ToolMessage::Brush(event) = event else {
match (self, event) { return self;
(BrushToolFsmState::Ready, BrushToolMessage::DragStart) => { };
responses.add(DocumentMessage::StartTransaction); match (self, event) {
let layer_path = tool_data.load_existing_strokes(document); (BrushToolFsmState::Ready, BrushToolMessage::DragStart) => {
let new_layer = layer_path.is_none(); responses.add(DocumentMessage::StartTransaction);
if new_layer { let layer_path = tool_data.load_existing_strokes(document);
responses.add(DocumentMessage::DeselectAllLayers); let new_layer = layer_path.is_none();
tool_data.layer_path = document.get_path_for_new_layer(); if new_layer {
} responses.add(DocumentMessage::DeselectAllLayers);
let layer_position = tool_data.transform.inverse().transform_point2(document_position); tool_data.layer_path = document.get_path_for_new_layer();
// TODO: Also scale it based on the input image ('Background' parameter).
// TODO: Resizing the input image results in a different brush size from the chosen diameter.
let layer_scale = 0.0001_f64 // Safety against division by zero
.max((tool_data.transform.matrix2 * glam::DVec2::X).length())
.max((tool_data.transform.matrix2 * glam::DVec2::Y).length());
// Start a new stroke with a single sample
let blend_mode = match tool_options.draw_mode {
DrawMode::Draw => tool_options.blend_mode,
DrawMode::Erase => BlendMode::Erase,
DrawMode::Restore => BlendMode::Restore,
};
tool_data.strokes.push(BrushStroke {
trace: vec![BrushInputSample { position: layer_position }],
style: BrushStyle {
color: tool_options.color.active_color().unwrap_or_default(),
diameter: tool_options.diameter / layer_scale,
hardness: tool_options.hardness,
flow: tool_options.flow,
spacing: tool_options.spacing,
blend_mode,
},
});
if new_layer {
add_brush_render(tool_options, tool_data, responses);
}
tool_data.update_strokes(responses);
BrushToolFsmState::Drawing
} }
let layer_position = tool_data.transform.inverse().transform_point2(document_position);
// TODO: Also scale it based on the input image ('Background' parameter).
// TODO: Resizing the input image results in a different brush size from the chosen diameter.
let layer_scale = 0.0001_f64 // Safety against division by zero
.max((tool_data.transform.matrix2 * glam::DVec2::X).length())
.max((tool_data.transform.matrix2 * glam::DVec2::Y).length());
(BrushToolFsmState::Drawing, BrushToolMessage::PointerMove) => { // Start a new stroke with a single sample
if let Some(stroke) = tool_data.strokes.last_mut() { let blend_mode = match tool_options.draw_mode {
stroke.trace.push(BrushInputSample { position: layer_position }) DrawMode::Draw => tool_options.blend_mode,
} DrawMode::Erase => BlendMode::Erase,
tool_data.update_strokes(responses); DrawMode::Restore => BlendMode::Restore,
};
tool_data.strokes.push(BrushStroke {
trace: vec![BrushInputSample { position: layer_position }],
style: BrushStyle {
color: tool_options.color.active_color().unwrap_or_default(),
diameter: tool_options.diameter / layer_scale,
hardness: tool_options.hardness,
flow: tool_options.flow,
spacing: tool_options.spacing,
blend_mode,
},
});
BrushToolFsmState::Drawing if new_layer {
add_brush_render(tool_options, tool_data, responses);
} }
tool_data.update_strokes(responses);
(BrushToolFsmState::Drawing, BrushToolMessage::DragStop) | (BrushToolFsmState::Drawing, BrushToolMessage::Abort) => { BrushToolFsmState::Drawing
if !tool_data.strokes.is_empty() {
responses.add(DocumentMessage::CommitTransaction);
} else {
responses.add(DocumentMessage::AbortTransaction);
}
tool_data.strokes.clear();
BrushToolFsmState::Ready
}
(_, BrushToolMessage::WorkingColorChanged) => {
responses.add(BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} else {
self (BrushToolFsmState::Drawing, BrushToolMessage::PointerMove) => {
if let Some(stroke) = tool_data.strokes.last_mut() {
stroke.trace.push(BrushInputSample { position: layer_position })
}
tool_data.update_strokes(responses);
BrushToolFsmState::Drawing
}
(BrushToolFsmState::Drawing, BrushToolMessage::DragStop) | (BrushToolFsmState::Drawing, BrushToolMessage::Abort) => {
if !tool_data.strokes.is_empty() {
responses.add(DocumentMessage::CommitTransaction);
} else {
responses.add(DocumentMessage::AbortTransaction);
}
tool_data.strokes.clear();
BrushToolFsmState::Ready
}
(_, BrushToolMessage::WorkingColorChanged) => {
responses.add(BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} }
@@ -1,19 +1,11 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; use super::tool_prelude::*;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::resize::Resize;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use graphene_core::vector::style::{Fill, Stroke}; use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::Color; use graphene_core::Color;
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct EllipseTool { pub struct EllipseTool {
fsm_state: EllipseToolFsmState, fsm_state: EllipseToolFsmState,
@@ -120,33 +112,31 @@ impl LayoutHolder for EllipseTool {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EllipseTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EllipseTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Ellipse(EllipseToolMessage::UpdateOptions(action)) = message { let ToolMessage::Ellipse(EllipseToolMessage::UpdateOptions(action)) = message else{
match action { self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true);
EllipseOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
EllipseOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
EllipseOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
EllipseOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
EllipseOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
EllipseOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
}
self.send_layout(responses, LayoutTarget::ToolOptions);
return; return;
};
match action {
EllipseOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
EllipseOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
EllipseOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
EllipseOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
EllipseOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
EllipseOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
} }
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true); self.send_layout(responses, LayoutTarget::ToolOptions);
} }
fn actions(&self) -> ActionList { fn actions(&self) -> ActionList {
@@ -192,88 +182,79 @@ impl Fsm for EllipseToolFsmState {
type ToolData = EllipseToolData; type ToolData = EllipseToolData;
type ToolOptions = EllipseToolOptions; type ToolOptions = EllipseToolOptions;
fn transition( fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
self, let ToolActionHandlerData {
event: ToolMessage,
tool_data: &mut Self::ToolData,
ToolActionHandlerData {
document, document,
global_tool_data, global_tool_data,
input, input,
render_data, render_data,
.. ..
}: &mut ToolActionHandlerData, } = tool_action_data;
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
use EllipseToolFsmState::*;
use EllipseToolMessage::*;
let shape_data = &mut tool_data.data; let shape_data = &mut tool_data.data;
if let ToolMessage::Ellipse(event) = event { let ToolMessage::Ellipse(event) = event else {
match (self, event) { return self;
(Drawing, CanvasTransformed) => { };
tool_data.data.recalculate_snaps(document, input, render_data); match (self, event) {
self (EllipseToolFsmState::Drawing, EllipseToolMessage::CanvasTransformed) => {
} tool_data.data.recalculate_snaps(document, input, render_data);
(Ready, DragStart) => { self
shape_data.start(responses, document, input, render_data);
responses.add(DocumentMessage::StartTransaction);
// Create a new layer path for this shape
let layer_path = document.get_path_for_new_layer();
shape_data.path = Some(layer_path.clone());
// Create a new ellipse vector shape
let subpath = bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE);
let manipulator_groups = subpath.manipulator_groups().to_vec();
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
graph_modification_utils::set_manipulator_mirror_angle(&manipulator_groups, &layer_path, true, responses);
let fill_color = tool_options.fill.active_color();
responses.add(GraphOperationMessage::FillSet {
layer: layer_path.clone(),
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
});
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path,
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
});
Drawing
}
(state, Resize { center, lock_ratio }) => {
if let Some(message) = shape_data.calculate_transform(responses, document, input, center, lock_ratio, false) {
responses.add(message);
}
state
}
(Drawing, DragStop) => {
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
shape_data.cleanup(responses);
Ready
}
(Drawing, Abort) => {
responses.add(DocumentMessage::AbortTransaction);
shape_data.cleanup(responses);
Ready
}
(_, WorkingColorChanged) => {
responses.add(EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} else { (EllipseToolFsmState::Ready, EllipseToolMessage::DragStart) => {
self shape_data.start(responses, document, input, render_data);
responses.add(DocumentMessage::StartTransaction);
// Create a new layer path for this shape
let layer_path = document.get_path_for_new_layer();
shape_data.path = Some(layer_path.clone());
// Create a new ellipse vector shape
let subpath = bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE);
let manipulator_groups = subpath.manipulator_groups().to_vec();
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
graph_modification_utils::set_manipulator_mirror_angle(&manipulator_groups, &layer_path, true, responses);
let fill_color = tool_options.fill.active_color();
responses.add(GraphOperationMessage::FillSet {
layer: layer_path.clone(),
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
});
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path,
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
});
EllipseToolFsmState::Drawing
}
(state, EllipseToolMessage::Resize { center, lock_ratio }) => {
if let Some(message) = shape_data.calculate_transform(responses, document, input, center, lock_ratio, false) {
responses.add(message);
}
state
}
(EllipseToolFsmState::Drawing, EllipseToolMessage::DragStop) => {
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
shape_data.cleanup(responses);
EllipseToolFsmState::Ready
}
(EllipseToolFsmState::Drawing, EllipseToolMessage::Abort) => {
responses.add(DocumentMessage::AbortTransaction);
shape_data.cleanup(responses);
EllipseToolFsmState::Ready
}
(_, EllipseToolMessage::WorkingColorChanged) => {
responses.add(EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} }
@@ -1,11 +1,5 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; use super::tool_prelude::*;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::tool::utility_types::DocumentToolData;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct EyedropperTool { pub struct EyedropperTool {
@@ -87,58 +81,49 @@ impl Fsm for EyedropperToolFsmState {
type ToolData = EyedropperToolData; type ToolData = EyedropperToolData;
type ToolOptions = (); type ToolOptions = ();
fn transition( fn transition(self, event: ToolMessage, _tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
self, let ToolActionHandlerData { global_tool_data, input, .. } = tool_action_data;
event: ToolMessage,
_tool_data: &mut Self::ToolData,
ToolActionHandlerData { global_tool_data, input, .. }: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
use EyedropperToolFsmState::*;
use EyedropperToolMessage::*;
if let ToolMessage::Eyedropper(event) = event { let ToolMessage::Eyedropper(event) = event else{
match (self, event) { return self;
// Ready -> Sampling };
(Ready, mouse_down) | (Ready, mouse_down) if mouse_down == LeftPointerDown || mouse_down == RightPointerDown => { match (self, event) {
update_cursor_preview(responses, input, global_tool_data, None); // Ready -> Sampling
(EyedropperToolFsmState::Ready, mouse_down) if matches!(mouse_down, EyedropperToolMessage::LeftPointerDown | EyedropperToolMessage::RightPointerDown) => {
update_cursor_preview(responses, input, global_tool_data, None);
if mouse_down == LeftPointerDown { if mouse_down == EyedropperToolMessage::LeftPointerDown {
SamplingPrimary EyedropperToolFsmState::SamplingPrimary
} else { } else {
SamplingSecondary EyedropperToolFsmState::SamplingSecondary
}
} }
// Sampling -> Sampling
(SamplingPrimary, PointerMove) | (SamplingSecondary, PointerMove) => {
if input.viewport_bounds.in_bounds(input.mouse.position) {
update_cursor_preview(responses, input, global_tool_data, None);
} else {
disable_cursor_preview(responses);
}
self
}
// Sampling -> Ready
(SamplingPrimary, mouse_up) | (SamplingSecondary, mouse_up) if mouse_up == LeftPointerUp || mouse_up == RightPointerUp => {
let set_color_choice = if self == SamplingPrimary { "Primary".to_string() } else { "Secondary".to_string() };
update_cursor_preview(responses, input, global_tool_data, Some(set_color_choice));
disable_cursor_preview(responses);
Ready
}
// Any -> Ready
(_, Abort) => {
disable_cursor_preview(responses);
Ready
}
// Ready -> Ready
_ => self,
} }
} else { // Sampling -> Sampling
self (EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::PointerMove) => {
if input.viewport_bounds.in_bounds(input.mouse.position) {
update_cursor_preview(responses, input, global_tool_data, None);
} else {
disable_cursor_preview(responses);
}
self
}
// Sampling -> Ready
(EyedropperToolFsmState::SamplingPrimary, EyedropperToolMessage::LeftPointerUp) | (EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::RightPointerUp) => {
let set_color_choice = if self == EyedropperToolFsmState::SamplingPrimary { "Primary" } else { "Secondary" }.to_string();
update_cursor_preview(responses, input, global_tool_data, Some(set_color_choice));
disable_cursor_preview(responses);
EyedropperToolFsmState::Ready
}
// Any -> Ready
(_, EyedropperToolMessage::Abort) => {
disable_cursor_preview(responses);
EyedropperToolFsmState::Ready
}
// Ready -> Ready
_ => self,
} }
} }
@@ -1,32 +1,15 @@
use crate::consts::SELECTION_TOLERANCE; use super::tool_prelude::*;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::intersection::Quad;
use document_legacy::layers::layer_layer::CachedOutputData;
use document_legacy::layers::style::Fill; use document_legacy::layers::style::Fill;
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct FillTool { pub struct FillTool {
fsm_state: FillToolFsmState, fsm_state: FillToolFsmState,
data: FillToolData,
} }
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, ToolMessage, Fill)] #[impl_message(Message, ToolMessage, Fill)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
pub enum FillToolMessage { pub enum FillToolMessage {
// Standard messages
#[remain::unsorted]
Abort,
// Tool-specific messages // Tool-specific messages
LeftPointerDown, LeftPointerDown,
RightPointerDown, RightPointerDown,
@@ -52,7 +35,7 @@ impl LayoutHolder for FillTool {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true); self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true);
} }
advertise_actions!(FillToolMessageDiscriminant; advertise_actions!(FillToolMessageDiscriminant;
@@ -63,10 +46,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillToo
impl ToolTransition for FillTool { impl ToolTransition for FillTool {
fn event_to_message_map(&self) -> EventToMessageMap { fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap { EventToMessageMap::default()
tool_abort: Some(FillToolMessage::Abort.into()),
..Default::default()
}
} }
} }
@@ -76,71 +56,37 @@ enum FillToolFsmState {
Ready, Ready,
} }
#[derive(Clone, Debug, Default)]
struct FillToolData {}
impl Fsm for FillToolFsmState { impl Fsm for FillToolFsmState {
type ToolData = FillToolData; type ToolData = ();
type ToolOptions = (); type ToolOptions = ();
fn transition( fn transition(self, event: ToolMessage, _tool_data: &mut Self::ToolData, handler_data: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
self, let ToolActionHandlerData {
event: ToolMessage, document, global_tool_data, input, ..
_tool_data: &mut Self::ToolData, } = handler_data;
ToolActionHandlerData {
document,
global_tool_data,
input,
render_data,
..
}: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
use FillToolFsmState::*;
use FillToolMessage::*;
if let ToolMessage::Fill(event) = event { let ToolMessage::Fill(event) = event else {
match (self, event) { return self;
(Ready, lmb_or_rmb) if lmb_or_rmb == LeftPointerDown || lmb_or_rmb == RightPointerDown => { };
let mouse_pos = input.mouse.position; let Some((layer_identifier, _)) = document.document_legacy.metadata.click(input.mouse.position) else {
let tolerance = DVec2::splat(SELECTION_TOLERANCE); return self;
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]); };
let layer = layer_identifier.to_path();
if let Some(path) = document.document_legacy.intersects_quad_root(quad, render_data).last() { let color = match event {
let is_bitmap = document FillToolMessage::LeftPointerDown => global_tool_data.primary_color,
.document_legacy FillToolMessage::RightPointerDown => global_tool_data.secondary_color,
.layer(path) };
.ok() let fill = Fill::Solid(color);
.and_then(|layer| layer.as_layer().ok())
.map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)));
if is_bitmap { responses.add(DocumentMessage::StartTransaction);
return self; responses.add(DocumentMessage::SetSelectedLayers {
} replacement_selected_layers: vec![layer.clone()],
});
responses.add(GraphOperationMessage::FillSet { layer, fill });
responses.add(DocumentMessage::CommitTransaction);
let color = match lmb_or_rmb { FillToolFsmState::Ready
LeftPointerDown => global_tool_data.primary_color,
RightPointerDown => global_tool_data.secondary_color,
Abort => unreachable!(),
};
let fill = Fill::Solid(color);
responses.add(DocumentMessage::StartTransaction);
responses.add(DocumentMessage::SetSelectedLayers {
replacement_selected_layers: vec![path.to_vec()],
});
responses.add(GraphOperationMessage::FillSet { layer: path.to_vec(), fill });
responses.add(DocumentMessage::CommitTransaction);
}
Ready
}
_ => self,
}
} else {
self
}
} }
fn update_hints(&self, responses: &mut VecDeque<Message>) { fn update_hints(&self, responses: &mut VecDeque<Message>) {
@@ -1,18 +1,10 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; use super::tool_prelude::*;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph; use crate::messages::portfolio::document::node_graph;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::path_outline::PathOutline; use crate::messages::tool::common_functionality::path_outline::PathOutline;
use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::resize::Resize;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::Operation; use document_legacy::Operation;
use glam::DAffine2;
use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct FrameTool { pub struct FrameTool {
fsm_state: NodeGraphToolFsmState, fsm_state: NodeGraphToolFsmState,
@@ -107,79 +99,70 @@ impl Fsm for NodeGraphToolFsmState {
type ToolData = NodeGraphToolData; type ToolData = NodeGraphToolData;
type ToolOptions = (); type ToolOptions = ();
fn transition( fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
self, let ToolActionHandlerData { document, input, render_data, .. } = tool_action_data;
event: ToolMessage,
tool_data: &mut Self::ToolData,
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
use FrameToolMessage::*;
use NodeGraphToolFsmState::*;
let shape_data = &mut tool_data.data; let shape_data = &mut tool_data.data;
if let ToolMessage::Frame(event) = event { let ToolMessage::Frame(event) = event else {
match (self, event) { return self;
(_, DocumentIsDirty | SelectionChanged) => { };
tool_data.path_outlines.clear_selected(responses); match (self, event) {
tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data); (_, FrameToolMessage::DocumentIsDirty | FrameToolMessage::SelectionChanged) => {
tool_data.path_outlines.clear_selected(responses);
tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data);
self self
}
(Ready, DragStart) => {
tool_data.path_outlines.clear_selected(responses);
shape_data.start(responses, document, input, render_data);
responses.add(DocumentMessage::StartTransaction);
shape_data.path = Some(document.get_path_for_new_layer());
responses.add(DocumentMessage::DeselectAllLayers);
let network = node_graph::new_image_network(8, 0);
responses.add(Operation::AddFrame {
path: shape_data.path.clone().unwrap(),
insert_index: -1,
transform: DAffine2::ZERO.to_cols_array(),
network,
});
Drawing
}
(state, Resize { center, lock_ratio }) => {
let message = shape_data.calculate_transform(responses, document, input, center, lock_ratio, true);
responses.try_add(message);
state
}
(Drawing, DragStop) => {
if let Some(layer_path) = &shape_data.path {
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.to_vec() });
}
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
shape_data.cleanup(responses);
Ready
}
(Drawing, Abort) => {
responses.add(DocumentMessage::AbortTransaction);
shape_data.cleanup(responses);
tool_data.path_outlines.clear_selected(responses);
Ready
}
(_, Abort) => {
tool_data.path_outlines.clear_selected(responses);
Ready
}
_ => self,
} }
} else { (NodeGraphToolFsmState::Ready, FrameToolMessage::DragStart) => {
self tool_data.path_outlines.clear_selected(responses);
shape_data.start(responses, document, input, render_data);
responses.add(DocumentMessage::StartTransaction);
shape_data.path = Some(document.get_path_for_new_layer());
responses.add(DocumentMessage::DeselectAllLayers);
let network = node_graph::new_image_network(8, 0);
responses.add(Operation::AddFrame {
path: shape_data.path.clone().unwrap(),
insert_index: -1,
transform: DAffine2::ZERO.to_cols_array(),
network,
});
NodeGraphToolFsmState::Drawing
}
(state, FrameToolMessage::Resize { center, lock_ratio }) => {
let message = shape_data.calculate_transform(responses, document, input, center, lock_ratio, true);
responses.try_add(message);
state
}
(NodeGraphToolFsmState::Drawing, FrameToolMessage::DragStop) => {
if let Some(layer_path) = &shape_data.path {
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.to_vec() });
}
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
shape_data.cleanup(responses);
NodeGraphToolFsmState::Ready
}
(NodeGraphToolFsmState::Drawing, FrameToolMessage::Abort) => {
responses.add(DocumentMessage::AbortTransaction);
shape_data.cleanup(responses);
tool_data.path_outlines.clear_selected(responses);
NodeGraphToolFsmState::Ready
}
(_, FrameToolMessage::Abort) => {
tool_data.path_outlines.clear_selected(responses);
NodeGraphToolFsmState::Ready
}
_ => self,
} }
} }
@@ -1,14 +1,8 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; use super::tool_prelude::*;
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::LayerId; use document_legacy::LayerId;
use document_legacy::Operation;
use graphene_core::vector::style::{Fill, Stroke}; use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::Color; use graphene_core::Color;
@@ -123,33 +117,31 @@ impl LayoutHolder for FreehandTool {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FreehandTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FreehandTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message { let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else{
match action { self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true);
FreehandOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
FreehandOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
FreehandOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
FreehandOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
FreehandOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
FreehandOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
}
self.send_layout(responses, LayoutTarget::ToolOptions);
return; return;
};
match action {
FreehandOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
FreehandOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
FreehandOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
FreehandOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
FreehandOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
FreehandOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
} }
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true); self.send_layout(responses, LayoutTarget::ToolOptions);
} }
fn actions(&self) -> ActionList { fn actions(&self) -> ActionList {
@@ -191,74 +183,65 @@ impl Fsm for FreehandToolFsmState {
type ToolData = FreehandToolData; type ToolData = FreehandToolData;
type ToolOptions = FreehandOptions; type ToolOptions = FreehandOptions;
fn transition( fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
self, let ToolActionHandlerData {
event: ToolMessage,
tool_data: &mut Self::ToolData,
ToolActionHandlerData {
document, global_tool_data, input, .. document, global_tool_data, input, ..
}: &mut ToolActionHandlerData, } = tool_action_data;
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
use FreehandToolFsmState::*;
use FreehandToolMessage::*;
let transform = document.document_legacy.root.transform; let transform = document.document_legacy.metadata.document_to_viewport;
if let ToolMessage::Freehand(event) = event { let ToolMessage::Freehand(event) = event else {
match (self, event) { return self;
(Ready, DragStart) => { };
responses.add(DocumentMessage::StartTransaction); match (self, event) {
responses.add(DocumentMessage::DeselectAllLayers); (FreehandToolFsmState::Ready, FreehandToolMessage::DragStart) => {
tool_data.path = Some(document.get_path_for_new_layer()); responses.add(DocumentMessage::StartTransaction);
responses.add(DocumentMessage::DeselectAllLayers);
tool_data.path = Some(document.get_path_for_new_layer());
let pos = transform.inverse().transform_point2(input.mouse.position); let pos = transform.inverse().transform_point2(input.mouse.position);
tool_data.points.push(pos); tool_data.points.push(pos);
tool_data.weight = tool_options.line_weight; tool_data.weight = tool_options.line_weight;
add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses); add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses);
Drawing FreehandToolFsmState::Drawing
}
(Drawing, PointerMove) => {
let pos = transform.inverse().transform_point2(input.mouse.position);
if tool_data.points.last() != Some(&pos) {
tool_data.points.push(pos);
}
add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses);
Drawing
}
(Drawing, DragStop) | (Drawing, Abort) => {
if tool_data.points.len() >= 2 {
responses.add(remove_preview(tool_data));
add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses);
responses.add(DocumentMessage::CommitTransaction);
} else {
responses.add(DocumentMessage::AbortTransaction);
}
tool_data.path = None;
tool_data.points.clear();
Ready
}
(_, FreehandToolMessage::WorkingColorChanged) => {
responses.add(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} else { (FreehandToolFsmState::Drawing, FreehandToolMessage::PointerMove) => {
self let pos = transform.inverse().transform_point2(input.mouse.position);
if tool_data.points.last() != Some(&pos) {
tool_data.points.push(pos);
}
add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses);
FreehandToolFsmState::Drawing
}
(FreehandToolFsmState::Drawing, FreehandToolMessage::DragStop | FreehandToolMessage::Abort) => {
if tool_data.points.len() >= 2 {
responses.add(remove_preview(tool_data));
add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses);
responses.add(DocumentMessage::CommitTransaction);
} else {
responses.add(DocumentMessage::AbortTransaction);
}
tool_data.path = None;
tool_data.points.clear();
FreehandToolFsmState::Ready
}
(_, FreehandToolMessage::WorkingColorChanged) => {
responses.add(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} }
@@ -277,7 +260,7 @@ impl Fsm for FreehandToolFsmState {
} }
fn remove_preview(data: &FreehandToolData) -> Message { fn remove_preview(data: &FreehandToolData) -> Message {
Operation::DeleteLayer { path: data.path.clone().unwrap() }.into() GraphOperationMessage::DeleteLayer { id: data.path.clone().unwrap()[0] }.into()
} }
fn add_polyline(data: &FreehandToolData, stroke_color: Option<Color>, fill_color: Option<Color>, responses: &mut VecDeque<Message>) { fn add_polyline(data: &FreehandToolData, stroke_color: Option<Color>, fill_color: Option<Color>, responses: &mut VecDeque<Message>) {
@@ -1,12 +1,7 @@
use super::tool_prelude::*;
use crate::application::generate_uuid; use crate::application::generate_uuid;
use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE}; use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::intersection::Quad; use document_legacy::intersection::Quad;
use document_legacy::layers::layer_info::Layer; use document_legacy::layers::layer_info::Layer;
@@ -16,9 +11,6 @@ use document_legacy::LayerId;
use document_legacy::Operation; use document_legacy::Operation;
use graphene_core::raster::color::Color; use graphene_core::raster::color::Color;
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct GradientTool { pub struct GradientTool {
fsm_state: GradientToolFsmState, fsm_state: GradientToolFsmState,
@@ -26,16 +18,11 @@ pub struct GradientTool {
options: GradientOptions, options: GradientOptions,
} }
#[derive(Default)]
pub struct GradientOptions { pub struct GradientOptions {
gradient_type: GradientType, gradient_type: GradientType,
} }
impl Default for GradientOptions {
fn default() -> Self {
Self { gradient_type: GradientType::Linear }
}
}
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, ToolMessage, Gradient)] #[impl_message(Message, ToolMessage, Gradient)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
@@ -77,20 +64,19 @@ impl ToolMetadata for GradientTool {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for GradientTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for GradientTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message { let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else {
match action { self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, false);
GradientOptionsUpdate::Type(gradient_type) => { return;
self.options.gradient_type = gradient_type; };
if let Some(selected_gradient) = &mut self.data.selected_gradient { match action {
selected_gradient.gradient.gradient_type = gradient_type; GradientOptionsUpdate::Type(gradient_type) => {
selected_gradient.render_gradient(responses); self.options.gradient_type = gradient_type;
} if let Some(selected_gradient) = &mut self.data.selected_gradient {
selected_gradient.gradient.gradient_type = gradient_type;
selected_gradient.render_gradient(responses);
} }
} }
return;
} }
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, false);
} }
advertise_actions!(GradientToolMessageDiscriminant; advertise_actions!(GradientToolMessageDiscriminant;
@@ -394,253 +380,248 @@ impl Fsm for GradientToolFsmState {
type ToolData = GradientToolData; type ToolData = GradientToolData;
type ToolOptions = GradientOptions; type ToolOptions = GradientOptions;
fn transition( fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
self, let ToolActionHandlerData {
event: ToolMessage,
tool_data: &mut Self::ToolData,
ToolActionHandlerData {
document, document,
global_tool_data, global_tool_data,
input, input,
render_data, render_data,
.. ..
}: &mut ToolActionHandlerData, } = tool_action_data;
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
if let ToolMessage::Gradient(event) = event {
match (self, event) {
(_, GradientToolMessage::DocumentIsDirty) => {
while let Some(overlay) = tool_data.gradient_overlays.pop() {
overlay.delete_overlays(responses);
}
if self != GradientToolFsmState::Drawing { let ToolMessage::Gradient(event) = event else {
SelectedGradient::update(&mut tool_data.selected_gradient, document, render_data, responses); return self;
} };
for path in document.selected_visible_layers() { match (self, event) {
let layer = document.document_legacy.layer(path).unwrap(); (_, GradientToolMessage::DocumentIsDirty) => {
while let Some(overlay) = tool_data.gradient_overlays.pop() {
if let Ok(Fill::Gradient(gradient)) = layer.style().map(|style| style.fill()) { overlay.delete_overlays(responses);
let dragging = tool_data
.selected_gradient
.as_ref()
.and_then(|selected| if selected.path == path { Some(selected.dragging) } else { None });
tool_data
.gradient_overlays
.push(GradientOverlay::new(gradient, dragging, path, layer, document, responses, render_data))
}
}
self
} }
(GradientToolFsmState::Ready, GradientToolMessage::DeleteStop) => {
let Some(selected_gradient) = &mut tool_data.selected_gradient else {
return self;
};
// Skip if invalid gradient if self != GradientToolFsmState::Drawing {
if selected_gradient.gradient.positions.len() < 2 { SelectedGradient::update(&mut tool_data.selected_gradient, document, render_data, responses);
return self;
}
// Remove the selected point
match selected_gradient.dragging {
GradientDragTarget::Start => selected_gradient.gradient.positions.remove(0),
GradientDragTarget::End => selected_gradient.gradient.positions.pop().unwrap(),
GradientDragTarget::Step(index) => selected_gradient.gradient.positions.remove(index),
};
// The gradient has only one point and so should become a fill
if selected_gradient.gradient.positions.len() == 1 {
let fill = Fill::Solid(selected_gradient.gradient.positions[0].1.unwrap_or(Color::BLACK));
let layer = selected_gradient.path.clone();
responses.add(GraphOperationMessage::FillSet { layer, fill });
return self;
}
// Find the minimum and maximum positions
let min_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::min).expect("No min");
let max_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::max).expect("No max");
// Recompute the start and end posiiton of the gradient (in viewport transform)
let transform = selected_gradient.transform;
let (start, end) = (transform.transform_point2(selected_gradient.gradient.start), transform.transform_point2(selected_gradient.gradient.end));
let (new_start, new_end) = (start.lerp(end, min_position), start.lerp(end, max_position));
selected_gradient.gradient.start = transform.inverse().transform_point2(new_start);
selected_gradient.gradient.end = transform.inverse().transform_point2(new_end);
// Remap the positions
for (position, _) in selected_gradient.gradient.positions.iter_mut() {
*position = (*position - min_position) / (max_position - min_position);
}
// Render the new gradient
selected_gradient.render_gradient(responses);
self
} }
(_, GradientToolMessage::InsertStop) => {
for overlay in &tool_data.gradient_overlays {
let mouse = input.mouse.position;
let (start, end) = (overlay.evaluate_gradient_start(), overlay.evaluate_gradient_end());
// Compute the distance from the mouse to the gradient line in viewport space for path in document.selected_visible_layers() {
let distance = (end - start).angle_between(mouse - start).sin() * (mouse - start).length(); let layer = document.document_legacy.layer(path).unwrap();
// If click is on the line then insert point if let Ok(Fill::Gradient(gradient)) = layer.style().map(|style| style.fill()) {
if distance < SELECTION_THRESHOLD { let dragging = tool_data
let mut gradient = overlay.gradient.clone(); .selected_gradient
.as_ref()
// Try and insert the new stop .and_then(|selected| if selected.path == path { Some(selected.dragging) } else { None });
if let Some(index) = gradient.insert_stop(mouse, overlay.transform) { tool_data
document.backup_nonmut(responses); .gradient_overlays
.push(GradientOverlay::new(gradient, dragging, path, layer, document, responses, render_data))
let layer = document.document_legacy.layer(&overlay.path);
if let Ok(layer) = layer {
let mut selected_gradient = SelectedGradient::new(gradient, &overlay.path, layer, document, render_data);
// Select the new point
selected_gradient.dragging = GradientDragTarget::Step(index);
// Update the layer fill
selected_gradient.render_gradient(responses);
tool_data.selected_gradient = Some(selected_gradient);
}
break;
}
}
} }
self
} }
(GradientToolFsmState::Ready, GradientToolMessage::PointerDown) => {
responses.add(BroadcastEvent::DocumentIsDirty);
self
}
(GradientToolFsmState::Ready, GradientToolMessage::DeleteStop) => {
let Some(selected_gradient) = &mut tool_data.selected_gradient else {
return self;
};
// Skip if invalid gradient
if selected_gradient.gradient.positions.len() < 2 {
return self;
}
// Remove the selected point
match selected_gradient.dragging {
GradientDragTarget::Start => selected_gradient.gradient.positions.remove(0),
GradientDragTarget::End => selected_gradient.gradient.positions.pop().unwrap(),
GradientDragTarget::Step(index) => selected_gradient.gradient.positions.remove(index),
};
// The gradient has only one point and so should become a fill
if selected_gradient.gradient.positions.len() == 1 {
let fill = Fill::Solid(selected_gradient.gradient.positions[0].1.unwrap_or(Color::BLACK));
let layer = selected_gradient.path.clone();
responses.add(GraphOperationMessage::FillSet { layer, fill });
return self;
}
// Find the minimum and maximum positions
let min_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::min).expect("No min");
let max_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::max).expect("No max");
// Recompute the start and end posiiton of the gradient (in viewport transform)
let transform = selected_gradient.transform;
let (start, end) = (transform.transform_point2(selected_gradient.gradient.start), transform.transform_point2(selected_gradient.gradient.end));
let (new_start, new_end) = (start.lerp(end, min_position), start.lerp(end, max_position));
selected_gradient.gradient.start = transform.inverse().transform_point2(new_start);
selected_gradient.gradient.end = transform.inverse().transform_point2(new_end);
// Remap the positions
for (position, _) in selected_gradient.gradient.positions.iter_mut() {
*position = (*position - min_position) / (max_position - min_position);
}
// Render the new gradient
selected_gradient.render_gradient(responses);
self
}
(_, GradientToolMessage::InsertStop) => {
for overlay in &tool_data.gradient_overlays {
let mouse = input.mouse.position; let mouse = input.mouse.position;
tool_data.drag_start = mouse; let (start, end) = (overlay.evaluate_gradient_start(), overlay.evaluate_gradient_end());
let tolerance = MANIPULATOR_GROUP_MARKER_SIZE.powi(2);
let mut dragging = false; // Compute the distance from the mouse to the gradient line in viewport space
for overlay in &tool_data.gradient_overlays { let distance = (end - start).angle_between(mouse - start).sin() * (mouse - start).length();
// Check for dragging step
for (index, (pos, _)) in overlay.gradient.positions.iter().enumerate() {
let pos = overlay.transform.transform_point2(overlay.gradient.start.lerp(overlay.gradient.end, *pos));
if pos.distance_squared(mouse) < tolerance {
dragging = true;
tool_data.selected_gradient = Some(SelectedGradient {
path: overlay.path.clone(),
transform: overlay.transform,
gradient: overlay.gradient.clone(),
dragging: GradientDragTarget::Step(index),
})
}
}
// Check dragging start or end handle // If click is on the line then insert point
for (pos, dragging_target) in [ if distance < SELECTION_THRESHOLD {
(overlay.evaluate_gradient_start(), GradientDragTarget::Start), let mut gradient = overlay.gradient.clone();
(overlay.evaluate_gradient_end(), GradientDragTarget::End),
] { // Try and insert the new stop
if pos.distance_squared(mouse) < tolerance { if let Some(index) = gradient.insert_stop(mouse, overlay.transform) {
dragging = true; document.backup_nonmut(responses);
start_snap(&mut tool_data.snap_manager, document, input, render_data);
tool_data.selected_gradient = Some(SelectedGradient { let layer = document.document_legacy.layer(&overlay.path);
path: overlay.path.clone(), if let Ok(layer) = layer {
transform: overlay.transform, let mut selected_gradient = SelectedGradient::new(gradient, &overlay.path, layer, document, render_data);
gradient: overlay.gradient.clone(),
dragging: dragging_target, // Select the new point
}) selected_gradient.dragging = GradientDragTarget::Step(index);
// Update the layer fill
selected_gradient.render_gradient(responses);
tool_data.selected_gradient = Some(selected_gradient);
} }
break;
} }
} }
if dragging { }
document.backup_nonmut(responses);
self
}
(GradientToolFsmState::Ready, GradientToolMessage::PointerDown) => {
responses.add(BroadcastEvent::DocumentIsDirty);
let mouse = input.mouse.position;
tool_data.drag_start = mouse;
let tolerance = MANIPULATOR_GROUP_MARKER_SIZE.powi(2);
let mut dragging = false;
for overlay in &tool_data.gradient_overlays {
// Check for dragging step
for (index, (pos, _)) in overlay.gradient.positions.iter().enumerate() {
let pos = overlay.transform.transform_point2(overlay.gradient.start.lerp(overlay.gradient.end, *pos));
if pos.distance_squared(mouse) < tolerance {
dragging = true;
tool_data.selected_gradient = Some(SelectedGradient {
path: overlay.path.clone(),
transform: overlay.transform,
gradient: overlay.gradient.clone(),
dragging: GradientDragTarget::Step(index),
})
}
}
// Check dragging start or end handle
for (pos, dragging_target) in [
(overlay.evaluate_gradient_start(), GradientDragTarget::Start),
(overlay.evaluate_gradient_end(), GradientDragTarget::End),
] {
if pos.distance_squared(mouse) < tolerance {
dragging = true;
start_snap(&mut tool_data.snap_manager, document, input, render_data);
tool_data.selected_gradient = Some(SelectedGradient {
path: overlay.path.clone(),
transform: overlay.transform,
gradient: overlay.gradient.clone(),
dragging: dragging_target,
})
}
}
}
if dragging {
document.backup_nonmut(responses);
GradientToolFsmState::Drawing
} else {
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]);
let intersection = document.document_legacy.intersects_quad_root(quad, render_data).pop();
// the intersection is the layer where the gradient is being applied
if let Some(intersection) = intersection {
let is_bitmap = document
.document_legacy
.layer(&intersection)
.ok()
.and_then(|layer| layer.as_layer().ok())
.map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)));
if is_bitmap {
return self;
}
if !document.selected_layers_contains(&intersection) {
let replacement_selected_layers = vec![intersection.clone()];
responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers });
}
let layer = document.document_legacy.layer(&intersection).unwrap();
responses.add(DocumentMessage::StartTransaction);
// Use the already existing gradient if it exists
let gradient = if let Some(gradient) = layer.style().ok().map(|style| style.fill()).and_then(|fill| fill.as_gradient()) {
gradient.clone()
} else {
// Generate a new gradient
Gradient::new(
DVec2::ZERO,
global_tool_data.secondary_color,
DVec2::ONE,
global_tool_data.primary_color,
DAffine2::IDENTITY,
generate_uuid(),
tool_options.gradient_type,
)
};
let selected_gradient = SelectedGradient::new(gradient, &intersection, layer, document, render_data).with_gradient_start(input.mouse.position);
tool_data.selected_gradient = Some(selected_gradient);
start_snap(&mut tool_data.snap_manager, document, input, render_data);
GradientToolFsmState::Drawing GradientToolFsmState::Drawing
} else { } else {
let tolerance = DVec2::splat(SELECTION_TOLERANCE); GradientToolFsmState::Ready
let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]);
let intersection = document.document_legacy.intersects_quad_root(quad, render_data).pop();
// the intersection is the layer where the gradient is being applied
if let Some(intersection) = intersection {
let is_bitmap = document
.document_legacy
.layer(&intersection)
.ok()
.and_then(|layer| layer.as_layer().ok())
.map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)));
if is_bitmap {
return self;
}
if !document.selected_layers_contains(&intersection) {
let replacement_selected_layers = vec![intersection.clone()];
responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers });
}
let layer = document.document_legacy.layer(&intersection).unwrap();
responses.add(DocumentMessage::StartTransaction);
// Use the already existing gradient if it exists
let gradient = if let Some(gradient) = layer.style().ok().map(|style| style.fill()).and_then(|fill| fill.as_gradient()) {
gradient.clone()
} else {
// Generate a new gradient
Gradient::new(
DVec2::ZERO,
global_tool_data.secondary_color,
DVec2::ONE,
global_tool_data.primary_color,
DAffine2::IDENTITY,
generate_uuid(),
tool_options.gradient_type,
)
};
let selected_gradient = SelectedGradient::new(gradient, &intersection, layer, document, render_data).with_gradient_start(input.mouse.position);
tool_data.selected_gradient = Some(selected_gradient);
start_snap(&mut tool_data.snap_manager, document, input, render_data);
GradientToolFsmState::Drawing
} else {
GradientToolFsmState::Ready
}
} }
} }
(GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
if let Some(selected_gradient) = &mut tool_data.selected_gradient {
let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
selected_gradient.update_gradient(mouse, responses, input.keyboard.get(constrain_axis as usize), selected_gradient.gradient.gradient_type);
}
GradientToolFsmState::Drawing
}
(GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => {
input.mouse.finish_transaction(tool_data.drag_start, responses);
tool_data.snap_manager.cleanup(responses);
GradientToolFsmState::Ready
}
(_, GradientToolMessage::Abort) => {
tool_data.snap_manager.cleanup(responses);
while let Some(overlay) = tool_data.gradient_overlays.pop() {
overlay.delete_overlays(responses);
}
GradientToolFsmState::Ready
}
_ => self,
} }
} else { (GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
self if let Some(selected_gradient) = &mut tool_data.selected_gradient {
let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
selected_gradient.update_gradient(mouse, responses, input.keyboard.get(constrain_axis as usize), selected_gradient.gradient.gradient_type);
}
GradientToolFsmState::Drawing
}
(GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => {
input.mouse.finish_transaction(tool_data.drag_start, responses);
tool_data.snap_manager.cleanup(responses);
GradientToolFsmState::Ready
}
(_, GradientToolMessage::Abort) => {
tool_data.snap_manager.cleanup(responses);
while let Some(overlay) = tool_data.gradient_overlays.pop() {
overlay.delete_overlays(responses);
}
GradientToolFsmState::Ready
}
_ => self,
} }
} }
@@ -1,12 +1,7 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; use super::tool_prelude::*;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::{self, IMAGINATE_NODE}; use crate::messages::portfolio::document::node_graph::{self, IMAGINATE_NODE};
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::path_outline::PathOutline; use crate::messages::tool::common_functionality::path_outline::PathOutline;
use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::resize::Resize;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::Operation; use document_legacy::Operation;
@@ -115,102 +110,98 @@ impl Fsm for ImaginateToolFsmState {
_tool_options: &Self::ToolOptions, _tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
use ImaginateToolFsmState::*;
use ImaginateToolMessage::*;
let shape_data = &mut tool_data.data; let shape_data = &mut tool_data.data;
if let ToolMessage::Imaginate(event) = event { let ToolMessage::Imaginate(event) = event else{
match (self, event) { return self;
(_, DocumentIsDirty | SelectionChanged) => { };
tool_data.path_outlines.clear_selected(responses); match (self, event) {
tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data); (_, ImaginateToolMessage::DocumentIsDirty | ImaginateToolMessage::SelectionChanged) => {
tool_data.path_outlines.clear_selected(responses);
tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data);
self self
}
(Ready, DragStart) => {
tool_data.path_outlines.clear_selected(responses);
shape_data.start(responses, document, input, render_data);
responses.add(DocumentMessage::StartTransaction);
shape_data.path = Some(document.get_path_for_new_layer());
responses.add(DocumentMessage::DeselectAllLayers);
use graph_craft::document::*;
// Utility function to offset the position of each consecutive node
let mut pos = 8;
let mut next_pos = || {
pos += 8;
graph_craft::document::DocumentNodeMetadata::position((pos, 4))
};
// Get the node type for the Transform and Imaginate nodes
let Some(transform_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Transform") else {
warn!("Transform node should be in registry");
return Drawing;
};
let imaginate_node_type = &*IMAGINATE_NODE;
// Give them a unique ID
let [transform_node_id, imaginate_node_id] = [100, 101];
// Create the network based on the Input -> Output passthrough default network
let mut network = node_graph::new_image_network(16, imaginate_node_id);
// Insert the nodes into the default network
network
.nodes
.insert(transform_node_id, transform_node_type.to_document_node_default_inputs([Some(NodeInput::node(0, 0))], next_pos()));
network.nodes.insert(
imaginate_node_id,
imaginate_node_type.to_document_node_default_inputs([Some(graph_craft::document::NodeInput::node(transform_node_id, 0))], next_pos()),
);
// Add a layer with a frame to the document
responses.add(Operation::AddFrame {
path: shape_data.path.clone().unwrap(),
insert_index: -1,
transform: DAffine2::ZERO.to_cols_array(),
network,
});
responses.add(NodeGraphMessage::ShiftNode { node_id: imaginate_node_id });
Drawing
}
(state, Resize { center, lock_ratio }) => {
let message = shape_data.calculate_transform(responses, document, input, center, lock_ratio, true);
responses.try_add(message);
state
}
(Drawing, DragStop) => {
if let Some(layer_path) = &shape_data.path {
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.to_vec() });
}
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
shape_data.cleanup(responses);
Ready
}
(Drawing, Abort) => {
responses.add(DocumentMessage::AbortTransaction);
shape_data.cleanup(responses);
tool_data.path_outlines.clear_selected(responses);
Ready
}
(_, Abort) => {
tool_data.path_outlines.clear_selected(responses);
Ready
}
_ => self,
} }
} else { (ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => {
self tool_data.path_outlines.clear_selected(responses);
shape_data.start(responses, document, input, render_data);
responses.add(DocumentMessage::StartTransaction);
shape_data.path = Some(document.get_path_for_new_layer());
responses.add(DocumentMessage::DeselectAllLayers);
use graph_craft::document::*;
// Utility function to offset the position of each consecutive node
let mut pos = 8;
let mut next_pos = || {
pos += 8;
graph_craft::document::DocumentNodeMetadata::position((pos, 4))
};
// Get the node type for the Transform and Imaginate nodes
let Some(transform_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Transform") else {
warn!("Transform node should be in registry");
return ImaginateToolFsmState::Drawing;
};
let imaginate_node_type = &*IMAGINATE_NODE;
// Give them a unique ID
let [transform_node_id, imaginate_node_id] = [100, 101];
// Create the network based on the Input -> Output passthrough default network
let mut network = node_graph::new_image_network(16, imaginate_node_id);
// Insert the nodes into the default network
network
.nodes
.insert(transform_node_id, transform_node_type.to_document_node_default_inputs([Some(NodeInput::node(0, 0))], next_pos()));
network.nodes.insert(
imaginate_node_id,
imaginate_node_type.to_document_node_default_inputs([Some(graph_craft::document::NodeInput::node(transform_node_id, 0))], next_pos()),
);
// Add a layer with a frame to the document
responses.add(Operation::AddFrame {
path: shape_data.path.clone().unwrap(),
insert_index: -1,
transform: DAffine2::ZERO.to_cols_array(),
network,
});
responses.add(NodeGraphMessage::ShiftNode { node_id: imaginate_node_id });
ImaginateToolFsmState::Drawing
}
(state, ImaginateToolMessage::Resize { center, lock_ratio }) => {
let message = shape_data.calculate_transform(responses, document, input, center, lock_ratio, true);
responses.try_add(message);
state
}
(ImaginateToolFsmState::Drawing, ImaginateToolMessage::DragStop) => {
if let Some(layer_path) = &shape_data.path {
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.to_vec() });
}
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
shape_data.cleanup(responses);
ImaginateToolFsmState::Ready
}
(ImaginateToolFsmState::Drawing, ImaginateToolMessage::Abort) => {
responses.add(DocumentMessage::AbortTransaction);
shape_data.cleanup(responses);
tool_data.path_outlines.clear_selected(responses);
ImaginateToolFsmState::Ready
}
(_, ImaginateToolMessage::Abort) => {
tool_data.path_outlines.clear_selected(responses);
ImaginateToolFsmState::Ready
}
_ => self,
} }
} }
@@ -1,22 +1,13 @@
use super::tool_prelude::*;
use crate::consts::LINE_ROTATE_SNAP_ANGLE; use crate::consts::LINE_ROTATE_SNAP_ANGLE;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::LayerId; use document_legacy::LayerId;
use graphene_core::vector::style::Stroke; use graphene_core::vector::style::Stroke;
use graphene_core::Color; use graphene_core::Color;
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct LineTool { pub struct LineTool {
fsm_state: LineToolFsmState, fsm_state: LineToolFsmState,
@@ -112,26 +103,24 @@ impl LayoutHolder for LineTool {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for LineTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for LineTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message { let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message else {
match action { self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
LineOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
LineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
LineOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
}
}
self.send_layout(responses, LayoutTarget::ToolOptions);
return; return;
};
match action {
LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
LineOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
LineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
LineOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
}
} }
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); self.send_layout(responses, LayoutTarget::ToolOptions);
} }
fn actions(&self) -> ActionList { fn actions(&self) -> ActionList {
@@ -162,8 +151,8 @@ enum LineToolFsmState {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
struct LineToolData { struct LineToolData {
drag_start: ViewportPosition, drag_start: DVec2,
drag_current: ViewportPosition, drag_current: DVec2,
angle: f64, angle: f64,
weight: f64, weight: f64,
path: Option<Vec<LayerId>>, path: Option<Vec<LayerId>>,
@@ -174,76 +163,70 @@ impl Fsm for LineToolFsmState {
type ToolData = LineToolData; type ToolData = LineToolData;
type ToolOptions = LineOptions; type ToolOptions = LineOptions;
fn transition( fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
self, let ToolActionHandlerData {
event: ToolMessage,
tool_data: &mut Self::ToolData,
ToolActionHandlerData {
document, document,
global_tool_data, global_tool_data,
input, input,
render_data, render_data,
.. ..
}: &mut ToolActionHandlerData, } = tool_action_data;
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
use LineToolFsmState::*;
use LineToolMessage::*;
if let ToolMessage::Line(event) = event { let ToolMessage::Line(event) = event else {
match (self, event) { return self;
(Ready, DragStart) => { };
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); match (self, event) {
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); (LineToolFsmState::Ready, LineToolMessage::DragStart) => {
tool_data.drag_start = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
let subpath = bezier_rs::Subpath::new_line(DVec2::ZERO, DVec2::X); let viewport_start = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
tool_data.drag_start = document.document_legacy.metadata.document_to_viewport.inverse().transform_point2(viewport_start);
responses.add(DocumentMessage::StartTransaction); let subpath = bezier_rs::Subpath::new_line(DVec2::ZERO, DVec2::X);
let layer_path = document.get_path_for_new_layer();
tool_data.path = Some(layer_path.clone());
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path,
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
});
tool_data.weight = tool_options.line_weight; responses.add(DocumentMessage::StartTransaction);
let layer_path = document.get_path_for_new_layer();
tool_data.path = Some(layer_path.clone());
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path,
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
});
Drawing tool_data.weight = tool_options.line_weight;
}
(Drawing, Redraw { center, snap_angle, lock_angle }) => {
tool_data.drag_current = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
let keyboard = &input.keyboard; LineToolFsmState::Drawing
responses.add(generate_transform(tool_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)));
Drawing
}
(Drawing, DragStop) => {
tool_data.snap_manager.cleanup(responses);
input.mouse.finish_transaction(tool_data.drag_start, responses);
tool_data.path = None;
Ready
}
(Drawing, Abort) => {
tool_data.snap_manager.cleanup(responses);
responses.add(DocumentMessage::AbortTransaction);
tool_data.path = None;
Ready
}
(_, WorkingColorChanged) => {
responses.add(LineToolMessage::UpdateOptions(LineOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} else { (LineToolFsmState::Drawing, LineToolMessage::Redraw { center, snap_angle, lock_angle }) => {
self tool_data.drag_current = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
let keyboard = &input.keyboard;
let transform = document.document_legacy.metadata.document_to_viewport;
responses.add(generate_transform(tool_data, transform, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)));
LineToolFsmState::Drawing
}
(LineToolFsmState::Drawing, LineToolMessage::DragStop) => {
tool_data.snap_manager.cleanup(responses);
input.mouse.finish_transaction(tool_data.drag_start, responses);
tool_data.path = None;
LineToolFsmState::Ready
}
(LineToolFsmState::Drawing, LineToolMessage::Abort) => {
tool_data.snap_manager.cleanup(responses);
responses.add(DocumentMessage::AbortTransaction);
tool_data.path = None;
LineToolFsmState::Ready
}
(_, LineToolMessage::WorkingColorChanged) => {
responses.add(LineToolMessage::UpdateOptions(LineOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} }
@@ -270,8 +253,8 @@ impl Fsm for LineToolFsmState {
} }
} }
fn generate_transform(tool_data: &mut LineToolData, lock_angle: bool, snap_angle: bool, center: bool) -> Message { fn generate_transform(tool_data: &mut LineToolData, document_to_viewport: DAffine2, lock_angle: bool, snap_angle: bool, center: bool) -> Message {
let mut start = tool_data.drag_start; let mut start = document_to_viewport.transform_point2(tool_data.drag_start);
let line_vector = tool_data.drag_current - start; let line_vector = tool_data.drag_current - start;
let mut angle = -line_vector.angle_between(DVec2::X); let mut angle = -line_vector.angle_between(DVec2::X);
@@ -16,3 +16,15 @@ pub mod rectangle_tool;
pub mod select_tool; pub mod select_tool;
pub mod spline_tool; pub mod spline_tool;
pub mod text_tool; pub mod text_tool;
pub mod tool_prelude {
pub use crate::messages::frontend::utility_types::MouseCursorIcon;
pub use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
pub use crate::messages::layout::utility_types::widget_prelude::*;
pub use crate::messages::prelude::*;
pub use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
pub use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
pub use glam::{DAffine2, DVec2};
pub use serde::{Deserialize, Serialize};
}
@@ -1,12 +1,4 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; use super::tool_prelude::*;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct NavigateTool { pub struct NavigateTool {
@@ -112,59 +104,57 @@ impl Fsm for NavigateToolFsmState {
_tool_options: &Self::ToolOptions, _tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
if let ToolMessage::Navigate(navigate) = message { let ToolMessage::Navigate(navigate) = message else{
use NavigateToolMessage::*; return self;
};
match navigate { match navigate {
ClickZoom { zoom_in } => { NavigateToolMessage::ClickZoom { zoom_in } => {
responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false }); responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false });
// Mouse has not moved from pointerdown to pointerup // Mouse has not moved from pointerdown to pointerup
if tool_data.drag_start == input.mouse.position { if tool_data.drag_start == input.mouse.position {
responses.add_front(if zoom_in { responses.add_front(if zoom_in {
NavigationMessage::IncreaseCanvasZoom { center_on_mouse: true } NavigationMessage::IncreaseCanvasZoom { center_on_mouse: true }
} else { } else {
NavigationMessage::DecreaseCanvasZoom { center_on_mouse: true } NavigationMessage::DecreaseCanvasZoom { center_on_mouse: true }
});
}
NavigateToolFsmState::Ready
}
PointerMove { snap_angle, snap_zoom } => {
responses.add_front(NavigationMessage::PointerMove {
snap_angle,
wait_for_snap_angle_release: false,
snap_zoom,
zoom_from_viewport: Some(tool_data.drag_start),
}); });
self
}
TranslateCanvasBegin => {
tool_data.drag_start = input.mouse.position;
responses.add_front(NavigationMessage::TranslateCanvasBegin);
NavigateToolFsmState::Panning
}
RotateCanvasBegin => {
tool_data.drag_start = input.mouse.position;
responses.add_front(NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu: false });
NavigateToolFsmState::Tilting
}
ZoomCanvasBegin => {
tool_data.drag_start = input.mouse.position;
responses.add_front(NavigationMessage::ZoomCanvasBegin);
NavigateToolFsmState::Zooming
}
TransformCanvasEnd => {
responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false });
NavigateToolFsmState::Ready
}
Abort => {
responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false });
NavigateToolFsmState::Ready
} }
NavigateToolFsmState::Ready
}
NavigateToolMessage::PointerMove { snap_angle, snap_zoom } => {
responses.add_front(NavigationMessage::PointerMove {
snap_angle,
wait_for_snap_angle_release: false,
snap_zoom,
zoom_from_viewport: Some(tool_data.drag_start),
});
self
}
NavigateToolMessage::TranslateCanvasBegin => {
tool_data.drag_start = input.mouse.position;
responses.add_front(NavigationMessage::TranslateCanvasBegin);
NavigateToolFsmState::Panning
}
NavigateToolMessage::RotateCanvasBegin => {
tool_data.drag_start = input.mouse.position;
responses.add_front(NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu: false });
NavigateToolFsmState::Tilting
}
NavigateToolMessage::ZoomCanvasBegin => {
tool_data.drag_start = input.mouse.position;
responses.add_front(NavigationMessage::ZoomCanvasBegin);
NavigateToolFsmState::Zooming
}
NavigateToolMessage::TransformCanvasEnd => {
responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false });
NavigateToolFsmState::Ready
}
NavigateToolMessage::Abort => {
responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false });
NavigateToolFsmState::Ready
} }
} else {
self
} }
} }
@@ -1,24 +1,18 @@
use std::vec; use std::vec;
use super::tool_prelude::*;
use crate::consts::{DRAG_THRESHOLD, SELECTION_THRESHOLD, SELECTION_TOLERANCE}; use crate::consts::{DRAG_THRESHOLD, SELECTION_THRESHOLD, SELECTION_TOLERANCE};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer; use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, ShapeState}; use crate::messages::tool::common_functionality::shape_editor::{ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, ShapeState};
use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::common_functionality::transformation_cage::{add_bounding_box, transform_from_box}; use crate::messages::tool::common_functionality::transformation_cage::{add_bounding_box, transform_from_box};
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, HintData, HintGroup, HintInfo, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use document_legacy::document::Document; use document_legacy::document::Document;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::intersection::Quad; use document_legacy::intersection::Quad;
use document_legacy::{LayerId, Operation}; use document_legacy::{LayerId, Operation};
use graphene_core::vector::{ManipulatorPointId, SelectedType}; use graphene_core::vector::{ManipulatorPointId, SelectedType};
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct PathTool { pub struct PathTool {
fsm_state: PathToolFsmState, fsm_state: PathToolFsmState,
@@ -219,12 +213,13 @@ impl PathToolData {
fn refresh_overlays(&mut self, document: &DocumentMessageHandler, shape_editor: &mut ShapeState, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) { fn refresh_overlays(&mut self, document: &DocumentMessageHandler, shape_editor: &mut ShapeState, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) {
// Set the previously selected layers to invisible // Set the previously selected layers to invisible
for layer_path in document.all_layers() { for layer_path in document.all_layers() {
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses); let layer = LayerNodeIdentifier::from_path(layer_path, document.network());
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer, false, responses);
} }
// Render the new overlays // Render the new overlays
for layer_path in shape_editor.selected_shape_state.keys() { for &layer in shape_editor.selected_shape_state.keys() {
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses); shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses);
} }
self.opposing_handle_lengths = None; self.opposing_handle_lengths = None;
@@ -235,313 +230,313 @@ impl Fsm for PathToolFsmState {
type ToolData = PathToolData; type ToolData = PathToolData;
type ToolOptions = (); type ToolOptions = ();
fn transition( fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
self, let ToolActionHandlerData {
event: ToolMessage,
tool_data: &mut Self::ToolData,
ToolActionHandlerData {
document, document,
input, input,
render_data, render_data,
shape_editor, shape_editor,
shape_overlay, shape_overlay,
.. ..
}: &mut ToolActionHandlerData, } = tool_action_data;
_tool_options: &Self::ToolOptions, let ToolMessage::Path(event) = event else {
responses: &mut VecDeque<Message>, return self;
) -> Self { };
if let ToolMessage::Path(event) = event { match (self, event) {
match (self, event) { (_, PathToolMessage::SelectionChanged) => {
(_, PathToolMessage::SelectionChanged) => { // Set the newly targeted layers to visible
// Set the newly targeted layers to visible let layer_paths = document
let layer_paths = document.selected_visible_layers().map(|layer_path| layer_path.to_vec()).collect(); .selected_visible_layers()
shape_editor.set_selected_layers(layer_paths); .map(|layer_path| LayerNodeIdentifier::from_path(layer_path, document.network()))
.collect();
shape_editor.set_selected_layers(layer_paths);
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
responses.add(PathToolMessage::SelectedPointUpdated);
// This can happen in any state (which is why we return self)
self
}
(_, PathToolMessage::DocumentIsDirty) => {
// When the document has moved / needs to be redraw, re-render the overlays
// TODO the overlay system should probably receive this message instead of the tool
for layer_path in document.selected_visible_layers() {
let layer = LayerNodeIdentifier::from_path(layer_path, document.network());
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses);
}
responses.add(PathToolMessage::SelectedPointUpdated);
self
}
// Mouse down
(_, PathToolMessage::DragStart { add_to_selection }) => {
let shift_pressed = input.keyboard.get(add_to_selection as usize);
tool_data.opposing_handle_lengths = None;
let _selected_layers = shape_editor.selected_layers().cloned().collect::<Vec<_>>();
// Select the first point within the threshold (in pixels)
if let Some(mut selected_points) = shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift_pressed) {
responses.add(DocumentMessage::StartTransaction);
//tool_data
// .snap_manager
// .start_snap(document, input, document.bounding_boxes(Some(&selected_layers), None, render_data), true, true);
// Do not snap against handles when anchor is selected
let mut additional_selected_points = Vec::new();
for point in selected_points.points.iter() {
if point.point_id.manipulator_type == SelectedType::Anchor {
additional_selected_points.push(ManipulatorPointInfo {
layer: point.layer,
point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::InHandle),
});
additional_selected_points.push(ManipulatorPointInfo {
layer: point.layer,
point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::OutHandle),
});
}
}
selected_points.points.extend(additional_selected_points);
//let include_handles: Vec<_> = selected_layers.iter().map(|x| x.as_slice()).collect();
//tool_data.snap_manager.add_all_document_handles(document, input, &include_handles, &[], &selected_points.points);
tool_data.drag_start_pos = input.mouse.position;
tool_data.previous_mouse_position = input.mouse.position - selected_points.offset;
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses); tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
responses.add(PathToolMessage::SelectedPointUpdated); responses.add(PathToolMessage::SelectedPointUpdated);
// This can happen in any state (which is why we return self)
self
}
(_, PathToolMessage::DocumentIsDirty) => {
// When the document has moved / needs to be redraw, re-render the overlays
// TODO the overlay system should probably receive this message instead of the tool
for layer_path in document.selected_visible_layers() {
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
}
responses.add(PathToolMessage::SelectedPointUpdated);
self
}
// Mouse down
(_, PathToolMessage::DragStart { add_to_selection }) => {
let shift_pressed = input.keyboard.get(add_to_selection as usize);
tool_data.opposing_handle_lengths = None;
let selected_layers = shape_editor.selected_layers().cloned().collect();
// Select the first point within the threshold (in pixels)
if let Some(mut selected_points) = shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift_pressed) {
responses.add(DocumentMessage::StartTransaction);
tool_data
.snap_manager
.start_snap(document, input, document.bounding_boxes(Some(&selected_layers), None, render_data), true, true);
// Do not snap against handles when anchor is selected
let mut additional_selected_points = Vec::new();
for point in selected_points.points.iter() {
if point.point_id.manipulator_type == SelectedType::Anchor {
additional_selected_points.push(ManipulatorPointInfo {
shape_layer_path: point.shape_layer_path,
point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::InHandle),
});
additional_selected_points.push(ManipulatorPointInfo {
shape_layer_path: point.shape_layer_path,
point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::OutHandle),
});
}
}
selected_points.points.extend(additional_selected_points);
let include_handles: Vec<_> = selected_layers.iter().map(|x| x.as_slice()).collect();
tool_data.snap_manager.add_all_document_handles(document, input, &include_handles, &[], &selected_points.points);
tool_data.drag_start_pos = input.mouse.position;
tool_data.previous_mouse_position = input.mouse.position - selected_points.offset;
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
responses.add(PathToolMessage::SelectedPointUpdated);
PathToolFsmState::Dragging
}
// We didn't find a point nearby, so consider selecting the nearest shape instead
else {
let selection_size = DVec2::new(2.0, 2.0);
// Select shapes directly under our mouse
let intersection = document
.document_legacy
.intersects_quad_root(Quad::from_box([input.mouse.position - selection_size, input.mouse.position + selection_size]), render_data);
if !intersection.is_empty() {
if shift_pressed {
responses.add(DocumentMessage::AddSelectedLayers { additional_layers: intersection });
} else {
// Selects the topmost layer when selecting intersecting shapes
let top_most_intersection = intersection[intersection.len() - 1].clone();
responses.add(DocumentMessage::SetSelectedLayers {
replacement_selected_layers: vec![top_most_intersection.clone()],
});
tool_data.drag_start_pos = input.mouse.position;
tool_data.previous_mouse_position = input.mouse.position;
// Selects all the anchor points when clicking in a filled area of shape. If two shapes intersect we pick the topmost layer.
shape_editor.select_all_anchors(&document.document_legacy, &top_most_intersection);
return PathToolFsmState::Dragging;
}
} else {
// an empty intersection means that the user is drawing a box
tool_data.drag_start_pos = input.mouse.position;
tool_data.previous_mouse_position = input.mouse.position;
tool_data.drag_box_overlay_layer = Some(add_bounding_box(responses));
return PathToolFsmState::DrawingBox;
}
PathToolFsmState::Ready
}
}
(PathToolFsmState::DrawingBox, PathToolMessage::PointerMove { .. }) => {
tool_data.previous_mouse_position = input.mouse.position;
responses.add_front(DocumentMessage::Overlays(
Operation::SetLayerTransformInViewport {
path: tool_data.drag_box_overlay_layer.clone().unwrap(),
transform: transform_from_box(tool_data.drag_start_pos, tool_data.previous_mouse_position, DAffine2::IDENTITY).to_cols_array(),
}
.into(),
));
PathToolFsmState::DrawingBox
}
// Dragging
(
PathToolFsmState::Dragging,
PathToolMessage::PointerMove {
alt_mirror_angle,
shift_mirror_distance,
},
) => {
// Determine when alt state changes
let alt_pressed = input.keyboard.get(alt_mirror_angle as usize);
// Only on alt down
if alt_pressed && !tool_data.alt_debounce {
tool_data.opposing_handle_lengths = None;
shape_editor.toggle_handle_mirroring_on_selected(responses);
}
tool_data.alt_debounce = alt_pressed;
// Determine when shift state changes
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
if shift_pressed {
if tool_data.opposing_handle_lengths.is_none() {
tool_data.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(&document.document_legacy));
}
} else if let Some(opposing_handle_lengths) = &tool_data.opposing_handle_lengths {
shape_editor.reset_opposing_handle_lengths(&document.document_legacy, opposing_handle_lengths, responses);
tool_data.opposing_handle_lengths = None;
}
// Move the selected points by the mouse position
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
shape_editor.move_selected_points(&document.document_legacy, snapped_position - tool_data.previous_mouse_position, shift_pressed, responses);
tool_data.previous_mouse_position = snapped_position;
PathToolFsmState::Dragging PathToolFsmState::Dragging
} }
// We didn't find a point nearby, so consider selecting the nearest shape instead
(PathToolFsmState::DrawingBox, PathToolMessage::Enter { add_to_selection }) => { else {
let shift_pressed = input.keyboard.get(add_to_selection as usize); let selection_size = DVec2::new(2.0, 2.0);
// Select shapes directly under our mouse
if tool_data.drag_start_pos == tool_data.previous_mouse_position { let intersection = document
responses.add(DocumentMessage::DeselectAllLayers); .document_legacy
.intersects_quad_root(Quad::from_box([input.mouse.position - selection_size, input.mouse.position + selection_size]), render_data);
if !intersection.is_empty() {
if shift_pressed {
responses.add(DocumentMessage::AddSelectedLayers { additional_layers: intersection });
} else {
// Selects the topmost layer when selecting intersecting shapes
let top_most_intersection = intersection[intersection.len() - 1].clone();
responses.add(DocumentMessage::SetSelectedLayers {
replacement_selected_layers: vec![top_most_intersection.clone()],
});
tool_data.drag_start_pos = input.mouse.position;
tool_data.previous_mouse_position = input.mouse.position;
// Selects all the anchor points when clicking in a filled area of shape. If two shapes intersect we pick the topmost layer.
let layer = LayerNodeIdentifier::from_path(&top_most_intersection, document.network());
shape_editor.select_all_anchors(&document.document_legacy, layer);
return PathToolFsmState::Dragging;
}
} else { } else {
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed); // an empty intersection means that the user is drawing a box
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses); tool_data.drag_start_pos = input.mouse.position;
}; tool_data.previous_mouse_position = input.mouse.position;
responses.add_front(DocumentMessage::Overlays( tool_data.drag_box_overlay_layer = Some(add_bounding_box(responses));
Operation::DeleteLayer { return PathToolFsmState::DrawingBox;
path: tool_data.drag_box_overlay_layer.take().unwrap(),
}
.into(),
));
responses.add(PathToolMessage::SelectedPointUpdated);
PathToolFsmState::Ready
}
// Mouse up
(PathToolFsmState::DrawingBox, PathToolMessage::DragStop { shift_mirror_distance }) => {
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
responses.add(DocumentMessage::DeselectAllLayers);
} else {
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
};
responses.add_front(DocumentMessage::Overlays(
Operation::DeleteLayer {
path: tool_data.drag_box_overlay_layer.take().unwrap(),
}
.into(),
));
responses.add(PathToolMessage::SelectedPointUpdated);
PathToolFsmState::Ready
}
(_, PathToolMessage::DragStop { shift_mirror_distance }) => {
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
let nearest_point = shape_editor
.find_nearest_point_indices(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD)
.map(|(_, nearest_point)| nearest_point);
shape_editor.delete_selected_handles_with_zero_length(&document.document_legacy, &tool_data.opposing_handle_lengths, responses);
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed {
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == Some(point));
if clicked_selected {
shape_editor.deselect_all();
shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, false);
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
}
} }
responses.add(PathToolMessage::SelectedPointUpdated);
tool_data.snap_manager.cleanup(responses);
PathToolFsmState::Ready PathToolFsmState::Ready
} }
// Delete key
(_, PathToolMessage::Delete) => {
// Delete the selected points and clean up overlays
responses.add(DocumentMessage::StartTransaction);
shape_editor.delete_selected_points(responses);
responses.add(PathToolMessage::SelectionChanged);
for layer_path in document.all_layers() {
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
}
PathToolFsmState::Ready
}
(_, PathToolMessage::InsertPoint) => {
// First we try and flip the sharpness (if they have clicked on an anchor)
if !shape_editor.flip_sharp(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses) {
// If not, then we try and split the path that may have been clicked upon
shape_editor.split(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses);
}
responses.add(PathToolMessage::SelectedPointUpdated);
self
}
(_, PathToolMessage::Abort) => {
// TODO Tell overlay manager to remove the overlays
for layer_path in document.all_layers() {
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
}
PathToolFsmState::Ready
}
(
_,
PathToolMessage::PointerMove {
alt_mirror_angle: _,
shift_mirror_distance: _,
},
) => self,
(_, PathToolMessage::NudgeSelectedPoints { delta_x, delta_y }) => {
shape_editor.move_selected_points(&document.document_legacy, (delta_x, delta_y).into(), true, responses);
PathToolFsmState::Ready
}
(_, PathToolMessage::SelectAllPoints) => {
shape_editor.select_all_points(&document.document_legacy);
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
PathToolFsmState::Ready
}
(_, PathToolMessage::SelectedPointXChanged { new_x }) => {
if let Some(&SingleSelectedPoint { coordinates, id, ref layer_path, .. }) = tool_data.selection_status.as_one() {
shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(new_x, coordinates.y), layer_path);
}
PathToolFsmState::Ready
}
(_, PathToolMessage::SelectedPointYChanged { new_y }) => {
if let Some(&SingleSelectedPoint { coordinates, id, ref layer_path, .. }) = tool_data.selection_status.as_one() {
shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(coordinates.x, new_y), layer_path);
}
PathToolFsmState::Ready
}
(_, PathToolMessage::SelectedPointUpdated) => {
tool_data.selection_status = get_selection_status(&document.document_legacy, shape_editor);
self
}
(_, PathToolMessage::ManipulatorAngleMakeSmooth) => {
responses.add(DocumentMessage::StartTransaction);
shape_editor.set_handle_mirroring_on_selected(true, responses);
shape_editor.smooth_selected_groups(responses, &document.document_legacy);
responses.add(DocumentMessage::CommitTransaction);
PathToolFsmState::Ready
}
(_, PathToolMessage::ManipulatorAngleMakeSharp) => {
responses.add(DocumentMessage::StartTransaction);
shape_editor.set_handle_mirroring_on_selected(false, responses);
responses.add(DocumentMessage::CommitTransaction);
PathToolFsmState::Ready
}
(_, _) => PathToolFsmState::Ready,
} }
} else { (PathToolFsmState::DrawingBox, PathToolMessage::PointerMove { .. }) => {
self tool_data.previous_mouse_position = input.mouse.position;
responses.add_front(DocumentMessage::Overlays(
Operation::SetLayerTransformInViewport {
path: tool_data.drag_box_overlay_layer.clone().unwrap(),
transform: transform_from_box(tool_data.drag_start_pos, tool_data.previous_mouse_position, DAffine2::IDENTITY).to_cols_array(),
}
.into(),
));
PathToolFsmState::DrawingBox
}
// Dragging
(
PathToolFsmState::Dragging,
PathToolMessage::PointerMove {
alt_mirror_angle,
shift_mirror_distance,
},
) => {
// Determine when alt state changes
let alt_pressed = input.keyboard.get(alt_mirror_angle as usize);
// Only on alt down
if alt_pressed && !tool_data.alt_debounce {
tool_data.opposing_handle_lengths = None;
shape_editor.toggle_handle_mirroring_on_selected(responses);
}
tool_data.alt_debounce = alt_pressed;
// Determine when shift state changes
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
if shift_pressed {
if tool_data.opposing_handle_lengths.is_none() {
tool_data.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(&document.document_legacy));
}
} else if let Some(opposing_handle_lengths) = &tool_data.opposing_handle_lengths {
shape_editor.reset_opposing_handle_lengths(&document.document_legacy, opposing_handle_lengths, responses);
tool_data.opposing_handle_lengths = None;
}
// Move the selected points by the mouse position
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
shape_editor.move_selected_points(&document.document_legacy, snapped_position - tool_data.previous_mouse_position, shift_pressed, responses);
tool_data.previous_mouse_position = snapped_position;
PathToolFsmState::Dragging
}
(PathToolFsmState::DrawingBox, PathToolMessage::Enter { add_to_selection }) => {
let shift_pressed = input.keyboard.get(add_to_selection as usize);
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
responses.add(DocumentMessage::DeselectAllLayers);
} else {
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
};
responses.add_front(DocumentMessage::Overlays(
Operation::DeleteLayer {
path: tool_data.drag_box_overlay_layer.take().unwrap(),
}
.into(),
));
responses.add(PathToolMessage::SelectedPointUpdated);
PathToolFsmState::Ready
}
// Mouse up
(PathToolFsmState::DrawingBox, PathToolMessage::DragStop { shift_mirror_distance }) => {
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
responses.add(DocumentMessage::DeselectAllLayers);
} else {
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
};
responses.add_front(DocumentMessage::Overlays(
Operation::DeleteLayer {
path: tool_data.drag_box_overlay_layer.take().unwrap(),
}
.into(),
));
responses.add(PathToolMessage::SelectedPointUpdated);
PathToolFsmState::Ready
}
(_, PathToolMessage::DragStop { shift_mirror_distance }) => {
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
let nearest_point = shape_editor
.find_nearest_point_indices(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD)
.map(|(_, nearest_point)| nearest_point);
shape_editor.delete_selected_handles_with_zero_length(&document.document_legacy, &tool_data.opposing_handle_lengths, responses);
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed {
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == Some(point));
if clicked_selected {
shape_editor.deselect_all();
shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, false);
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
}
}
responses.add(PathToolMessage::SelectedPointUpdated);
tool_data.snap_manager.cleanup(responses);
PathToolFsmState::Ready
}
// Delete key
(_, PathToolMessage::Delete) => {
// Delete the selected points and clean up overlays
responses.add(DocumentMessage::StartTransaction);
shape_editor.delete_selected_points(responses);
responses.add(PathToolMessage::SelectionChanged);
for layer_path in document.all_layers() {
let layer = LayerNodeIdentifier::from_path(layer_path, document.network());
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer, responses);
}
PathToolFsmState::Ready
}
(_, PathToolMessage::InsertPoint) => {
// First we try and flip the sharpness (if they have clicked on an anchor)
if !shape_editor.flip_sharp(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses) {
// If not, then we try and split the path that may have been clicked upon
shape_editor.split(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses);
}
responses.add(PathToolMessage::SelectedPointUpdated);
self
}
(_, PathToolMessage::Abort) => {
// TODO Tell overlay manager to remove the overlays
for layer_path in document.all_layers() {
let layer = LayerNodeIdentifier::from_path(layer_path, document.network());
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer, responses);
}
PathToolFsmState::Ready
}
(
_,
PathToolMessage::PointerMove {
alt_mirror_angle: _,
shift_mirror_distance: _,
},
) => self,
(_, PathToolMessage::NudgeSelectedPoints { delta_x, delta_y }) => {
shape_editor.move_selected_points(&document.document_legacy, (delta_x, delta_y).into(), true, responses);
PathToolFsmState::Ready
}
(_, PathToolMessage::SelectAllPoints) => {
shape_editor.select_all_points(&document.document_legacy);
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
PathToolFsmState::Ready
}
(_, PathToolMessage::SelectedPointXChanged { new_x }) => {
if let Some(&SingleSelectedPoint { coordinates, id, ref layer_path, .. }) = tool_data.selection_status.as_one() {
shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(new_x, coordinates.y), layer_path);
}
PathToolFsmState::Ready
}
(_, PathToolMessage::SelectedPointYChanged { new_y }) => {
if let Some(&SingleSelectedPoint { coordinates, id, ref layer_path, .. }) = tool_data.selection_status.as_one() {
shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(coordinates.x, new_y), layer_path);
}
PathToolFsmState::Ready
}
(_, PathToolMessage::SelectedPointUpdated) => {
tool_data.selection_status = get_selection_status(&document.document_legacy, shape_editor);
self
}
(_, PathToolMessage::ManipulatorAngleMakeSmooth) => {
responses.add(DocumentMessage::StartTransaction);
shape_editor.set_handle_mirroring_on_selected(true, responses);
shape_editor.smooth_selected_groups(responses, &document.document_legacy);
responses.add(DocumentMessage::CommitTransaction);
PathToolFsmState::Ready
}
(_, PathToolMessage::ManipulatorAngleMakeSharp) => {
responses.add(DocumentMessage::StartTransaction);
shape_editor.set_handle_mirroring_on_selected(false, responses);
responses.add(DocumentMessage::CommitTransaction);
PathToolFsmState::Ready
}
(_, _) => PathToolFsmState::Ready,
} }
} }
@@ -620,7 +615,7 @@ fn get_selection_status(document: &Document, shape_state: &mut ShapeState) -> Se
// Check to see if only one manipulator group is selected // Check to see if only one manipulator group is selected
let selection_layers: Vec<_> = shape_state.selected_shape_state.iter().take(2).map(|(k, v)| (k, v.selected_points_count())).collect(); let selection_layers: Vec<_> = shape_state.selected_shape_state.iter().take(2).map(|(k, v)| (k, v.selected_points_count())).collect();
if let [(layer, 1)] = selection_layers[..] { if let [(layer, 1)] = selection_layers[..] {
let Some(layer_data) = document.layer(layer).ok() else { return SelectionStatus::None }; let Some(layer_data) = document.layer(&layer.to_path()).ok() else { return SelectionStatus::None };
let Some(vector_data) = layer_data.as_vector_data() else { return SelectionStatus::None }; let Some(vector_data) = layer_data.as_vector_data() else { return SelectionStatus::None };
let Some(point) = shape_state.selected_points().next() else { let Some(point) = shape_state.selected_points().next() else {
return SelectionStatus::None; return SelectionStatus::None;
@@ -641,7 +636,7 @@ fn get_selection_status(document: &Document, shape_state: &mut ShapeState) -> Se
return SelectionStatus::One(SingleSelectedPoint { return SelectionStatus::One(SingleSelectedPoint {
coordinates: layer_data.transform.transform_point2(local_position) + layer_data.pivot, coordinates: layer_data.transform.transform_point2(local_position) + layer_data.pivot,
layer_path: layer.clone(), layer_path: layer.to_path(),
id: *point, id: *point,
manipulator_angle, manipulator_angle,
}); });
+170 -179
View File
@@ -1,28 +1,22 @@
use super::tool_prelude::*;
use crate::consts::LINE_ROTATE_SNAP_ANGLE; use crate::consts::LINE_ROTATE_SNAP_ANGLE;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::VectorDataModification; use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer; use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::messages::tool::tool_messages::pen_tool::graph_modification_utils::NodeGraphLayer;
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use bezier_rs::Subpath; use bezier_rs::Subpath;
use document_legacy::document::Document;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::LayerId; use document_legacy::LayerId;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::NodeInput;
use graphene_core::uuid::ManipulatorGroupId; use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::style::{Fill, Stroke}; use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::vector::{ManipulatorPointId, SelectedType}; use graphene_core::vector::{ManipulatorPointId, SelectedType};
use graphene_core::Color; use graphene_core::Color;
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct PenTool { pub struct PenTool {
fsm_state: PenToolFsmState, fsm_state: PenToolFsmState,
@@ -144,33 +138,31 @@ impl LayoutHolder for PenTool {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message { let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else {
match action { self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
PenOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
PenOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
PenOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
PenOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
PenOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
}
self.send_layout(responses, LayoutTarget::ToolOptions);
return; return;
};
match action {
PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
PenOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
PenOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
PenOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
PenOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
PenOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
} }
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); self.send_layout(responses, LayoutTarget::ToolOptions);
} }
fn actions(&self) -> ActionList { fn actions(&self) -> ActionList {
@@ -227,7 +219,7 @@ impl PenToolData {
self.subpath_index = subpath_index; self.subpath_index = subpath_index;
// Stop the handles on the first point from mirroring // Stop the handles on the first point from mirroring
let Some(subpaths) = get_subpaths(layer, document) else { return }; let Some(subpaths) = get_subpaths (LayerNodeIdentifier::from_path(layer, document.network()), &document.document_legacy) else { return };
let manipulator_groups = subpaths[subpath_index].manipulator_groups(); let manipulator_groups = subpaths[subpath_index].manipulator_groups();
let Some(last_handle) = (if from_start { manipulator_groups.first() } else { manipulator_groups.last() }) else { let Some(last_handle) = (if from_start { manipulator_groups.first() } else { manipulator_groups.last() }) else {
return; return;
@@ -257,7 +249,7 @@ impl PenToolData {
let layer_path = document.get_path_for_new_layer(); let layer_path = document.get_path_for_new_layer();
// Get the position and set properties // Get the position and set properties
let transform = document.document_legacy.multiply_transforms(&layer_path[..layer_path.len() - 1]).unwrap_or_default(); let transform = document.document_legacy.metadata.document_to_viewport * document.document_legacy.multiply_transforms(&layer_path[..layer_path.len() - 1]).unwrap_or_default();
let snapped_position = self.snap_manager.snap_position(responses, document, input.mouse.position); let snapped_position = self.snap_manager.snap_position(responses, document, input.mouse.position);
let start_position = transform.inverse().transform_point2(snapped_position); let start_position = transform.inverse().transform_point2(snapped_position);
self.weight = line_weight; self.weight = line_weight;
@@ -286,7 +278,7 @@ impl PenToolData {
fn check_break(&mut self, document: &DocumentMessageHandler, transform: DAffine2, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) -> Option<()> { fn check_break(&mut self, document: &DocumentMessageHandler, transform: DAffine2, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) -> Option<()> {
// Get subpath // Get subpath
let layer_path = self.path.as_ref()?; let layer_path = self.path.as_ref()?;
let subpath = &get_subpaths(layer_path, document)?[self.subpath_index]; let subpath = &get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?[self.subpath_index];
// Get the last manipulator group and the one previous to that // Get the last manipulator group and the one previous to that
let mut manipulator_groups = subpath.manipulator_groups().iter(); let mut manipulator_groups = subpath.manipulator_groups().iter();
@@ -327,7 +319,7 @@ impl PenToolData {
// The overlay system cannot detect deleted points so we must just delete all the overlays // The overlay system cannot detect deleted points so we must just delete all the overlays
for layer_path in document.all_layers() { for layer_path in document.all_layers() {
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); shape_overlay.clear_subpath_overlays(&document.document_legacy, LayerNodeIdentifier::from_path(layer_path, document.network()), responses);
} }
self.should_mirror = false; self.should_mirror = false;
@@ -337,7 +329,7 @@ impl PenToolData {
fn finish_placing_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> { fn finish_placing_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
// Get subpath // Get subpath
let layer_path = self.path.as_ref()?; let layer_path = self.path.as_ref()?;
let subpath = &get_subpaths(layer_path, document)?[self.subpath_index]; let subpath = &get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?[self.subpath_index];
// Get the last manipulator group and the one previous to that // Get the last manipulator group and the one previous to that
let mut manipulator_groups = subpath.manipulator_groups().iter(); let mut manipulator_groups = subpath.manipulator_groups().iter();
@@ -394,7 +386,7 @@ impl PenToolData {
// Clean up overlays // Clean up overlays
for layer_path in document.all_layers() { for layer_path in document.all_layers() {
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); shape_overlay.clear_subpath_overlays(&document.document_legacy, LayerNodeIdentifier::from_path(layer_path, document.network()), responses);
} }
// Clean up tool data // Clean up tool data
@@ -415,7 +407,7 @@ impl PenToolData {
fn drag_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> { fn drag_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
// Get subpath // Get subpath
let layer_path = self.path.as_ref()?; let layer_path = self.path.as_ref()?;
let subpath = &get_subpaths(layer_path, document)?[self.subpath_index]; let subpath = &get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?[self.subpath_index];
// Get the last manipulator group // Get the last manipulator group
let manipulator_groups = subpath.manipulator_groups(); let manipulator_groups = subpath.manipulator_groups();
@@ -468,7 +460,7 @@ impl PenToolData {
fn place_anchor(&mut self, document: &DocumentMessageHandler, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> { fn place_anchor(&mut self, document: &DocumentMessageHandler, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
// Get subpath // Get subpath
let layer_path = self.path.as_ref()?; let layer_path = self.path.as_ref()?;
let subpath = &get_subpaths(layer_path, document)?[self.subpath_index]; let subpath = &get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?[self.subpath_index];
// Get the last manipulator group and the one previous to that // Get the last manipulator group and the one previous to that
let mut manipulator_groups = subpath.manipulator_groups().iter(); let mut manipulator_groups = subpath.manipulator_groups().iter();
@@ -513,7 +505,7 @@ impl PenToolData {
fn finish_transaction(&mut self, fsm: PenToolFsmState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Option<DocumentMessage> { fn finish_transaction(&mut self, fsm: PenToolFsmState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Option<DocumentMessage> {
// Get subpath // Get subpath
let layer_path = self.path.as_ref()?; let layer_path = self.path.as_ref()?;
let subpath = &get_subpaths(layer_path, document)?[self.subpath_index]; let subpath = &get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?[self.subpath_index];
// Abort if only one manipulator group has been placed // Abort if only one manipulator group has been placed
if fsm == PenToolFsmState::PlacingAnchor && subpath.len() < 3 { if fsm == PenToolFsmState::PlacingAnchor && subpath.len() < 3 {
@@ -560,11 +552,8 @@ impl Fsm for PenToolFsmState {
type ToolData = PenToolData; type ToolData = PenToolData;
type ToolOptions = PenOptions; type ToolOptions = PenOptions;
fn transition( fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
self, let ToolActionHandlerData {
event: ToolMessage,
tool_data: &mut Self::ToolData,
ToolActionHandlerData {
document, document,
global_tool_data, global_tool_data,
input, input,
@@ -572,10 +561,8 @@ impl Fsm for PenToolFsmState {
shape_editor, shape_editor,
shape_overlay, shape_overlay,
.. ..
}: &mut ToolActionHandlerData, } = tool_action_data;
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
let mut transform = tool_data.path.as_ref().and_then(|path| document.document_legacy.multiply_transforms(path).ok()).unwrap_or_default(); let mut transform = tool_data.path.as_ref().and_then(|path| document.document_legacy.multiply_transforms(path).ok()).unwrap_or_default();
if !transform.inverse().is_finite() { if !transform.inverse().is_finite() {
@@ -591,117 +578,118 @@ impl Fsm for PenToolFsmState {
transform = DAffine2::IDENTITY; transform = DAffine2::IDENTITY;
} }
if let ToolMessage::Pen(event) = event { transform = document.document_legacy.metadata.document_to_viewport * transform;
match (self, event) {
(_, PenToolMessage::CanvasTransformed) => {
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
self
}
(_, PenToolMessage::DocumentIsDirty) => {
// When the document has moved / needs to be redraw, re-render the overlays
// TODO the overlay system should probably receive this message instead of the tool
for layer_path in document.selected_visible_layers() {
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
}
self
}
(_, PenToolMessage::SelectionChanged) => {
// Set the previously selected layers to invisible
for layer_path in document.all_layers() {
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses);
}
// Redraw the overlays of the newly selected layers let ToolMessage::Pen(event) = event else {
for layer_path in document.selected_visible_layers() { return self;
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses); };
} match (self, event) {
self (_, PenToolMessage::CanvasTransformed) => {
} tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
(_, PenToolMessage::WorkingColorChanged) => { self
responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
(PenToolFsmState::Ready, PenToolMessage::DragStart) => {
responses.add(DocumentMessage::StartTransaction);
// Initialize snapping
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
// Disable this tool's mirroring
tool_data.should_mirror = false;
// Perform extension of an existing path
if let Some((layer, subpath_index, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
tool_data.extend_subpath(layer, subpath_index, from_start, document, responses);
} else {
tool_data.create_new_path(
document,
tool_options.line_weight,
tool_options.stroke.active_color(),
tool_options.fill.active_color(),
input,
responses,
);
}
// Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle
PenToolFsmState::DraggingHandle
}
(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => {
tool_data.check_break(document, transform, shape_overlay, responses);
PenToolFsmState::DraggingHandle
}
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => {
tool_data.should_mirror = true;
tool_data.finish_placing_handle(document, transform, shape_overlay, responses).unwrap_or(PenToolFsmState::PlacingAnchor)
}
(PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
let modifiers = ModifierState {
snap_angle: input.keyboard.key(snap_angle),
lock_angle: input.keyboard.key(lock_angle),
break_handle: input.keyboard.key(break_handle),
};
tool_data.drag_handle(document, transform, input.mouse.position, modifiers, responses).unwrap_or(PenToolFsmState::Ready)
}
(PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
let modifiers = ModifierState {
snap_angle: input.keyboard.key(snap_angle),
lock_angle: input.keyboard.key(lock_angle),
break_handle: input.keyboard.key(break_handle),
};
tool_data
.place_anchor(document, transform, input.mouse.position, modifiers, responses)
.unwrap_or(PenToolFsmState::Ready)
}
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => {
// Abort or commit the transaction to the undo history
let message = tool_data.finish_transaction(self, document, responses).unwrap_or(DocumentMessage::AbortTransaction);
responses.add(message);
// Clean up overlays
for layer_path in document.all_layers() {
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
}
tool_data.path = None;
tool_data.snap_manager.cleanup(responses);
PenToolFsmState::Ready
}
(_, PenToolMessage::Abort) => {
// Clean up overlays
for layer_path in document.all_layers() {
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
}
self
}
_ => self,
} }
} else { (_, PenToolMessage::DocumentIsDirty) => {
self // When the document has moved / needs to be redraw, re-render the overlays
// TODO the overlay system should probably receive this message instead of the tool
for layer in document.document_legacy.metadata.selected_layers() {
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses);
}
self
}
(_, PenToolMessage::SelectionChanged) => {
// Set the previously selected layers to invisible
for layer in document.document_legacy.metadata.all_layers() {
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer, false, responses);
}
// Redraw the overlays of the newly selected layers
for layer in document.document_legacy.metadata.selected_layers() {
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses);
}
self
}
(_, PenToolMessage::WorkingColorChanged) => {
responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
(PenToolFsmState::Ready, PenToolMessage::DragStart) => {
responses.add(DocumentMessage::StartTransaction);
// Initialize snapping
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
// Disable this tool's mirroring
tool_data.should_mirror = false;
// Perform extension of an existing path
if let Some((layer, subpath_index, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
tool_data.extend_subpath(layer, subpath_index, from_start, document, responses);
} else {
tool_data.create_new_path(
document,
tool_options.line_weight,
tool_options.stroke.active_color(),
tool_options.fill.active_color(),
input,
responses,
);
}
// Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle
PenToolFsmState::DraggingHandle
}
(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => {
tool_data.check_break(document, transform, shape_overlay, responses);
PenToolFsmState::DraggingHandle
}
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => {
tool_data.should_mirror = true;
tool_data.finish_placing_handle(document, transform, shape_overlay, responses).unwrap_or(PenToolFsmState::PlacingAnchor)
}
(PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
let modifiers = ModifierState {
snap_angle: input.keyboard.key(snap_angle),
lock_angle: input.keyboard.key(lock_angle),
break_handle: input.keyboard.key(break_handle),
};
tool_data.drag_handle(document, transform, input.mouse.position, modifiers, responses).unwrap_or(PenToolFsmState::Ready)
}
(PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
let modifiers = ModifierState {
snap_angle: input.keyboard.key(snap_angle),
lock_angle: input.keyboard.key(lock_angle),
break_handle: input.keyboard.key(break_handle),
};
tool_data
.place_anchor(document, transform, input.mouse.position, modifiers, responses)
.unwrap_or(PenToolFsmState::Ready)
}
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => {
// Abort or commit the transaction to the undo history
let message = tool_data.finish_transaction(self, document, responses).unwrap_or(DocumentMessage::AbortTransaction);
responses.add(message);
// Clean up overlays
for layer_path in document.all_layers() {
shape_overlay.clear_subpath_overlays(&document.document_legacy, LayerNodeIdentifier::from_path(layer_path, document.network()), responses);
}
tool_data.path = None;
tool_data.snap_manager.cleanup(responses);
PenToolFsmState::Ready
}
(_, PenToolMessage::Abort) => {
// Clean up overlays
for layer_path in document.all_layers() {
shape_overlay.clear_subpath_overlays(&document.document_legacy, LayerNodeIdentifier::from_path(layer_path, document.network()), responses);
}
self
}
_ => self,
} }
} }
@@ -772,7 +760,7 @@ fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64)
continue; continue;
}; };
let subpaths = get_subpaths(layer_path, document)?; let subpaths = get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?;
for (subpath_index, subpath) in subpaths.iter().enumerate() { for (subpath_index, subpath) in subpaths.iter().enumerate() {
if subpath.closed() { if subpath.closed() {
continue; continue;
@@ -794,22 +782,25 @@ fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64)
best best
} }
fn get_subpaths<'a>(layer_path: &[LayerId], document: &'a DocumentMessageHandler) -> Option<&'a Vec<Subpath<ManipulatorGroupId>>> { pub fn get_subpaths(layer: LayerNodeIdentifier, document: &Document) -> Option<&Vec<Subpath<ManipulatorGroupId>>> {
let layer = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_layer().ok())?; if let TaggedValue::Subpaths(subpaths) = NodeGraphLayer::new(layer, document)?.find_input("Shape", 0)? {
let network = &layer.network; Some(subpaths)
for (node, _node_id) in network.primary_flow() { } else {
if node.name == "Shape" { None
let subpaths_input = node.inputs.get(0)?;
let NodeInput::Value {
tagged_value: TaggedValue::Subpaths(subpaths),
..
} = subpaths_input
else {
continue;
};
return Some(subpaths);
}
} }
None }
pub fn get_mirror_handles(layer: LayerNodeIdentifier, document: &Document) -> Option<&Vec<ManipulatorGroupId>> {
if let TaggedValue::ManipulatorGroupIds(mirror_handles) = NodeGraphLayer::new(layer, document)?.find_input("Shape", 1)? {
Some(mirror_handles)
} else {
None
}
}
pub fn get_manipulator_groups(subpaths: &[Subpath<ManipulatorGroupId>]) -> impl Iterator<Item = &bezier_rs::ManipulatorGroup<ManipulatorGroupId>> + DoubleEndedIterator {
subpaths.iter().flat_map(|subpath| subpath.manipulator_groups())
}
pub fn get_manipulator_from_id(subpaths: &[Subpath<ManipulatorGroupId>], id: ManipulatorGroupId) -> Option<&bezier_rs::ManipulatorGroup<ManipulatorGroupId>> {
subpaths.iter().find_map(|subpath| subpath.manipulator_from_id(id))
} }
@@ -1,19 +1,11 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; use super::tool_prelude::*;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::resize::Resize;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use graphene_core::vector::style::{Fill, Stroke}; use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::Color; use graphene_core::Color;
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct PolygonTool { pub struct PolygonTool {
fsm_state: PolygonToolFsmState, fsm_state: PolygonToolFsmState,
@@ -158,35 +150,33 @@ impl LayoutHolder for PolygonTool {
} }
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PolygonTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PolygonTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Polygon(PolygonToolMessage::UpdateOptions(action)) = message { let ToolMessage::Polygon(PolygonToolMessage::UpdateOptions(action)) = message else {
match action { self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
PolygonOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices,
PolygonOptionsUpdate::PrimitiveShapeType(primitive_shape_type) => self.options.primitive_shape_type = primitive_shape_type,
PolygonOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
PolygonOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
PolygonOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
PolygonOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
PolygonOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
PolygonOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
}
self.send_layout(responses, LayoutTarget::ToolOptions);
return; return;
};
match action {
PolygonOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices,
PolygonOptionsUpdate::PrimitiveShapeType(primitive_shape_type) => self.options.primitive_shape_type = primitive_shape_type,
PolygonOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
PolygonOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
PolygonOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
PolygonOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
PolygonOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
PolygonOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
} }
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); self.send_layout(responses, LayoutTarget::ToolOptions);
} }
fn actions(&self) -> ActionList { fn actions(&self) -> ActionList {
@@ -232,87 +222,78 @@ impl Fsm for PolygonToolFsmState {
type ToolData = PolygonToolData; type ToolData = PolygonToolData;
type ToolOptions = PolygonOptions; type ToolOptions = PolygonOptions;
fn transition( fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
self, let ToolActionHandlerData {
event: ToolMessage,
tool_data: &mut Self::ToolData,
ToolActionHandlerData {
document, document,
global_tool_data, global_tool_data,
input, input,
render_data, render_data,
.. ..
}: &mut ToolActionHandlerData, } = tool_action_data;
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
use PolygonToolFsmState::*;
use PolygonToolMessage::*;
let polygon_data = &mut tool_data.data; let polygon_data = &mut tool_data.data;
if let ToolMessage::Polygon(event) = event { let ToolMessage::Polygon(event) = event else {
match (self, event) { return self;
(Drawing, CanvasTransformed) => { };
tool_data.data.recalculate_snaps(document, input, render_data); match (self, event) {
self (PolygonToolFsmState::Drawing, PolygonToolMessage::CanvasTransformed) => {
} tool_data.data.recalculate_snaps(document, input, render_data);
(Ready, DragStart) => { self
polygon_data.start(responses, document, input, render_data);
responses.add(DocumentMessage::StartTransaction);
let layer_path = document.get_path_for_new_layer();
polygon_data.path = Some(layer_path.clone());
let subpath = match tool_options.primitive_shape_type {
PrimitiveShapeType::Polygon => bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_options.vertices as u64, 1.),
PrimitiveShapeType::Star => bezier_rs::Subpath::new_star_polygon(DVec2::ZERO, tool_options.vertices as u64, 1., 0.5),
};
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
let fill_color = tool_options.fill.active_color();
responses.add(GraphOperationMessage::FillSet {
layer: layer_path.clone(),
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
});
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path,
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
});
Drawing
}
(state, Resize { center, lock_ratio }) => {
if let Some(message) = polygon_data.calculate_transform(responses, document, input, center, lock_ratio, false) {
responses.add(message);
}
state
}
(Drawing, DragStop) => {
input.mouse.finish_transaction(polygon_data.viewport_drag_start(document), responses);
polygon_data.cleanup(responses);
Ready
}
(Drawing, Abort) => {
responses.add(DocumentMessage::AbortTransaction);
polygon_data.cleanup(responses);
Ready
}
(_, WorkingColorChanged) => {
responses.add(PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} else { (PolygonToolFsmState::Ready, PolygonToolMessage::DragStart) => {
self polygon_data.start(responses, document, input, render_data);
responses.add(DocumentMessage::StartTransaction);
let layer_path = document.get_path_for_new_layer();
polygon_data.path = Some(layer_path.clone());
let subpath = match tool_options.primitive_shape_type {
PrimitiveShapeType::Polygon => bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_options.vertices as u64, 1.),
PrimitiveShapeType::Star => bezier_rs::Subpath::new_star_polygon(DVec2::ZERO, tool_options.vertices as u64, 1., 0.5),
};
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
let fill_color = tool_options.fill.active_color();
responses.add(GraphOperationMessage::FillSet {
layer: layer_path.clone(),
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
});
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path,
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
});
PolygonToolFsmState::Drawing
}
(state, PolygonToolMessage::Resize { center, lock_ratio }) => {
if let Some(message) = polygon_data.calculate_transform(responses, document, input, center, lock_ratio, false) {
responses.add(message);
}
state
}
(PolygonToolFsmState::Drawing, PolygonToolMessage::DragStop) => {
input.mouse.finish_transaction(polygon_data.viewport_drag_start(document), responses);
polygon_data.cleanup(responses);
PolygonToolFsmState::Ready
}
(PolygonToolFsmState::Drawing, PolygonToolMessage::Abort) => {
responses.add(DocumentMessage::AbortTransaction);
polygon_data.cleanup(responses);
PolygonToolFsmState::Ready
}
(_, PolygonToolMessage::WorkingColorChanged) => {
responses.add(PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} }
@@ -1,17 +1,10 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; use super::tool_prelude::*;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::resize::Resize;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use glam::DVec2;
use graphene_core::vector::style::{Fill, Stroke}; use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::Color; use graphene_core::Color;
use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct RectangleTool { pub struct RectangleTool {
@@ -107,33 +100,32 @@ impl LayoutHolder for RectangleTool {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for RectangleTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for RectangleTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Rectangle(RectangleToolMessage::UpdateOptions(action)) = message { let ToolMessage::Rectangle(RectangleToolMessage::UpdateOptions(action)) = message else {
match action { self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
RectangleOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
RectangleOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
RectangleOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
RectangleOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
RectangleOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
RectangleOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
}
self.send_layout(responses, LayoutTarget::ToolOptions);
return; return;
};
match action {
RectangleOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
RectangleOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
RectangleOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
RectangleOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
RectangleOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
RectangleOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
} }
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); self.send_layout(responses, LayoutTarget::ToolOptions);
} }
fn actions(&self) -> ActionList { fn actions(&self) -> ActionList {
@@ -210,66 +202,66 @@ impl Fsm for RectangleToolFsmState {
let shape_data = &mut tool_data.data; let shape_data = &mut tool_data.data;
if let ToolMessage::Rectangle(event) = event { let ToolMessage::Rectangle(event) = event else {
match (self, event) { return self;
(Drawing, CanvasTransformed) => { };
tool_data.data.recalculate_snaps(document, input, render_data);
self
}
(Ready, DragStart) => {
shape_data.start(responses, document, input, render_data);
let subpath = bezier_rs::Subpath::new_rect(DVec2::ZERO, DVec2::ONE); match (self, event) {
(Drawing, CanvasTransformed) => {
let layer_path = document.get_path_for_new_layer(); tool_data.data.recalculate_snaps(document, input, render_data);
responses.add(DocumentMessage::StartTransaction); self
shape_data.path = Some(layer_path.clone());
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
let fill_color = tool_options.fill.active_color();
responses.add(GraphOperationMessage::FillSet {
layer: layer_path.clone(),
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
});
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path,
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
});
Drawing
}
(state, Resize { center, lock_ratio }) => {
if let Some(message) = shape_data.calculate_transform(responses, document, input, center, lock_ratio, false) {
responses.add(message);
}
state
}
(Drawing, DragStop) => {
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
shape_data.cleanup(responses);
Ready
}
(Drawing, Abort) => {
responses.add(DocumentMessage::AbortTransaction);
shape_data.cleanup(responses);
Ready
}
(_, WorkingColorChanged) => {
responses.add(RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} else { (Ready, DragStart) => {
self shape_data.start(responses, document, input, render_data);
let subpath = bezier_rs::Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
let layer_path = document.get_path_for_new_layer();
responses.add(DocumentMessage::StartTransaction);
shape_data.path = Some(layer_path.clone());
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
let fill_color = tool_options.fill.active_color();
responses.add(GraphOperationMessage::FillSet {
layer: layer_path.clone(),
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
});
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path,
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
});
Drawing
}
(state, Resize { center, lock_ratio }) => {
if let Some(message) = shape_data.calculate_transform(responses, document, input, center, lock_ratio, false) {
responses.add(message);
}
state
}
(Drawing, DragStop) => {
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
shape_data.cleanup(responses);
Ready
}
(Drawing, Abort) => {
responses.add(DocumentMessage::AbortTransaction);
shape_data.cleanup(responses);
Ready
}
(_, WorkingColorChanged) => {
responses.add(RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} }
@@ -1,19 +1,14 @@
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
use super::tool_prelude::*;
use crate::application::generate_uuid; use crate::application::generate_uuid;
use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE}; use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis}; use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
use crate::messages::portfolio::document::utility_types::transformation::Selected; use crate::messages::portfolio::document::utility_types::transformation::Selected;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::path_outline::*; use crate::messages::tool::common_functionality::path_outline::*;
use crate::messages::tool::common_functionality::pivot::Pivot; use crate::messages::tool::common_functionality::pivot::Pivot;
use crate::messages::tool::common_functionality::snapping::{self, SnapManager}; use crate::messages::tool::common_functionality::snapping::{self, SnapManager};
use crate::messages::tool::common_functionality::transformation_cage::*; use crate::messages::tool::common_functionality::transformation_cage::*;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::document::Document; use document_legacy::document::Document;
use document_legacy::intersection::Quad; use document_legacy::intersection::Quad;
@@ -21,8 +16,6 @@ use document_legacy::layers::layer_info::{Layer, LayerDataType};
use document_legacy::LayerId; use document_legacy::LayerId;
use document_legacy::Operation; use document_legacy::Operation;
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
#[derive(Default)] #[derive(Default)]
@@ -446,268 +439,137 @@ impl Fsm for SelectToolFsmState {
use SelectToolFsmState::*; use SelectToolFsmState::*;
use SelectToolMessage::*; use SelectToolMessage::*;
if let ToolMessage::Select(event) = event { let ToolMessage::Select(event) = event else {
match (self, event) { return self;
(_, DocumentIsDirty | SelectionChanged) => { };
let selected_layers_count = document.selected_layers().count(); match (self, event) {
let selected_layers_changed = selected_layers_count != tool_data.selected_layers_count; (_, DocumentIsDirty | SelectionChanged) => {
let selected_layers_count = document.selected_layers().count();
let selected_layers_changed = selected_layers_count != tool_data.selected_layers_count;
if selected_layers_changed { if selected_layers_changed {
tool_data.selected_layers_count = selected_layers_count; tool_data.selected_layers_count = selected_layers_count;
tool_data.selected_layers_changed = true; tool_data.selected_layers_changed = true;
} else { } else {
tool_data.selected_layers_changed = false; tool_data.selected_layers_changed = false;
}
match (document.selected_visible_layers_bounding_box(render_data), tool_data.bounding_box_overlays.take()) {
(None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses),
(Some(bounds), paths) => {
let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses));
bounding_box_overlays.bounds = bounds;
bounding_box_overlays.transform = DAffine2::IDENTITY;
bounding_box_overlays.transform(responses);
tool_data.bounding_box_overlays = Some(bounding_box_overlays);
}
(_, _) => {}
};
tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data);
tool_data.path_outlines.intersect_test_hovered(input, document, responses, render_data);
tool_data.pivot.update_pivot(document, render_data, responses);
self
} }
(_, EditLayer) => {
// Setup required data for checking the clicked layer
let mouse_pos = input.mouse.position;
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
// Check the last (topmost) intersection layer match (document.selected_visible_layers_bounding_box(render_data), tool_data.bounding_box_overlays.take()) {
if let Some(intersect_layer_path) = document.document_legacy.intersects_quad_root(quad, render_data).last() { (None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses),
if let Ok(intersect) = document.document_legacy.layer(intersect_layer_path) { (Some(bounds), paths) => {
match tool_data.nested_selection_behavior { let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses));
NestedSelectionBehavior::Shallowest => edit_layer_shallowest_manipulation(document, intersect_layer_path, tool_data, responses),
NestedSelectionBehavior::Deepest => edit_layer_deepest_manipulation(intersect, responses), bounding_box_overlays.bounds = bounds;
} bounding_box_overlays.transform = DAffine2::IDENTITY;
bounding_box_overlays.transform(responses);
tool_data.bounding_box_overlays = Some(bounding_box_overlays);
}
(_, _) => {}
};
tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data);
tool_data.path_outlines.intersect_test_hovered(input, document, responses, render_data);
tool_data.pivot.update_pivot(document, render_data, responses);
self
}
(_, EditLayer) => {
// Setup required data for checking the clicked layer
let mouse_pos = input.mouse.position;
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
// Check the last (topmost) intersection layer
if let Some(intersect_layer_path) = document.document_legacy.intersects_quad_root(quad, render_data).last() {
if let Ok(intersect) = document.document_legacy.layer(intersect_layer_path) {
match tool_data.nested_selection_behavior {
NestedSelectionBehavior::Shallowest => edit_layer_shallowest_manipulation(document, intersect_layer_path, tool_data, responses),
NestedSelectionBehavior::Deepest => edit_layer_deepest_manipulation(intersect, responses),
} }
} }
self
} }
(Ready, DragStart { add_to_selection, select_deepest }) => {
tool_data.path_outlines.clear_hovered(responses);
tool_data.drag_start = input.mouse.position; self
tool_data.drag_current = input.mouse.position; }
(Ready, DragStart { add_to_selection, select_deepest }) => {
tool_data.path_outlines.clear_hovered(responses);
let dragging_bounds = tool_data.bounding_box_overlays.as_mut().and_then(|bounding_box| { tool_data.drag_start = input.mouse.position;
let edges = bounding_box.check_selected_edges(input.mouse.position); tool_data.drag_current = input.mouse.position;
bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| { let dragging_bounds = tool_data.bounding_box_overlays.as_mut().and_then(|bounding_box| {
let selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds); let edges = bounding_box.check_selected_edges(input.mouse.position);
bounding_box.opposite_pivot = selected_edges.calculate_pivot();
selected_edges
});
edges bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| {
let selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds);
bounding_box.opposite_pivot = selected_edges.calculate_pivot();
selected_edges
}); });
let rotating_bounds = tool_data edges
.bounding_box_overlays });
.as_ref()
.map(|bounding_box| bounding_box.check_rotate(input.mouse.position))
.unwrap_or_default();
let mut selected: Vec<_> = document.selected_visible_layers().map(|path| path.to_vec()).collect(); let rotating_bounds = tool_data
let quad = tool_data.selection_quad(); .bounding_box_overlays
let mut intersection = document.document_legacy.intersects_quad_root(quad, render_data); .as_ref()
.map(|bounding_box| bounding_box.check_rotate(input.mouse.position))
.unwrap_or_default();
// If the user is dragging the bounding box bounds, go into ResizingBounds mode. let mut selected: Vec<_> = document.selected_visible_layers().map(|path| path.to_vec()).collect();
// If the user is dragging the rotate trigger, go into RotatingBounds mode. let quad = tool_data.selection_quad();
// If the user clicks on a layer that is in their current selection, go into the dragging mode. let mut intersection = document.document_legacy.intersects_quad_root(quad, render_data);
// If the user clicks on new shape, make that layer their new selection.
// Otherwise enter the box select mode
let state = if tool_data.pivot.is_over(input.mouse.position) {
responses.add(DocumentMessage::StartTransaction);
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); // If the user is dragging the bounding box bounds, go into ResizingBounds mode.
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); // If the user is dragging the rotate trigger, go into RotatingBounds mode.
// If the user clicks on a layer that is in their current selection, go into the dragging mode.
// If the user clicks on new shape, make that layer their new selection.
// Otherwise enter the box select mode
let state = if tool_data.pivot.is_over(input.mouse.position) {
responses.add(DocumentMessage::StartTransaction);
DraggingPivot tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
} else if let Some(selected_edges) = dragging_bounds { tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
responses.add(DocumentMessage::StartTransaction);
let snap_x = selected_edges.2 || selected_edges.3; DraggingPivot
let snap_y = selected_edges.0 || selected_edges.1; } else if let Some(selected_edges) = dragging_bounds {
responses.add(DocumentMessage::StartTransaction);
tool_data let snap_x = selected_edges.2 || selected_edges.3;
.snap_manager let snap_y = selected_edges.0 || selected_edges.1;
.start_snap(document, input, document.bounding_boxes(Some(&selected), None, render_data), snap_x, snap_y);
tool_data
.snap_manager
.add_all_document_handles(document, input, &[], &selected.iter().map(|x| x.as_slice()).collect::<Vec<_>>(), &[]);
tool_data.layers_dragging = selected; tool_data
.snap_manager
.start_snap(document, input, document.bounding_boxes(Some(&selected), None, render_data), snap_x, snap_y);
tool_data
.snap_manager
.add_all_document_handles(document, input, &[], &selected.iter().map(|x| x.as_slice()).collect::<Vec<_>>(), &[]);
if let Some(bounds) = &mut tool_data.bounding_box_overlays { tool_data.layers_dragging = selected;
let document = &document.document_legacy;
let selected = &tool_data.layers_dragging.iter().collect::<Vec<_>>();
let mut selected = Selected::new(
&mut bounds.original_transforms,
&mut bounds.center_of_transformation,
selected,
responses,
document,
None,
&ToolType::Select,
);
bounds.center_of_transformation = selected.mean_average_of_pivots(render_data);
}
ResizingBounds
} else if rotating_bounds {
responses.add(DocumentMessage::StartTransaction);
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
let selected = selected.iter().collect::<Vec<_>>();
let mut selected = Selected::new(
&mut bounds.original_transforms,
&mut bounds.center_of_transformation,
&selected,
responses,
&document.document_legacy,
None,
&ToolType::Select,
);
bounds.center_of_transformation = selected.mean_average_of_pivots(render_data);
}
tool_data.layers_dragging = selected;
RotatingBounds
} else if intersection.last().map(|last| selected.iter().any(|selected_layer| last.starts_with(selected_layer))).unwrap_or(false)
&& tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest
{
responses.add(DocumentMessage::StartTransaction);
tool_data.layers_dragging = selected;
tool_data
.snap_manager
.start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, render_data), true, true);
Dragging
} else {
if !input.keyboard.get(add_to_selection as usize) && tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest {
responses.add(DocumentMessage::DeselectAllLayers);
tool_data.layers_dragging.clear();
}
if let Some(intersection) = intersection.pop() {
tool_data.layer_selected_on_start = Some(intersection.clone());
selected = vec![intersection.clone()];
match tool_data.nested_selection_behavior {
NestedSelectionBehavior::Shallowest => drag_shallowest_manipulation(document, selected, input, select_deepest, add_to_selection, tool_data, responses),
NestedSelectionBehavior::Deepest => drag_deepest_manipulation(responses, selected, tool_data, document, input, render_data),
}
Dragging
} else {
// Deselect all layers if using shallowest selection behavior
// Necessary since for shallowest mode, we need to know the current selected layers to determine the next
if tool_data.nested_selection_behavior == NestedSelectionBehavior::Shallowest {
responses.add(DocumentMessage::DeselectAllLayers);
tool_data.layers_dragging.clear();
}
tool_data.drag_box_overlay_layer = Some(add_bounding_box(responses));
DrawingBox
}
};
tool_data.not_duplicated_layers = None;
state
}
(Dragging, PointerMove { axis_align, duplicate, .. }) => {
tool_data.is_dragging = true;
// TODO: This is a cheat. Break out the relevant functionality from the handler above and call it from there and here.
responses.add_front(SelectToolMessage::DocumentIsDirty);
let mouse_position = axis_align_drag(input.keyboard.get(axis_align as usize), input.mouse.position, tool_data.drag_start);
let mouse_delta = mouse_position - tool_data.drag_current;
let snap = tool_data
.layers_dragging
.iter()
.filter_map(|path| document.document_legacy.viewport_bounding_box(path, render_data).ok()?)
.flat_map(snapping::expand_bounds)
.collect();
let closest_move = tool_data.snap_manager.snap_layers(responses, document, snap, mouse_delta);
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
for path in Document::shallowest_unique_layers(tool_data.layers_dragging.iter()) {
responses.add_front(GraphOperationMessage::TransformChange {
layer: path.to_vec(),
transform: DAffine2::from_translation(mouse_delta + closest_move),
transform_in: TransformIn::Viewport,
skip_rerender: true,
});
}
tool_data.drag_current = mouse_position + closest_move;
if input.keyboard.get(duplicate as usize) && tool_data.not_duplicated_layers.is_none() {
tool_data.start_duplicates(document, responses);
} else if !input.keyboard.get(duplicate as usize) && tool_data.not_duplicated_layers.is_some() {
tool_data.stop_duplicates(responses);
}
Dragging
}
(ResizingBounds, PointerMove { axis_align, center, .. }) => {
if let Some(bounds) = &mut tool_data.bounding_box_overlays { if let Some(bounds) = &mut tool_data.bounding_box_overlays {
if let Some(movement) = &mut bounds.selected_edges { let document = &document.document_legacy;
let (center, axis_align) = (input.keyboard.get(center as usize), input.keyboard.get(axis_align as usize));
let mouse_position = input.mouse.position; let selected = &tool_data.layers_dragging.iter().collect::<Vec<_>>();
let mut selected = Selected::new(
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position); &mut bounds.original_transforms,
&mut bounds.center_of_transformation,
let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, center, bounds.center_of_transformation, axis_align); selected,
let (delta, mut _pivot) = movement.bounds_to_scale_transform(position, size); responses,
document,
let selected = &tool_data.layers_dragging.iter().collect::<Vec<_>>(); None,
let mut selected = Selected::new(&mut bounds.original_transforms, &mut _pivot, selected, responses, &document.document_legacy, None, &ToolType::Select); &ToolType::Select,
);
selected.update_transforms(delta); bounds.center_of_transformation = selected.mean_average_of_pivots(render_data);
}
} }
ResizingBounds ResizingBounds
} } else if rotating_bounds {
(RotatingBounds, PointerMove { snap_angle, .. }) => { responses.add(DocumentMessage::StartTransaction);
if let Some(bounds) = &mut tool_data.bounding_box_overlays { if let Some(bounds) = &mut tool_data.bounding_box_overlays {
let angle = { let selected = selected.iter().collect::<Vec<_>>();
let start_offset = tool_data.drag_start - bounds.center_of_transformation;
let end_offset = input.mouse.position - bounds.center_of_transformation;
start_offset.angle_between(end_offset)
};
let snapped_angle = if input.keyboard.get(snap_angle as usize) {
let snap_resolution = ROTATE_SNAP_ANGLE.to_radians();
(angle / snap_resolution).round() * snap_resolution
} else {
angle
};
let delta = DAffine2::from_angle(snapped_angle);
let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
let mut selected = Selected::new( let mut selected = Selected::new(
&mut bounds.original_transforms, &mut bounds.original_transforms,
&mut bounds.center_of_transformation, &mut bounds.center_of_transformation,
@@ -718,233 +580,363 @@ impl Fsm for SelectToolFsmState {
&ToolType::Select, &ToolType::Select,
); );
selected.update_transforms(delta); bounds.center_of_transformation = selected.mean_average_of_pivots(render_data);
} }
tool_data.layers_dragging = selected;
RotatingBounds RotatingBounds
} } else if intersection.last().map(|last| selected.iter().any(|selected_layer| last.starts_with(selected_layer))).unwrap_or(false)
(DraggingPivot, PointerMove { .. }) => { && tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest
let mouse_position = input.mouse.position; {
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position); responses.add(DocumentMessage::StartTransaction);
tool_data.pivot.set_viewport_position(snapped_mouse_position, document, render_data, responses);
DraggingPivot tool_data.layers_dragging = selected;
}
(DrawingBox, PointerMove { .. }) => {
tool_data.drag_current = input.mouse.position;
responses.add_front(DocumentMessage::Overlays( tool_data
Operation::SetLayerTransformInViewport { .snap_manager
path: tool_data.drag_box_overlay_layer.clone().unwrap(), .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, render_data), true, true);
transform: transform_from_box(tool_data.drag_start, tool_data.drag_current, DAffine2::IDENTITY).to_cols_array(),
}
.into(),
));
DrawingBox
}
(Ready, PointerMove { .. }) => {
let mut cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true));
// Dragging the pivot overrules the other operations Dragging
if tool_data.pivot.is_over(input.mouse.position) { } else {
cursor = MouseCursorIcon::Move; if !input.keyboard.get(add_to_selection as usize) && tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest {
responses.add(DocumentMessage::DeselectAllLayers);
tool_data.layers_dragging.clear();
} }
// Generate the select outline (but not if the user is going to use the bound overlays) if let Some(intersection) = intersection.pop() {
if cursor == MouseCursorIcon::Default { tool_data.layer_selected_on_start = Some(intersection.clone());
tool_data.path_outlines.intersect_test_hovered(input, document, responses, render_data); selected = vec![intersection];
match tool_data.nested_selection_behavior {
NestedSelectionBehavior::Shallowest => drag_shallowest_manipulation(document, selected, input, select_deepest, add_to_selection, tool_data, responses),
NestedSelectionBehavior::Deepest => drag_deepest_manipulation(responses, selected, tool_data, document, input, render_data),
}
Dragging
} else { } else {
tool_data.path_outlines.clear_hovered(responses); // Deselect all layers if using shallowest selection behavior
} // Necessary since for shallowest mode, we need to know the current selected layers to determine the next
if tool_data.nested_selection_behavior == NestedSelectionBehavior::Shallowest {
if tool_data.cursor != cursor { responses.add(DocumentMessage::DeselectAllLayers);
tool_data.cursor = cursor;
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
}
Ready
}
(Dragging, Enter) => {
rerender_selected_layers(tool_data, responses);
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
};
tool_data.snap_manager.cleanup(responses);
responses.add_front(response);
Ready
}
(Dragging, DragStop { remove_from_selection }) => {
rerender_selected_layers(tool_data, responses);
// Deselect layer if not snap dragging
if !tool_data.is_dragging && input.keyboard.get(remove_from_selection as usize) && tool_data.layer_selected_on_start.is_none() {
let quad = tool_data.selection_quad();
let intersection = document.document_legacy.intersects_quad_root(quad, render_data);
if let Some(path) = intersection.last() {
let replacement_selected_layers: Vec<_> = document.selected_layers().filter(|&layer| !path.starts_with(layer)).map(|path| path.to_vec()).collect();
tool_data.layers_dragging.clear(); tool_data.layers_dragging.clear();
tool_data.layers_dragging.append(replacement_selected_layers.clone().as_mut());
responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers });
} }
tool_data.drag_box_overlay_layer = Some(add_bounding_box(responses));
DrawingBox
} }
};
tool_data.not_duplicated_layers = None;
tool_data.is_dragging = false; state
tool_data.layer_selected_on_start = None; }
(Dragging, PointerMove { axis_align, duplicate, .. }) => {
tool_data.is_dragging = true;
// TODO: This is a cheat. Break out the relevant functionality from the handler above and call it from there and here.
responses.add_front(SelectToolMessage::DocumentIsDirty);
responses.add(DocumentMessage::CommitTransaction); let mouse_position = axis_align_drag(input.keyboard.get(axis_align as usize), input.mouse.position, tool_data.drag_start);
tool_data.snap_manager.cleanup(responses);
Ready let mouse_delta = mouse_position - tool_data.drag_current;
}
(ResizingBounds, DragStop { .. } | Enter) => {
rerender_selected_layers(tool_data, responses);
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON { let snap = tool_data
true => DocumentMessage::Undo, .layers_dragging
false => DocumentMessage::CommitTransaction, .iter()
}; .filter_map(|path| document.document_legacy.viewport_bounding_box(path, render_data).ok()?)
responses.add(response); .flat_map(snapping::expand_bounds)
.collect();
tool_data.snap_manager.cleanup(responses); let closest_move = tool_data.snap_manager.snap_layers(responses, document, snap, mouse_delta);
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
if let Some(bounds) = &mut tool_data.bounding_box_overlays { for path in Document::shallowest_unique_layers(tool_data.layers_dragging.iter()) {
bounds.original_transforms.clear(); responses.add_front(GraphOperationMessage::TransformChange {
} layer: path.to_vec(),
transform: DAffine2::from_translation(mouse_delta + closest_move),
Ready transform_in: TransformIn::Viewport,
} skip_rerender: true,
(RotatingBounds, DragStop { .. } | Enter) => {
rerender_selected_layers(tool_data, responses);
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
};
responses.add(response);
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
bounds.original_transforms.clear();
}
Ready
}
(DraggingPivot, DragStop { .. } | Enter) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
};
responses.add(response);
tool_data.snap_manager.cleanup(responses);
Ready
}
(DrawingBox, DragStop { .. } | Enter) => {
let quad = tool_data.selection_quad();
// For shallow select we don't update dragging layers until inside drag_start_shallowest_manipulation()
tool_data.layers_dragging = document.document_legacy.intersects_quad_root(quad, render_data);
responses.add_front(DocumentMessage::AddSelectedLayers {
additional_layers: document.document_legacy.intersects_quad_root(quad, render_data),
}); });
responses.add_front(DocumentMessage::Overlays(
Operation::DeleteLayer {
path: tool_data.drag_box_overlay_layer.take().unwrap(),
}
.into(),
));
Ready
} }
(Ready, Enter) => { tool_data.drag_current = mouse_position + closest_move;
let mut selected_layers = document.selected_layers();
if let Some(layer_path) = selected_layers.next() { if input.keyboard.get(duplicate as usize) && tool_data.not_duplicated_layers.is_none() {
// Check that only one layer is selected tool_data.start_duplicates(document, responses);
if selected_layers.next().is_none() { } else if !input.keyboard.get(duplicate as usize) && tool_data.not_duplicated_layers.is_some() {
if let Ok(layer) = document.document_legacy.layer(layer_path) { tool_data.stop_duplicates(responses);
if let Ok(network) = layer.as_layer_network() { }
if network.nodes.values().any(|node| node.name == "Text") {
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }); Dragging
responses.add(TextToolMessage::EditSelected); }
} (ResizingBounds, PointerMove { axis_align, center, .. }) => {
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
if let Some(movement) = &mut bounds.selected_edges {
let (center, axis_align) = (input.keyboard.get(center as usize), input.keyboard.get(axis_align as usize));
let mouse_position = input.mouse.position;
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, center, bounds.center_of_transformation, axis_align);
let (delta, mut _pivot) = movement.bounds_to_scale_transform(position, size);
let selected = &tool_data.layers_dragging.iter().collect::<Vec<_>>();
let mut selected = Selected::new(&mut bounds.original_transforms, &mut _pivot, selected, responses, &document.document_legacy, None, &ToolType::Select);
selected.update_transforms(delta);
}
}
ResizingBounds
}
(RotatingBounds, PointerMove { snap_angle, .. }) => {
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
let angle = {
let start_offset = tool_data.drag_start - bounds.center_of_transformation;
let end_offset = input.mouse.position - bounds.center_of_transformation;
start_offset.angle_between(end_offset)
};
let snapped_angle = if input.keyboard.get(snap_angle as usize) {
let snap_resolution = ROTATE_SNAP_ANGLE.to_radians();
(angle / snap_resolution).round() * snap_resolution
} else {
angle
};
let delta = DAffine2::from_angle(snapped_angle);
let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
let mut selected = Selected::new(
&mut bounds.original_transforms,
&mut bounds.center_of_transformation,
&selected,
responses,
&document.document_legacy,
None,
&ToolType::Select,
);
selected.update_transforms(delta);
}
RotatingBounds
}
(DraggingPivot, PointerMove { .. }) => {
let mouse_position = input.mouse.position;
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
tool_data.pivot.set_viewport_position(snapped_mouse_position, document, render_data, responses);
DraggingPivot
}
(DrawingBox, PointerMove { .. }) => {
tool_data.drag_current = input.mouse.position;
responses.add_front(DocumentMessage::Overlays(
Operation::SetLayerTransformInViewport {
path: tool_data.drag_box_overlay_layer.clone().unwrap(),
transform: transform_from_box(tool_data.drag_start, tool_data.drag_current, DAffine2::IDENTITY).to_cols_array(),
}
.into(),
));
DrawingBox
}
(Ready, PointerMove { .. }) => {
let mut cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true));
// Dragging the pivot overrules the other operations
if tool_data.pivot.is_over(input.mouse.position) {
cursor = MouseCursorIcon::Move;
}
// Generate the select outline (but not if the user is going to use the bound overlays)
if cursor == MouseCursorIcon::Default {
tool_data.path_outlines.intersect_test_hovered(input, document, responses, render_data);
} else {
tool_data.path_outlines.clear_hovered(responses);
}
if tool_data.cursor != cursor {
tool_data.cursor = cursor;
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
}
Ready
}
(Dragging, Enter) => {
rerender_selected_layers(tool_data, responses);
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
};
tool_data.snap_manager.cleanup(responses);
responses.add_front(response);
Ready
}
(Dragging, DragStop { remove_from_selection }) => {
rerender_selected_layers(tool_data, responses);
// Deselect layer if not snap dragging
if !tool_data.is_dragging && input.keyboard.get(remove_from_selection as usize) && tool_data.layer_selected_on_start.is_none() {
let quad = tool_data.selection_quad();
let intersection = document.document_legacy.intersects_quad_root(quad, render_data);
if let Some(path) = intersection.last() {
let replacement_selected_layers: Vec<_> = document.selected_layers().filter(|&layer| !path.starts_with(layer)).map(|path| path.to_vec()).collect();
tool_data.layers_dragging.clear();
tool_data.layers_dragging.append(replacement_selected_layers.clone().as_mut());
responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers });
}
}
tool_data.is_dragging = false;
tool_data.layer_selected_on_start = None;
responses.add(DocumentMessage::CommitTransaction);
tool_data.snap_manager.cleanup(responses);
Ready
}
(ResizingBounds, DragStop { .. } | Enter) => {
rerender_selected_layers(tool_data, responses);
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
};
responses.add(response);
tool_data.snap_manager.cleanup(responses);
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
bounds.original_transforms.clear();
}
Ready
}
(RotatingBounds, DragStop { .. } | Enter) => {
rerender_selected_layers(tool_data, responses);
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
};
responses.add(response);
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
bounds.original_transforms.clear();
}
Ready
}
(DraggingPivot, DragStop { .. } | Enter) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
};
responses.add(response);
tool_data.snap_manager.cleanup(responses);
Ready
}
(DrawingBox, DragStop { .. } | Enter) => {
let quad = tool_data.selection_quad();
// For shallow select we don't update dragging layers until inside drag_start_shallowest_manipulation()
tool_data.layers_dragging = document.document_legacy.intersects_quad_root(quad, render_data);
responses.add_front(DocumentMessage::AddSelectedLayers {
additional_layers: document.document_legacy.intersects_quad_root(quad, render_data),
});
responses.add_front(DocumentMessage::Overlays(
Operation::DeleteLayer {
path: tool_data.drag_box_overlay_layer.take().unwrap(),
}
.into(),
));
Ready
}
(Ready, Enter) => {
let mut selected_layers = document.selected_layers();
if let Some(layer_path) = selected_layers.next() {
// Check that only one layer is selected
if selected_layers.next().is_none() {
if let Ok(layer) = document.document_legacy.layer(layer_path) {
if let Ok(network) = layer.as_layer_network() {
if network.nodes.values().any(|node| node.name == "Text") {
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });
responses.add(TextToolMessage::EditSelected);
} }
} }
} }
} }
Ready
} }
(Dragging, Abort) => {
rerender_selected_layers(tool_data, responses);
tool_data.snap_manager.cleanup(responses); Ready
responses.add(DocumentMessage::Undo);
tool_data.path_outlines.clear_selected(responses);
tool_data.pivot.clear_overlays(responses);
Ready
}
(_, Abort) => {
if let Some(path) = tool_data.drag_box_overlay_layer.take() {
responses.add_front(DocumentMessage::Overlays(Operation::DeleteLayer { path }.into()))
};
if let Some(mut bounding_box_overlays) = tool_data.bounding_box_overlays.take() {
let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
let mut selected = Selected::new(
&mut bounding_box_overlays.original_transforms,
&mut bounding_box_overlays.opposite_pivot,
&selected,
responses,
&document.document_legacy,
None,
&ToolType::Select,
);
selected.revert_operation();
bounding_box_overlays.delete(responses);
}
tool_data.path_outlines.clear_hovered(responses);
tool_data.path_outlines.clear_selected(responses);
tool_data.pivot.clear_overlays(responses);
tool_data.snap_manager.cleanup(responses);
Ready
}
(_, Align { axis, aggregate }) => {
responses.add(DocumentMessage::AlignSelectedLayers { axis, aggregate });
self
}
(_, FlipHorizontal) => {
responses.add(DocumentMessage::FlipSelectedLayers { flip_axis: FlipAxis::X });
self
}
(_, FlipVertical) => {
responses.add(DocumentMessage::FlipSelectedLayers { flip_axis: FlipAxis::Y });
self
}
(_, SetPivot { position }) => {
responses.add(DocumentMessage::StartTransaction);
let pos: Option<DVec2> = position.into();
tool_data.pivot.set_normalized_position(pos.unwrap(), document, render_data, responses);
self
}
_ => self,
} }
} else { (Dragging, Abort) => {
self rerender_selected_layers(tool_data, responses);
tool_data.snap_manager.cleanup(responses);
responses.add(DocumentMessage::Undo);
tool_data.path_outlines.clear_selected(responses);
tool_data.pivot.clear_overlays(responses);
Ready
}
(_, Abort) => {
if let Some(path) = tool_data.drag_box_overlay_layer.take() {
responses.add_front(DocumentMessage::Overlays(Operation::DeleteLayer { path }.into()))
};
if let Some(mut bounding_box_overlays) = tool_data.bounding_box_overlays.take() {
let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
let mut selected = Selected::new(
&mut bounding_box_overlays.original_transforms,
&mut bounding_box_overlays.opposite_pivot,
&selected,
responses,
&document.document_legacy,
None,
&ToolType::Select,
);
selected.revert_operation();
bounding_box_overlays.delete(responses);
}
tool_data.path_outlines.clear_hovered(responses);
tool_data.path_outlines.clear_selected(responses);
tool_data.pivot.clear_overlays(responses);
tool_data.snap_manager.cleanup(responses);
Ready
}
(_, Align { axis, aggregate }) => {
responses.add(DocumentMessage::AlignSelectedLayers { axis, aggregate });
self
}
(_, FlipHorizontal) => {
responses.add(DocumentMessage::FlipSelectedLayers { flip_axis: FlipAxis::X });
self
}
(_, FlipVertical) => {
responses.add(DocumentMessage::FlipSelectedLayers { flip_axis: FlipAxis::Y });
self
}
(_, SetPivot { position }) => {
responses.add(DocumentMessage::StartTransaction);
let pos: Option<DVec2> = position.into();
tool_data.pivot.set_normalized_position(pos.unwrap(), document, render_data, responses);
self
}
_ => self,
} }
} }
@@ -1117,7 +1109,7 @@ fn drag_shallowest_manipulation(
combined_layers.push(incoming_layer_path_vector); combined_layers.push(incoming_layer_path_vector);
} }
// Shared shallowest common folder of the combined layers // Shared shallowest common folder of the combined layers
let shallowest_common_folder = document.document_legacy.shallowest_common_folder(combined_layers.iter().copied()).unwrap_or_default().to_vec(); let shallowest_common_folder = document.document_legacy.shallowest_common_folder(combined_layers.into_iter()).unwrap_or_default().to_vec();
let mut selected_layer_path_parent = shallowest_common_folder.to_vec(); let mut selected_layer_path_parent = shallowest_common_folder.to_vec();
// Determine if the incoming layer path is already selected // Determine if the incoming layer path is already selected
@@ -1175,12 +1167,12 @@ fn drag_shallowest_manipulation(
} }
} else if selected_layers_count > 1 { } else if selected_layers_count > 1 {
let direct_child = incoming_layer_path_vector let direct_child = incoming_layer_path_vector
.iter() .into_iter()
.copied()
.filter(|path| !shallowest_common_folder.contains(path)) .filter(|path| !shallowest_common_folder.contains(path))
.take(1) .take(1)
.copied()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let already_selected_direct_child = selected_layers_collected.contains(&direct_child.clone().as_slice()); let already_selected_direct_child = selected_layers_collected.contains(&direct_child.as_slice());
// Update layer tree by filtering any duplicate layers (e.g. If a parent and one of its children are selected) // Update layer tree by filtering any duplicate layers (e.g. If a parent and one of its children are selected)
let mut replacement_selected_layers: Vec<Vec<u64>> = Vec::new(); let mut replacement_selected_layers: Vec<Vec<u64>> = Vec::new();
@@ -1,21 +1,13 @@
use super::tool_prelude::*;
use crate::consts::DRAG_THRESHOLD; use crate::consts::DRAG_THRESHOLD;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::{LayerId, Operation}; use document_legacy::{LayerId, Operation};
use graphene_core::vector::style::{Fill, Stroke}; use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::Color; use graphene_core::Color;
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct SplineTool { pub struct SplineTool {
fsm_state: SplineToolFsmState, fsm_state: SplineToolFsmState,
@@ -128,33 +120,31 @@ impl LayoutHolder for SplineTool {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message { let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else {
match action { self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
SplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
SplineOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
SplineOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
SplineOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
SplineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
SplineOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
}
self.send_layout(responses, LayoutTarget::ToolOptions);
return; return;
};
match action {
SplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
SplineOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
SplineOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
SplineOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
SplineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
SplineOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
} }
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); self.send_layout(responses, LayoutTarget::ToolOptions);
} }
fn actions(&self) -> ActionList { fn actions(&self) -> ActionList {
@@ -219,86 +209,85 @@ impl Fsm for SplineToolFsmState {
use SplineToolFsmState::*; use SplineToolFsmState::*;
use SplineToolMessage::*; use SplineToolMessage::*;
let transform = document.document_legacy.root.transform; let transform = document.document_legacy.metadata.document_to_viewport;
if let ToolMessage::Spline(event) = event { let ToolMessage::Spline(event) = event else {
match (self, event) { return self;
(_, CanvasTransformed) => { };
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); match (self, event) {
self (_, CanvasTransformed) => {
} tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
(Ready, DragStart) => { self
responses.add(DocumentMessage::StartTransaction);
responses.add(DocumentMessage::DeselectAllLayers);
tool_data.path = Some(document.get_path_for_new_layer());
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
let pos = transform.inverse().transform_point2(snapped_position);
tool_data.points.push(pos);
tool_data.next_point = pos;
tool_data.weight = tool_options.line_weight;
add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
Drawing
}
(Drawing, DragStop) => {
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
let pos = transform.inverse().transform_point2(snapped_position);
if let Some(last_pos) = tool_data.points.last() {
if last_pos.distance(pos) > DRAG_THRESHOLD {
tool_data.points.push(pos);
tool_data.next_point = pos;
}
}
responses.add(remove_preview(tool_data));
add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
Drawing
}
(Drawing, PointerMove) => {
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
let pos = transform.inverse().transform_point2(snapped_position);
tool_data.next_point = pos;
responses.add(remove_preview(tool_data));
add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
Drawing
}
(Drawing, Confirm) | (Drawing, Abort) => {
if tool_data.points.len() >= 2 {
responses.add(remove_preview(tool_data));
add_spline(tool_data, false, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
responses.add(DocumentMessage::CommitTransaction);
} else {
responses.add(DocumentMessage::AbortTransaction);
}
tool_data.path = None;
tool_data.points.clear();
tool_data.snap_manager.cleanup(responses);
Ready
}
(_, WorkingColorChanged) => {
responses.add(SplineToolMessage::UpdateOptions(SplineOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} else { (Ready, DragStart) => {
self responses.add(DocumentMessage::StartTransaction);
responses.add(DocumentMessage::DeselectAllLayers);
tool_data.path = Some(document.get_path_for_new_layer());
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
let pos = transform.inverse().transform_point2(snapped_position);
tool_data.points.push(pos);
tool_data.next_point = pos;
tool_data.weight = tool_options.line_weight;
add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
Drawing
}
(Drawing, DragStop) => {
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
let pos = transform.inverse().transform_point2(snapped_position);
if let Some(last_pos) = tool_data.points.last() {
if last_pos.distance(pos) > DRAG_THRESHOLD {
tool_data.points.push(pos);
tool_data.next_point = pos;
}
}
responses.add(remove_preview(tool_data));
add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
Drawing
}
(Drawing, PointerMove) => {
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
let pos = transform.inverse().transform_point2(snapped_position);
tool_data.next_point = pos;
responses.add(remove_preview(tool_data));
add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
Drawing
}
(Drawing, Confirm) | (Drawing, Abort) => {
if tool_data.points.len() >= 2 {
responses.add(remove_preview(tool_data));
add_spline(tool_data, false, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
responses.add(DocumentMessage::CommitTransaction);
} else {
responses.add(DocumentMessage::AbortTransaction);
}
tool_data.path = None;
tool_data.points.clear();
tool_data.snap_manager.cleanup(responses);
Ready
}
(_, WorkingColorChanged) => {
responses.add(SplineToolMessage::UpdateOptions(SplineOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} }
@@ -1,15 +1,9 @@
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
use super::tool_prelude::*;
use crate::application::generate_uuid; use crate::application::generate_uuid;
use crate::consts::{COLOR_ACCENT, SELECTION_TOLERANCE}; use crate::consts::{COLOR_ACCENT, SELECTION_TOLERANCE};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::new_text_network; use crate::messages::portfolio::document::node_graph::new_text_network;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::intersection::Quad; use document_legacy::intersection::Quad;
use document_legacy::layers::layer_info::Layer; use document_legacy::layers::layer_info::Layer;
@@ -21,9 +15,6 @@ use graph_craft::document::{DocumentNode, NodeId, NodeInput, NodeNetwork};
use graphene_core::text::{load_face, Font}; use graphene_core::text::{load_face, Font};
use graphene_core::Color; use graphene_core::Color;
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct TextTool { pub struct TextTool {
fsm_state: TextToolFsmState, fsm_state: TextToolFsmState,
@@ -154,32 +145,30 @@ impl LayoutHolder for TextTool {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message { let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else {
match action { self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
TextOptionsUpdate::Font { family, style } => {
self.options.font_name = family;
self.options.font_style = style;
self.send_layout(responses, LayoutTarget::ToolOptions);
}
TextOptionsUpdate::FontSize(font_size) => self.options.font_size = font_size,
TextOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
TextOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
TextOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
}
self.send_layout(responses, LayoutTarget::ToolOptions);
return; return;
};
match action {
TextOptionsUpdate::Font { family, style } => {
self.options.font_name = family;
self.options.font_style = style;
self.send_layout(responses, LayoutTarget::ToolOptions);
}
TextOptionsUpdate::FontSize(font_size) => self.options.font_size = font_size,
TextOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
TextOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
TextOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
} }
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); self.send_layout(responses, LayoutTarget::ToolOptions);
} }
fn actions(&self) -> ActionList { fn actions(&self) -> ActionList {
@@ -503,88 +492,87 @@ impl Fsm for TextToolFsmState {
render_data, render_data,
.. ..
} = transition_data; } = transition_data;
if let ToolMessage::Text(event) = event { let ToolMessage::Text(event) = event else {
match (self, event) { return self;
(TextToolFsmState::Editing, TextToolMessage::DocumentIsDirty) => { };
responses.add(FrontendMessage::DisplayEditableTextboxTransform { match (self, event) {
transform: document.document_legacy.multiply_transforms(&tool_data.layer_path).ok().unwrap_or_default().to_cols_array(), (TextToolFsmState::Editing, TextToolMessage::DocumentIsDirty) => {
}); responses.add(FrontendMessage::DisplayEditableTextboxTransform {
tool_data.update_bounds_overlay(document, render_data, responses); transform: document.document_legacy.multiply_transforms(&tool_data.layer_path).ok().unwrap_or_default().to_cols_array(),
TextToolFsmState::Editing });
} tool_data.update_bounds_overlay(document, render_data, responses);
(state, TextToolMessage::DocumentIsDirty) => { TextToolFsmState::Editing
update_overlays(document, tool_data, responses, render_data);
state
}
(state, TextToolMessage::Interact) => {
tool_data.editing_text = Some(EditingText {
text: String::new(),
transform: DAffine2::from_translation(input.mouse.position),
font_size: tool_options.font_size as f64,
font: Font::new(tool_options.font_name.clone(), tool_options.font_style.clone()),
color: tool_options.fill.active_color(),
});
tool_data.new_text = String::new();
tool_data.layer_path = document.get_path_for_new_layer();
tool_data.interact(state, input.mouse.position, document, render_data, responses)
}
(state, TextToolMessage::EditSelected) => {
if let Some(layer_path) = can_edit_selected(document) {
tool_data.start_editing_layer(&layer_path, state, document, render_data, responses);
return TextToolFsmState::Editing;
}
state
}
(state, TextToolMessage::Abort) => {
if state == TextToolFsmState::Editing {
tool_data.set_editing(false, render_data, responses);
}
resize_overlays(&mut tool_data.overlays, responses, 0);
TextToolFsmState::Ready
}
(TextToolFsmState::Editing, TextToolMessage::CommitText) => {
responses.add(FrontendMessage::TriggerTextCommit);
TextToolFsmState::Editing
}
(TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => {
let layer_path = tool_data.layer_path.clone();
let network = get_network(&layer_path, document).unwrap();
tool_data.fix_text_bounds(&new_text, document, render_data, responses);
responses.add(NodeGraphMessage::SetQualifiedInputValue {
layer_path,
node_path: vec![get_text_node_id(network).unwrap()],
input_index: 1,
value: TaggedValue::String(new_text),
});
tool_data.set_editing(false, render_data, responses);
resize_overlays(&mut tool_data.overlays, responses, 0);
TextToolFsmState::Ready
}
(TextToolFsmState::Editing, TextToolMessage::UpdateBounds { new_text }) => {
tool_data.new_text = new_text;
tool_data.update_bounds_overlay(document, render_data, responses);
TextToolFsmState::Editing
}
(_, TextToolMessage::WorkingColorChanged) => {
responses.add(TextToolMessage::UpdateOptions(TextOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} else { (state, TextToolMessage::DocumentIsDirty) => {
self update_overlays(document, tool_data, responses, render_data);
state
}
(state, TextToolMessage::Interact) => {
tool_data.editing_text = Some(EditingText {
text: String::new(),
transform: DAffine2::from_translation(input.mouse.position),
font_size: tool_options.font_size as f64,
font: Font::new(tool_options.font_name.clone(), tool_options.font_style.clone()),
color: tool_options.fill.active_color(),
});
tool_data.new_text = String::new();
tool_data.layer_path = document.get_path_for_new_layer();
tool_data.interact(state, input.mouse.position, document, render_data, responses)
}
(state, TextToolMessage::EditSelected) => {
if let Some(layer_path) = can_edit_selected(document) {
tool_data.start_editing_layer(&layer_path, state, document, render_data, responses);
return TextToolFsmState::Editing;
}
state
}
(state, TextToolMessage::Abort) => {
if state == TextToolFsmState::Editing {
tool_data.set_editing(false, render_data, responses);
}
resize_overlays(&mut tool_data.overlays, responses, 0);
TextToolFsmState::Ready
}
(TextToolFsmState::Editing, TextToolMessage::CommitText) => {
responses.add(FrontendMessage::TriggerTextCommit);
TextToolFsmState::Editing
}
(TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => {
let layer_path = tool_data.layer_path.clone();
let network = get_network(&layer_path, document).unwrap();
tool_data.fix_text_bounds(&new_text, document, render_data, responses);
responses.add(NodeGraphMessage::SetQualifiedInputValue {
layer_path,
node_path: vec![get_text_node_id(network).unwrap()],
input_index: 1,
value: TaggedValue::String(new_text),
});
tool_data.set_editing(false, render_data, responses);
resize_overlays(&mut tool_data.overlays, responses, 0);
TextToolFsmState::Ready
}
(TextToolFsmState::Editing, TextToolMessage::UpdateBounds { new_text }) => {
tool_data.new_text = new_text;
tool_data.update_bounds_overlay(document, render_data, responses);
TextToolFsmState::Editing
}
(_, TextToolMessage::WorkingColorChanged) => {
responses.add(TextToolMessage::UpdateOptions(TextOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
} }
} }
@@ -5,6 +5,7 @@ use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::common_functionality::shape_editor::ShapeState;
use crate::messages::tool::utility_types::{ToolData, ToolType}; use crate::messages::tool::utility_types::{ToolData, ToolType};
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::layers::style::RenderData; use document_legacy::layers::style::RenderData;
use graphene_core::vector::ManipulatorPointId; use graphene_core::vector::ManipulatorPointId;
@@ -228,7 +229,10 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
self.mouse_position = ipp.mouse.position; self.mouse_position = ipp.mouse.position;
} }
SelectionChanged => { SelectionChanged => {
let layer_paths = document.selected_visible_layers().map(|layer_path| layer_path.to_vec()).collect(); let layer_paths = document
.selected_visible_layers()
.map(|layer_path| LayerNodeIdentifier::from_path(layer_path, document.network()))
.collect();
shape_editor.set_selected_layers(layer_paths); shape_editor.set_selected_layers(layer_paths);
} }
TypeBackspace => self.transform_operation.grs_typed(self.typing.type_backspace(), &mut selected, self.snap), TypeBackspace => self.transform_operation.grs_typed(self.typing.type_backspace(), &mut selected, self.snap),
+70 -31
View File
@@ -1,10 +1,12 @@
use crate::messages::frontend::utility_types::FrontendImageData; use crate::messages::frontend::utility_types::FrontendImageData;
use crate::messages::portfolio::document::node_graph::wrap_network_in_scope; use crate::messages::portfolio::document::node_graph::wrap_network_in_scope;
use crate::messages::portfolio::document::utility_types::misc::{LayerMetadata, LayerPanelEntry};
use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use document_legacy::layers::layer_info::LayerDataType; use document_legacy::document::Document as DocumentLegacy;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant};
use document_legacy::{LayerId, Operation}; use document_legacy::{LayerId, Operation};
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
@@ -14,8 +16,9 @@ use graph_craft::imaginate_input::ImaginatePreferences;
use graph_craft::{concrete, Type, TypeDescriptor}; use graph_craft::{concrete, Type, TypeDescriptor};
use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender}; use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender};
use graphene_core::raster::{Image, ImageFrame}; use graphene_core::raster::{Image, ImageFrame};
use graphene_core::renderer::{SvgSegment, SvgSegmentList}; use graphene_core::renderer::{ClickTarget, SvgSegment, SvgSegmentList};
use graphene_core::text::FontCache; use graphene_core::text::FontCache;
use graphene_core::transform::Transform;
use graphene_core::vector::style::ViewMode; use graphene_core::vector::style::ViewMode;
use graphene_core::{Color, SurfaceFrame, SurfaceId}; use graphene_core::{Color, SurfaceFrame, SurfaceId};
@@ -52,7 +55,9 @@ pub struct NodeRuntime {
sender: InternalNodeGraphUpdateSender, sender: InternalNodeGraphUpdateSender,
wasm_io: Option<WasmApplicationIo>, wasm_io: Option<WasmApplicationIo>,
imaginate_preferences: ImaginatePreferences, imaginate_preferences: ImaginatePreferences,
pub(crate) thumbnails: HashMap<GraphIdentifier, HashMap<NodeId, SvgSegmentList>>, pub(crate) thumbnails: HashMap<NodeId, SvgSegmentList>,
pub(crate) click_targets: HashMap<NodeId, Vec<ClickTarget>>,
pub(crate) transforms: HashMap<NodeId, DAffine2>,
canvas_cache: HashMap<Vec<LayerId>, SurfaceId>, canvas_cache: HashMap<Vec<LayerId>, SurfaceId>,
} }
@@ -73,7 +78,9 @@ pub(crate) struct GenerationResponse {
generation_id: u64, generation_id: u64,
result: Result<TaggedValue, String>, result: Result<TaggedValue, String>,
updates: VecDeque<Message>, updates: VecDeque<Message>,
new_thumbnails: HashMap<GraphIdentifier, HashMap<NodeId, SvgSegmentList>>, new_thumbnails: HashMap<NodeId, SvgSegmentList>,
new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
new_transforms: HashMap<LayerNodeIdentifier, DAffine2>,
} }
enum NodeGraphUpdate { enum NodeGraphUpdate {
@@ -110,7 +117,9 @@ impl NodeRuntime {
imaginate_preferences: Default::default(), imaginate_preferences: Default::default(),
thumbnails: Default::default(), thumbnails: Default::default(),
wasm_io: None, wasm_io: None,
canvas_cache: Default::default(), canvas_cache: HashMap::new(),
click_targets: HashMap::new(),
transforms: HashMap::new(),
} }
} }
pub async fn run(&mut self) { pub async fn run(&mut self) {
@@ -150,6 +159,8 @@ impl NodeRuntime {
result, result,
updates: responses, updates: responses,
new_thumbnails: self.thumbnails.clone(), new_thumbnails: self.thumbnails.clone(),
new_click_targets: self.click_targets.clone().into_iter().map(|(id, targets)| (LayerNodeIdentifier::new_unchecked(id), targets)).collect(),
new_transforms: self.transforms.clone().into_iter().map(|(id, transform)| (LayerNodeIdentifier::new_unchecked(id), transform)).collect(),
}; };
self.sender.send_generation_response(response); self.sender.send_generation_response(response);
} }
@@ -204,41 +215,60 @@ impl NodeRuntime {
/// Recomputes the thumbnails for the layers in the graph, modifying the state and updating the UI. /// Recomputes the thumbnails for the layers in the graph, modifying the state and updating the UI.
pub fn update_thumbnails(&mut self, layer_path: &[LayerId], monitor_nodes: Vec<Vec<u64>>, responses: &mut VecDeque<Message>) { pub fn update_thumbnails(&mut self, layer_path: &[LayerId], monitor_nodes: Vec<Vec<u64>>, responses: &mut VecDeque<Message>) {
let mut thumbnails_changed: bool = false;
let mut image_data: Vec<_> = Vec::new(); let mut image_data: Vec<_> = Vec::new();
for node_path in monitor_nodes { for node_path in monitor_nodes {
let Some(value) = self.executor.introspect(&node_path).flatten() else { let Some(node_id) = node_path.get(node_path.len() - 2).copied() else {
warn!("No introspect"); warn!("Monitor node has invalid node id");
continue; continue;
}; };
let Some(graphic_group) = value.downcast_ref::<graphene_core::GraphicGroup>() else { let Some(value) = self.executor.introspect(&node_path).flatten() else {
warn!("Not graphic"); warn!("Failed to introspect monitor node for thumbnail");
continue;
};
let Some(graphic_element_data) = value.downcast_ref::<graphene_core::GraphicElementData>() else {
warn!("Failed to downcast thumbnail to graphic element data");
continue; continue;
}; };
use graphene_core::renderer::*; use graphene_core::renderer::*;
let bounds = graphic_group.bounding_box(DAffine2::IDENTITY); let bounds = graphic_element_data.bounding_box(DAffine2::IDENTITY);
let render_params = RenderParams::new(ViewMode::Normal, bounds, true); let render_params = RenderParams::new(ViewMode::Normal, bounds, true);
let mut render = SvgRender::new(); let mut render = SvgRender::new();
graphic_group.render_svg(&mut render, &render_params); graphic_element_data.render_svg(&mut render, &render_params);
let [min, max] = bounds.unwrap_or_default(); let [min, max] = bounds.unwrap_or_default();
render.format_svg(min, max); render.format_svg(min, max);
if let Some(node_id) = node_path.get(node_path.len() - 2).copied() { let click_targets = self.click_targets.entry(node_id).or_default();
let graph_identifier = GraphIdentifier::new(layer_path.last().copied()); click_targets.clear();
let old_thumbnail = self.thumbnails.entry(graph_identifier).or_default().entry(node_id).or_default(); graphic_element_data.add_click_targets(click_targets);
if *old_thumbnail != render.svg {
*old_thumbnail = render.svg; self.transforms.insert(node_id, graphic_element_data.transform());
thumbnails_changed = true;
} let old_thumbnail = self.thumbnails.entry(node_id).or_default();
if *old_thumbnail != render.svg {
responses.add(FrontendMessage::UpdateDocumentLayerDetails {
data: LayerPanelEntry {
name: "Layer".to_string(),
tooltip: format!("Layer id: {node_id}"),
visible: true,
layer_type: LayerDataTypeDiscriminant::Layer,
layer_metadata: LayerMetadata::new(true),
path: vec![node_id],
thumbnail: render.svg.to_string(),
},
});
responses.add(FrontendMessage::UpdateNodeThumbnail {
id: node_id,
value: render.svg.to_string(),
});
*old_thumbnail = render.svg;
} }
let resize = Some(DVec2::splat(100.)); let resize = Some(DVec2::splat(100.));
let create_image_data = |(node_id, image)| NodeGraphExecutor::to_frontend_image_data(image, None, layer_path, Some(node_id), resize).ok(); let create_image_data = |(node_id, image)| NodeGraphExecutor::to_frontend_image_data(image, None, layer_path, Some(node_id), resize).ok();
image_data.extend(render.image_data.into_iter().filter_map(create_image_data)) image_data.extend(render.image_data.into_iter().filter_map(create_image_data))
} }
if !image_data.is_empty() { if !image_data.is_empty() {
responses.add(FrontendMessage::UpdateImageData { document_id: 0, image_data }); responses.add(FrontendMessage::UpdateImageData { document_id: 0, image_data });
} else if thumbnails_changed {
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
} }
} }
} }
@@ -279,7 +309,7 @@ pub struct NodeGraphExecutor {
receiver: Receiver<NodeGraphUpdate>, receiver: Receiver<NodeGraphUpdate>,
// TODO: This is a memory leak since layers are never removed // TODO: This is a memory leak since layers are never removed
pub(crate) last_output_type: HashMap<Vec<LayerId>, Option<Type>>, pub(crate) last_output_type: HashMap<Vec<LayerId>, Option<Type>>,
pub(crate) thumbnails: HashMap<GraphIdentifier, HashMap<NodeId, SvgSegmentList>>, pub(crate) thumbnails: HashMap<NodeId, SvgSegmentList>,
futures: HashMap<u64, ExecutionContext>, futures: HashMap<u64, ExecutionContext>,
} }
@@ -408,7 +438,7 @@ impl NodeGraphExecutor {
// Get the node graph layer // Get the node graph layer
let document = documents.get_mut(&document_id).ok_or_else(|| "Invalid document".to_string())?; let document = documents.get_mut(&document_id).ok_or_else(|| "Invalid document".to_string())?;
let network = if layer_path.is_empty() { let network = if layer_path.is_empty() {
document.document_legacy.document_network.clone() document.network().clone()
} else { } else {
let layer = document.document_legacy.layer(&layer_path).map_err(|e| format!("No layer: {e:?}"))?; let layer = document.document_legacy.layer(&layer_path).map_err(|e| format!("No layer: {e:?}"))?;
@@ -431,7 +461,7 @@ impl NodeGraphExecutor {
Ok(()) Ok(())
} }
pub fn poll_node_graph_evaluation(&mut self, transform: DAffine2, responses: &mut VecDeque<Message>) -> Result<(), String> { pub fn poll_node_graph_evaluation(&mut self, document: &mut DocumentLegacy, responses: &mut VecDeque<Message>) -> Result<(), String> {
let results = self.receiver.try_iter().collect::<Vec<_>>(); let results = self.receiver.try_iter().collect::<Vec<_>>();
for response in results { for response in results {
match response { match response {
@@ -440,12 +470,16 @@ impl NodeGraphExecutor {
result, result,
updates, updates,
new_thumbnails, new_thumbnails,
new_click_targets,
new_transforms,
}) => { }) => {
self.thumbnails = new_thumbnails; self.thumbnails = new_thumbnails;
document.metadata.update_transforms(new_transforms);
document.metadata.update_click_targets(new_click_targets);
let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {:?}", e))?; let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {:?}", e))?;
let execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?; let execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?;
responses.extend(updates); responses.extend(updates);
self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), transform, responses, execution_context.document_id)?; self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), responses, execution_context.document_id)?;
responses.add(DocumentMessage::LayerChanged { responses.add(DocumentMessage::LayerChanged {
affected_layer_path: execution_context.layer_path, affected_layer_path: execution_context.layer_path,
}); });
@@ -464,7 +498,7 @@ impl NodeGraphExecutor {
Ok(()) Ok(())
} }
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec<LayerId>, _transform: DAffine2, responses: &mut VecDeque<Message>, document_id: u64) -> Result<(), String> { fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>, document_id: u64) -> Result<(), String> {
self.last_output_type.insert(layer_path.clone(), Some(node_graph_output.ty())); self.last_output_type.insert(layer_path.clone(), Some(node_graph_output.ty()));
match node_graph_output { match node_graph_output {
TaggedValue::VectorData(vector_data) => { TaggedValue::VectorData(vector_data) => {
@@ -528,12 +562,17 @@ impl NodeGraphExecutor {
} }
/// When a blob url for a thumbnail is loaded, update the state and the UI. /// When a blob url for a thumbnail is loaded, update the state and the UI.
pub fn insert_thumbnail_blob_url(&mut self, blob_url: String, layer_id: Option<LayerId>, node_id: NodeId, responses: &mut VecDeque<Message>) { pub fn insert_thumbnail_blob_url(&mut self, blob_url: String, node_id: NodeId, responses: &mut VecDeque<Message>) {
if let Some(layer) = self.thumbnails.get_mut(&GraphIdentifier::new(layer_id)) { for segment_list in self.thumbnails.values_mut() {
if let Some(segment) = layer.values_mut().flat_map(|segments| segments.iter_mut()).find(|segment| **segment == SvgSegment::BlobUrl(node_id)) { if let Some(segment) = segment_list.iter_mut().find(|segment| **segment == SvgSegment::BlobUrl(node_id)) {
*segment = SvgSegment::String(blob_url); *segment = SvgSegment::String(blob_url);
responses.add(NodeGraphMessage::SendGraph { should_rerender: false }); responses.add(FrontendMessage::UpdateNodeThumbnail {
id: node_id,
value: segment_list.to_string(),
});
return;
} }
} }
warn!("Recieved blob url for invalid segment")
} }
} }
+7 -3
View File
@@ -652,8 +652,8 @@
</svg> </svg>
</div> </div>
<div class="thumbnail"> <div class="thumbnail">
{#if node.thumbnailSvg} {#if $nodeGraph.thumbnails.has(node.id)}
{@html node.thumbnailSvg} {@html $nodeGraph.thumbnails.get(node.id) }
{/if} {/if}
{#if node.primaryOutput} {#if node.primaryOutput}
<svg <svg
@@ -702,7 +702,7 @@
class:selected={selected.includes(node.id)} class:selected={selected.includes(node.id)}
class:previewed={node.previewed} class:previewed={node.previewed}
class:disabled={node.disabled} class:disabled={node.disabled}
class:is-layer={node.thumbnailSvg !== undefined} class:is-layer={node.displayName === "Layer"}
style:--offset-left={(node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)} style:--offset-left={(node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)}
style:--offset-top={(node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)} style:--offset-top={(node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)}
style:--clip-path-id={`url(#${clipPathId})`} style:--clip-path-id={`url(#${clipPathId})`}
@@ -1173,6 +1173,10 @@
.expand-arrow { .expand-arrow {
margin-right: 4px; margin-right: 4px;
} }
svg {
width: 30px;
height: 20px;
}
} }
} }
@@ -7,6 +7,7 @@ import {
type FrontendNodeType, type FrontendNodeType,
UpdateNodeGraph, UpdateNodeGraph,
UpdateNodeTypes, UpdateNodeTypes,
UpdateNodeThumbnail,
UpdateZoomWithScroll, UpdateZoomWithScroll,
} from "@graphite/wasm-communication/messages"; } from "@graphite/wasm-communication/messages";
@@ -17,6 +18,7 @@ export function createNodeGraphState(editor: Editor) {
links: [] as FrontendNodeLink[], links: [] as FrontendNodeLink[],
nodeTypes: [] as FrontendNodeType[], nodeTypes: [] as FrontendNodeType[],
zoomWithScroll: false as boolean, zoomWithScroll: false as boolean,
thumbnails: new Map<bigint, string>(),
}); });
// Set up message subscriptions on creation // Set up message subscriptions on creation
@@ -24,6 +26,13 @@ export function createNodeGraphState(editor: Editor) {
update((state) => { update((state) => {
state.nodes = updateNodeGraph.nodes; state.nodes = updateNodeGraph.nodes;
state.links = updateNodeGraph.links; state.links = updateNodeGraph.links;
let newThumbnails = new Map<bigint, string>();
state.nodes.forEach((node) => {
const thumbnail = state.thumbnails.get(node.id);
if (thumbnail)
newThumbnails.set(node.id, thumbnail);
});
state.thumbnails = newThumbnails;
return state; return state;
}); });
}); });
@@ -33,6 +42,12 @@ export function createNodeGraphState(editor: Editor) {
return state; return state;
}); });
}); });
editor.subscriptions.subscribeJsMessage(UpdateNodeThumbnail, (updateNodeThumbnail) => {
update((state) => {
state.thumbnails.set(updateNodeThumbnail.id, updateNodeThumbnail.value);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateZoomWithScroll, (updateZoomWithScroll) => { editor.subscriptions.subscribeJsMessage(UpdateZoomWithScroll, (updateZoomWithScroll) => {
update((state) => { update((state) => {
state.zoomWithScroll = updateZoomWithScroll.zoomWithScroll; state.zoomWithScroll = updateZoomWithScroll.zoomWithScroll;
+7 -2
View File
@@ -37,6 +37,12 @@ export class UpdateNodeTypes extends JsMessage {
readonly nodeTypes!: FrontendNodeType[]; readonly nodeTypes!: FrontendNodeType[];
} }
export class UpdateNodeThumbnail extends JsMessage {
readonly id!: bigint;
readonly value!: string;
}
export class UpdateNodeGraphSelection extends JsMessage { export class UpdateNodeGraphSelection extends JsMessage {
@Type(() => BigInt) @Type(() => BigInt)
readonly selected!: bigint[]; readonly selected!: bigint[];
@@ -106,8 +112,6 @@ export class FrontendNode {
readonly previewed!: boolean; readonly previewed!: boolean;
readonly disabled!: boolean; readonly disabled!: boolean;
readonly thumbnailSvg!: string | undefined;
} }
export class FrontendNodeLink { export class FrontendNodeLink {
@@ -1435,6 +1439,7 @@ export const messageMakers: Record<string, MessageMaker> = {
UpdateNodeGraph, UpdateNodeGraph,
UpdateNodeGraphBarLayout, UpdateNodeGraphBarLayout,
UpdateNodeGraphSelection, UpdateNodeGraphSelection,
UpdateNodeThumbnail,
UpdateNodeTypes, UpdateNodeTypes,
UpdateOpenDocumentsList, UpdateOpenDocumentsList,
UpdatePropertyPanelOptionsLayout, UpdatePropertyPanelOptionsLayout,
+7
View File
@@ -97,6 +97,13 @@ fn construct_layer<Data: Into<GraphicElementData>>(
stack stack
} }
pub struct ToGraphicElementData {}
#[node_fn(ToGraphicElementData)]
fn to_graphic_element_data<Data: Into<GraphicElementData>>(graphic_element_data: Data) -> GraphicElementData {
graphic_element_data.into()
}
pub struct ConstructArtboardNode<Location, Dimensions, Background, Clip> { pub struct ConstructArtboardNode<Location, Dimensions, Background, Clip> {
location: Location, location: Location,
dimensions: Dimensions, dimensions: Dimensions,
@@ -1,11 +1,56 @@
use crate::raster::{Image, ImageFrame}; use crate::raster::{Image, ImageFrame};
use crate::{uuid::generate_uuid, vector::VectorData, Artboard, Color, GraphicElementData, GraphicGroup}; use crate::uuid::{generate_uuid, ManipulatorGroupId};
use quad::Quad; use crate::{vector::VectorData, Artboard, Color, GraphicElementData, GraphicGroup};
use bezier_rs::Subpath;
pub use quad::Quad;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
mod quad; mod quad;
/// Represents a clickable target for the layer
#[derive(Clone, Debug)]
pub struct ClickTarget {
pub subpath: bezier_rs::Subpath<ManipulatorGroupId>,
pub stroke_width: f64,
}
impl ClickTarget {
/// Does the click target intersect the rectangle
pub fn intersect_rectangle(&self, document_quad: Quad, layer_transform: DAffine2) -> bool {
let quad = layer_transform.inverse() * document_quad;
// Check if outlines intersect
if self
.subpath
.iter()
.any(|path_segment| quad.bezier_lines().any(|line| !path_segment.intersections(&line, None, None).is_empty()))
{
return true;
}
// Check if selection is entirely within the shape
if self.subpath.closed() && self.subpath.contains_point(quad.center()) {
return true;
}
// Check if shape is entirely within selection
self.subpath
.manipulator_groups()
.first()
.map(|group| group.anchor)
.map(|shape_point| quad.contains(shape_point))
.unwrap_or_default()
}
/// Does the click target intersect the point (accounting for stroke size)
pub fn intersect_point(&self, point: DVec2, layer_transform: DAffine2) -> bool {
// Allows for selecting lines
// TODO: actual intersection of stroke
let inflated_quad = Quad::from_box([point - DVec2::splat(self.stroke_width / 2.), point + DVec2::splat(self.stroke_width / 2.)]);
self.intersect_rectangle(inflated_quad, layer_transform)
}
}
/// Mutable state used whilst rendering to an SVG /// Mutable state used whilst rendering to an SVG
pub struct SvgRender { pub struct SvgRender {
pub svg: SvgSegmentList, pub svg: SvgSegmentList,
@@ -109,6 +154,7 @@ pub fn format_transform_matrix(transform: DAffine2) -> String {
pub trait GraphicElementRendered { pub trait GraphicElementRendered {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams); fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams);
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]>; fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]>;
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>);
} }
impl GraphicElementRendered for GraphicGroup { impl GraphicElementRendered for GraphicGroup {
@@ -118,6 +164,7 @@ impl GraphicElementRendered for GraphicGroup {
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.iter().filter_map(|element| element.graphic_element_data.bounding_box(transform)).reduce(Quad::combine_bounds) self.iter().filter_map(|element| element.graphic_element_data.bounding_box(transform)).reduce(Quad::combine_bounds)
} }
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
} }
impl GraphicElementRendered for VectorData { impl GraphicElementRendered for VectorData {
@@ -140,6 +187,14 @@ impl GraphicElementRendered for VectorData {
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.bounding_box_with_transform(self.transform * transform) self.bounding_box_with_transform(self.transform * transform)
} }
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let stroke_width = self.style.stroke().as_ref().map_or(0., crate::vector::style::Stroke::weight);
let update_closed = |mut subpath: bezier_rs::Subpath<ManipulatorGroupId>| {
subpath.set_closed(self.style.fill().is_some());
subpath
};
click_targets.extend(self.subpaths.iter().cloned().map(update_closed).map(|subpath| ClickTarget { stroke_width, subpath }))
}
} }
impl GraphicElementRendered for Artboard { impl GraphicElementRendered for Artboard {
@@ -195,7 +250,15 @@ impl GraphicElementRendered for Artboard {
} }
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
let artboard_bounds = (transform * Quad::from_box([self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()])).bounding_box(); let artboard_bounds = (transform * Quad::from_box([self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()])).bounding_box();
[self.graphic_group.bounding_box(transform), Some(artboard_bounds)].into_iter().flatten().reduce(Quad::combine_bounds) if self.clip {
Some(artboard_bounds)
} else {
[self.graphic_group.bounding_box(transform), Some(artboard_bounds)].into_iter().flatten().reduce(Quad::combine_bounds)
}
}
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let subpath = Subpath::new_rect(self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2());
click_targets.push(ClickTarget { stroke_width: 0., subpath });
} }
} }
@@ -216,6 +279,10 @@ impl GraphicElementRendered for ImageFrame<Color> {
let transform = self.transform * transform; let transform = self.transform * transform;
(transform.matrix2 != glam::DMat2::ZERO).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box()) (transform.matrix2 != glam::DMat2::ZERO).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
} }
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
click_targets.push(ClickTarget { subpath, stroke_width: 0. });
}
} }
impl GraphicElementRendered for GraphicElementData { impl GraphicElementRendered for GraphicElementData {
@@ -238,6 +305,16 @@ impl GraphicElementRendered for GraphicElementData {
GraphicElementData::Artboard(artboard) => artboard.bounding_box(transform), GraphicElementData::Artboard(artboard) => artboard.bounding_box(transform),
} }
} }
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
match self {
GraphicElementData::VectorShape(vector_data) => vector_data.add_click_targets(click_targets),
GraphicElementData::ImageFrame(image_frame) => image_frame.add_click_targets(click_targets),
GraphicElementData::Text(_) => todo!("click target for text GraphicElementData"),
GraphicElementData::GraphicGroup(graphic_group) => graphic_group.add_click_targets(click_targets),
GraphicElementData::Artboard(artboard) => artboard.add_click_targets(click_targets),
}
}
} }
/// A segment of an svg string to allow for embedding blob urls /// A segment of an svg string to allow for embedding blob urls
@@ -5,6 +5,11 @@ use glam::{DAffine2, DVec2};
pub struct Quad([DVec2; 4]); pub struct Quad([DVec2; 4]);
impl Quad { impl Quad {
/// Create a zero sized quad at the point
pub fn from_point(point: DVec2) -> Self {
Self([point; 4])
}
/// Convert a box defined by two corner points to a quad. /// Convert a box defined by two corner points to a quad.
pub fn from_box(bbox: [DVec2; 2]) -> Self { pub fn from_box(bbox: [DVec2; 2]) -> Self {
let size = bbox[1] - bbox[0]; let size = bbox[1] - bbox[0];
@@ -12,7 +17,7 @@ impl Quad {
} }
/// Get all the edges in the quad. /// Get all the edges in the quad.
pub fn lines_glam(&self) -> impl Iterator<Item = bezier_rs::Bezier> + '_ { pub fn bezier_lines(&self) -> impl Iterator<Item = bezier_rs::Bezier> + '_ {
[[self.0[0], self.0[1]], [self.0[1], self.0[2]], [self.0[2], self.0[3]], [self.0[3], self.0[0]]] [[self.0[0], self.0[1]], [self.0[1], self.0[2]], [self.0[2], self.0[3]], [self.0[3], self.0[0]]]
.into_iter() .into_iter()
.map(|[start, end]| bezier_rs::Bezier::from_linear_dvec2(start, end)) .map(|[start, end]| bezier_rs::Bezier::from_linear_dvec2(start, end))
@@ -40,6 +45,33 @@ impl Quad {
pub fn combine_bounds(a: [DVec2; 2], b: [DVec2; 2]) -> [DVec2; 2] { pub fn combine_bounds(a: [DVec2; 2], b: [DVec2; 2]) -> [DVec2; 2] {
[a[0].min(b[0]), a[1].max(b[1])] [a[0].min(b[0]), a[1].max(b[1])]
} }
/// Expand a quad by a certain amount on all sides.
///
/// Not currently very optimised
pub fn inflate(&self, offset: f64) -> Quad {
let offset = |index_before, index, index_after| {
let [point_before, point, point_after]: [DVec2; 3] = [self.0[index_before], self.0[index], self.0[index_after]];
let [line_in, line_out] = [point - point_before, point_after - point];
let angle = line_in.angle_between(-line_out);
let offset_length = offset / (std::f64::consts::FRAC_PI_2 - angle / 2.).cos();
point + (line_in.perp().normalize_or_zero() + line_out.perp().normalize_or_zero()).normalize_or_zero() * offset_length
};
Self([offset(3, 0, 1), offset(0, 1, 2), offset(1, 2, 3), offset(2, 3, 0)])
}
/// Does this quad contain a point
///
/// Code from https://wrfranklin.org/Research/Short_Notes/pnpoly.html
pub fn contains(&self, p: DVec2) -> bool {
let mut inside = false;
for (i, j) in (0..4).zip([3, 0, 1, 2]) {
if (self.0[i].y > p.y) != (self.0[j].y > p.y) && p.x < (self.0[j].x - self.0[i].x * (p.y - self.0[i].y) / (self.0[j].y - self.0[i].y) + self.0[i].x) {
inside = !inside;
}
}
inside
}
} }
impl core::ops::Mul<Quad> for DAffine2 { impl core::ops::Mul<Quad> for DAffine2 {
@@ -49,3 +81,26 @@ impl core::ops::Mul<Quad> for DAffine2 {
Quad(rhs.0.map(|point| self.transform_point2(point))) Quad(rhs.0.map(|point| self.transform_point2(point)))
} }
} }
#[test]
fn offset_quad() {
fn eq(a: Quad, b: Quad) -> bool {
a.0.iter().zip(b.0).all(|(a, b)| a.abs_diff_eq(b, 0.0001))
}
assert!(eq(Quad::from_box([DVec2::ZERO, DVec2::ONE]).inflate(0.5), Quad::from_box([DVec2::splat(-0.5), DVec2::splat(1.5)])));
assert!(eq(Quad::from_box([DVec2::ONE, DVec2::ZERO]).inflate(0.5), Quad::from_box([DVec2::splat(1.5), DVec2::splat(-0.5)])));
assert!(eq(
(DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::ZERO, DVec2::ONE])).inflate(0.5),
DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::splat(-0.5), DVec2::splat(1.5)])
));
}
#[test]
fn quad_contains() {
assert!(Quad::from_box([DVec2::ZERO, DVec2::ONE]).contains(DVec2::splat(0.5)));
assert!(Quad::from_box([DVec2::ONE, DVec2::ZERO]).contains(DVec2::splat(0.5)));
assert!((DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::ZERO, DVec2::ONE])).contains(DVec2::new(-0.5, 0.5)));
assert!(!Quad::from_box([DVec2::ZERO, DVec2::ONE]).contains(DVec2::new(1., 1.1)));
assert!(!Quad::from_box([DVec2::ONE, DVec2::ZERO]).contains(DVec2::new(0.5, -0.01)));
assert!(!(DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::ZERO, DVec2::ONE])).contains(DVec2::splat(0.5)));
}
+47
View File
@@ -5,6 +5,7 @@ use glam::DVec2;
use crate::raster::ImageFrame; use crate::raster::ImageFrame;
use crate::raster::Pixel; use crate::raster::Pixel;
use crate::vector::VectorData; use crate::vector::VectorData;
use crate::GraphicElementData;
use crate::Node; use crate::Node;
pub trait Transform { pub trait Transform {
@@ -42,6 +43,52 @@ impl<P: Pixel> TransformMut for ImageFrame<P> {
&mut self.transform &mut self.transform
} }
} }
impl Transform for GraphicElementData {
fn transform(&self) -> DAffine2 {
match self {
GraphicElementData::VectorShape(vector_shape) => vector_shape.transform(),
GraphicElementData::ImageFrame(image_frame) => image_frame.transform(),
GraphicElementData::Text(_) => todo!("Transform of text"),
GraphicElementData::GraphicGroup(_graphic_group) => DAffine2::IDENTITY,
GraphicElementData::Artboard(_artboard) => DAffine2::IDENTITY,
}
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
match self {
GraphicElementData::VectorShape(vector_shape) => vector_shape.local_pivot(pivot),
GraphicElementData::ImageFrame(image_frame) => image_frame.local_pivot(pivot),
GraphicElementData::Text(_) => todo!("Transform of text"),
GraphicElementData::GraphicGroup(_graphic_group) => pivot,
GraphicElementData::Artboard(_artboard) => pivot,
}
}
fn decompose_scale(&self) -> DVec2 {
let standard = || {
DVec2::new(
self.transform().transform_vector2((1., 0.).into()).length(),
self.transform().transform_vector2((0., 1.).into()).length(),
)
};
match self {
GraphicElementData::VectorShape(vector_shape) => vector_shape.decompose_scale(),
GraphicElementData::ImageFrame(image_frame) => image_frame.decompose_scale(),
GraphicElementData::Text(_) => todo!("Transform of text"),
GraphicElementData::GraphicGroup(_graphic_group) => standard(),
GraphicElementData::Artboard(_artboard) => standard(),
}
}
}
impl TransformMut for GraphicElementData {
fn transform_mut(&mut self) -> &mut DAffine2 {
match self {
GraphicElementData::VectorShape(vector_shape) => vector_shape.transform_mut(),
GraphicElementData::ImageFrame(image_frame) => image_frame.transform_mut(),
GraphicElementData::Text(_) => todo!("Transform of text"),
GraphicElementData::GraphicGroup(_graphic_group) => todo!("Mutable transform of graphic group"),
GraphicElementData::Artboard(_artboard) => todo!("Mutable transform of artboard"),
}
}
}
impl Transform for VectorData { impl Transform for VectorData {
fn transform(&self) -> DAffine2 { fn transform(&self) -> DAffine2 {
+1 -3
View File
@@ -453,9 +453,7 @@ impl NodeNetwork {
return true; return true;
} }
// Get the outputs // Get the outputs
let Some(mut stack) = self.outputs.iter().map(|&output| self.nodes.get(&output.node_id)).collect::<Option<Vec<_>>>() else { let mut stack = self.outputs.iter().filter_map(|&output| self.nodes.get(&output.node_id)).collect::<Vec<_>>();
return false;
};
let mut already_visited = HashSet::new(); let mut already_visited = HashSet::new();
already_visited.extend(self.outputs.iter().map(|output| output.node_id)); already_visited.extend(self.outputs.iter().map(|output| output.node_id));
+3 -3
View File
@@ -328,7 +328,7 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
#[cfg(feature = "quantization")] #[cfg(feature = "quantization")]
buffers: vec![width_uniform.clone(), storage_buffer.clone(), quantization_uniform.clone()], buffers: vec![width_uniform.clone(), storage_buffer.clone(), quantization_uniform.clone()],
#[cfg(not(feature = "quantization"))] #[cfg(not(feature = "quantization"))]
buffers: vec![width_uniform.clone(), storage_buffer.clone()], buffers: vec![width_uniform, storage_buffer],
}; };
let shader = gpu_executor::Shader { let shader = gpu_executor::Shader {
@@ -343,13 +343,13 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
shader: shader.into(), shader: shader.into(),
entry_point: "eval".to_string(), entry_point: "eval".to_string(),
bind_group: bind_group.into(), bind_group: bind_group.into(),
output_buffer: output_buffer.clone(), output_buffer,
}; };
log::debug!("created pipeline"); log::debug!("created pipeline");
Ok(ComputePass { Ok(ComputePass {
pipeline_layout: pipeline, pipeline_layout: pipeline,
readback_buffer: Some(readback_buffer.clone()), readback_buffer: Some(readback_buffer),
}) })
} }
/* /*
@@ -318,7 +318,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
)], )],
register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]), register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]),
register_node!(graphene_core::memo::MonitorNode<_>, input: ImageFrame<Color>, params: []), register_node!(graphene_core::memo::MonitorNode<_>, input: ImageFrame<Color>, params: []),
register_node!(graphene_core::memo::MonitorNode<_>, input: graphene_core::GraphicGroup, params: []), register_node!(graphene_core::memo::MonitorNode<_>, input: graphene_core::GraphicElementData, params: []),
async_node!(graphene_std::wasm_application_io::LoadResourceNode<_>, input: WasmEditorApi, output: Arc<[u8]>, params: [String]), async_node!(graphene_std::wasm_application_io::LoadResourceNode<_>, input: WasmEditorApi, output: Arc<[u8]>, params: [String]),
register_node!(graphene_std::wasm_application_io::DecodeImageNode, input: Arc<[u8]>, params: []), register_node!(graphene_std::wasm_application_io::DecodeImageNode, input: Arc<[u8]>, params: []),
async_node!(graphene_std::wasm_application_io::CreateSurfaceNode, input: WasmEditorApi, output: Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>, params: []), async_node!(graphene_std::wasm_application_io::CreateSurfaceNode, input: WasmEditorApi, output: Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>, params: []),
@@ -653,10 +653,11 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::text::TextGenerator<_, _, _>, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]), register_node!(graphene_core::text::TextGenerator<_, _, _>, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]),
register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []), register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []),
register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, params: []), register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, params: []),
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::vector::VectorData, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]), register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::GraphicElementData, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: ImageFrame<Color>, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]), register_node!(graphene_core::ToGraphicElementData, input: graphene_core::vector::VectorData, params: []),
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::GraphicGroup, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]), register_node!(graphene_core::ToGraphicElementData, input: ImageFrame<Color>, params: []),
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::Artboard, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]), register_node!(graphene_core::ToGraphicElementData, input: graphene_core::GraphicGroup, params: []),
register_node!(graphene_core::ToGraphicElementData, input: graphene_core::Artboard, params: []),
register_node!(graphene_core::ConstructArtboardNode<_, _, _, _>, input: graphene_core::GraphicGroup, params: [glam::IVec2, glam::IVec2, Color, bool]), register_node!(graphene_core::ConstructArtboardNode<_, _, _, _>, input: graphene_core::GraphicGroup, params: [glam::IVec2, glam::IVec2, Color, bool]),
]; ];
let mut map: HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new(); let mut map: HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
-1
View File
@@ -4,7 +4,6 @@ use syn::{Path, PathArguments, PathSegment, Token};
/// Returns `Ok(Vec<T>)` if all items are `Ok(T)`, else returns a combination of every error encountered (not just the first one) /// Returns `Ok(Vec<T>)` if all items are `Ok(T)`, else returns a combination of every error encountered (not just the first one)
pub fn fold_error_iter<T>(iter: impl Iterator<Item = syn::Result<T>>) -> syn::Result<Vec<T>> { pub fn fold_error_iter<T>(iter: impl Iterator<Item = syn::Result<T>>) -> syn::Result<Vec<T>> {
#[allow(clippy::manual_try_fold)]
iter.fold(Ok(vec![]), |acc, x| match acc { iter.fold(Ok(vec![]), |acc, x| match acc {
Ok(mut v) => x.map(|x| { Ok(mut v) => x.map(|x| {
v.push(x); v.push(x);