Replace the Artboard struct with a Table<Table<Graphic>> shape (#4077)

* Replace the Artboard struct with a Table<Table<Graphic>> shape

* Remove the never-functional, seemingly unneeded migrate_type_descriptor_names due to typo

* Allow negative artboard sizes
This commit is contained in:
Keavon Chambers
2026-04-28 22:58:33 -07:00
committed by GitHub
parent 0847d7b0ab
commit ba63c26c62
27 changed files with 350 additions and 442 deletions
@@ -7,6 +7,7 @@ use crate::messages::tool::tool_messages::tool_prelude::*;
use glam::{Affine2, DAffine2, Vec2};
use graph_craft::document::NodeId;
use graphene_std::Context;
use graphene_std::Graphic;
use graphene_std::gradient::GradientStops;
use graphene_std::memo::IORecord;
use graphene_std::raster_types::{CPU, GPU, Raster};
@@ -14,7 +15,6 @@ use graphene_std::table::Table;
use graphene_std::vector::Vector;
use graphene_std::vector::style::{Fill, FillChoice};
use graphene_std::{AlphaBlending, Color};
use graphene_std::{Artboard, Graphic};
use std::any::Any;
use std::sync::Arc;
@@ -182,7 +182,7 @@ fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'st
return Some(table_node_id_path_layout_with_breadcrumb(&io.output, data));
}
generate_layout_downcast!(introspected_data, data, [
Table<Artboard>,
Table<Table<Graphic>>,
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
@@ -300,18 +300,6 @@ impl<T: TableRowLayout> TableRowLayout for Table<T> {
}
}
impl TableRowLayout for Artboard {
fn type_name() -> &'static str {
"Artboard"
}
fn identifier(&self) -> String {
self.label.clone()
}
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
self.content.value_page(data)
}
}
impl TableRowLayout for Graphic {
fn type_name() -> &'static str {
"Graphic"
@@ -652,7 +640,7 @@ impl TableRowLayout for bool {
"Bool".to_string()
}
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
TextLabel::new(self.to_string()).narrow(true).widget_instance()
CheckboxInput::new(*self).disabled(true).widget_instance()
}
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
@@ -897,7 +885,7 @@ impl TableRowLayout for NodeId {
macro_rules! known_table_row_types {
($apply:ident) => {
$apply!(
Table<Artboard>,
Table<Table<Graphic>>,
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
@@ -926,7 +914,6 @@ macro_rules! known_table_row_types {
Vector,
Raster<CPU>,
Raster<GPU>,
Artboard,
Graphic,
);
};
@@ -28,7 +28,7 @@ use crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys;
use crate::messages::tool::tool_messages::tool_prelude::Key;
use crate::messages::tool::utility_types::ToolType;
use crate::node_graph_executor::NodeGraphExecutor;
use glam::{DAffine2, DVec2, IVec2};
use glam::{DAffine2, DVec2};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
use graphene_std::math::quad::Quad;
@@ -1396,11 +1396,11 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
// When artboard_canvas is provided (SVG file-open flow), use the declared canvas origin and dimensions;
// no content-shift Transform node needed since the SVG was already placed at its natural coordinates.
let (artboard_location, artboard_dimensions, content_shift) = if let Some((origin, dimensions)) = artboard_canvas {
(origin, dimensions, DVec2::ZERO)
(origin.as_dvec2(), dimensions.as_dvec2(), DVec2::ZERO)
} else {
// No declared canvas (image or clipboard paste): derive location and dimensions from the content bounding box.
let location = if place_artboard_at_origin { IVec2::ZERO } else { bounds[0].round().as_ivec2() };
(location, (bounds[1] - bounds[0]).round().as_ivec2(), -bounds[0].round())
let location = if place_artboard_at_origin { DVec2::ZERO } else { bounds[0].round() };
(location, (bounds[1] - bounds[0]).round(), -bounds[0].round())
};
// Create an artboard and set its dimensions to the bounding box size and location
@@ -2,9 +2,8 @@ use super::utility_types::TransformIn;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
use crate::messages::prelude::*;
use glam::{DAffine2, IVec2};
use glam::{DAffine2, DVec2};
use graph_craft::document::NodeId;
use graphene_std::Artboard;
use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::color::Color;
use graphene_std::raster::BlendMode;
@@ -66,7 +65,10 @@ pub enum GraphOperationMessage {
},
NewArtboard {
id: NodeId,
artboard: Artboard,
location: DVec2,
dimensions: DVec2,
background: Color,
clip: bool,
},
NewBitmapLayer {
id: NodeId,
@@ -119,8 +121,8 @@ pub enum GraphOperationMessage {
},
ResizeArtboard {
layer: LayerNodeIdentifier,
location: IVec2,
dimensions: IVec2,
location: DVec2,
dimensions: DVec2,
},
RemoveArtboards,
NewSvg {
@@ -108,11 +108,16 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
network_interface.force_set_upstream_to_chain(&first_chain_node, &[]);
}
GraphOperationMessage::NewArtboard { id, artboard } => {
GraphOperationMessage::NewArtboard {
id,
location,
dimensions,
background,
clip,
} => {
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
let artboard_location = artboard.location;
let artboard_layer = modify_inputs.create_artboard(id, artboard);
let artboard_layer = modify_inputs.create_artboard(id, location, dimensions, background, clip);
network_interface.move_layer_to_stack(artboard_layer, LayerNodeIdentifier::ROOT_PARENT, 0, &[]);
// If there is a non artboard feeding into the primary input of the artboard, move it to the secondary input
@@ -138,7 +143,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
// Apply a translation to prevent the content from shifting
responses.add(GraphOperationMessage::TransformChange {
layer,
transform: DAffine2::from_translation(-artboard_location.as_dvec2()),
transform: DAffine2::from_translation(-location),
transform_in: TransformIn::Local,
skip_rerender: true,
});
@@ -3,7 +3,7 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
use crate::messages::prelude::*;
use glam::{DAffine2, IVec2};
use glam::{DAffine2, DVec2};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput};
use graph_craft::{ProtoNodeIdentifier, concrete};
@@ -16,7 +16,7 @@ use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::Vector;
use graphene_std::vector::style::{Fill, Stroke};
use graphene_std::vector::{PointId, VectorModification, VectorModificationType};
use graphene_std::{Artboard, Color, Graphic, NodeInputDecleration};
use graphene_std::{Color, Graphic, NodeInputDecleration};
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub enum TransformIn {
@@ -130,15 +130,15 @@ impl<'a> ModifyInputsContext<'a> {
LayerNodeIdentifier::new(new_id, self.network_interface)
}
/// Creates an artboard as the primary export for the document network
pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> LayerNodeIdentifier {
/// Creates an artboard as the primary export for the document network.
pub fn create_artboard(&mut self, new_id: NodeId, location: DVec2, dimensions: DVec2, background: Color, clip: bool) -> LayerNodeIdentifier {
let artboard_node_template = resolve_network_node_type("Artboard").expect("Node").node_template_input_override([
Some(NodeInput::value(TaggedValue::Artboard(Default::default()), true)),
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
Some(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)),
Some(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)),
Some(NodeInput::value(TaggedValue::Color(Table::new_from_element(artboard.background)), false)),
Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)),
Some(NodeInput::value(TaggedValue::DVec2(location), false)),
Some(NodeInput::value(TaggedValue::DVec2(dimensions), false)),
Some(NodeInput::value(TaggedValue::Color(Table::new_from_element(background)), false)),
Some(NodeInput::value(TaggedValue::Bool(clip), false)),
]);
self.network_interface.insert_node(new_id, artboard_node_template, &[]);
LayerNodeIdentifier::new(new_id, self.network_interface)
@@ -584,7 +584,7 @@ impl<'a> ModifyInputsContext<'a> {
self.set_input_with_refresh(InputConnector::node(brush_node_id, 1), NodeInput::value(TaggedValue::BrushStrokeTable(strokes_table), false), false);
}
pub fn resize_artboard(&mut self, location: IVec2, dimensions: IVec2) {
pub fn resize_artboard(&mut self, location: DVec2, dimensions: DVec2) {
let Some(artboard_node_id) = self.existing_network_node_id("Artboard", true) else {
return;
};
@@ -592,16 +592,16 @@ impl<'a> ModifyInputsContext<'a> {
let mut dimensions = dimensions;
let mut location = location;
if dimensions.x < 0 {
dimensions.x *= -1;
if dimensions.x < 0. {
dimensions.x = -dimensions.x;
location.x -= dimensions.x;
}
if dimensions.y < 0 {
dimensions.y *= -1;
if dimensions.y < 0. {
dimensions.y = -dimensions.y;
location.y -= dimensions.y;
}
self.set_input_with_refresh(InputConnector::node(artboard_node_id, 2), NodeInput::value(TaggedValue::DVec2(location.into()), false), false);
self.set_input_with_refresh(InputConnector::node(artboard_node_id, 3), NodeInput::value(TaggedValue::DVec2(dimensions.into()), false), false);
self.set_input_with_refresh(InputConnector::node(artboard_node_id, 2), NodeInput::value(TaggedValue::DVec2(location), false), false);
self.set_input_with_refresh(InputConnector::node(artboard_node_id, 3), NodeInput::value(TaggedValue::DVec2(dimensions), false), false);
}
/// Set the input, refresh the Properties panel, and run the document graph if skip_rerender is false
@@ -354,13 +354,11 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(4), 0)],
nodes: [
// Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias
DocumentNode {
call_argument: generic!(T),
implementation: DocumentNodeImplementation::ProtoNode(artboard::create_artboard::IDENTIFIER),
inputs: vec![
NodeInput::import(concrete!(TaggedValue), 1),
NodeInput::value(TaggedValue::String(String::from("Artboard")), false),
NodeInput::import(concrete!(TaggedValue), 2),
NodeInput::import(concrete!(TaggedValue), 3),
NodeInput::import(concrete!(TaggedValue), 4),
@@ -396,7 +394,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
},
DocumentNode {
inputs: vec![
NodeInput::import(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),
NodeInput::import(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Table<Graphic>>))), 0),
NodeInput::node(NodeId(3), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
@@ -4571,32 +4571,7 @@ impl NodeNetworkInterface {
return;
}
node_metadata.persistent_metadata.display_name.clone_from(&display_name);
// Keep the alias in sync with the `ToArtboard` name input
if self
.reference(node_id, network_path)
.is_some_and(|reference| reference == DefinitionIdentifier::Network("Artboard".into()))
{
let Some(nested_network) = self.network_mut(network_path) else {
return;
};
let Some(artboard_node) = nested_network.nodes.get_mut(node_id) else {
return;
};
let DocumentNodeImplementation::Network(network) = &mut artboard_node.implementation else {
return;
};
// Keep this in sync with the definition
let Some(to_artboard) = network.nodes.get_mut(&NodeId(0)) else {
return;
};
let label_index = 1;
let label = if !display_name.is_empty() { display_name } else { "Artboard".to_string() };
let label_input = NodeInput::value(TaggedValue::String(label), false);
to_artboard.inputs[label_index] = label_input;
}
node_metadata.persistent_metadata.display_name = display_name;
self.transaction_modified();
self.try_unload_layer_width(node_id, network_path);