Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ pub enum FrontendGraphDataType {
Boolean,
#[serde(rename = "vec2")]
Vector,
#[serde(rename = "graphic")]
GraphicGroup,
#[serde(rename = "artboard")]
Artboard,
}
impl FrontendGraphDataType {
pub const fn with_tagged_value(value: &TaggedValue) -> Self {
Expand All @@ -46,6 +50,8 @@ impl FrontendGraphDataType {
TaggedValue::ImageFrame(_) => Self::Raster,
TaggedValue::Color(_) => Self::Color,
TaggedValue::RcSubpath(_) | TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::Subpath,
TaggedValue::GraphicGroup(_) => Self::GraphicGroup,
TaggedValue::Artboard(_) => Self::Artboard,
_ => Self::General,
}
}
Expand Down Expand Up @@ -750,6 +756,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
warn!("No network");
return;
};
debug_assert!(network.is_acyclic(), "Not acyclic. Network: {network:#?}");
let outwards_links = network.collect_outwards_links();
let required_shift = |left: NodeId, right: NodeId, network: &NodeNetwork| {
if let (Some(left), Some(right)) = (network.nodes.get(&left), network.nodes.get(&right)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,34 @@ fn static_nodes() -> Vec<DocumentNodeType> {
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::General)],
properties: |_document_node, _node_id, _context| node_properties::string_properties("The Monitor node stores the value of its last evaluation"),
},
DocumentNodeType {
name: "Layer",
category: "General",
identifier: NodeImplementation::proto("graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>"),
inputs: vec![
DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
DocumentInputType::value("Name", TaggedValue::String(String::new()), false),
DocumentInputType::value("Blend Mode", TaggedValue::BlendMode(BlendMode::Normal), false),
DocumentInputType::value("Opacity", TaggedValue::F32(100.), false),
DocumentInputType::value("Visible", TaggedValue::Bool(true), false),
DocumentInputType::value("Locked", TaggedValue::Bool(false), false),
DocumentInputType::value("Collapsed", TaggedValue::Bool(false), false),
DocumentInputType::value("Stack", TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true),
],
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::GraphicGroup)],
properties: node_properties::layer_properties,
},
DocumentNodeType {
name: "Artboard",
category: "General",
identifier: NodeImplementation::proto("graphene_core::ConstructArtboardNode<_>"),
inputs: vec![
DocumentInputType::value("Graphic Group", TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true),
DocumentInputType::value("Bounds", TaggedValue::Optional2IVec2(None), false),
],
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::Artboard)],
properties: node_properties::artboard_properties,
},
DocumentNodeType {
name: "Downres",
category: "Ignore",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ fn start_widgets(document_node: &DocumentNode, node_id: NodeId, index: usize, na
widgets
}

#[cfg(feature = "gpu")]
fn text_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> Vec<WidgetHolder> {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Text, blank_assist);

Expand Down Expand Up @@ -203,7 +202,7 @@ fn number_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, na
WidgetHolder::unrelated_separator(),
number_props
.value(Some(x))
.on_update(update_value(|x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, index))
.on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, index))
.widget_holder(),
])
} else if let NodeInput::Value {
Expand All @@ -215,7 +214,19 @@ fn number_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, na
WidgetHolder::unrelated_separator(),
number_props
.value(Some(x as f64))
.on_update(update_value(|x: &NumberInput| TaggedValue::U32(x.value.unwrap() as u32), node_id, index))
.on_update(update_value(move |x: &NumberInput| TaggedValue::U32((x.value.unwrap()) as u32), node_id, index))
.widget_holder(),
])
} else if let NodeInput::Value {
tagged_value: TaggedValue::F32(x),
exposed: false,
} = document_node.inputs[index]
{
widgets.extend_from_slice(&[
WidgetHolder::unrelated_separator(),
number_props
.value(Some(x as f64))
.on_update(update_value(move |x: &NumberInput| TaggedValue::F32((x.value.unwrap()) as f32), node_id, index))
.widget_holder(),
])
}
Expand Down Expand Up @@ -788,14 +799,8 @@ pub fn quantize_properties(document_node: &DocumentNode, node_id: NodeId, _conte
pub fn exposure_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let exposure = number_widget(document_node, node_id, 1, "Exposure", NumberInput::default().min(-20.).max(20.), true);
let offset = number_widget(document_node, node_id, 2, "Offset", NumberInput::default().min(-0.5).max(0.5), true);
let gamma_correction = number_widget(
document_node,
node_id,
3,
"Gamma Correction",
NumberInput::default().min(0.01).max(9.99).mode_increment().increment_step(0.1),
true,
);
let gamma_input = NumberInput::default().min(0.01).max(9.99).mode_increment().increment_step(0.1);
let gamma_correction = number_widget(document_node, node_id, 3, "Gamma Correction", gamma_input, true);

vec![
LayoutGroup::Row { widgets: exposure },
Expand Down Expand Up @@ -1434,7 +1439,8 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
};

let blur_radius = {
let widgets = number_widget(document_node, node_id, mask_blur_index, "Mask Blur", NumberInput::default().unit(" px").min(0.).max(25.).int(), true);
let number_props = NumberInput::default().unit(" px").min(0.).max(25.).int();
let widgets = number_widget(document_node, node_id, mask_blur_index, "Mask Blur", number_props, true);
LayoutGroup::Row { widgets }.with_tooltip("Blur radius for the mask. Useful for softening sharp edges to blend the masked area with the rest of the image.")
};

Expand Down Expand Up @@ -1590,3 +1596,25 @@ pub fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context:

widgets
}

