mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
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:
committed by
Keavon Chambers
parent
639a24d8ad
commit
959e790cdf
@@ -7,26 +7,31 @@ use crate::vector::VectorData;
|
||||
use crate::Node;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TransformNode<Translation, Rotation, Scale, Shear> {
|
||||
pub struct TransformNode<Translation, Rotation, Scale, Shear, Pivot> {
|
||||
pub(crate) translate: Translation,
|
||||
pub(crate) rotate: Rotation,
|
||||
pub(crate) scale: Scale,
|
||||
pub(crate) shear: Shear,
|
||||
pub(crate) pivot: Pivot,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(TransformNode)]
|
||||
pub(crate) fn transform_vector_data(mut vector_data: VectorData, translate: DVec2, rotate: f64, scale: DVec2, shear: DVec2) -> VectorData {
|
||||
let transform = generate_transform(shear, &vector_data.transform, scale, rotate, translate);
|
||||
vector_data.transform = transform * vector_data.transform;
|
||||
pub(crate) fn transform_vector_data(mut vector_data: VectorData, translate: DVec2, rotate: f64, scale: DVec2, shear: DVec2, pivot: DVec2) -> VectorData {
|
||||
let pivot = DAffine2::from_translation(vector_data.local_pivot(pivot));
|
||||
|
||||
let modification = pivot * DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]) * pivot.inverse();
|
||||
vector_data.transform = modification * vector_data.transform;
|
||||
|
||||
vector_data
|
||||
}
|
||||
|
||||
impl<'input, Translation: 'input, Rotation: 'input, Scale: 'input, Shear: 'input> Node<'input, ImageFrame> for TransformNode<Translation, Rotation, Scale, Shear>
|
||||
impl<'input, Translation: 'input, Rotation: 'input, Scale: 'input, Shear: 'input, Pivot: 'input> Node<'input, ImageFrame> for TransformNode<Translation, Rotation, Scale, Shear, Pivot>
|
||||
where
|
||||
Translation: for<'any_input> Node<'any_input, (), Output = DVec2>,
|
||||
Rotation: for<'any_input> Node<'any_input, (), Output = f64>,
|
||||
Scale: for<'any_input> Node<'any_input, (), Output = DVec2>,
|
||||
Shear: for<'any_input> Node<'any_input, (), Output = DVec2>,
|
||||
Pivot: for<'any_input> Node<'any_input, (), Output = DVec2>,
|
||||
{
|
||||
type Output = ImageFrame;
|
||||
#[inline]
|
||||
@@ -48,6 +53,5 @@ fn generate_transform(shear: DVec2, transform: &DAffine2, scale: DVec2, rotate:
|
||||
let pivot = transform.transform_point2(DVec2::splat(0.5));
|
||||
let translate_to_center = DAffine2::from_translation(-pivot);
|
||||
|
||||
let transformation = translate_to_center.inverse() * DAffine2::from_scale_angle_translation(scale, rotate, translate) * shear_matrix * translate_to_center;
|
||||
transformation
|
||||
translate_to_center.inverse() * DAffine2::from_scale_angle_translation(scale, rotate, translate) * shear_matrix * translate_to_center
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, specta::Type)]
|
||||
@@ -70,7 +71,7 @@ mod uuid_generation {
|
||||
|
||||
pub use uuid_generation::*;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ManipulatorGroupId(u64);
|
||||
|
||||
|
||||
@@ -17,41 +17,43 @@ pub struct UnitSquareGenerator;
|
||||
|
||||
#[node_macro::node_fn(UnitSquareGenerator)]
|
||||
fn unit_square(_input: ()) -> VectorData {
|
||||
super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE))
|
||||
super::VectorData::from_subpaths(vec![Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE)])
|
||||
}
|
||||
|
||||
// TODO: I removed the Arc requirement we shouuld think about when it makes sense to use its
|
||||
// vs making a generic value node
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PathGenerator;
|
||||
pub struct PathGenerator<Mirror> {
|
||||
mirror: Mirror,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(PathGenerator)]
|
||||
fn generate_path(path_data: Subpath<ManipulatorGroupId>) -> super::VectorData {
|
||||
super::VectorData::from_subpath(path_data)
|
||||
fn generate_path(path_data: Vec<Subpath<ManipulatorGroupId>>, mirror: Vec<ManipulatorGroupId>) -> super::VectorData {
|
||||
let mut vector_data = super::VectorData::from_subpaths(path_data);
|
||||
vector_data.mirror_angle = mirror;
|
||||
vector_data
|
||||
}
|
||||
|
||||
use crate::raster::Image;
|
||||
// #[derive(Debug, Clone, Copy)]
|
||||
// pub struct BlitSubpath<P> {
|
||||
// path_data: P,
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BlitSubpath<P> {
|
||||
path_data: P,
|
||||
}
|
||||
// #[node_macro::node_fn(BlitSubpath)]
|
||||
// fn blit_subpath(base_image: Image, path_data: VectorData) -> Image {
|
||||
// // TODO: Get forma to compile
|
||||
// use forma::prelude::*;
|
||||
// let composition = Composition::new();
|
||||
// let mut renderer = cpu::Renderer::new();
|
||||
// let mut path_builder = PathBuilder::new();
|
||||
// for path_segment in path_data.bezier_iter() {
|
||||
// let points = path_segment.internal.get_points().collect::<Vec<_>>();
|
||||
// match points.len() {
|
||||
// 2 => path_builder.line_to(points[1].into()),
|
||||
// 3 => path_builder.quad_to(points[1].into(), points[2].into()),
|
||||
// 4 => path_builder.cubic_to(points[1].into(), points[2].into(), points[3].into()),
|
||||
// }
|
||||
// }
|
||||
|
||||
#[node_macro::node_fn(BlitSubpath)]
|
||||
fn bilt_subpath(base_image: Image, path_data: VectorData) -> Image {
|
||||
// TODO: Get forma to compile
|
||||
/*use forma::prelude::*;
|
||||
let composition = Composition::new();
|
||||
let mut renderer = cpu::Renderer::new();
|
||||
let mut path_builder = PathBuilder::new();
|
||||
for path_segment in path_data.bezier_iter() {
|
||||
let points = path_segment.internal.get_points().collect::<Vec<_>>();
|
||||
match points.len() {
|
||||
2 => path_builder.line_to(points[1].into()),
|
||||
3 => path_builder.quad_to(points[1].into(), points[2].into()),
|
||||
4 => path_builder.cubic_to(points[1].into(), points[2].into(), points[3].into()),
|
||||
}
|
||||
}*/
|
||||
|
||||
base_image
|
||||
}
|
||||
// base_image
|
||||
// }
|
||||
|
||||
@@ -10,7 +10,7 @@ pub mod subpath;
|
||||
pub use subpath::Subpath;
|
||||
|
||||
mod vector_data;
|
||||
pub use vector_data::VectorData;
|
||||
pub use vector_data::*;
|
||||
|
||||
mod id_vec;
|
||||
pub use id_vec::IdBackedVec;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::uuid::ManipulatorGroupId;
|
||||
|
||||
use super::consts::ManipulatorType;
|
||||
use super::id_vec::IdBackedVec;
|
||||
use super::manipulator_group::ManipulatorGroup;
|
||||
@@ -45,6 +47,21 @@ impl Subpath {
|
||||
shape.path_elements(0.1).into()
|
||||
}
|
||||
|
||||
/// Convert to the legacy Subpath from the `bezier_rs::Subpath`.
|
||||
pub fn from_bezier_crate(value: &[bezier_rs::Subpath<ManipulatorGroupId>]) -> Self {
|
||||
let mut groups = IdBackedVec::new();
|
||||
for subpath in value {
|
||||
for group in subpath.manipulator_groups() {
|
||||
groups.push(ManipulatorGroup::new_with_handles(group.anchor, group.in_handle, group.out_handle));
|
||||
}
|
||||
if subpath.closed() {
|
||||
let group = subpath.manipulator_groups()[0];
|
||||
groups.push(ManipulatorGroup::new_with_handles(group.anchor, group.in_handle, group.out_handle));
|
||||
}
|
||||
}
|
||||
Self(groups)
|
||||
}
|
||||
|
||||
// ** PRIMITIVE CONSTRUCTION **
|
||||
|
||||
/// constructs a rectangle with `p1` as the lower left and `p2` as the top right
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use super::style::{PathStyle, Stroke};
|
||||
use crate::{uuid::ManipulatorGroupId, Color};
|
||||
|
||||
use bezier_rs::ManipulatorGroup;
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use glam::DAffine2;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
/// [VectorData] is passed between nodes.
|
||||
/// It contains a list of subpaths (that may be open or closed), a transform and some style information.
|
||||
@@ -12,22 +13,146 @@ pub struct VectorData {
|
||||
pub subpaths: Vec<bezier_rs::Subpath<ManipulatorGroupId>>,
|
||||
pub transform: DAffine2,
|
||||
pub style: PathStyle,
|
||||
pub mirror_angle: Vec<ManipulatorGroupId>,
|
||||
}
|
||||
|
||||
impl VectorData {
|
||||
/// An empty subpath with no data, an identity transform and a black fill.
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
subpaths: Vec::new(),
|
||||
transform: DAffine2::IDENTITY,
|
||||
style: PathStyle::new(Some(Stroke::new(Color::BLACK, 0.)), super::style::Fill::Solid(Color::BLACK)),
|
||||
style: PathStyle::new(Some(Stroke::new(Color::BLACK, 0.)), super::style::Fill::None),
|
||||
mirror_angle: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over the manipulator groups of the subpaths
|
||||
pub fn manipulator_groups(&self) -> impl Iterator<Item = &ManipulatorGroup<ManipulatorGroupId>> + DoubleEndedIterator {
|
||||
self.subpaths.iter().flat_map(|subpath| subpath.manipulator_groups())
|
||||
}
|
||||
|
||||
pub fn manipulator_from_id(&self, id: ManipulatorGroupId) -> Option<&ManipulatorGroup<ManipulatorGroupId>> {
|
||||
self.subpaths.iter().find_map(|subpath| subpath.manipulator_from_id(id))
|
||||
}
|
||||
|
||||
/// Construct some new vector data from a single subpath with an identy transform and black fill.
|
||||
pub fn from_subpath(subpath: bezier_rs::Subpath<ManipulatorGroupId>) -> Self {
|
||||
super::VectorData {
|
||||
subpaths: vec![subpath],
|
||||
transform: DAffine2::IDENTITY,
|
||||
style: PathStyle::default(),
|
||||
Self::from_subpaths(vec![subpath])
|
||||
}
|
||||
|
||||
/// Construct some new vector data from subpaths with an identy transform and black fill.
|
||||
pub fn from_subpaths(subpaths: Vec<bezier_rs::Subpath<ManipulatorGroupId>>) -> Self {
|
||||
super::VectorData { subpaths, ..Self::empty() }
|
||||
}
|
||||
|
||||
/// Compute the bounding boxes of the subpaths without any transform
|
||||
pub fn bounding_box(&self) -> Option<[DVec2; 2]> {
|
||||
self.bounding_box_with_transform(DAffine2::IDENTITY)
|
||||
}
|
||||
|
||||
/// Compute the bounding boxes of the subpaths with the specified transform
|
||||
pub fn bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.subpaths
|
||||
.iter()
|
||||
.filter_map(|subpath| subpath.bounding_box_with_transform(transform))
|
||||
.reduce(|b1, b2| [b1[0].min(b2[0]), b1[1].max(b2[1])])
|
||||
}
|
||||
|
||||
/// Calculate the corners of the bounding box but with a nonzero size.
|
||||
///
|
||||
/// If the layer bounds are `0` in either axis then they are changed to be `1`.
|
||||
pub fn nonzero_bounding_box(&self) -> [DVec2; 2] {
|
||||
let [bounds_min, mut bounds_max] = self.bounding_box().unwrap_or_default();
|
||||
|
||||
let bounds_size = bounds_max - bounds_min;
|
||||
if bounds_size.x < 1e-10 {
|
||||
bounds_max.x = bounds_min.x + 1.;
|
||||
}
|
||||
if bounds_size.y < 1e-10 {
|
||||
bounds_max.y = bounds_min.y + 1.;
|
||||
}
|
||||
|
||||
[bounds_min, bounds_max]
|
||||
}
|
||||
|
||||
/// Compute the pivot of the layer in layerspace (the coordinates of the subpaths)
|
||||
pub fn layerspace_pivot(&self, normalised_pivot: DVec2) -> DVec2 {
|
||||
let [bounds_min, bounds_max] = self.nonzero_bounding_box();
|
||||
let bounds_size = bounds_max - bounds_min;
|
||||
bounds_min + bounds_size * normalised_pivot
|
||||
}
|
||||
|
||||
/// Compute the pivot in local space with the current transform applied
|
||||
pub fn local_pivot(&self, normalised_pivot: DVec2) -> DVec2 {
|
||||
self.transform.transform_point2(self.layerspace_pivot(normalised_pivot))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VectorData {
|
||||
fn default() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ManipulatorPointId {
|
||||
pub group: ManipulatorGroupId,
|
||||
pub manipulator_type: SelectedType,
|
||||
}
|
||||
impl ManipulatorPointId {
|
||||
pub fn new(group: ManipulatorGroupId, manipulator_type: SelectedType) -> Self {
|
||||
Self { group, manipulator_type }
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum SelectedType {
|
||||
Anchor = 1 << 0,
|
||||
InHandle = 1 << 1,
|
||||
OutHandle = 1 << 2,
|
||||
}
|
||||
impl SelectedType {
|
||||
/// Get the location of the [SelectedType] in the [ManipulatorGroup]
|
||||
pub fn get_position(&self, manipulator_group: &ManipulatorGroup<ManipulatorGroupId>) -> Option<DVec2> {
|
||||
match self {
|
||||
Self::Anchor => Some(manipulator_group.anchor),
|
||||
Self::InHandle => manipulator_group.in_handle,
|
||||
Self::OutHandle => manipulator_group.out_handle,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the closest [SelectedType] in the [ManipulatorGroup].
|
||||
pub fn closest_widget(manipulator_group: &ManipulatorGroup<ManipulatorGroupId>, transform_space: DAffine2, target: DVec2, hide_handle_distance: f64) -> (Self, f64) {
|
||||
let anchor = transform_space.transform_point2(manipulator_group.anchor);
|
||||
// Skip handles under the anchor
|
||||
let not_under_anchor = |&(selected_type, position): &(SelectedType, DVec2)| selected_type == Self::Anchor || position.distance_squared(anchor) > hide_handle_distance.powi(2);
|
||||
let compute_distance = |selected_type: Self| {
|
||||
selected_type.get_position(manipulator_group).and_then(|position| {
|
||||
Some((selected_type, transform_space.transform_point2(position)))
|
||||
.filter(not_under_anchor)
|
||||
.map(|(selected_type, pos)| (selected_type, pos.distance_squared(target)))
|
||||
})
|
||||
};
|
||||
[Self::Anchor, Self::InHandle, Self::OutHandle]
|
||||
.into_iter()
|
||||
.filter_map(compute_distance)
|
||||
.min_by(|a, b| a.1.total_cmp(&b.1))
|
||||
.unwrap_or((Self::Anchor, manipulator_group.anchor.distance_squared(target)))
|
||||
}
|
||||
|
||||
/// Opposite handle
|
||||
pub fn opposite(&self) -> Self {
|
||||
match self {
|
||||
SelectedType::Anchor => SelectedType::Anchor,
|
||||
SelectedType::InHandle => SelectedType::OutHandle,
|
||||
SelectedType::OutHandle => SelectedType::InHandle,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if handle
|
||||
pub fn is_handle(self) -> bool {
|
||||
self != SelectedType::Anchor
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ pub struct SetFillNode<FillType, SolidColor, GradientType, Start, End, Transform
|
||||
fn set_vector_data_fill(
|
||||
mut vector_data: VectorData,
|
||||
fill_type: FillType,
|
||||
solid_color: Color,
|
||||
solid_color: Option<Color>,
|
||||
gradient_type: GradientType,
|
||||
start: DVec2,
|
||||
end: DVec2,
|
||||
@@ -26,8 +26,7 @@ fn set_vector_data_fill(
|
||||
positions: Vec<(f64, Option<Color>)>,
|
||||
) -> VectorData {
|
||||
vector_data.style.set_fill(match fill_type {
|
||||
FillType::None => Fill::None,
|
||||
FillType::Solid => Fill::Solid(solid_color),
|
||||
FillType::None | FillType::Solid => solid_color.map_or(Fill::None, |solid_color| Fill::Solid(solid_color)),
|
||||
FillType::Gradient => Fill::Gradient(Gradient {
|
||||
start,
|
||||
end,
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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>),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,12 +609,12 @@ mod test {
|
||||
assert_eq!(
|
||||
ids,
|
||||
vec![
|
||||
7332206428857154453,
|
||||
946497269036214321,
|
||||
3038115864048241698,
|
||||
1932610308557160863,
|
||||
2105748431407297710,
|
||||
8596220090685862327
|
||||
10739226043134366700,
|
||||
17332796976541881019,
|
||||
7897288931440576543,
|
||||
7388412494950743023,
|
||||
359700384277940942,
|
||||
12822947441562012352
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ where
|
||||
fn eval<'node: 'input>(&'node self, input: Any<'input>) -> Self::Output {
|
||||
{
|
||||
let node_name = core::any::type_name::<N>();
|
||||
let input: Box<_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyNode Input, {e} in:\n{node_name}"));
|
||||
let input: Box<_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyRefNode Input, {e} in:\n{node_name}"));
|
||||
Box::new(self.node.eval(*input))
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ where
|
||||
fn eval<'node: 'input>(&'node self, input: Any<'input>) -> Self::Output {
|
||||
{
|
||||
let node_name = core::any::type_name::<N>();
|
||||
let input: Box<&_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyNode Input, {e} in:\n{node_name}"));
|
||||
let input: Box<&_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyInRefNode Input, {e} in:\n{node_name}"));
|
||||
Box::new(self.node.eval(*input))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +197,16 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||
},
|
||||
NodeIOTypes::new(generic!(T), concrete!(ImageFrame), vec![(concrete!(()), concrete!(ImageFrame))]),
|
||||
),
|
||||
(
|
||||
NodeIdentifier::new("graphene_std::memo::EndLetNode<_>"),
|
||||
|args| {
|
||||
let input: DowncastBothNode<(), VectorData> = DowncastBothNode::new(args[0]);
|
||||
let node = graphene_std::memo::EndLetNode::new(input);
|
||||
let any: DynAnyInRefNode<ImageFrame, _, _> = graphene_std::any::DynAnyInRefNode::new(node);
|
||||
any.into_type_erased()
|
||||
},
|
||||
NodeIOTypes::new(generic!(T), concrete!(ImageFrame), vec![(concrete!(()), concrete!(VectorData))]),
|
||||
),
|
||||
(
|
||||
NodeIdentifier::new("graphene_std::memo::RefNode<_, _>"),
|
||||
|args| {
|
||||
@@ -336,15 +346,15 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||
raster_node!(graphene_core::quantization::QuantizeNode<_>, params: [QuantizationChannels]),
|
||||
raster_node!(graphene_core::quantization::DeQuantizeNode<_>, params: [QuantizationChannels]),
|
||||
register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _>, input: VectorData, params: [DVec2, f64, DVec2, DVec2]),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _>, input: ImageFrame, params: [DVec2, f64, DVec2, DVec2]),
|
||||
register_node!(graphene_core::vector::SetFillNode<_, _, _, _, _, _, _>, input: VectorData, params: [ graphene_core::vector::style::FillType, graphene_core::Color, graphene_core::vector::style::GradientType, DVec2, DVec2, DAffine2, Vec<(f64, Option<graphene_core::Color>)>]),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: VectorData, params: [DVec2, f64, DVec2, DVec2, DVec2]),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: ImageFrame, params: [DVec2, f64, DVec2, DVec2, DVec2]),
|
||||
register_node!(graphene_core::vector::SetFillNode<_, _, _, _, _, _, _>, input: VectorData, params: [ graphene_core::vector::style::FillType, Option<graphene_core::Color>, graphene_core::vector::style::GradientType, DVec2, DVec2, DAffine2, Vec<(f64, Option<graphene_core::Color>)>]),
|
||||
register_node!(graphene_core::vector::SetStrokeNode<_, _, _, _, _, _, _>, input: VectorData, params: [graphene_core::Color, f64, Vec<f32>, f64, graphene_core::vector::style::LineCap, graphene_core::vector::style::LineJoin, f64]),
|
||||
register_node!(graphene_core::vector::generator_nodes::UnitCircleGenerator, input: (), params: []),
|
||||
register_node!(
|
||||
graphene_core::vector::generator_nodes::PathGenerator,
|
||||
input: graphene_core::vector::bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>,
|
||||
params: []
|
||||
graphene_core::vector::generator_nodes::PathGenerator<_>,
|
||||
input: Vec<graphene_core::vector::bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>,
|
||||
params: [Vec<graphene_core::uuid::ManipulatorGroupId>]
|
||||
),
|
||||
];
|
||||
let mut map: HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
|
||||
|
||||
Reference in New Issue
Block a user