Migrate vector data and tools to use nodes (#1065)

* Add rendering to vector nodes

* Add line, shape, rectange and freehand tool

* Fix transforms, strokes and fills

* Migrate spline tool

* Remove blank lines

* Fix test

* Fix fill in properties

* Select layers when filling

* Properties panel transform around pivot

* Fix select tool outlines

* Select tool modifies node graph pivot

* Add the pivot assist to the properties

* Improve setting non existant fill UX

* Cleanup hash function

* Path and pen tools

* Bug fixes

* Disable boolean ops

* Fix default handle smoothing on ellipses

* Fix test and warnings

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube 2023-03-26 08:03:51 +01:00 committed by Keavon Chambers
parent 639a24d8ad
commit 959e790cdf
64 changed files with 2639 additions and 1552 deletions

View file

@ -123,27 +123,25 @@ impl DocumentNode {
}
/// Represents the possible inputs to a node.
/// # ShortCircuting
///
/// # Short circuting
///
/// In Graphite nodes are functions and by default, these are composed into a single function
/// by inserting Compose nodes.
///
///
///
///
/// ```text
/// ┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐
/// │ │◄──────────────┤ │◄───────────────┤ │
/// │ A │ │ B │ │ C │
/// │ ├──────────────►│ ├───────────────►│ │
/// └─────────────────┘ └──────────────────┘ └──────────────────┘
/// ```
///
///
///
/// This is equivalent to calling c(b(a(input))) when evaluating c with input ( `c.eval(input)`)
/// This is equivalent to calling c(b(a(input))) when evaluating c with input ( `c.eval(input)`).
/// But sometimes we might want to have a little more control over the order of execution.
/// This is why we allow nodes to opt out of the input forwarding by consuming the input directly.
///
///
///
/// ```text
/// ┌─────────────────────┐ ┌─────────────┐
/// │ │◄───────────────┤ │
/// │ Cache Node │ │ C │
@ -153,20 +151,26 @@ impl DocumentNode {
/// │ A │ │ * Cached Node │
/// │ ├──────────────►│ │
/// └──────────────────┘ └─────────────────────┘
/// ```
///
///
///
///
/// In this case the Cache node actually consumes it's input and then manually forwards it to it's parameter
/// Node. This is necessary because the Cache Node needs to short-circut the actual node evaluation
/// In this case the Cache node actually consumes its input and then manually forwards it to its parameter Node.
/// This is necessary because the Cache Node needs to short-circut the actual node evaluation.
#[derive(Debug, Clone, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum NodeInput {
Node { node_id: NodeId, output_index: usize, lambda: bool },
Value { tagged_value: crate::document::value::TaggedValue, exposed: bool },
Node {
node_id: NodeId,
output_index: usize,
lambda: bool,
},
Value {
tagged_value: crate::document::value::TaggedValue,
exposed: bool,
},
Network(Type),
// A short circuting input represents an input that is not resolved through function composition but
// actually consuming the provided input instead of passing it to its predecessor
/// A short circuting input represents an input that is not resolved through function composition
/// but actually consuming the provided input instead of passing it to its predecessor.
/// See [NodeInput] docs for more explanation.
ShortCircut(Type),
}
@ -293,7 +297,7 @@ impl NodeNetwork {
let mut duplicating_nodes = HashMap::new();
// Find the nodes where the inputs require duplicating
for node in &mut self.nodes.values_mut() {
// Recursivly duplicate children
// Recursively duplicate children
if let DocumentNodeImplementation::Network(network) = &mut node.implementation {
network.duplicate_outputs(gen_id);
}
@ -420,7 +424,6 @@ impl NodeNetwork {
network_input.populate_first_network_input(node_id, output_index, *offset, lambda);
}
NodeInput::Value { tagged_value, exposed } => {
// Skip formatting very large values for seconds in performance speedup
let name = "Value".to_string();
let new_id = map_ids(id, gen_id());
let value_node = DocumentNode {
@ -588,6 +591,38 @@ impl NodeNetwork {
pub fn previous_outputs_contain(&self, node_id: NodeId) -> Option<bool> {
self.previous_outputs.as_ref().map(|outputs| outputs.iter().any(|output| output.node_id == node_id))
}
/// A iterator of all nodes connected by primary inputs.
///
/// Used for the properties panel and tools.
pub fn primary_flow(&self) -> impl Iterator<Item = (&DocumentNode, u64)> {
struct FlowIter<'a> {
stack: Vec<NodeId>,
network: &'a NodeNetwork,
}
impl<'a> Iterator for FlowIter<'a> {
type Item = (&'a DocumentNode, NodeId);
fn next(&mut self) -> Option<Self::Item> {
loop {
let node_id = self.stack.pop()?;
if let Some(document_node) = self.network.nodes.get(&node_id) {
self.stack.extend(
document_node
.inputs
.iter()
.take(1) // Only show the primary input
.filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }),
);
return Some((document_node, node_id));
};
}
}
}
FlowIter {
stack: self.outputs.iter().map(|output| output.node_id).collect(),
network: &self,
}
}
}
#[cfg(test)]

View file

@ -27,7 +27,7 @@ pub enum TaggedValue {
RcImage(Option<Arc<graphene_core::raster::Image>>),
ImageFrame(graphene_core::raster::ImageFrame),
Color(graphene_core::raster::color::Color),
Subpath(bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>),
Subpaths(Vec<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>),
RcSubpath(Arc<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>),
BlendMode(BlendMode),
LuminanceCalculation(LuminanceCalculation),
@ -45,141 +45,65 @@ pub enum TaggedValue {
GradientType(graphene_core::vector::style::GradientType),
GradientPositions(Vec<(f64, Option<graphene_core::Color>)>),
Quantization(graphene_core::quantization::QuantizationChannels),
OptionalColor(Option<graphene_core::raster::color::Color>),
ManipulatorGroupIds(Vec<graphene_core::uuid::ManipulatorGroupId>),
}
#[allow(clippy::derived_hash_with_manual_eq)]
impl Hash for TaggedValue {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match self {
Self::None => 0.hash(state),
Self::String(s) => {
1.hash(state);
s.hash(state)
}
Self::U32(u) => {
2.hash(state);
u.hash(state)
}
Self::F32(f) => {
3.hash(state);
f.to_bits().hash(state)
}
Self::F64(f) => {
4.hash(state);
f.to_bits().hash(state)
}
Self::Bool(b) => {
5.hash(state);
b.hash(state)
}
Self::DVec2(v) => {
6.hash(state);
v.to_array().iter().for_each(|x| x.to_bits().hash(state))
}
Self::OptionalDVec2(None) => 7.hash(state),
Self::None => {}
Self::String(s) => s.hash(state),
Self::U32(u) => u.hash(state),
Self::F32(f) => f.to_bits().hash(state),
Self::F64(f) => f.to_bits().hash(state),
Self::Bool(b) => b.hash(state),
Self::DVec2(v) => v.to_array().iter().for_each(|x| x.to_bits().hash(state)),
Self::OptionalDVec2(None) => 0.hash(state),
Self::OptionalDVec2(Some(v)) => {
8.hash(state);
1.hash(state);
Self::DVec2(*v).hash(state)
}
Self::DAffine2(m) => {
9.hash(state);
m.to_cols_array().iter().for_each(|x| x.to_bits().hash(state))
}
Self::Image(i) => {
10.hash(state);
i.hash(state)
}
Self::RcImage(i) => {
11.hash(state);
i.hash(state)
}
Self::Color(c) => {
12.hash(state);
c.hash(state)
}
Self::Subpath(s) => {
13.hash(state);
s.hash(state)
}
Self::RcSubpath(s) => {
14.hash(state);
s.hash(state)
}
Self::BlendMode(b) => {
15.hash(state);
b.hash(state)
}
Self::LuminanceCalculation(l) => {
16.hash(state);
l.hash(state)
}
Self::ImaginateSamplingMethod(m) => {
17.hash(state);
m.hash(state)
}
Self::ImaginateMaskStartingFill(f) => {
18.hash(state);
f.hash(state)
}
Self::ImaginateStatus(s) => {
19.hash(state);
s.hash(state)
}
Self::LayerPath(p) => {
20.hash(state);
p.hash(state)
}
Self::DAffine2(m) => m.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)),
Self::Image(i) => i.hash(state),
Self::RcImage(i) => i.hash(state),
Self::Color(c) => c.hash(state),
Self::Subpaths(s) => s.iter().for_each(|subpath| subpath.hash(state)),
Self::RcSubpath(s) => s.hash(state),
Self::BlendMode(b) => b.hash(state),
Self::LuminanceCalculation(l) => l.hash(state),
Self::ImaginateSamplingMethod(m) => m.hash(state),
Self::ImaginateMaskStartingFill(f) => f.hash(state),
Self::ImaginateStatus(s) => s.hash(state),
Self::LayerPath(p) => p.hash(state),
Self::ImageFrame(i) => {
21.hash(state);
i.image.hash(state);
i.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state))
}
Self::VectorData(vector_data) => {
22.hash(state);
vector_data.subpaths.hash(state);
vector_data.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
vector_data.style.hash(state);
}
Self::Fill(fill) => {
23.hash(state);
fill.hash(state);
}
Self::Stroke(stroke) => {
24.hash(state);
stroke.hash(state);
}
Self::VecF32(vec_f32) => {
25.hash(state);
vec_f32.iter().for_each(|val| val.to_bits().hash(state));
}
Self::LineCap(line_cap) => {
26.hash(state);
line_cap.hash(state);
}
Self::LineJoin(line_join) => {
27.hash(state);
line_join.hash(state);
}
Self::FillType(fill_type) => {
28.hash(state);
fill_type.hash(state);
}
Self::GradientType(gradient_type) => {
29.hash(state);
gradient_type.hash(state);
}
Self::Fill(fill) => fill.hash(state),
Self::Stroke(stroke) => stroke.hash(state),
Self::VecF32(vec_f32) => vec_f32.iter().for_each(|val| val.to_bits().hash(state)),
Self::LineCap(line_cap) => line_cap.hash(state),
Self::LineJoin(line_join) => line_join.hash(state),
Self::FillType(fill_type) => fill_type.hash(state),
Self::GradientType(gradient_type) => gradient_type.hash(state),
Self::GradientPositions(gradient_positions) => {
30.hash(state);
gradient_positions.len().hash(state);
for (position, color) in gradient_positions {
position.to_bits().hash(state);
color.hash(state);
}
}
Self::Quantization(quantized_image) => {
31.hash(state);
quantized_image.hash(state);
}
Self::Quantization(quantized_image) => quantized_image.hash(state),
Self::OptionalColor(color) => color.hash(state),
Self::ManipulatorGroupIds(mirror) => mirror.hash(state),
}
}
}
@ -201,7 +125,7 @@ impl<'a> TaggedValue {
TaggedValue::RcImage(x) => Box::new(x),
TaggedValue::ImageFrame(x) => Box::new(x),
TaggedValue::Color(x) => Box::new(x),
TaggedValue::Subpath(x) => Box::new(x),
TaggedValue::Subpaths(x) => Box::new(x),
TaggedValue::RcSubpath(x) => Box::new(x),
TaggedValue::BlendMode(x) => Box::new(x),
TaggedValue::LuminanceCalculation(x) => Box::new(x),
@ -219,6 +143,8 @@ impl<'a> TaggedValue {
TaggedValue::GradientType(x) => Box::new(x),
TaggedValue::GradientPositions(x) => Box::new(x),
TaggedValue::Quantization(x) => Box::new(x),
TaggedValue::OptionalColor(x) => Box::new(x),
TaggedValue::ManipulatorGroupIds(x) => Box::new(x),
}
}
@ -238,7 +164,7 @@ impl<'a> TaggedValue {
TaggedValue::RcImage(_) => concrete!(Option<Arc<graphene_core::raster::Image>>),
TaggedValue::ImageFrame(_) => concrete!(graphene_core::raster::ImageFrame),
TaggedValue::Color(_) => concrete!(graphene_core::raster::Color),
TaggedValue::Subpath(_) => concrete!(bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>),
TaggedValue::Subpaths(_) => concrete!(Vec<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>),
TaggedValue::RcSubpath(_) => concrete!(Arc<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>),
TaggedValue::BlendMode(_) => concrete!(BlendMode),
TaggedValue::ImaginateSamplingMethod(_) => concrete!(ImaginateSamplingMethod),
@ -257,6 +183,8 @@ impl<'a> TaggedValue {
TaggedValue::GradientType(_) => concrete!(graphene_core::vector::style::GradientType),
TaggedValue::GradientPositions(_) => concrete!(Vec<(f64, Option<graphene_core::Color>)>),
TaggedValue::Quantization(_) => concrete!(graphene_core::quantization::QuantizationChannels),
TaggedValue::OptionalColor(_) => concrete!(Option<graphene_core::Color>),
TaggedValue::ManipulatorGroupIds(_) => concrete!(Vec<graphene_core::uuid::ManipulatorGroupId>),
}
}
}

View file

@ -609,12 +609,12 @@ mod test {
assert_eq!(
ids,
vec![
7332206428857154453,
946497269036214321,
3038115864048241698,
1932610308557160863,
2105748431407297710,
8596220090685862327
10739226043134366700,
17332796976541881019,
7897288931440576543,
7388412494950743023,
359700384277940942,
12822947441562012352
]
);
}