pub fn layer_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let name = text_widget(document_node, node_id, 1, "Name", true);
let blend_mode = blend_mode(document_node, node_id, 2, "Blend Mode", true);
let opacity = number_widget(document_node, node_id, 3, "Opacity", NumberInput::default().min(0.).max(100.).unit("%"), true);
let visible = bool_widget(document_node, node_id, 4, "Visible", true);
let locked = bool_widget(document_node, node_id, 5, "Locked", true);
let collapsed = bool_widget(document_node, node_id, 6, "Collapsed", true);

vec![
LayoutGroup::Row { widgets: name },
blend_mode,
LayoutGroup::Row { widgets: opacity },
LayoutGroup::Row { widgets: visible },
LayoutGroup::Row { widgets: locked },
LayoutGroup::Row { widgets: collapsed },
]
}
pub fn artboard_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let label = text_widget(document_node, node_id, 1, "Label", true);
vec![LayoutGroup::Row { widgets: label }]
}
10 changes: 9 additions & 1 deletion editor/src/node_graph_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ impl NodeGraphExecutor {
self.last_output_type.insert(layer_path.clone(), Some(concrete!(VectorData)));
responses.add(Operation::SetLayerTransform { path: layer_path.clone(), transform });
responses.add(Operation::SetVectorData { path: layer_path, vector_data });
} else {
} else if core::any::TypeId::of::<ImageFrame<Color>>() == DynAny::type_id(boxed_node_graph_output.as_ref()) {
// Attempt to downcast to an image frame
let ImageFrame { image, transform } = dyn_any::downcast(boxed_node_graph_output).map(|image_frame| *image_frame)?;
self.last_output_type.insert(layer_path.clone(), Some(concrete!(ImageFrame<Color>)));
Expand Down Expand Up @@ -316,6 +316,14 @@ impl NodeGraphExecutor {
}];
responses.add(FrontendMessage::UpdateImageData { document_id, image_data });
}
} else if core::any::TypeId::of::<graphene_core::Artboard>() == DynAny::type_id(boxed_node_graph_output.as_ref()) {
let artboard: graphene_core::Artboard = dyn_any::downcast(boxed_node_graph_output).map(|artboard| *artboard)?;
info!("{artboard:#?}");
return Err(format!("Artboard (see console)"));
} else if core::any::TypeId::of::<graphene_core::GraphicGroup>() == DynAny::type_id(boxed_node_graph_output.as_ref()) {
let graphic_group: graphene_core::GraphicGroup = dyn_any::downcast(boxed_node_graph_output).map(|graphic| *graphic)?;
info!("{graphic_group:#?}");
return Err(format!("Graphic group (see console)"));
}

Ok(())
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/components/Editor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@
--color-data-vec2-dim: #71008d;
--color-data-color: #70a898;
--color-data-color-dim: #43645b;
--color-data-graphic: #e4bb72;
--color-data-graphic-dim: #8b7752;
--color-data-artboard: #70a898;
--color-data-artboard-dim: #3a6156;

--color-none: white;
--color-none-repeat: no-repeat;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/wasm-communication/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export class FrontendDocumentDetails extends DocumentDetails {
readonly id!: bigint;
}

export type FrontendGraphDataType = "general" | "raster" | "color" | "vector" | "number";
export type FrontendGraphDataType = "general" | "raster" | "color" | "vector" | "vec2" | "graphic" | "artboard";

export class NodeGraphInput {
readonly dataType!: FrontendGraphDataType;
Expand Down
140 changes: 140 additions & 0 deletions node-graph/gcore/src/graphic_element.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
4 changes: 4 additions & 0 deletions node-graph/gcore/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
9 changes: 9 additions & 0 deletions node-graph/gcore/src/vector/vector_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading