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:
parent
0847d7b0ab
commit
ba63c26c62
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use glam::{IVec2, UVec2};
|
use glam::UVec2;
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
|
|
||||||
|
|
@ -47,7 +47,10 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
|
||||||
// Finite canvas: create an artboard with the specified dimensions
|
// Finite canvas: create an artboard with the specified dimensions
|
||||||
responses.add(GraphOperationMessage::NewArtboard {
|
responses.add(GraphOperationMessage::NewArtboard {
|
||||||
id: NodeId::new(),
|
id: NodeId::new(),
|
||||||
artboard: graphene_std::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),
|
location: glam::DVec2::ZERO,
|
||||||
|
dimensions: self.dimensions.as_dvec2(),
|
||||||
|
background: Color::WHITE,
|
||||||
|
clip: true,
|
||||||
});
|
});
|
||||||
responses.add(NavigationMessage::CanvasPan { delta: self.dimensions.as_dvec2() });
|
responses.add(NavigationMessage::CanvasPan { delta: self.dimensions.as_dvec2() });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||||
use glam::{Affine2, DAffine2, Vec2};
|
use glam::{Affine2, DAffine2, Vec2};
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::Context;
|
use graphene_std::Context;
|
||||||
|
use graphene_std::Graphic;
|
||||||
use graphene_std::gradient::GradientStops;
|
use graphene_std::gradient::GradientStops;
|
||||||
use graphene_std::memo::IORecord;
|
use graphene_std::memo::IORecord;
|
||||||
use graphene_std::raster_types::{CPU, GPU, Raster};
|
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::Vector;
|
||||||
use graphene_std::vector::style::{Fill, FillChoice};
|
use graphene_std::vector::style::{Fill, FillChoice};
|
||||||
use graphene_std::{AlphaBlending, Color};
|
use graphene_std::{AlphaBlending, Color};
|
||||||
use graphene_std::{Artboard, Graphic};
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::sync::Arc;
|
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));
|
return Some(table_node_id_path_layout_with_breadcrumb(&io.output, data));
|
||||||
}
|
}
|
||||||
generate_layout_downcast!(introspected_data, data, [
|
generate_layout_downcast!(introspected_data, data, [
|
||||||
Table<Artboard>,
|
Table<Table<Graphic>>,
|
||||||
Table<Graphic>,
|
Table<Graphic>,
|
||||||
Table<Vector>,
|
Table<Vector>,
|
||||||
Table<Raster<CPU>>,
|
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 {
|
impl TableRowLayout for Graphic {
|
||||||
fn type_name() -> &'static str {
|
fn type_name() -> &'static str {
|
||||||
"Graphic"
|
"Graphic"
|
||||||
|
|
@ -652,7 +640,7 @@ impl TableRowLayout for bool {
|
||||||
"Bool".to_string()
|
"Bool".to_string()
|
||||||
}
|
}
|
||||||
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
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> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
|
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 {
|
macro_rules! known_table_row_types {
|
||||||
($apply:ident) => {
|
($apply:ident) => {
|
||||||
$apply!(
|
$apply!(
|
||||||
Table<Artboard>,
|
Table<Table<Graphic>>,
|
||||||
Table<Graphic>,
|
Table<Graphic>,
|
||||||
Table<Vector>,
|
Table<Vector>,
|
||||||
Table<Raster<CPU>>,
|
Table<Raster<CPU>>,
|
||||||
|
|
@ -926,7 +914,6 @@ macro_rules! known_table_row_types {
|
||||||
Vector,
|
Vector,
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
Raster<GPU>,
|
Raster<GPU>,
|
||||||
Artboard,
|
|
||||||
Graphic,
|
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::tool_messages::tool_prelude::Key;
|
||||||
use crate::messages::tool::utility_types::ToolType;
|
use crate::messages::tool::utility_types::ToolType;
|
||||||
use crate::node_graph_executor::NodeGraphExecutor;
|
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::value::TaggedValue;
|
||||||
use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
|
use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
|
||||||
use graphene_std::math::quad::Quad;
|
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;
|
// 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.
|
// 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 {
|
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 {
|
} else {
|
||||||
// No declared canvas (image or clipboard paste): derive location and dimensions from the content bounding box.
|
// 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() };
|
let location = if place_artboard_at_origin { DVec2::ZERO } else { bounds[0].round() };
|
||||||
(location, (bounds[1] - bounds[0]).round().as_ivec2(), -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
|
// 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::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
|
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use glam::{DAffine2, IVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::Artboard;
|
|
||||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||||
use graphene_std::color::Color;
|
use graphene_std::color::Color;
|
||||||
use graphene_std::raster::BlendMode;
|
use graphene_std::raster::BlendMode;
|
||||||
|
|
@ -66,7 +65,10 @@ pub enum GraphOperationMessage {
|
||||||
},
|
},
|
||||||
NewArtboard {
|
NewArtboard {
|
||||||
id: NodeId,
|
id: NodeId,
|
||||||
artboard: Artboard,
|
location: DVec2,
|
||||||
|
dimensions: DVec2,
|
||||||
|
background: Color,
|
||||||
|
clip: bool,
|
||||||
},
|
},
|
||||||
NewBitmapLayer {
|
NewBitmapLayer {
|
||||||
id: NodeId,
|
id: NodeId,
|
||||||
|
|
@ -119,8 +121,8 @@ pub enum GraphOperationMessage {
|
||||||
},
|
},
|
||||||
ResizeArtboard {
|
ResizeArtboard {
|
||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
location: IVec2,
|
location: DVec2,
|
||||||
dimensions: IVec2,
|
dimensions: DVec2,
|
||||||
},
|
},
|
||||||
RemoveArtboards,
|
RemoveArtboards,
|
||||||
NewSvg {
|
NewSvg {
|
||||||
|
|
|
||||||
|
|
@ -108,11 +108,16 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
||||||
|
|
||||||
network_interface.force_set_upstream_to_chain(&first_chain_node, &[]);
|
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 mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
|
||||||
|
|
||||||
let artboard_location = artboard.location;
|
let artboard_layer = modify_inputs.create_artboard(id, location, dimensions, background, clip);
|
||||||
let artboard_layer = modify_inputs.create_artboard(id, artboard);
|
|
||||||
network_interface.move_layer_to_stack(artboard_layer, LayerNodeIdentifier::ROOT_PARENT, 0, &[]);
|
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
|
// 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
|
// Apply a translation to prevent the content from shifting
|
||||||
responses.add(GraphOperationMessage::TransformChange {
|
responses.add(GraphOperationMessage::TransformChange {
|
||||||
layer,
|
layer,
|
||||||
transform: DAffine2::from_translation(-artboard_location.as_dvec2()),
|
transform: DAffine2::from_translation(-location),
|
||||||
transform_in: TransformIn::Local,
|
transform_in: TransformIn::Local,
|
||||||
skip_rerender: true,
|
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::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
|
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use glam::{DAffine2, IVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{NodeId, NodeInput};
|
use graph_craft::document::{NodeId, NodeInput};
|
||||||
use graph_craft::{ProtoNodeIdentifier, concrete};
|
use graph_craft::{ProtoNodeIdentifier, concrete};
|
||||||
|
|
@ -16,7 +16,7 @@ use graphene_std::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::Vector;
|
use graphene_std::vector::Vector;
|
||||||
use graphene_std::vector::style::{Fill, Stroke};
|
use graphene_std::vector::style::{Fill, Stroke};
|
||||||
use graphene_std::vector::{PointId, VectorModification, VectorModificationType};
|
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)]
|
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum TransformIn {
|
pub enum TransformIn {
|
||||||
|
|
@ -130,15 +130,15 @@ impl<'a> ModifyInputsContext<'a> {
|
||||||
LayerNodeIdentifier::new(new_id, self.network_interface)
|
LayerNodeIdentifier::new(new_id, self.network_interface)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an artboard as the primary export for the document network
|
/// Creates an artboard as the primary export for the document network.
|
||||||
pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> LayerNodeIdentifier {
|
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([
|
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::Artboard(Default::default()), true)),
|
||||||
Some(NodeInput::value(TaggedValue::Graphic(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(location), false)),
|
||||||
Some(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)),
|
Some(NodeInput::value(TaggedValue::DVec2(dimensions), false)),
|
||||||
Some(NodeInput::value(TaggedValue::Color(Table::new_from_element(artboard.background)), false)),
|
Some(NodeInput::value(TaggedValue::Color(Table::new_from_element(background)), false)),
|
||||||
Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)),
|
Some(NodeInput::value(TaggedValue::Bool(clip), false)),
|
||||||
]);
|
]);
|
||||||
self.network_interface.insert_node(new_id, artboard_node_template, &[]);
|
self.network_interface.insert_node(new_id, artboard_node_template, &[]);
|
||||||
LayerNodeIdentifier::new(new_id, self.network_interface)
|
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);
|
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 {
|
let Some(artboard_node_id) = self.existing_network_node_id("Artboard", true) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -592,16 +592,16 @@ impl<'a> ModifyInputsContext<'a> {
|
||||||
let mut dimensions = dimensions;
|
let mut dimensions = dimensions;
|
||||||
let mut location = location;
|
let mut location = location;
|
||||||
|
|
||||||
if dimensions.x < 0 {
|
if dimensions.x < 0. {
|
||||||
dimensions.x *= -1;
|
dimensions.x = -dimensions.x;
|
||||||
location.x -= dimensions.x;
|
location.x -= dimensions.x;
|
||||||
}
|
}
|
||||||
if dimensions.y < 0 {
|
if dimensions.y < 0. {
|
||||||
dimensions.y *= -1;
|
dimensions.y = -dimensions.y;
|
||||||
location.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, 2), NodeInput::value(TaggedValue::DVec2(location), 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, 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
|
/// 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 {
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
exports: vec![NodeInput::node(NodeId(4), 0)],
|
exports: vec![NodeInput::node(NodeId(4), 0)],
|
||||||
nodes: [
|
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 {
|
DocumentNode {
|
||||||
call_argument: generic!(T),
|
call_argument: generic!(T),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(artboard::create_artboard::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(artboard::create_artboard::IDENTIFIER),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::import(concrete!(TaggedValue), 1),
|
NodeInput::import(concrete!(TaggedValue), 1),
|
||||||
NodeInput::value(TaggedValue::String(String::from("Artboard")), false),
|
|
||||||
NodeInput::import(concrete!(TaggedValue), 2),
|
NodeInput::import(concrete!(TaggedValue), 2),
|
||||||
NodeInput::import(concrete!(TaggedValue), 3),
|
NodeInput::import(concrete!(TaggedValue), 3),
|
||||||
NodeInput::import(concrete!(TaggedValue), 4),
|
NodeInput::import(concrete!(TaggedValue), 4),
|
||||||
|
|
@ -396,7 +394,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![
|
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),
|
NodeInput::node(NodeId(3), 0),
|
||||||
],
|
],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
|
||||||
|
|
|
||||||
|
|
@ -4571,32 +4571,7 @@ impl NodeNetworkInterface {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
node_metadata.persistent_metadata.display_name.clone_from(&display_name);
|
node_metadata.persistent_metadata.display_name = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.transaction_modified();
|
self.transaction_modified();
|
||||||
self.try_unload_layer_width(node_id, network_path);
|
self.try_unload_layer_width(node_id, network_path);
|
||||||
|
|
|
||||||
|
|
@ -1313,14 +1313,6 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[3].clone(), network_path);
|
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[3].clone(), network_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade artboard name being passed as hidden value input to "Create Artboard"
|
|
||||||
if reference == DefinitionIdentifier::Network("Artboard".into()) && reset_node_definitions_on_open {
|
|
||||||
let label = document.network_interface.display_name(node_id, network_path);
|
|
||||||
document
|
|
||||||
.network_interface
|
|
||||||
.set_input(&InputConnector::node(NodeId(0), 1), NodeInput::value(TaggedValue::String(label), false), &[*node_id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::raster_nodes::std_nodes::image::IDENTIFIER) && inputs_count == 1 {
|
if reference == DefinitionIdentifier::ProtoNode(graphene_std::raster_nodes::std_nodes::image::IDENTIFIER) && inputs_count == 1 {
|
||||||
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
||||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||||
|
|
|
||||||
|
|
@ -1125,8 +1125,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||||
layer: item_id,
|
layer: item_id,
|
||||||
location: new_artboard_origin_doc.round().as_ivec2(),
|
location: new_artboard_origin_doc.round(),
|
||||||
dimensions: dimensions_doc.round().as_ivec2(),
|
dimensions: dimensions_doc.round(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ use crate::messages::tool::common_functionality::snapping::SnapCandidatePoint;
|
||||||
use crate::messages::tool::common_functionality::snapping::SnapData;
|
use crate::messages::tool::common_functionality::snapping::SnapData;
|
||||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::Artboard;
|
|
||||||
use graphene_std::renderer::{Quad, Rect};
|
use graphene_std::renderer::{Quad, Rect};
|
||||||
use graphene_std::table::Table;
|
|
||||||
|
|
||||||
#[derive(Default, ExtractField)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct ArtboardTool {
|
pub struct ArtboardTool {
|
||||||
|
|
@ -113,7 +111,7 @@ struct ArtboardToolData {
|
||||||
drag_current: DVec2,
|
drag_current: DVec2,
|
||||||
auto_panning: AutoPanning,
|
auto_panning: AutoPanning,
|
||||||
snap_candidates: Vec<SnapCandidatePoint>,
|
snap_candidates: Vec<SnapCandidatePoint>,
|
||||||
dragging_current_artboard_location: glam::IVec2,
|
dragging_current_artboard_location: glam::DVec2,
|
||||||
draw: Resize,
|
draw: Resize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,7 +140,7 @@ impl ArtboardToolData {
|
||||||
fn start_resizing(&mut self, _selected_edges: (bool, bool, bool, bool), _document: &DocumentMessageHandler, _input: &InputPreprocessorMessageHandler) {
|
fn start_resizing(&mut self, _selected_edges: (bool, bool, bool, bool), _document: &DocumentMessageHandler, _input: &InputPreprocessorMessageHandler) {
|
||||||
if let Some(bounds) = &mut self.bounding_box_manager {
|
if let Some(bounds) = &mut self.bounding_box_manager {
|
||||||
bounds.center_of_transformation = bounds.transform.transform_point2((bounds.bounds[0] + bounds.bounds[1]) / 2.);
|
bounds.center_of_transformation = bounds.transform.transform_point2((bounds.bounds[0] + bounds.bounds[1]) / 2.);
|
||||||
self.dragging_current_artboard_location = bounds.bounds[0].round().as_ivec2();
|
self.dragging_current_artboard_location = bounds.bounds[0].round();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,14 +209,14 @@ impl ArtboardToolData {
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||||
layer: selected_artboard,
|
layer: selected_artboard,
|
||||||
location: position.round().as_ivec2(),
|
location: position.round(),
|
||||||
dimensions: size.round().as_ivec2(),
|
dimensions: size.round(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let translation = position.round().as_ivec2() - self.dragging_current_artboard_location;
|
let translation = position.round() - self.dragging_current_artboard_location;
|
||||||
self.dragging_current_artboard_location = position.round().as_ivec2();
|
self.dragging_current_artboard_location = position.round();
|
||||||
for child in selected_artboard.children(document.metadata()) {
|
for child in selected_artboard.children(document.metadata()) {
|
||||||
let local_translation = document.metadata().downstream_transform_to_document(child).inverse().transform_vector2(-translation.as_dvec2());
|
let local_translation = document.metadata().downstream_transform_to_document(child).inverse().transform_vector2(-translation);
|
||||||
responses.add(GraphOperationMessage::TransformChange {
|
responses.add(GraphOperationMessage::TransformChange {
|
||||||
layer: child,
|
layer: child,
|
||||||
transform: DAffine2::from_translation(local_translation),
|
transform: DAffine2::from_translation(local_translation),
|
||||||
|
|
@ -348,8 +346,8 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
}
|
}
|
||||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||||
layer: tool_data.selected_artboard.unwrap(),
|
layer: tool_data.selected_artboard.unwrap(),
|
||||||
location: position.round().as_ivec2(),
|
location: position.round(),
|
||||||
dimensions: size.round().as_ivec2(),
|
dimensions: size.round(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// The second term is added to prevent the slow change in position due to rounding errors.
|
// The second term is added to prevent the slow change in position due to rounding errors.
|
||||||
|
|
@ -379,8 +377,8 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||||
layer: artboard,
|
layer: artboard,
|
||||||
location: start.min(end).round().as_ivec2(),
|
location: start.min(end).round(),
|
||||||
dimensions: (start.round() - end.round()).abs().as_ivec2(),
|
dimensions: (start.round() - end.round()).abs(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let id = NodeId::new();
|
let id = NodeId::new();
|
||||||
|
|
@ -389,14 +387,10 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::NewArtboard {
|
responses.add(GraphOperationMessage::NewArtboard {
|
||||||
id,
|
id,
|
||||||
artboard: Artboard {
|
location: start.min(end).round(),
|
||||||
content: Table::new(),
|
dimensions: (start.round() - end.round()).abs(),
|
||||||
label: String::from("Artboard"),
|
background: graphene_std::Color::WHITE,
|
||||||
location: start.min(end).round().as_ivec2(),
|
clip: true,
|
||||||
dimensions: (start.round() - end.round()).abs().as_ivec2(),
|
|
||||||
background: graphene_std::Color::WHITE,
|
|
||||||
clip: true,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -516,8 +510,8 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||||
layer: selected_artboard,
|
layer: selected_artboard,
|
||||||
location: DVec2::new(existing_top_left.x + delta.x, existing_top_left.y + delta.y).round().as_ivec2(),
|
location: DVec2::new(existing_top_left.x + delta.x, existing_top_left.y + delta.y).round(),
|
||||||
dimensions: (existing_bottom_right - existing_top_left).round().as_ivec2(),
|
dimensions: (existing_bottom_right - existing_top_left).round(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return ArtboardToolFsmState::Ready { hovered };
|
return ArtboardToolFsmState::Ready { hovered };
|
||||||
|
|
@ -563,8 +557,8 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||||
layer: selected_artboard,
|
layer: selected_artboard,
|
||||||
location: position.round().as_ivec2(),
|
location: position.round(),
|
||||||
dimensions: new.transform_vector2(existing_bottom_right - existing_top_left).round().as_ivec2(),
|
dimensions: new.transform_vector2(existing_bottom_right - existing_top_left).round(),
|
||||||
});
|
});
|
||||||
|
|
||||||
ArtboardToolFsmState::Ready { hovered }
|
ArtboardToolFsmState::Ready { hovered }
|
||||||
|
|
@ -617,26 +611,27 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_artboard {
|
mod test_artboard {
|
||||||
pub use crate::test_utils::test_prelude::*;
|
pub use crate::test_utils::test_prelude::*;
|
||||||
|
use graphene_std::Graphic;
|
||||||
use graphene_std::table::Table;
|
use graphene_std::table::Table;
|
||||||
|
|
||||||
async fn get_artboards(editor: &mut EditorTestUtils) -> Table<graphene_std::Artboard> {
|
async fn get_artboards(editor: &mut EditorTestUtils) -> Table<Table<Graphic>> {
|
||||||
let instrumented = match editor.eval_graph().await {
|
let instrumented = match editor.eval_graph().await {
|
||||||
Ok(instrumented) => instrumented,
|
Ok(instrumented) => instrumented,
|
||||||
Err(e) => panic!("Failed to evaluate graph: {e}"),
|
Err(e) => panic!("Failed to evaluate graph: {e}"),
|
||||||
};
|
};
|
||||||
instrumented
|
instrumented
|
||||||
.grab_all_input::<graphene_std::graphic::extend::NewInput<graphene_std::Artboard>>(&editor.runtime)
|
.grab_all_input::<graphene_std::graphic::extend::NewInput<Table<graphene_std::Graphic>>>(&editor.runtime)
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
struct ArtboardLayoutDocument {
|
struct ArtboardLayoutDocument {
|
||||||
position: IVec2,
|
position: DVec2,
|
||||||
dimensions: IVec2,
|
dimensions: DVec2,
|
||||||
}
|
}
|
||||||
impl ArtboardLayoutDocument {
|
impl ArtboardLayoutDocument {
|
||||||
pub fn new(position: impl Into<IVec2>, dimensions: impl Into<IVec2>) -> Self {
|
pub fn new(position: impl Into<DVec2>, dimensions: impl Into<DVec2>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
position: position.into(),
|
position: position.into(),
|
||||||
dimensions: dimensions.into(),
|
dimensions: dimensions.into(),
|
||||||
|
|
@ -649,8 +644,9 @@ mod test_artboard {
|
||||||
let artboards = get_artboards(editor).await;
|
let artboards = get_artboards(editor).await;
|
||||||
let artboards = (0..artboards.len())
|
let artboards = (0..artboards.len())
|
||||||
.map(|index| {
|
.map(|index| {
|
||||||
let element = artboards.element(index).unwrap();
|
let location: DVec2 = artboards.attribute_cloned_or_default(graphene_std::ATTR_LOCATION, index);
|
||||||
ArtboardLayoutDocument::new(element.location, element.dimensions)
|
let dimensions: DVec2 = artboards.attribute_cloned_or_default(graphene_std::ATTR_DIMENSIONS, index);
|
||||||
|
ArtboardLayoutDocument::new(location, dimensions)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
assert_eq!(artboards.len(), expected.len(), "incorrect len: actual {:?}, expected {:?}", artboards, expected);
|
assert_eq!(artboards.len(), expected.len(), "incorrect len: actual {:?}, expected {:?}", artboards, expected);
|
||||||
|
|
@ -668,7 +664,7 @@ mod test_artboard {
|
||||||
let mut editor = EditorTestUtils::create();
|
let mut editor = EditorTestUtils::create();
|
||||||
editor.new_document().await;
|
editor.new_document().await;
|
||||||
editor.drag_tool(ToolType::Artboard, 10.1, 10.8, 19.9, 0.2, ModifierKeys::empty()).await;
|
editor.drag_tool(ToolType::Artboard, 10.1, 10.8, 19.9, 0.2, ModifierKeys::empty()).await;
|
||||||
has_artboards(&mut editor, vec![ArtboardLayoutDocument::new((10, 0), (10, 11))]).await;
|
has_artboards(&mut editor, vec![ArtboardLayoutDocument::new((10., 0.), (10., 11.))]).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -678,7 +674,11 @@ mod test_artboard {
|
||||||
editor.set_viewport_size(DVec2::splat(-1000.), DVec2::splat(1000.)).await; // Necessary for doing snapping since snaps outside of the viewport are discarded
|
editor.set_viewport_size(DVec2::splat(-1000.), DVec2::splat(1000.)).await; // Necessary for doing snapping since snaps outside of the viewport are discarded
|
||||||
editor.drag_tool(ToolType::Artboard, 10., 10., 20., 20., ModifierKeys::empty()).await;
|
editor.drag_tool(ToolType::Artboard, 10., 10., 20., 20., ModifierKeys::empty()).await;
|
||||||
editor.drag_tool(ToolType::Artboard, 11., 50., 19., 60., ModifierKeys::empty()).await;
|
editor.drag_tool(ToolType::Artboard, 11., 50., 19., 60., ModifierKeys::empty()).await;
|
||||||
has_artboards(&mut editor, vec![ArtboardLayoutDocument::new((10, 10), (10, 10)), ArtboardLayoutDocument::new((10, 50), (10, 10))]).await;
|
has_artboards(
|
||||||
|
&mut editor,
|
||||||
|
vec![ArtboardLayoutDocument::new((10., 10.), (10., 10.)), ArtboardLayoutDocument::new((10., 50.), (10., 10.))],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -686,7 +686,7 @@ mod test_artboard {
|
||||||
let mut editor = EditorTestUtils::create();
|
let mut editor = EditorTestUtils::create();
|
||||||
editor.new_document().await;
|
editor.new_document().await;
|
||||||
editor.drag_tool(ToolType::Artboard, 10., 10., -10., 11., ModifierKeys::SHIFT).await;
|
editor.drag_tool(ToolType::Artboard, 10., 10., -10., 11., ModifierKeys::SHIFT).await;
|
||||||
has_artboards(&mut editor, vec![ArtboardLayoutDocument::new((-10, 10), (20, 20))]).await;
|
has_artboards(&mut editor, vec![ArtboardLayoutDocument::new((-10., 10.), (20., 20.))]).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -703,7 +703,7 @@ mod test_artboard {
|
||||||
editor.drag_tool(ToolType::Artboard, 0., 0., 0., 10., ModifierKeys::SHIFT).await;
|
editor.drag_tool(ToolType::Artboard, 0., 0., 0., 10., ModifierKeys::SHIFT).await;
|
||||||
let desired_size = DVec2::splat(f64::consts::FRAC_1_SQRT_2 * 10.);
|
let desired_size = DVec2::splat(f64::consts::FRAC_1_SQRT_2 * 10.);
|
||||||
|
|
||||||
has_artboards(&mut editor, vec![ArtboardLayoutDocument::new(IVec2::new(0, 0), desired_size.round().as_ivec2())]).await;
|
has_artboards(&mut editor, vec![ArtboardLayoutDocument::new(DVec2::new(0., 0.), desired_size.round())]).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -719,8 +719,8 @@ mod test_artboard {
|
||||||
.await;
|
.await;
|
||||||
// Viewport coordinates
|
// Viewport coordinates
|
||||||
editor.drag_tool(ToolType::Artboard, 0., 0., 0., 10., ModifierKeys::SHIFT | ModifierKeys::ALT).await;
|
editor.drag_tool(ToolType::Artboard, 0., 0., 0., 10., ModifierKeys::SHIFT | ModifierKeys::ALT).await;
|
||||||
let desired_location = DVec2::splat(f64::consts::FRAC_1_SQRT_2 * -10.).as_ivec2();
|
let desired_location = DVec2::splat(f64::consts::FRAC_1_SQRT_2 * -10.).round();
|
||||||
let desired_size = DVec2::splat(f64::consts::FRAC_1_SQRT_2 * 20.).as_ivec2();
|
let desired_size = DVec2::splat(f64::consts::FRAC_1_SQRT_2 * 20.).round();
|
||||||
has_artboards(&mut editor, vec![ArtboardLayoutDocument::new(desired_location, desired_size)]).await;
|
has_artboards(&mut editor, vec![ArtboardLayoutDocument::new(desired_location, desired_size)]).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -752,7 +752,7 @@ mod test_artboard {
|
||||||
editor.drag_tool(ToolType::Artboard, 10., 10., 20., 22., ModifierKeys::empty()).await; // Artboard to drag
|
editor.drag_tool(ToolType::Artboard, 10., 10., 20., 22., ModifierKeys::empty()).await; // Artboard to drag
|
||||||
editor.drag_tool(ToolType::Artboard, 15., 15., 65., 65., ModifierKeys::empty()).await; // Drag from the middle by (50,50)
|
editor.drag_tool(ToolType::Artboard, 15., 15., 65., 65., ModifierKeys::empty()).await; // Drag from the middle by (50,50)
|
||||||
|
|
||||||
has_artboards(&mut editor, vec![ArtboardLayoutDocument::new((60, 60), (10, 12))]).await;
|
has_artboards(&mut editor, vec![ArtboardLayoutDocument::new((60., 60.), (10., 12.))]).await;
|
||||||
}
|
}
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn artboard_move_snapping() {
|
async fn artboard_move_snapping() {
|
||||||
|
|
@ -763,7 +763,11 @@ mod test_artboard {
|
||||||
editor.drag_tool(ToolType::Artboard, 70., 0., 80., 100., ModifierKeys::empty()).await; // Artboard to snap to
|
editor.drag_tool(ToolType::Artboard, 70., 0., 80., 100., ModifierKeys::empty()).await; // Artboard to snap to
|
||||||
editor.drag_tool(ToolType::Artboard, 15., 15., 15. + 49., 15., ModifierKeys::empty()).await; // Drag the artboard so it should snap to the edge
|
editor.drag_tool(ToolType::Artboard, 15., 15., 15. + 49., 15., ModifierKeys::empty()).await; // Drag the artboard so it should snap to the edge
|
||||||
|
|
||||||
has_artboards(&mut editor, vec![ArtboardLayoutDocument::new((60, 10), (10, 12)), ArtboardLayoutDocument::new((70, 0), (10, 100))]).await;
|
has_artboards(
|
||||||
|
&mut editor,
|
||||||
|
vec![ArtboardLayoutDocument::new((60., 10.), (10., 12.)), ArtboardLayoutDocument::new((70., 0.), (10., 100.))],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -777,7 +781,7 @@ mod test_artboard {
|
||||||
|
|
||||||
// Put the artboard in
|
// Put the artboard in
|
||||||
editor.drag_tool(ToolType::Artboard, 5., 5., 30., 10., ModifierKeys::empty()).await;
|
editor.drag_tool(ToolType::Artboard, 5., 5., 30., 10., ModifierKeys::empty()).await;
|
||||||
has_artboards(&mut editor, vec![ArtboardLayoutDocument::new((5, 5), (25, 5))]).await;
|
has_artboards(&mut editor, vec![ArtboardLayoutDocument::new((5., 5.), (25., 5.))]).await;
|
||||||
let document = editor.active_document();
|
let document = editor.active_document();
|
||||||
|
|
||||||
// artboard
|
// artboard
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ use graphene_std::text::FontCache;
|
||||||
use graphene_std::transform::RenderQuality;
|
use graphene_std::transform::RenderQuality;
|
||||||
use graphene_std::vector::Vector;
|
use graphene_std::vector::Vector;
|
||||||
use graphene_std::vector::style::RenderMode;
|
use graphene_std::vector::style::RenderMode;
|
||||||
use graphene_std::{Artboard, Context, Graphic};
|
use graphene_std::{Context, Graphic};
|
||||||
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
|
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
|
||||||
use interpreted_executor::util::wrap_network_in_scope;
|
use interpreted_executor::util::wrap_network_in_scope;
|
||||||
use spin::Mutex;
|
use spin::Mutex;
|
||||||
|
|
@ -439,7 +439,7 @@ impl NodeRuntime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Artboard table: thumbnail
|
// Artboard table: thumbnail
|
||||||
else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
|
else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Table<Graphic>>>>() {
|
||||||
if update_thumbnails {
|
if update_thumbnails {
|
||||||
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses)
|
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ pub use glam::{DAffine2, DVec2, IVec2, UVec2};
|
||||||
use graphic_types::raster_types::{CPU, Image, Raster};
|
use graphic_types::raster_types::{CPU, Image, Raster};
|
||||||
use graphic_types::vector_types::vector::style::{Fill, Gradient, GradientStops, Stroke};
|
use graphic_types::vector_types::vector::style::{Fill, Gradient, GradientStops, Stroke};
|
||||||
use graphic_types::vector_types::vector::{self, ReferencePoint};
|
use graphic_types::vector_types::vector::{self, ReferencePoint};
|
||||||
use graphic_types::{Artboard, Graphic, Vector};
|
use graphic_types::{Graphic, Vector};
|
||||||
use raster_nodes::curve::Curve;
|
use raster_nodes::curve::Curve;
|
||||||
use rendering::RenderMetadata;
|
use rendering::RenderMetadata;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
@ -184,7 +184,7 @@ tagged_value! {
|
||||||
Graphic(Table<Graphic>),
|
Graphic(Table<Graphic>),
|
||||||
#[serde(deserialize_with = "graphic_types::artboard::migrate_artboard")] // TODO: Eventually remove this migration document upgrade code
|
#[serde(deserialize_with = "graphic_types::artboard::migrate_artboard")] // TODO: Eventually remove this migration document upgrade code
|
||||||
#[serde(alias = "ArtboardGroup")]
|
#[serde(alias = "ArtboardGroup")]
|
||||||
Artboard(Table<Artboard>),
|
Artboard(Table<Table<Graphic>>),
|
||||||
#[serde(deserialize_with = "core_types::misc::migrate_color")] // TODO: Eventually remove this migration document upgrade code
|
#[serde(deserialize_with = "core_types::misc::migrate_color")] // TODO: Eventually remove this migration document upgrade code
|
||||||
#[serde(alias = "ColorTable", alias = "OptionalColor", alias = "ColorNotInTable")]
|
#[serde(alias = "ColorTable", alias = "OptionalColor", alias = "ColorNotInTable")]
|
||||||
Color(Table<Color>),
|
Color(Table<Color>),
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ use graphene_std::table::Table;
|
||||||
use graphene_std::transform::Footprint;
|
use graphene_std::transform::Footprint;
|
||||||
use graphene_std::uuid::NodeId;
|
use graphene_std::uuid::NodeId;
|
||||||
use graphene_std::vector::Vector;
|
use graphene_std::vector::Vector;
|
||||||
use graphene_std::{Artboard, Context, Graphic, NodeIO, NodeIOTypes, ProtoNodeIdentifier, concrete, fn_type_fut, future};
|
use graphene_std::{Context, Graphic, NodeIO, NodeIOTypes, ProtoNodeIdentifier, concrete, fn_type_fut, future};
|
||||||
use node_registry_macros::{async_node, convert_node, into_node};
|
use node_registry_macros::{async_node, convert_node, into_node};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
|
|
@ -63,7 +63,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
// MONITOR NODES
|
// MONITOR NODES
|
||||||
// =============
|
// =============
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ()]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ()]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Artboard>]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Table<Graphic>>]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Graphic>]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Graphic>]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Vector>]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Vector>]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
|
||||||
|
|
@ -145,7 +145,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
// ==========
|
// ==========
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ()]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ()]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => bool]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => bool]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Artboard>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Table<Graphic>>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Graphic>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Graphic>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Vector>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Vector>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,9 @@ pub use num_traits;
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
pub use table::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_END, ATTR_NAME, ATTR_START, ATTR_TRANSFORM, ATTR_TYPE};
|
pub use table::{
|
||||||
|
ATTR_ALPHA_BLENDING, ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_END, ATTR_LOCATION, ATTR_NAME, ATTR_START, ATTR_TRANSFORM, ATTR_TYPE,
|
||||||
|
};
|
||||||
#[cfg(feature = "wasm")]
|
#[cfg(feature = "wasm")]
|
||||||
pub use tsify;
|
pub use tsify;
|
||||||
pub use types::Cow;
|
pub use types::Cow;
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,18 @@ pub const ATTR_NAME: &str = "name";
|
||||||
/// `json_query_all` text node alongside each extracted value.
|
/// `json_query_all` text node alongside each extracted value.
|
||||||
pub const ATTR_TYPE: &str = "type";
|
pub const ATTR_TYPE: &str = "type";
|
||||||
|
|
||||||
|
/// Attribute key for an artboard row's `DVec2` top-left corner location in document coordinates.
|
||||||
|
pub const ATTR_LOCATION: &str = "location";
|
||||||
|
|
||||||
|
/// Attribute key for an artboard row's `DVec2` width and height.
|
||||||
|
pub const ATTR_DIMENSIONS: &str = "dimensions";
|
||||||
|
|
||||||
|
/// Attribute key for an artboard row's `Color` background fill.
|
||||||
|
pub const ATTR_BACKGROUND: &str = "background";
|
||||||
|
|
||||||
|
/// Attribute key for an artboard row's `bool` flag indicating whether content is clipped to the artboard bounds.
|
||||||
|
pub const ATTR_CLIP: &str = "clip";
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// TRAIT: AttributeValue
|
// TRAIT: AttributeValue
|
||||||
// =====================
|
// =====================
|
||||||
|
|
|
||||||
|
|
@ -153,41 +153,12 @@ impl Display for ProtoNodeIdentifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Cow<'static, str>, D::Error> {
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
let name = String::deserialize(deserializer)?;
|
|
||||||
let name = match name.as_str() {
|
|
||||||
"f32" => "f64".to_string(),
|
|
||||||
"grahpene_core::transform::Footprint" => "std::option::Option<std::sync::Arc<grahpene_core::context::OwnedContextImpl>>".to_string(),
|
|
||||||
"grahpene_core::graphic_element::GraphicGroup" => "grahpene_core::table::Table<grahpene_core::graphic_types::Graphic>".to_string(),
|
|
||||||
"grahpene_core::raster::image::ImageFrame<Color>"
|
|
||||||
| "grahpene_core::raster::image::ImageFrame<grahpene_core::raster::color::Color>"
|
|
||||||
| "grahpene_core::instances::Instances<grahpene_core::raster::image::ImageFrame<Color>>"
|
|
||||||
| "grahpene_core::instances::Instances<grahpene_core::raster::image::ImageFrame<grahpene_core::raster::color::Color>>"
|
|
||||||
| "grahpene_core::instances::Instances<grahpene_core::raster::image::Image<grahpene_core::raster::color::Color>>" => {
|
|
||||||
"grahpene_core::table::Table<grahpene_core::raster::image::Image<grahpene_core::raster::color::Color>>".to_string()
|
|
||||||
}
|
|
||||||
"grahpene_core::vector::vector_data::VectorData"
|
|
||||||
| "grahpene_core::instances::Instances<grahpene_core::vector::vector_data::VectorData>"
|
|
||||||
| "grahpene_core::table::Table<grahpene_core::vector::vector_data::VectorData>"
|
|
||||||
| "grahpene_core::table::Table<grahpene_core::vector::vector_data::Vector>" => "grahpene_core::table::Table<grahpene_core::vector::vector_types::Vector>".to_string(),
|
|
||||||
"grahpene_core::instances::Instances<grahpene_core::graphic_element::Artboard>" => "grahpene_core::table::Table<grahpene_core::artboard::Artboard>".to_string(),
|
|
||||||
"grahpene_core::vector::vector_data::modification::VectorModification" => "grahpene_core::vector::vector_modification::VectorModification".to_string(),
|
|
||||||
"grahpene_core::table::Table<grahpene_core::graphic_element::Graphic>" => "grahpene_core::table::Table<grahpene_core::graphic_types::Graphic>".to_string(),
|
|
||||||
_ => name,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Cow::Owned(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
#[derive(Clone, Debug, Eq)]
|
#[derive(Clone, Debug, Eq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct TypeDescriptor {
|
pub struct TypeDescriptor {
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
pub id: Option<TypeId>,
|
pub id: Option<TypeId>,
|
||||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "migrate_type_descriptor_names"))]
|
|
||||||
pub name: Cow<'static, str>,
|
pub name: Cow<'static, str>,
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
pub alias: Option<Cow<'static, str>>,
|
pub alias: Option<Cow<'static, str>>,
|
||||||
|
|
|
||||||
|
|
@ -1,107 +1,45 @@
|
||||||
use crate::graphic::Graphic;
|
use crate::graphic::Graphic;
|
||||||
use core_types::blending::AlphaBlending;
|
use core_types::blending::AlphaBlending;
|
||||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
|
||||||
use core_types::math::quad::Quad;
|
|
||||||
use core_types::render_complexity::RenderComplexity;
|
|
||||||
use core_types::table::{Table, TableRow};
|
use core_types::table::{Table, TableRow};
|
||||||
use core_types::transform::Transform;
|
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::{ATTR_TRANSFORM, Color};
|
use core_types::{ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_LOCATION, Color};
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::{DAffine2, DVec2, IVec2};
|
use glam::{DAffine2, IVec2};
|
||||||
use graphene_hash::CacheHash;
|
|
||||||
|
|
||||||
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
|
// An artboard table is `Table<Table<Graphic>>`: each row's element is the artboard's content
|
||||||
#[derive(Clone, Debug, CacheHash, PartialEq, DynAny)]
|
// (a `Table<Graphic>`), with the artboard's metadata stored alongside on the row as attributes
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
// (see `ATTR_LOCATION`, `ATTR_DIMENSIONS`, `ATTR_BACKGROUND`, `ATTR_CLIP`).
|
||||||
pub struct Artboard {
|
//
|
||||||
pub content: Table<Graphic>,
|
// The artboard's user-visible name is the parent layer's display name (resolved live from the
|
||||||
pub label: String,
|
// network interface via the row's `ATTR_EDITOR_LAYER_PATH` attribute) — not stored here, so it
|
||||||
pub location: IVec2,
|
// can never go stale.
|
||||||
pub dimensions: IVec2,
|
//
|
||||||
pub background: Color,
|
// These metadata attributes are populated at runtime by the `Artboard` proto node from its
|
||||||
pub clip: bool,
|
// inputs and therefore aren't persisted in document files; the proto node's input values are
|
||||||
}
|
// what get serialized.
|
||||||
|
|
||||||
impl Default for Artboard {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new(IVec2::ZERO, IVec2::new(1920, 1080))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Artboard {
|
|
||||||
pub fn new(location: IVec2, dimensions: IVec2) -> Self {
|
|
||||||
Self {
|
|
||||||
content: Table::new(),
|
|
||||||
label: "Artboard".to_string(),
|
|
||||||
location: location.min(location + dimensions),
|
|
||||||
dimensions: dimensions.abs(),
|
|
||||||
background: Color::WHITE,
|
|
||||||
clip: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BoundingBox for Artboard {
|
|
||||||
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
|
|
||||||
let artboard_bounds = || (transform * Quad::from_box([self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()])).bounding_box();
|
|
||||||
|
|
||||||
if self.clip {
|
|
||||||
return RenderBoundingBox::Rectangle(artboard_bounds());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut combined_bounds = None;
|
|
||||||
|
|
||||||
for (element, row_transform) in self.content.iter_element_values().zip(self.content.iter_attribute_values_or_default::<DAffine2>(ATTR_TRANSFORM)) {
|
|
||||||
match element.bounding_box(transform * row_transform, include_stroke) {
|
|
||||||
RenderBoundingBox::None => continue,
|
|
||||||
RenderBoundingBox::Infinite => return RenderBoundingBox::Infinite,
|
|
||||||
RenderBoundingBox::Rectangle(bounds) => match combined_bounds {
|
|
||||||
Some(existing) => combined_bounds = Some(Quad::combine_bounds(existing, bounds)),
|
|
||||||
None => combined_bounds = Some(bounds),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match combined_bounds {
|
|
||||||
Some(content_bounds) => RenderBoundingBox::Rectangle(Quad::combine_bounds(content_bounds, artboard_bounds())),
|
|
||||||
None => RenderBoundingBox::Rectangle(artboard_bounds()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderComplexity for Artboard {
|
|
||||||
fn render_complexity(&self) -> usize {
|
|
||||||
self.content.render_complexity()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementations for Artboard
|
|
||||||
impl Transform for Artboard {
|
|
||||||
fn transform(&self) -> DAffine2 {
|
|
||||||
DAffine2::from_translation(self.location.as_dvec2())
|
|
||||||
}
|
|
||||||
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
|
||||||
self.location.as_dvec2() + self.dimensions.as_dvec2() * pivot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Eventually remove this migration document upgrade code
|
// TODO: Eventually remove this migration document upgrade code
|
||||||
pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Artboard>, D::Error> {
|
pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Table<Graphic>>, D::Error> {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, PartialEq, DynAny)]
|
/// Pre-migration shape of the artboard's stored data: the struct that used to live as the element
|
||||||
|
/// of `Table<Artboard>`. Kept as a private type so we can deserialize legacy documents into the new
|
||||||
|
/// `Table<Table<Graphic>>` (element = `content`, other fields → row attributes).
|
||||||
|
#[derive(Clone, Debug, DynAny)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct ArtboardGroup {
|
pub struct LegacyArtboard {
|
||||||
pub artboards: Vec<(Artboard, Option<NodeId>)>,
|
pub content: Table<Graphic>,
|
||||||
|
pub label: String,
|
||||||
|
pub location: IVec2,
|
||||||
|
pub dimensions: IVec2,
|
||||||
|
pub background: Color,
|
||||||
|
pub clip: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug, DynAny)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
pub struct LegacyArtboardGroup {
|
||||||
enum ArtboardFormat {
|
pub artboards: Vec<(LegacyArtboard, Option<NodeId>)>,
|
||||||
ArtboardGroup(ArtboardGroup),
|
|
||||||
OldArtboardTable(OldTable<Artboard>),
|
|
||||||
ArtboardTable(Table<Artboard>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -113,13 +51,30 @@ pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re
|
||||||
alpha_blending: Vec<AlphaBlending>,
|
alpha_blending: Vec<AlphaBlending>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attributes (transform, alpha_blending, editor:layer_path) are not serialized, so migration only needs
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
// to recover the elements. Per-item attribute values are populated at runtime by the node graph.
|
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||||
|
enum ArtboardFormat {
|
||||||
|
ArtboardGroup(LegacyArtboardGroup),
|
||||||
|
OldArtboardTable(OldTable<LegacyArtboard>),
|
||||||
|
LegacyArtboardTable(Table<LegacyArtboard>),
|
||||||
|
// Note: this variant must come last so older formats above are tried first; an empty
|
||||||
|
// `Table<Table<Graphic>>` would otherwise match (since `Table<T>` has the same shell across `T`).
|
||||||
|
ArtboardTable(Table<Table<Graphic>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn legacy_to_row(legacy: LegacyArtboard) -> TableRow<Table<Graphic>> {
|
||||||
|
// Legacy `label` field is dropped — the artboard's name now comes from its parent layer's display name.
|
||||||
|
TableRow::new_from_element(legacy.content)
|
||||||
|
.with_attribute(ATTR_LOCATION, legacy.location.as_dvec2())
|
||||||
|
.with_attribute(ATTR_DIMENSIONS, legacy.dimensions.as_dvec2())
|
||||||
|
.with_attribute(ATTR_BACKGROUND, legacy.background)
|
||||||
|
.with_attribute(ATTR_CLIP, legacy.clip)
|
||||||
|
}
|
||||||
|
|
||||||
Ok(match ArtboardFormat::deserialize(deserializer)? {
|
Ok(match ArtboardFormat::deserialize(deserializer)? {
|
||||||
ArtboardFormat::ArtboardGroup(artboard_group) => artboard_group.artboards.into_iter().map(|(artboard, _)| TableRow::new_from_element(artboard)).collect(),
|
ArtboardFormat::ArtboardGroup(group) => group.artboards.into_iter().map(|(artboard, _)| legacy_to_row(artboard)).collect(),
|
||||||
ArtboardFormat::OldArtboardTable(old_table) => old_table.element.into_iter().map(TableRow::new_from_element).collect(),
|
ArtboardFormat::OldArtboardTable(old_table) => old_table.element.into_iter().map(legacy_to_row).collect(),
|
||||||
|
ArtboardFormat::LegacyArtboardTable(legacy_table) => legacy_table.into_iter().map(|row| legacy_to_row(row.into_element())).collect(),
|
||||||
ArtboardFormat::ArtboardTable(artboard_table) => artboard_table,
|
ArtboardFormat::ArtboardTable(artboard_table) => artboard_table,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node definitions moved to graphic-nodes crate
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ pub use raster_types;
|
||||||
pub use vector_types;
|
pub use vector_types;
|
||||||
|
|
||||||
// Re-export commonly used types at the crate root
|
// Re-export commonly used types at the crate root
|
||||||
pub use artboard::Artboard;
|
|
||||||
pub use graphic::{Graphic, IntoGraphicTable, TryFromGraphic, Vector};
|
pub use graphic::{Graphic, IntoGraphicTable, TryFromGraphic, Vector};
|
||||||
|
|
||||||
pub mod migrations {
|
pub mod migrations {
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,19 @@ use core_types::color::{Alpha, Color};
|
||||||
use core_types::math::quad::Quad;
|
use core_types::math::quad::Quad;
|
||||||
use core_types::render_complexity::RenderComplexity;
|
use core_types::render_complexity::RenderComplexity;
|
||||||
use core_types::table::{Table, TableRow};
|
use core_types::table::{Table, TableRow};
|
||||||
use core_types::transform::{Footprint, Transform};
|
use core_types::transform::Footprint;
|
||||||
use core_types::uuid::{NodeId, generate_uuid};
|
use core_types::uuid::{NodeId, generate_uuid};
|
||||||
use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM};
|
use core_types::{ATTR_ALPHA_BLENDING, ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_LOCATION, ATTR_TRANSFORM};
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene_hash::CacheHashWrapper;
|
use graphene_hash::CacheHashWrapper;
|
||||||
|
use graphic_types::Graphic;
|
||||||
use graphic_types::Vector;
|
use graphic_types::Vector;
|
||||||
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster};
|
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster};
|
||||||
use graphic_types::vector_types::gradient::{GradientStops, GradientType};
|
use graphic_types::vector_types::gradient::{GradientStops, GradientType};
|
||||||
use graphic_types::vector_types::subpath::Subpath;
|
use graphic_types::vector_types::subpath::Subpath;
|
||||||
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
|
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
|
||||||
use graphic_types::vector_types::vector::style::{Fill, PaintOrder, RenderMode, Stroke, StrokeAlign};
|
use graphic_types::vector_types::vector::style::{Fill, PaintOrder, RenderMode, Stroke, StrokeAlign};
|
||||||
use graphic_types::{Artboard, Graphic};
|
|
||||||
use kurbo::{Affine, Cap, Join, Shape};
|
use kurbo::{Affine, Cap, Join, Shape};
|
||||||
use num_traits::Zero;
|
use num_traits::Zero;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
@ -500,172 +500,172 @@ impl Render for Graphic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for Artboard {
|
/// Reads the artboard metadata for the row at `index` from a `Table<Table<Graphic>>` of artboards.
|
||||||
|
fn read_artboard_attributes(table: &Table<Table<Graphic>>, index: usize) -> (DVec2, DVec2, Color, bool) {
|
||||||
|
let location: DVec2 = table.attribute_cloned_or_default(ATTR_LOCATION, index);
|
||||||
|
let dimensions: DVec2 = table.attribute_cloned_or_default(ATTR_DIMENSIONS, index);
|
||||||
|
let background: Color = table.attribute_cloned_or_default(ATTR_BACKGROUND, index);
|
||||||
|
let clip: bool = table.attribute_cloned_or_default(ATTR_CLIP, index);
|
||||||
|
(location, dimensions, background, clip)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for Table<Table<Graphic>> {
|
||||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
let x = self.location.x.min(self.location.x + self.dimensions.x);
|
for index in 0..self.len() {
|
||||||
let y = self.location.y.min(self.location.y + self.dimensions.y);
|
let Some(content) = self.element(index) else { continue };
|
||||||
let width = self.dimensions.x.abs();
|
let (location, dimensions, background, clip) = read_artboard_attributes(self, index);
|
||||||
let height = self.dimensions.y.abs();
|
|
||||||
|
|
||||||
// Rectangle for the artboard
|
let x = location.x.min(location.x + dimensions.x);
|
||||||
if !render_params.hide_artboards {
|
let y = location.y.min(location.y + dimensions.y);
|
||||||
// Transparency checkerboard behind the artboard background (viewport only)
|
let width = dimensions.x.abs();
|
||||||
let show_checkerboard = self.background.alpha() < 1. && render_params.to_canvas();
|
let height = dimensions.y.abs();
|
||||||
if show_checkerboard && render_params.viewport_zoom > 0. {
|
|
||||||
let checker_id = format!("checkered-artboard-{}", generate_uuid());
|
|
||||||
let cell_size = 8. / render_params.viewport_zoom;
|
|
||||||
let pattern_size = cell_size * 2.;
|
|
||||||
|
|
||||||
// Anchor pattern at this artboard's top-left corner (x, y), not the document origin
|
// Rectangle for the artboard
|
||||||
let _ = write!(
|
if !render_params.hide_artboards {
|
||||||
&mut render.svg_defs,
|
// Transparency checkerboard behind the artboard background (viewport only)
|
||||||
r##"<pattern id="{checker_id}" x="{x}" y="{y}" width="{pattern_size}" height="{pattern_size}" patternUnits="userSpaceOnUse"><rect width="{pattern_size}" height="{pattern_size}" fill="#fff" /><rect x="{cell_size}" y="0" width="{cell_size}" height="{cell_size}" fill="#ccc" /><rect x="0" y="{cell_size}" width="{cell_size}" height="{cell_size}" fill="#ccc" /></pattern>"##
|
let show_checkerboard = background.alpha() < 1. && render_params.to_canvas();
|
||||||
);
|
if show_checkerboard && render_params.viewport_zoom > 0. {
|
||||||
|
let checker_id = format!("checkered-artboard-{}", generate_uuid());
|
||||||
|
let cell_size = 8. / render_params.viewport_zoom;
|
||||||
|
let pattern_size = cell_size * 2.;
|
||||||
|
|
||||||
|
// Anchor pattern at this artboard's top-left corner (x, y), not the document origin
|
||||||
|
let _ = write!(
|
||||||
|
&mut render.svg_defs,
|
||||||
|
r##"<pattern id="{checker_id}" x="{x}" y="{y}" width="{pattern_size}" height="{pattern_size}" patternUnits="userSpaceOnUse"><rect width="{pattern_size}" height="{pattern_size}" fill="#fff" /><rect x="{cell_size}" y="0" width="{cell_size}" height="{cell_size}" fill="#ccc" /><rect x="0" y="{cell_size}" width="{cell_size}" height="{cell_size}" fill="#ccc" /></pattern>"##
|
||||||
|
);
|
||||||
|
|
||||||
|
render.leaf_tag("rect", |attributes| {
|
||||||
|
attributes.push("x", x.to_string());
|
||||||
|
attributes.push("y", y.to_string());
|
||||||
|
attributes.push("width", width.to_string());
|
||||||
|
attributes.push("height", height.to_string());
|
||||||
|
attributes.push("fill", format!("url(#{checker_id})"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background
|
||||||
render.leaf_tag("rect", |attributes| {
|
render.leaf_tag("rect", |attributes| {
|
||||||
|
attributes.push("fill", format!("#{}", background.to_rgb_hex_srgb_from_gamma()));
|
||||||
|
if background.a() < 1. {
|
||||||
|
attributes.push("fill-opacity", ((background.a() * 1000.).round() / 1000.).to_string());
|
||||||
|
}
|
||||||
attributes.push("x", x.to_string());
|
attributes.push("x", x.to_string());
|
||||||
attributes.push("y", y.to_string());
|
attributes.push("y", y.to_string());
|
||||||
attributes.push("width", width.to_string());
|
attributes.push("width", width.to_string());
|
||||||
attributes.push("height", height.to_string());
|
attributes.push("height", height.to_string());
|
||||||
attributes.push("fill", format!("url(#{checker_id})"));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Background
|
// Artwork
|
||||||
render.leaf_tag("rect", |attributes| {
|
render.parent_tag(
|
||||||
attributes.push("fill", format!("#{}", self.background.to_rgb_hex_srgb_from_gamma()));
|
// SVG group tag
|
||||||
if self.background.a() < 1. {
|
"g",
|
||||||
attributes.push("fill-opacity", ((self.background.a() * 1000.).round() / 1000.).to_string());
|
// Group tag attributes
|
||||||
}
|
|attributes| {
|
||||||
attributes.push("x", x.to_string());
|
let matrix = format_transform_matrix(DAffine2::from_translation(location));
|
||||||
attributes.push("y", y.to_string());
|
if !matrix.is_empty() {
|
||||||
attributes.push("width", width.to_string());
|
attributes.push(ATTR_TRANSFORM, matrix);
|
||||||
attributes.push("height", height.to_string());
|
}
|
||||||
});
|
|
||||||
|
if clip {
|
||||||
|
let id = format!("artboard-{}", generate_uuid());
|
||||||
|
let selector = format!("url(#{id})");
|
||||||
|
|
||||||
|
write!(
|
||||||
|
&mut attributes.0.svg_defs,
|
||||||
|
r##"<clipPath id="{id}"><rect x="0" y="0" width="{}" height="{}" /></clipPath>"##,
|
||||||
|
dimensions.x, dimensions.y,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
attributes.push("clip-path", selector);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Artwork content
|
||||||
|
|render| {
|
||||||
|
let mut render_params = render_params.clone();
|
||||||
|
render_params.artboard_background = Some(background);
|
||||||
|
content.render_svg(render, &render_params);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Artwork
|
|
||||||
render.parent_tag(
|
|
||||||
// SVG group tag
|
|
||||||
"g",
|
|
||||||
// Group tag attributes
|
|
||||||
|attributes| {
|
|
||||||
let matrix = format_transform_matrix(self.transform());
|
|
||||||
if !matrix.is_empty() {
|
|
||||||
attributes.push(ATTR_TRANSFORM, matrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.clip {
|
|
||||||
let id = format!("artboard-{}", generate_uuid());
|
|
||||||
let selector = format!("url(#{id})");
|
|
||||||
|
|
||||||
write!(
|
|
||||||
&mut attributes.0.svg_defs,
|
|
||||||
r##"<clipPath id="{id}"><rect x="0" y="0" width="{}" height="{}" /></clipPath>"##,
|
|
||||||
self.dimensions.x, self.dimensions.y,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
attributes.push("clip-path", selector);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Artwork content
|
|
||||||
|render| {
|
|
||||||
let mut render_params = render_params.clone();
|
|
||||||
render_params.artboard_background = Some(self.background);
|
|
||||||
self.content.render_svg(render, &render_params);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
use vello::peniko;
|
use vello::peniko;
|
||||||
|
|
||||||
let [a, b] = [self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()];
|
for index in 0..self.len() {
|
||||||
let rect = kurbo::Rect::new(a.x.min(b.x), a.y.min(b.y), a.x.max(b.x), a.y.max(b.y));
|
let Some(content) = self.element(index) else { continue };
|
||||||
|
let (location, dimensions, background, clip) = read_artboard_attributes(self, index);
|
||||||
|
|
||||||
// Render background
|
let [a, b] = [location, location + dimensions];
|
||||||
if !render_params.hide_artboards {
|
let rect = kurbo::Rect::new(a.x.min(b.x), a.y.min(b.y), a.x.max(b.x), a.y.max(b.y));
|
||||||
let artboard_transform = kurbo::Affine::new(transform.to_cols_array());
|
|
||||||
|
|
||||||
// Transparency checkerboard behind the artboard background (viewport only)
|
// Render background
|
||||||
let show_checkerboard = self.background.alpha() < 1. && render_params.to_canvas();
|
if !render_params.hide_artboards {
|
||||||
if show_checkerboard && render_params.viewport_zoom > 0. {
|
let artboard_transform = kurbo::Affine::new(transform.to_cols_array());
|
||||||
// Anchor pattern at THIS artboard's top-left corner
|
|
||||||
// brush_transform is an image placement transform: it maps brush pixel coords → shape coords
|
// Transparency checkerboard behind the artboard background (viewport only)
|
||||||
// scale(1/zoom) sets each brush pixel to 1/zoom document units (constant CSS size after viewport transform)
|
let show_checkerboard = background.alpha() < 1. && render_params.to_canvas();
|
||||||
// then_translate places the brush origin at the artboard corner
|
if show_checkerboard && render_params.viewport_zoom > 0. {
|
||||||
let brush_transform = kurbo::Affine::scale(1. / render_params.viewport_zoom).then_translate(kurbo::Vec2::new(rect.x0, rect.y0));
|
// Anchor pattern at THIS artboard's top-left corner
|
||||||
scene.fill(peniko::Fill::NonZero, artboard_transform, &checkerboard_brush(), Some(brush_transform), &rect);
|
// brush_transform is an image placement transform: it maps brush pixel coords → shape coords
|
||||||
|
// scale(1/zoom) sets each brush pixel to 1/zoom document units (constant CSS size after viewport transform)
|
||||||
|
// then_translate places the brush origin at the artboard corner
|
||||||
|
let brush_transform = kurbo::Affine::scale(1. / render_params.viewport_zoom).then_translate(kurbo::Vec2::new(rect.x0, rect.y0));
|
||||||
|
scene.fill(peniko::Fill::NonZero, artboard_transform, &checkerboard_brush(), Some(brush_transform), &rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
let color = peniko::Color::new([background.r(), background.g(), background.b(), background.a()]);
|
||||||
|
scene.push_layer(peniko::Fill::NonZero, peniko::Mix::Normal, 1., artboard_transform, &rect);
|
||||||
|
scene.fill(peniko::Fill::NonZero, artboard_transform, color, None, &rect);
|
||||||
|
scene.pop_layer();
|
||||||
}
|
}
|
||||||
|
|
||||||
let color = peniko::Color::new([self.background.r(), self.background.g(), self.background.b(), self.background.a()]);
|
if clip {
|
||||||
scene.push_layer(peniko::Fill::NonZero, peniko::Mix::Normal, 1., artboard_transform, &rect);
|
scene.push_clip_layer(peniko::Fill::NonZero, kurbo::Affine::new(transform.to_cols_array()), &rect);
|
||||||
scene.fill(peniko::Fill::NonZero, artboard_transform, color, None, &rect);
|
|
||||||
scene.pop_layer();
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.clip {
|
|
||||||
scene.push_clip_layer(peniko::Fill::NonZero, kurbo::Affine::new(transform.to_cols_array()), &rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since the content's transform is right multiplied in when rendering the content, we just need to right multiply by the artboard offset here.
|
|
||||||
let child_transform = transform * DAffine2::from_translation(self.location.as_dvec2());
|
|
||||||
let mut render_params = render_params.clone();
|
|
||||||
render_params.artboard_background = Some(self.background);
|
|
||||||
self.content.render_to_vello(scene, child_transform, context, &render_params);
|
|
||||||
if self.clip {
|
|
||||||
scene.pop_layer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
|
|
||||||
if let Some(element_id) = element_id {
|
|
||||||
let subpath = Subpath::new_rectangle(DVec2::ZERO, self.dimensions.as_dvec2());
|
|
||||||
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
|
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
|
||||||
metadata.local_transforms.insert(element_id, DAffine2::from_translation(self.location.as_dvec2()));
|
|
||||||
if self.clip {
|
|
||||||
metadata.clip_targets.insert(element_id);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
footprint.transform *= self.transform();
|
|
||||||
self.content.collect_metadata(metadata, footprint, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
// Since the content's transform is right multiplied in when rendering the content, we just need to right multiply by the artboard offset here.
|
||||||
let subpath_rectangle = Subpath::new_rectangle(DVec2::ZERO, self.dimensions.as_dvec2());
|
let child_transform = transform * DAffine2::from_translation(location);
|
||||||
click_targets.push(ClickTarget::new_with_subpath(subpath_rectangle, 0.));
|
let mut render_params = render_params.clone();
|
||||||
}
|
render_params.artboard_background = Some(background);
|
||||||
|
content.render_to_vello(scene, child_transform, context, &render_params);
|
||||||
fn contains_artboard(&self) -> bool {
|
if clip {
|
||||||
true
|
scene.pop_layer();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for Table<Artboard> {
|
|
||||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
|
||||||
for element in self.iter_element_values() {
|
|
||||||
element.render_svg(render, render_params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
|
||||||
for element in self.iter_element_values() {
|
|
||||||
element.render_to_vello(scene, transform, context, render_params);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
|
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
|
||||||
for index in 0..self.len() {
|
for index in 0..self.len() {
|
||||||
|
let Some(content) = self.element(index) else { continue };
|
||||||
|
let (location, dimensions, _background, clip) = read_artboard_attributes(self, index);
|
||||||
|
|
||||||
let layer_path: Table<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
|
let layer_path: Table<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
|
||||||
let layer = layer_path.iter_element_values().next_back().copied();
|
let element_id = layer_path.iter_element_values().next_back().copied();
|
||||||
self.element(index).unwrap().collect_metadata(metadata, footprint, layer);
|
|
||||||
|
if let Some(element_id) = element_id {
|
||||||
|
let subpath = Subpath::new_rectangle(DVec2::ZERO, dimensions);
|
||||||
|
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
|
||||||
|
metadata.upstream_footprints.insert(element_id, footprint);
|
||||||
|
metadata.local_transforms.insert(element_id, DAffine2::from_translation(location));
|
||||||
|
if clip {
|
||||||
|
metadata.clip_targets.insert(element_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut child_footprint = footprint;
|
||||||
|
child_footprint.transform *= DAffine2::from_translation(location);
|
||||||
|
content.collect_metadata(metadata, child_footprint, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||||
for element in self.iter_element_values() {
|
for index in 0..self.len() {
|
||||||
element.add_upstream_click_targets(click_targets);
|
let dimensions: DVec2 = self.attribute_cloned_or_default(ATTR_DIMENSIONS, index);
|
||||||
|
let subpath_rectangle = Subpath::new_rectangle(DVec2::ZERO, dimensions);
|
||||||
|
click_targets.push(ClickTarget::new_with_subpath(subpath_rectangle, 0.));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use core_types::transform::Footprint;
|
||||||
use core_types::{CacheHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl};
|
use core_types::{CacheHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphic_types::vector_types::GradientStops;
|
use graphic_types::vector_types::GradientStops;
|
||||||
use graphic_types::{Artboard, Graphic, Vector};
|
use graphic_types::{Graphic, Vector};
|
||||||
use raster_types::{CPU, GPU, Raster};
|
use raster_types::{CPU, GPU, Raster};
|
||||||
|
|
||||||
const DAY: f64 = 1000. * 3600. * 24.;
|
const DAY: f64 = 1000. * 3600. * 24.;
|
||||||
|
|
@ -78,7 +78,7 @@ async fn quantize_real_time<T>(
|
||||||
Context -> Table<Raster<CPU>>,
|
Context -> Table<Raster<CPU>>,
|
||||||
Context -> Table<Raster<GPU>>,
|
Context -> Table<Raster<GPU>>,
|
||||||
Context -> Table<Color>,
|
Context -> Table<Color>,
|
||||||
Context -> Table<Artboard>,
|
Context -> Table<Table<Graphic>>,
|
||||||
Context -> Table<GradientStops>,
|
Context -> Table<GradientStops>,
|
||||||
Context -> Table<String>,
|
Context -> Table<String>,
|
||||||
Context -> Table<f64>,
|
Context -> Table<f64>,
|
||||||
|
|
@ -118,7 +118,7 @@ async fn quantize_animation_time<T>(
|
||||||
Context -> Table<Raster<CPU>>,
|
Context -> Table<Raster<CPU>>,
|
||||||
Context -> Table<Raster<GPU>>,
|
Context -> Table<Raster<GPU>>,
|
||||||
Context -> Table<Color>,
|
Context -> Table<Color>,
|
||||||
Context -> Table<Artboard>,
|
Context -> Table<Table<Graphic>>,
|
||||||
Context -> Table<GradientStops>,
|
Context -> Table<GradientStops>,
|
||||||
Context -> Table<String>,
|
Context -> Table<String>,
|
||||||
Context -> Table<f64>,
|
Context -> Table<f64>,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use core_types::uuid::NodeId;
|
||||||
use core_types::{Color, OwnedContextImpl};
|
use core_types::{Color, OwnedContextImpl};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphic_types::vector_types::GradientStops;
|
use graphic_types::vector_types::GradientStops;
|
||||||
use graphic_types::{Artboard, Graphic, Vector};
|
use graphic_types::{Graphic, Vector};
|
||||||
use raster_types::{CPU, GPU, Raster};
|
use raster_types::{CPU, GPU, Raster};
|
||||||
|
|
||||||
/// Filters out what should be unused components of the context based on the specified requirements.
|
/// Filters out what should be unused components of the context based on the specified requirements.
|
||||||
|
|
@ -35,7 +35,7 @@ async fn context_modification<T>(
|
||||||
Context -> Table<Raster<CPU>>,
|
Context -> Table<Raster<CPU>>,
|
||||||
Context -> Table<Raster<GPU>>,
|
Context -> Table<Raster<GPU>>,
|
||||||
Context -> Table<Color>,
|
Context -> Table<Color>,
|
||||||
Context -> Table<Artboard>,
|
Context -> Table<Table<Graphic>>,
|
||||||
Context -> Table<GradientStops>,
|
Context -> Table<GradientStops>,
|
||||||
)]
|
)]
|
||||||
value: impl Node<Context<'static>, Output = T>,
|
value: impl Node<Context<'static>, Output = T>,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,19 @@
|
||||||
use core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl, table::Table, transform::TransformMut};
|
use core_types::{
|
||||||
use glam::{DAffine2, DVec2, IVec2};
|
ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_LOCATION, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl,
|
||||||
|
table::{Table, TableRow},
|
||||||
|
transform::TransformMut,
|
||||||
|
};
|
||||||
|
use glam::{DAffine2, DVec2};
|
||||||
use graphic_types::{
|
use graphic_types::{
|
||||||
Artboard, Vector,
|
Vector,
|
||||||
graphic::{Graphic, IntoGraphicTable},
|
graphic::{Graphic, IntoGraphicTable},
|
||||||
};
|
};
|
||||||
use raster_types::{CPU, GPU, Raster};
|
use raster_types::{CPU, GPU, Raster};
|
||||||
use vector_types::GradientStops;
|
use vector_types::GradientStops;
|
||||||
|
|
||||||
/// Constructs a new single-item `Table<Artboard>` with the chosen properties.
|
/// Constructs a new single-item `Table<Table<Graphic>>` (an artboard table) where the row's element is
|
||||||
|
/// the artboard's content and the metadata (label, location, dimensions, background, clip) is stored as
|
||||||
|
/// per-row attributes.
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
pub async fn create_artboard<T: IntoGraphicTable + 'n>(
|
pub async fn create_artboard<T: IntoGraphicTable + 'n>(
|
||||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||||
|
|
@ -22,42 +28,40 @@ pub async fn create_artboard<T: IntoGraphicTable + 'n>(
|
||||||
Context -> DAffine2,
|
Context -> DAffine2,
|
||||||
)]
|
)]
|
||||||
content: impl Node<Context<'static>, Output = T>,
|
content: impl Node<Context<'static>, Output = T>,
|
||||||
/// Name of the artboard, shown in parts of the editor.
|
|
||||||
label: String,
|
|
||||||
/// Coordinate of the top-left corner of the artboard within the document.
|
/// Coordinate of the top-left corner of the artboard within the document.
|
||||||
location: DVec2,
|
location: DVec2,
|
||||||
/// Width and height of the artboard within the document. Only integers are valid.
|
/// Width and height of the artboard within the document.
|
||||||
dimensions: DVec2,
|
dimensions: DVec2,
|
||||||
/// Color of the artboard background. Only positive integers are valid.
|
/// Color of the artboard background.
|
||||||
background: Table<Color>,
|
background: Table<Color>,
|
||||||
/// Whether to cut off the contained content that extends outside the artboard, or keep it visible.
|
/// Whether to cut off the contained content that extends outside the artboard, or keep it visible.
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
clip: bool,
|
clip: bool,
|
||||||
) -> Table<Artboard> {
|
) -> Table<Table<Graphic>> {
|
||||||
let location = location.as_ivec2();
|
|
||||||
|
|
||||||
let footprint = ctx.try_footprint().copied();
|
let footprint = ctx.try_footprint().copied();
|
||||||
let mut new_ctx = OwnedContextImpl::from(ctx);
|
let mut new_ctx = OwnedContextImpl::from(ctx);
|
||||||
if let Some(mut footprint) = footprint {
|
if let Some(mut footprint) = footprint {
|
||||||
footprint.translate(location.as_dvec2());
|
footprint.translate(location);
|
||||||
new_ctx = new_ctx.with_footprint(footprint);
|
new_ctx = new_ctx.with_footprint(footprint);
|
||||||
}
|
}
|
||||||
let content = content.eval(new_ctx.into_context()).await.into_graphic_table();
|
let content = content.eval(new_ctx.into_context()).await.into_graphic_table();
|
||||||
|
|
||||||
let dimensions = dimensions.as_ivec2().max(IVec2::ONE);
|
// Normalize so `location` is the top-left corner and `dimensions` are positive (allowing negative input
|
||||||
|
// dimensions to represent dragging from the opposite corner). Compute the corner using the raw signed
|
||||||
let location = location.min(location + dimensions);
|
// dimensions before clamping, otherwise negative inputs collapse to the original corner instead of inverting.
|
||||||
|
let normalized_location = location.min(location + dimensions);
|
||||||
let dimensions = dimensions.abs();
|
let normalized_dimensions = dimensions.abs().max(DVec2::ONE);
|
||||||
|
|
||||||
let background = background.element(0).copied().unwrap_or(Color::WHITE);
|
let background = background.element(0).copied().unwrap_or(Color::WHITE);
|
||||||
|
|
||||||
Table::new_from_element(Artboard {
|
// The artboard's user-visible name is its parent layer's display name; not stored as an attribute here so
|
||||||
content,
|
// it can't go stale. The data panel resolves it live from the row's `editor:layer_path` NodeId via the network
|
||||||
label,
|
// interface, so renaming the layer reflects everywhere on the next refresh.
|
||||||
location,
|
Table::new_from_row(
|
||||||
dimensions,
|
TableRow::new_from_element(content)
|
||||||
background,
|
.with_attribute(ATTR_LOCATION, normalized_location)
|
||||||
clip,
|
.with_attribute(ATTR_DIMENSIONS, normalized_dimensions)
|
||||||
})
|
.with_attribute(ATTR_BACKGROUND, background)
|
||||||
|
.with_attribute(ATTR_CLIP, clip),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ use core_types::table::{Table, TableRow};
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, AnyHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, AnyHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
use graphic_types::Vector;
|
||||||
use graphic_types::graphic::{Graphic, IntoGraphicTable};
|
use graphic_types::graphic::{Graphic, IntoGraphicTable};
|
||||||
use graphic_types::{Artboard, Vector};
|
|
||||||
use raster_types::{CPU, GPU, Raster};
|
use raster_types::{CPU, GPU, Raster};
|
||||||
use vector_types::{GradientStop, GradientStops, ReferencePoint};
|
use vector_types::{GradientStop, GradientStops, ReferencePoint};
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ pub fn index_elements<T: graphic_types::graphic::AtIndex + Clone + Default>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
/// The list of data.
|
/// The list of data.
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Table<Artboard>,
|
Table<Table<Graphic>>,
|
||||||
Table<Graphic>,
|
Table<Graphic>,
|
||||||
Table<Vector>,
|
Table<Vector>,
|
||||||
Table<Raster<CPU>>,
|
Table<Raster<CPU>>,
|
||||||
|
|
@ -48,7 +48,7 @@ pub fn omit_element<T: graphic_types::graphic::OmitIndex + Clone + Default>(
|
||||||
/// The list of data.
|
/// The list of data.
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Table<String>,
|
Table<String>,
|
||||||
Table<Artboard>,
|
Table<Table<Graphic>>,
|
||||||
Table<Graphic>,
|
Table<Graphic>,
|
||||||
Table<Vector>,
|
Table<Vector>,
|
||||||
Table<Raster<CPU>>,
|
Table<Raster<CPU>>,
|
||||||
|
|
@ -86,7 +86,7 @@ pub fn extract_element<T: Clone + Default + Send + Sync + 'static>(
|
||||||
Table<Vector>,
|
Table<Vector>,
|
||||||
Table<Raster<CPU>>,
|
Table<Raster<CPU>>,
|
||||||
Table<Graphic>,
|
Table<Graphic>,
|
||||||
Table<Artboard>,
|
Table<Table<Graphic>>,
|
||||||
)]
|
)]
|
||||||
table: Table<T>,
|
table: Table<T>,
|
||||||
/// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the list, starting from -1 for the last item.
|
/// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the list, starting from -1 for the last item.
|
||||||
|
|
@ -225,7 +225,7 @@ async fn write_attribute<T: AnyHash + Clone + Send + Sync + core_types::CacheHas
|
||||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||||
/// The `Table` whose items will gain or have replaced the named attribute.
|
/// The `Table` whose items will gain or have replaced the named attribute.
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>,
|
Table<Table<Graphic>>, Table<Table<Graphic>>, Table<Table<Graphic>>, Table<Table<Graphic>>, Table<Table<Graphic>>, Table<Table<Graphic>>, Table<Table<Graphic>>, Table<Table<Graphic>>, Table<Table<Graphic>>, Table<Table<Graphic>>,
|
||||||
Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>,
|
Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>,
|
||||||
Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>,
|
Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>,
|
||||||
Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>,
|
Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>,
|
||||||
|
|
@ -262,11 +262,11 @@ async fn write_attribute<T: AnyHash + Clone + Send + Sync + core_types::CacheHas
|
||||||
pub async fn extend<T: 'n + Send + Clone>(
|
pub async fn extend<T: 'n + Send + Clone>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
/// The `Table` whose items will appear at the start of the extended `Table`.
|
/// The `Table` whose items will appear at the start of the extended `Table`.
|
||||||
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
|
#[implementations(Table<Table<Graphic>>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
|
||||||
base: Table<T>,
|
base: Table<T>,
|
||||||
/// The `Table` whose items will appear at the end of the extended `Table`.
|
/// The `Table` whose items will appear at the end of the extended `Table`.
|
||||||
#[expose]
|
#[expose]
|
||||||
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
|
#[implementations(Table<Table<Graphic>>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
|
||||||
new: Table<T>,
|
new: Table<T>,
|
||||||
) -> Table<T> {
|
) -> Table<T> {
|
||||||
let mut base = base;
|
let mut base = base;
|
||||||
|
|
@ -281,9 +281,9 @@ pub async fn extend<T: 'n + Send + Clone>(
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
pub async fn legacy_layer_extend<T: 'n + Send + Clone>(
|
pub async fn legacy_layer_extend<T: 'n + Send + Clone>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)] base: Table<T>,
|
#[implementations(Table<Table<Graphic>>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)] base: Table<T>,
|
||||||
#[expose]
|
#[expose]
|
||||||
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
|
#[implementations(Table<Table<Graphic>>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
|
||||||
new: Table<T>,
|
new: Table<T>,
|
||||||
nested_node_path: Table<NodeId>,
|
nested_node_path: Table<NodeId>,
|
||||||
) -> Table<T> {
|
) -> Table<T> {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ pub use graphene_application_io as application_io;
|
||||||
pub use graphene_core;
|
pub use graphene_core;
|
||||||
pub use graphene_core::debug;
|
pub use graphene_core::debug;
|
||||||
pub use graphic_nodes;
|
pub use graphic_nodes;
|
||||||
pub use graphic_types::{Artboard, Graphic, Vector};
|
pub use graphic_types::{Graphic, Vector};
|
||||||
pub use math_nodes;
|
pub use math_nodes;
|
||||||
pub use path_bool_nodes;
|
pub use path_bool_nodes;
|
||||||
pub use raster_nodes;
|
pub use raster_nodes;
|
||||||
|
|
@ -44,7 +44,6 @@ pub mod vector {
|
||||||
|
|
||||||
pub mod graphic {
|
pub mod graphic {
|
||||||
pub use graphic_nodes::graphic::*;
|
pub use graphic_nodes::graphic::*;
|
||||||
pub use graphic_types::Artboard;
|
|
||||||
pub use graphic_types::graphic::*;
|
pub use graphic_types::graphic::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ pub use graph_craft::document::value::RenderOutputType;
|
||||||
use graphene_application_io::{ApplicationIo, ExportFormat, RenderConfig};
|
use graphene_application_io::{ApplicationIo, ExportFormat, RenderConfig};
|
||||||
use graphic_types::raster_types::Image;
|
use graphic_types::raster_types::Image;
|
||||||
use graphic_types::raster_types::{CPU, Raster};
|
use graphic_types::raster_types::{CPU, Raster};
|
||||||
use graphic_types::{Artboard, Graphic, Vector};
|
use graphic_types::{Graphic, Vector};
|
||||||
use rendering::{Render, RenderOutputType as RenderOutputTypeRequest, RenderParams, RenderSvgSegmentList, SvgRender, checkerboard_brush};
|
use rendering::{Render, RenderOutputType as RenderOutputTypeRequest, RenderParams, RenderSvgSegmentList, SvgRender, checkerboard_brush};
|
||||||
use rendering::{RenderMetadata, SvgSegment};
|
use rendering::{RenderMetadata, SvgSegment};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
@ -39,7 +39,7 @@ pub struct RenderIntermediate {
|
||||||
async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send + Sync>(
|
async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send + Sync>(
|
||||||
ctx: impl Ctx + ExtractVarArgs + ExtractAll + CloneVarArgs,
|
ctx: impl Ctx + ExtractVarArgs + ExtractAll + CloneVarArgs,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Context -> Table<Artboard>,
|
Context -> Table<Table<Graphic>>,
|
||||||
Context -> Table<Graphic>,
|
Context -> Table<Graphic>,
|
||||||
Context -> Table<Vector>,
|
Context -> Table<Vector>,
|
||||||
Context -> Table<Raster<CPU>>,
|
Context -> Table<Raster<CPU>>,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use core_types::transform::Footprint;
|
||||||
use core_types::{Color, Ctx, num_traits};
|
use core_types::{Color, Ctx, num_traits};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphic_types::raster_types::{CPU, GPU, Raster};
|
use graphic_types::raster_types::{CPU, GPU, Raster};
|
||||||
use graphic_types::{Artboard, Graphic, Vector};
|
use graphic_types::{Graphic, Vector};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use math_parser::ast;
|
use math_parser::ast;
|
||||||
use math_parser::context::{EvalContext, NothingMap, ValueProvider};
|
use math_parser::context::{EvalContext, NothingMap, ValueProvider};
|
||||||
|
|
@ -753,7 +753,7 @@ async fn switch<T, C: Send + 'n + Clone>(
|
||||||
Context -> u64,
|
Context -> u64,
|
||||||
Context -> DVec2,
|
Context -> DVec2,
|
||||||
Context -> DAffine2,
|
Context -> DAffine2,
|
||||||
Context -> Table<Artboard>,
|
Context -> Table<Table<Graphic>>,
|
||||||
Context -> Table<Graphic>,
|
Context -> Table<Graphic>,
|
||||||
Context -> Table<Vector>,
|
Context -> Table<Vector>,
|
||||||
Context -> Table<Raster<CPU>>,
|
Context -> Table<Raster<CPU>>,
|
||||||
|
|
@ -772,7 +772,7 @@ async fn switch<T, C: Send + 'n + Clone>(
|
||||||
Context -> u64,
|
Context -> u64,
|
||||||
Context -> DVec2,
|
Context -> DVec2,
|
||||||
Context -> DAffine2,
|
Context -> DAffine2,
|
||||||
Context -> Table<Artboard>,
|
Context -> Table<Table<Graphic>>,
|
||||||
Context -> Table<Graphic>,
|
Context -> Table<Graphic>,
|
||||||
Context -> Table<Vector>,
|
Context -> Table<Vector>,
|
||||||
Context -> Table<Raster<CPU>>,
|
Context -> Table<Raster<CPU>>,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue