Add Layer and Artboard node definitions and underlying data structures (#1204)

* Add basic node defenitions

* Code review

* change widget code

* Artboard node changes

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube 2023-05-09 21:57:22 +01:00 committed by Keavon Chambers
parent 7e1b452757
commit 4e0c673a35
12 changed files with 295 additions and 36 deletions

View file

@ -0,0 +1,140 @@
use crate::raster::{BlendMode, ImageFrame};
use crate::vector::VectorData;
use crate::{Color, Node};
use dyn_any::{DynAny, StaticType};
use core::ops::{Deref, DerefMut};
use glam::IVec2;
use node_macro::node_fn;
/// A list of [`GraphicElement`]s
#[derive(Clone, Debug, Hash, PartialEq, DynAny, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GraphicGroup(Vec<GraphicElement>);
/// Internal data for a [`GraphicElement`]. Can be [`VectorData`], [`ImageFrame`], text, or a nested [`GraphicGroup`]
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum GraphicElementData {
VectorShape(Box<VectorData>),
ImageFrame(ImageFrame<Color>),
Text(String),
GraphicGroup(GraphicGroup),
Artboard(Artboard),
}
/// A named [`GraphicElementData`] with a blend mode, opacity, as well as visibility, locked, and collapsed states.
#[derive(Clone, Debug, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GraphicElement {
pub name: String,
pub blend_mode: BlendMode,
/// In range 0..=1
pub opacity: f32,
pub visible: bool,
pub locked: bool,
pub collapsed: bool,
pub graphic_element_data: GraphicElementData,
}
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
/// Similar to an Inkscape page: https://media.inkscape.org/media/doc/release_notes/1.2/Inkscape_1.2.html#Page_tool
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Artboard {
pub graphic_group: GraphicGroup,
pub bounds: Option<[IVec2; 2]>,
}
pub struct ConstructLayerNode<Name, BlendMode, Opacity, Visible, Locked, Collapsed, Stack> {
name: Name,
blend_mode: BlendMode,
opacity: Opacity,
visible: Visible,
locked: Locked,
collapsed: Collapsed,
stack: Stack,
}
#[node_fn(ConstructLayerNode)]
fn construct_layer<Data: Into<GraphicElementData>>(
graphic_element_data: Data,
name: String,
blend_mode: BlendMode,
opacity: f32,
visible: bool,
locked: bool,
collapsed: bool,
mut stack: GraphicGroup,
) -> GraphicGroup {
stack.push(GraphicElement {
name,
blend_mode,
opacity: opacity / 100.,
visible,
locked,
collapsed,
graphic_element_data: graphic_element_data.into(),
});
stack
}
pub struct ConstructArtboardNode<Bounds> {
bounds: Bounds,
}
#[node_fn(ConstructArtboardNode)]
fn construct_artboard(graphic_group: GraphicGroup, bounds: Option<[IVec2; 2]>) -> Artboard {
Artboard { graphic_group, bounds }
}
impl From<ImageFrame<Color>> for GraphicElementData {
fn from(image_frame: ImageFrame<Color>) -> Self {
GraphicElementData::ImageFrame(image_frame)
}
}
impl From<VectorData> for GraphicElementData {
fn from(vector_data: VectorData) -> Self {
GraphicElementData::VectorShape(Box::new(vector_data))
}
}
impl From<GraphicGroup> for GraphicElementData {
fn from(graphic_group: GraphicGroup) -> Self {
GraphicElementData::GraphicGroup(graphic_group)
}
}
impl From<Artboard> for GraphicElementData {
fn from(artboard: Artboard) -> Self {
GraphicElementData::Artboard(artboard)
}
}
impl Deref for GraphicGroup {
type Target = Vec<GraphicElement>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for GraphicGroup {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl GraphicGroup {
pub const EMPTY: Self = Self(Vec::new());
}
impl core::hash::Hash for GraphicElement {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.blend_mode.hash(state);
self.opacity.to_bits().hash(state);
self.visible.hash(state);
self.locked.hash(state);
self.collapsed.hash(state);
self.graphic_element_data.hash(state);
}
}

View file

@ -26,6 +26,10 @@ pub mod raster;
#[cfg(feature = "alloc")]
pub mod transform;
#[cfg(feature = "alloc")]
mod graphic_element;
#[cfg(feature = "alloc")]
pub use graphic_element::*;
#[cfg(feature = "alloc")]
pub mod vector;

View file

@ -17,6 +17,15 @@ pub struct VectorData {
pub mirror_angle: Vec<ManipulatorGroupId>,
}
impl core::hash::Hash for VectorData {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.subpaths.hash(state);
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
self.style.hash(state);
self.mirror_angle.hash(state);
}
}
impl VectorData {
/// An empty subpath with no data, an identity transform, and a black fill.
pub const fn empty() -> Self {

View file

@ -505,6 +505,30 @@ impl NodeNetwork {
network: self,
}
}
pub fn is_acyclic(&self) -> bool {
let mut dependencies: HashMap<u64, Vec<u64>> = HashMap::new();
for (node_id, node) in &self.nodes {
dependencies.insert(
*node_id,
node.inputs
.iter()
.filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None })
.collect(),
);
}
while !dependencies.is_empty() {
let Some((&disconnected, _)) = dependencies.iter().find(|(_, l)| l.is_empty()) else {
error!("Dependencies {dependencies:?}");
return false
};
dependencies.remove(&disconnected);
for connections in dependencies.values_mut() {
connections.retain(|&id| id != disconnected);
}
}
true
}
}
/// Functions for compiling the network
@ -762,19 +786,16 @@ impl NodeNetwork {
self.nodes.retain(|_, node| !matches!(node.implementation, DocumentNodeImplementation::Extract));
for (_, node) in &mut extraction_nodes {
match node.implementation {
DocumentNodeImplementation::Extract => {
assert_eq!(node.inputs.len(), 1);
let NodeInput::Node { node_id, output_index, lambda } = node.inputs.pop().unwrap() else {
panic!("Extract node has no input");
};
assert_eq!(output_index, 0);
assert!(lambda);
let input_node = self.nodes.get_mut(&node_id).unwrap();
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into());
node.inputs = vec![NodeInput::value(TaggedValue::DocumentNode(input_node.clone()), false)];
}
_ => (),
if let DocumentNodeImplementation::Extract = node.implementation {
assert_eq!(node.inputs.len(), 1);
let NodeInput::Node { node_id, output_index, lambda } = node.inputs.pop().unwrap() else {
panic!("Extract node has no input");
};
assert_eq!(output_index, 0);
assert!(lambda);
let input_node = self.nodes.get_mut(&node_id).unwrap();
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into());
node.inputs = vec![NodeInput::value(TaggedValue::DocumentNode(input_node.clone()), false)];
}
}
self.nodes.extend(extraction_nodes);

View file

@ -57,6 +57,9 @@ pub enum TaggedValue {
Segments(Vec<graphene_core::raster::ImageFrame<Color>>),
EditorApi(graphene_core::EditorApi<'static>),
DocumentNode(DocumentNode),
GraphicGroup(graphene_core::GraphicGroup),
Artboard(graphene_core::Artboard),
Optional2IVec2(Option<[glam::IVec2; 2]>),
}
#[allow(clippy::derived_hash_with_manual_eq)]
@ -88,15 +91,8 @@ impl Hash for TaggedValue {
Self::ImaginateMaskStartingFill(f) => f.hash(state),
Self::ImaginateStatus(s) => s.hash(state),
Self::LayerPath(p) => p.hash(state),
Self::ImageFrame(i) => {
i.image.hash(state);
i.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state))
}
Self::VectorData(vector_data) => {
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::ImageFrame(i) => i.hash(state),
Self::VectorData(vector_data) => vector_data.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)),
@ -131,6 +127,9 @@ impl Hash for TaggedValue {
}
Self::EditorApi(editor_api) => editor_api.hash(state),
Self::DocumentNode(document_node) => document_node.hash(state),
Self::GraphicGroup(graphic_group) => graphic_group.hash(state),
Self::Artboard(artboard) => artboard.hash(state),
Self::Optional2IVec2(v) => v.hash(state),
}
}
}
@ -180,6 +179,9 @@ impl<'a> TaggedValue {
TaggedValue::Segments(x) => Box::new(x),
TaggedValue::EditorApi(x) => Box::new(x),
TaggedValue::DocumentNode(x) => Box::new(x),
TaggedValue::GraphicGroup(x) => Box::new(x),
TaggedValue::Artboard(x) => Box::new(x),
TaggedValue::Optional2IVec2(x) => Box::new(x),
}
}
@ -240,6 +242,9 @@ impl<'a> TaggedValue {
TaggedValue::Segments(_) => concrete!(graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>),
TaggedValue::EditorApi(_) => concrete!(graphene_core::EditorApi),
TaggedValue::DocumentNode(_) => concrete!(crate::document::DocumentNode),
TaggedValue::GraphicGroup(_) => concrete!(graphene_core::GraphicGroup),
TaggedValue::Artboard(_) => concrete!(graphene_core::Artboard),
TaggedValue::Optional2IVec2(_) => concrete!(Option<[glam::IVec2; 2]>),
}
}
}

View file

@ -532,6 +532,11 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::text::TextGenerator<_, _, _>, input: graphene_core::EditorApi, params: [String, graphene_core::text::Font, f64]),
register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []),
register_node!(graphene_core::ExtractImageFrame, input: graphene_core::EditorApi, 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: ImageFrame<Color>, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::GraphicGroup, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::Artboard, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
register_node!(graphene_core::ConstructArtboardNode<_>, input: graphene_core::GraphicGroup, params: [Option<[glam::IVec2; 2]>]),
];
let mut map: HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
for (id, c, types) in node_types.into_iter().flatten() {