mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 06:58:11 +08:00
Remove serialization from Table<T> and make TaggedValue only store tooling/widget node inputs (#4129)
* Add TaggedValue::TypeDefault to avoid baking placeholder Tables into saved documents * Add TaggedValue::TypeDefault to avoid baking placeholder Tables into saved documents * Migrate empty Vector/Raster/Graphic/Artboard placeholder values to TypeDefault on load Documents written before the TypeDefault mechanism existed have empty Table<Vector>/<Raster>/<Graphic>/<Artboard> values baked into every unwired exposed input. Walk each migrated node's inputs and rewrite any such placeholder NodeInput::Value into the equivalent NodeInput::type_default, so re-saved documents shed the placeholder payloads. Marked with a TODO for eventual removal once enough documents have been re-saved. * Re-save demo artwork * Remove Graphic and Artboard placeholder containers from TaggedValue * Remove Raster placeholder TaggedValue variant * Simplify document migration * Remove Vector placeholder TaggedValue variant * Remove NodeIdTable from the TaggedValue * Remove StringTable from the TaggedValue * Remove F64Table in place of F64Array in TaggedValue * Replace TaggedValue::Color(Table<Color>) with ::Color(Option<Color>) * Replace TaggedValue::GradientTable(Table<GradientStops>) with ::Gradient(GradientStops) * Replace TaggedValue::BrushStrokeTable(Table<BrushStroke>) with ::BrushStrokes(Vec<BrushStroke>) * Make TaggedValue::DocumentNode runtime-only with TypeDefault placeholder * Make TaggedValue::ContextFeatures runtime-only * Remove Serialize/Deserialize from Table<T> * Add a widget for TaggedValue::BrushStrokes to visualize strokes and samples * Define a reusable list of TaggedValue::TypeDefault types for its generated methods * Re-save demo artwork
This commit is contained in:
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
@@ -29,6 +29,7 @@ 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};
|
use glam::{DAffine2, DVec2};
|
||||||
|
use graph_craft::descriptor;
|
||||||
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;
|
||||||
@@ -1760,12 +1761,7 @@ impl DocumentMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
|
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
|
||||||
// Walk the document JSON and rewrite any `TaggedValue` variants that have been removed since being released as `"None"` so the document still deserializes.
|
let document_message_handler = serde_json::from_str::<DocumentMessageHandler>(serialized_content)
|
||||||
// `migrate_node` then drops the resulting orphan node inputs so the value never reaches graph execution.
|
|
||||||
let mut json_value: serde_json::Value = serde_json::from_str(serialized_content).map_err(|e| EditorError::DocumentDeserialization(e.to_string()))?;
|
|
||||||
graph_craft::document::value::TaggedValue::scrub_removed_variants_from_json(&mut json_value);
|
|
||||||
|
|
||||||
let document_message_handler = serde_json::from_value::<DocumentMessageHandler>(json_value.clone())
|
|
||||||
.or_else(|e| {
|
.or_else(|e| {
|
||||||
log::warn!("Failed to directly load document with the following error: {e}. Trying old DocumentMessageHandler.");
|
log::warn!("Failed to directly load document with the following error: {e}. Trying old DocumentMessageHandler.");
|
||||||
// TODO: Eventually remove this document upgrade code
|
// TODO: Eventually remove this document upgrade code
|
||||||
@@ -1804,7 +1800,7 @@ impl DocumentMessageHandler {
|
|||||||
pub snapping_state: SnappingState,
|
pub snapping_state: SnappingState,
|
||||||
}
|
}
|
||||||
|
|
||||||
serde_json::from_value::<OldDocumentMessageHandler>(json_value).map(|old_message_handler| DocumentMessageHandler {
|
serde_json::from_str::<OldDocumentMessageHandler>(serialized_content).map(|old_message_handler| DocumentMessageHandler {
|
||||||
network_interface: NodeNetworkInterface::from_old_network(old_message_handler.network),
|
network_interface: NodeNetworkInterface::from_old_network(old_message_handler.network),
|
||||||
collapsed: old_message_handler.collapsed,
|
collapsed: old_message_handler.collapsed,
|
||||||
commit_hash: old_message_handler.commit_hash,
|
commit_hash: old_message_handler.commit_hash,
|
||||||
@@ -3349,7 +3345,7 @@ impl DocumentMessageHandler {
|
|||||||
/// Create a network interface with a single export
|
/// Create a network interface with a single export
|
||||||
fn default_document_network_interface() -> NodeNetworkInterface {
|
fn default_document_network_interface() -> NodeNetworkInterface {
|
||||||
let mut network_interface = NodeNetworkInterface::default();
|
let mut network_interface = NodeNetworkInterface::default();
|
||||||
network_interface.add_export(TaggedValue::Artboard(Default::default()), -1, "", &[]);
|
network_interface.add_export(TaggedValue::TypeDefault(descriptor!(graphene_std::table::Table<graphene_std::Artboard>)), -1, "", &[]);
|
||||||
network_interface
|
network_interface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -8,14 +8,14 @@ use crate::messages::portfolio::document::utility_types::nodes::CollapsedLayers;
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;
|
use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;
|
||||||
use glam::{DAffine2, DVec2, IVec2};
|
use glam::{DAffine2, DVec2, IVec2};
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::descriptor;
|
||||||
use graph_craft::document::{NodeId, NodeInput};
|
use graph_craft::document::{NodeId, NodeInput};
|
||||||
use graphene_std::Color;
|
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
|
use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
|
||||||
use graphene_std::table::Table;
|
use graphene_std::table::Table;
|
||||||
use graphene_std::text::{Font, TypesettingConfig};
|
use graphene_std::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::style::{Fill, Gradient, GradientSpreadMethod, GradientStop, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphene_std::vector::style::{Fill, Gradient, GradientSpreadMethod, GradientStop, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
|
use graphene_std::{Artboard, Color};
|
||||||
|
|
||||||
#[derive(ExtractField)]
|
#[derive(ExtractField)]
|
||||||
pub struct GraphOperationMessageContext<'a> {
|
pub struct GraphOperationMessageContext<'a> {
|
||||||
@@ -170,7 +170,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the bottom input of the artboard back to artboard
|
// Set the bottom input of the artboard back to artboard
|
||||||
let bottom_input = NodeInput::value(TaggedValue::Artboard(Table::new()), true);
|
let bottom_input = NodeInput::type_default(descriptor!(Table<Artboard>), true);
|
||||||
network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]);
|
network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]);
|
||||||
} else {
|
} else {
|
||||||
// We have some non layers (e.g. just a rectangle node). We disconnect the bottom input and connect it to the left input.
|
// We have some non layers (e.g. just a rectangle node). We disconnect the bottom input and connect it to the left input.
|
||||||
@@ -178,7 +178,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
|||||||
network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 1), primary_input, &[]);
|
network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 1), primary_input, &[]);
|
||||||
|
|
||||||
// Set the bottom input of the artboard back to artboard
|
// Set the bottom input of the artboard back to artboard
|
||||||
let bottom_input = NodeInput::value(TaggedValue::Artboard(Table::new()), true);
|
let bottom_input = NodeInput::type_default(descriptor!(Table<Artboard>), true);
|
||||||
network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]);
|
network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use crate::messages::prelude::*;
|
|||||||
use glam::{DAffine2, DVec2};
|
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, descriptor};
|
||||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||||
use graphene_std::raster::BlendMode;
|
use graphene_std::raster::BlendMode;
|
||||||
use graphene_std::raster_types::Image;
|
use graphene_std::raster_types::Image;
|
||||||
@@ -15,7 +15,7 @@ use graphene_std::table::Table;
|
|||||||
use graphene_std::text::{Font, TypesettingConfig};
|
use graphene_std::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::style::{Fill, GradientSpreadMethod, GradientType, Stroke};
|
use graphene_std::vector::style::{Fill, GradientSpreadMethod, GradientType, Stroke};
|
||||||
use graphene_std::vector::{GradientStops, PointId, Vector, VectorModification, VectorModificationType};
|
use graphene_std::vector::{GradientStops, PointId, Vector, VectorModification, VectorModificationType};
|
||||||
use graphene_std::{Color, Graphic, NodeInputDecleration};
|
use graphene_std::{Artboard, 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 {
|
||||||
@@ -132,11 +132,11 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
/// 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, location: DVec2, dimensions: DVec2, background: Color, clip: bool) -> 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::type_default(descriptor!(Table<Artboard>), true)),
|
||||||
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
|
Some(NodeInput::type_default(descriptor!(Table<Graphic>), true)),
|
||||||
Some(NodeInput::value(TaggedValue::DVec2(location), false)),
|
Some(NodeInput::value(TaggedValue::DVec2(location), false)),
|
||||||
Some(NodeInput::value(TaggedValue::DVec2(dimensions), false)),
|
Some(NodeInput::value(TaggedValue::DVec2(dimensions), false)),
|
||||||
Some(NodeInput::value(TaggedValue::Color(Table::new_from_element(background)), false)),
|
Some(NodeInput::value(TaggedValue::Color(Some(background)), false)),
|
||||||
Some(NodeInput::value(TaggedValue::Bool(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, &[]);
|
||||||
@@ -147,7 +147,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
let boolean = resolve_proto_node_type(graphene_std::path_bool_nodes::boolean_operation::IDENTIFIER)
|
let boolean = resolve_proto_node_type(graphene_std::path_bool_nodes::boolean_operation::IDENTIFIER)
|
||||||
.expect("Boolean node does not exist")
|
.expect("Boolean node does not exist")
|
||||||
.node_template_input_override([
|
.node_template_input_override([
|
||||||
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
|
Some(NodeInput::type_default(descriptor!(Table<Graphic>), true)),
|
||||||
Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)),
|
Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -157,10 +157,9 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_blend_data(&mut self, layer: LayerNodeIdentifier, count: f64) -> NodeId {
|
pub fn insert_blend_data(&mut self, layer: LayerNodeIdentifier, count: f64) -> NodeId {
|
||||||
let blend = resolve_network_node_type("Blend").expect("Blend node does not exist").node_template_input_override([
|
let blend = resolve_network_node_type("Blend")
|
||||||
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
|
.expect("Blend node does not exist")
|
||||||
Some(NodeInput::value(TaggedValue::F64(count), false)),
|
.node_template_input_override([Some(NodeInput::type_default(descriptor!(Table<Graphic>), true)), Some(NodeInput::value(TaggedValue::F64(count), false))]);
|
||||||
]);
|
|
||||||
|
|
||||||
let blend_id = NodeId::new();
|
let blend_id = NodeId::new();
|
||||||
self.network_interface.insert_node(blend_id, blend, &[]);
|
self.network_interface.insert_node(blend_id, blend, &[]);
|
||||||
@@ -172,10 +171,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
pub fn insert_morph_data(&mut self, layer: LayerNodeIdentifier) -> NodeId {
|
pub fn insert_morph_data(&mut self, layer: LayerNodeIdentifier) -> NodeId {
|
||||||
let morph = resolve_proto_node_type(graphene_std::vector::morph::IDENTIFIER)
|
let morph = resolve_proto_node_type(graphene_std::vector::morph::IDENTIFIER)
|
||||||
.expect("Morph node does not exist")
|
.expect("Morph node does not exist")
|
||||||
.node_template_input_override([
|
.node_template_input_override([Some(NodeInput::type_default(descriptor!(Table<Graphic>), true)), Some(NodeInput::value(TaggedValue::F64(0.5), false))]);
|
||||||
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
|
|
||||||
Some(NodeInput::value(TaggedValue::F64(0.5), false)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let morph_id = NodeId::new();
|
let morph_id = NodeId::new();
|
||||||
self.network_interface.insert_node(morph_id, morph, &[]);
|
self.network_interface.insert_node(morph_id, morph, &[]);
|
||||||
@@ -298,10 +294,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
pub fn insert_color_value(&mut self, color: Color, layer: LayerNodeIdentifier) {
|
pub fn insert_color_value(&mut self, color: Color, layer: LayerNodeIdentifier) {
|
||||||
let color_value = resolve_proto_node_type(graphene_std::math_nodes::color_value::IDENTIFIER)
|
let color_value = resolve_proto_node_type(graphene_std::math_nodes::color_value::IDENTIFIER)
|
||||||
.expect("Color Value node does not exist")
|
.expect("Color Value node does not exist")
|
||||||
.node_template_input_override([
|
.node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::Color(Some(color)), false))]);
|
||||||
Some(NodeInput::value(TaggedValue::None, false)),
|
|
||||||
Some(NodeInput::value(TaggedValue::Color(Table::new_from_element(color)), false)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let color_value_id = NodeId::new();
|
let color_value_id = NodeId::new();
|
||||||
self.network_interface.insert_node(color_value_id, color_value, &[]);
|
self.network_interface.insert_node(color_value_id, color_value, &[]);
|
||||||
@@ -427,15 +420,15 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
match &fill {
|
match &fill {
|
||||||
Fill::None => {
|
Fill::None => {
|
||||||
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
|
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Table::new()), false), true);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(None), false), true);
|
||||||
}
|
}
|
||||||
Fill::Solid(color) => {
|
Fill::Solid(color) => {
|
||||||
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
|
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Table::new_from_element(*color)), false), true);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Some(*color)), false), true);
|
||||||
}
|
}
|
||||||
Fill::Gradient(gradient) => {
|
Fill::Gradient(gradient) => {
|
||||||
let input_connector = InputConnector::node(fill_node_id, backup_gradient_index);
|
let input_connector = InputConnector::node(fill_node_id, backup_gradient_index);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Gradient(gradient.clone()), false), true);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::FillGradient(gradient.clone()), false), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let input_connector = InputConnector::node(fill_node_id, fill_index);
|
let input_connector = InputConnector::node(fill_node_id, fill_index);
|
||||||
@@ -502,8 +495,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let input_connector = InputConnector::node(gradient_node_id, graphene_std::math_nodes::gradient_value::GradientInput::INDEX);
|
let input_connector = InputConnector::node(gradient_node_id, graphene_std::math_nodes::gradient_value::GradientInput::INDEX);
|
||||||
let stops_table = Table::new_from_element(stops);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Gradient(stops), false), false);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::GradientTable(stops_table), false), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the gradient line so its endpoints are at `new_start` and `new_end`.
|
/// Update the gradient line so its endpoints are at `new_start` and `new_end`.
|
||||||
@@ -606,10 +598,8 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let stroke_color = if let Some(color) = stroke.color { Table::new_from_element(color) } else { Table::new() };
|
|
||||||
|
|
||||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::ColorInput::INDEX);
|
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::ColorInput::INDEX);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(stroke_color), false), true);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(stroke.color), false), true);
|
||||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::WeightInput::INDEX);
|
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::WeightInput::INDEX);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.weight), false), true);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.weight), false), true);
|
||||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::AlignInput::INDEX);
|
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::AlignInput::INDEX);
|
||||||
@@ -623,8 +613,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintOrderInput::INDEX);
|
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintOrderInput::INDEX);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::PaintOrder(stroke.paint_order), false), false);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::PaintOrder(stroke.paint_order), false), false);
|
||||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashLengthsInput::<graphene_std::table::Table<f64>>::INDEX);
|
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashLengthsInput::<graphene_std::table::Table<f64>>::INDEX);
|
||||||
let dash_lengths_table = stroke.dash_lengths.into_iter().map(graphene_std::table::TableRow::new_from_element).collect();
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64Array(stroke.dash_lengths), false), true);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64Table(dash_lengths_table), false), true);
|
|
||||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashOffsetInput::INDEX);
|
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashOffsetInput::INDEX);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.dash_offset), false), true);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.dash_offset), false), true);
|
||||||
}
|
}
|
||||||
@@ -726,8 +715,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
let Some(brush_node_id) = self.existing_proto_node_id(graphene_std::brush::brush::brush::IDENTIFIER, true) else {
|
let Some(brush_node_id) = self.existing_proto_node_id(graphene_std::brush::brush::brush::IDENTIFIER, true) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let strokes_table = strokes.into_iter().map(graphene_std::table::TableRow::new_from_element).collect();
|
self.set_input_with_refresh(InputConnector::node(brush_node_id, 1), NodeInput::value(TaggedValue::BrushStrokes(strokes), 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: DVec2, dimensions: DVec2) {
|
pub fn resize_artboard(&mut self, location: DVec2, dimensions: DVec2) {
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ use crate::messages::prelude::Message;
|
|||||||
use crate::node_graph_executor::NodeGraphExecutor;
|
use crate::node_graph_executor::NodeGraphExecutor;
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use graph_craft::ProtoNodeIdentifier;
|
use graph_craft::ProtoNodeIdentifier;
|
||||||
use graph_craft::concrete;
|
|
||||||
use graph_craft::document::value::*;
|
use graph_craft::document::value::*;
|
||||||
use graph_craft::document::*;
|
use graph_craft::document::*;
|
||||||
|
use graph_craft::{concrete, descriptor};
|
||||||
use graphene_std::extract_xy::XY;
|
use graphene_std::extract_xy::XY;
|
||||||
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
|
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
|
||||||
use graphene_std::raster_types::{CPU, Raster};
|
use graphene_std::raster_types::{CPU, Raster};
|
||||||
@@ -205,10 +205,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
.collect(),
|
.collect(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
inputs: vec![
|
inputs: vec![NodeInput::type_default(descriptor!(Table<Graphic>), true), NodeInput::type_default(descriptor!(Table<Graphic>), true)],
|
||||||
NodeInput::value(TaggedValue::Graphic(Default::default()), true),
|
|
||||||
NodeInput::value(TaggedValue::Graphic(Default::default()), true),
|
|
||||||
],
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
@@ -342,11 +339,11 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::value(TaggedValue::Artboard(Default::default()), true),
|
NodeInput::type_default(descriptor!(Table<Artboard>), true),
|
||||||
NodeInput::value(TaggedValue::Graphic(Default::default()), true),
|
NodeInput::type_default(descriptor!(Table<Graphic>), true),
|
||||||
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||||
NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false),
|
NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false),
|
||||||
NodeInput::value(TaggedValue::Color(Table::new_from_element(Color::WHITE)), false),
|
NodeInput::value(TaggedValue::Color(Some(Color::WHITE)), false),
|
||||||
NodeInput::value(TaggedValue::Bool(true), false),
|
NodeInput::value(TaggedValue::Bool(true), false),
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -576,11 +573,11 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::value(TaggedValue::Vector(Default::default()), true),
|
NodeInput::type_default(descriptor!(Table<Vector>), true),
|
||||||
NodeInput::value(TaggedValue::F64(10.), false),
|
NodeInput::value(TaggedValue::F64(10.), false),
|
||||||
NodeInput::value(TaggedValue::Bool(Default::default()), false),
|
NodeInput::value(TaggedValue::Bool(Default::default()), false),
|
||||||
NodeInput::value(TaggedValue::InterpolationDistribution(Default::default()), false),
|
NodeInput::value(TaggedValue::InterpolationDistribution(Default::default()), false),
|
||||||
NodeInput::value(TaggedValue::Vector(Default::default()), false),
|
NodeInput::type_default(descriptor!(Table<Vector>), false),
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -827,7 +824,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
.collect(),
|
.collect(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
inputs: vec![NodeInput::value(TaggedValue::Vector(Default::default()), true)],
|
inputs: vec![NodeInput::type_default(descriptor!(Table<Vector>), true)],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
@@ -1012,7 +1009,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::value(TaggedValue::Vector(Default::default()), true),
|
NodeInput::type_default(descriptor!(Table<Vector>), true),
|
||||||
NodeInput::value(
|
NodeInput::value(
|
||||||
TaggedValue::Footprint(Footprint {
|
TaggedValue::Footprint(Footprint {
|
||||||
transform: DAffine2::from_scale_angle_translation(DVec2::new(1000., 1000.), 0., DVec2::new(0., 0.)),
|
transform: DAffine2::from_scale_angle_translation(DVec2::new(1000., 1000.), 0., DVec2::new(0., 0.)),
|
||||||
@@ -1123,7 +1120,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
.collect(),
|
.collect(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
inputs: vec![NodeInput::type_default(descriptor!(Table<Raster<CPU>>), true)],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
@@ -1278,7 +1275,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
.collect(),
|
.collect(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
inputs: vec![NodeInput::type_default(descriptor!(Table<Raster<CPU>>), true)],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
@@ -1328,7 +1325,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::Extract,
|
implementation: DocumentNodeImplementation::Extract,
|
||||||
inputs: vec![NodeInput::value(TaggedValue::DocumentNode(DocumentNode::default()), true)],
|
inputs: vec![NodeInput::type_default(descriptor!(DocumentNode), true)],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
@@ -1480,7 +1477,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::value(TaggedValue::Vector(Default::default()), true),
|
NodeInput::type_default(descriptor!(Table<Vector>), true),
|
||||||
NodeInput::value(TaggedValue::VectorModification(Default::default()), false),
|
NodeInput::value(TaggedValue::VectorModification(Default::default()), false),
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@@ -15,22 +15,22 @@ use glam::{DAffine2, DVec2};
|
|||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
|
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
|
||||||
use graph_craft::{Type, concrete};
|
use graph_craft::{Type, concrete};
|
||||||
use graphene_std::ATTR_TRANSFORM;
|
|
||||||
use graphene_std::NodeInputDecleration;
|
use graphene_std::NodeInputDecleration;
|
||||||
use graphene_std::animation::RealTimeMode;
|
use graphene_std::animation::RealTimeMode;
|
||||||
|
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||||
use graphene_std::extract_xy::XY;
|
use graphene_std::extract_xy::XY;
|
||||||
use graphene_std::raster::{
|
use graphene_std::raster::{
|
||||||
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
|
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
|
||||||
SelectiveColorChoice,
|
SelectiveColorChoice,
|
||||||
};
|
};
|
||||||
use graphene_std::raster_types::Image;
|
use graphene_std::raster_types::Image;
|
||||||
use graphene_std::table::{Table, TableRow};
|
use graphene_std::table::Table;
|
||||||
use graphene_std::text::{Font, TextAlign};
|
use graphene_std::text::{Font, TextAlign};
|
||||||
use graphene_std::text_nodes::StringCapitalization;
|
use graphene_std::text_nodes::StringCapitalization;
|
||||||
use graphene_std::transform::{Footprint, ReferencePoint, ScaleType, Transform};
|
use graphene_std::transform::{Footprint, ReferencePoint, ScaleType, Transform};
|
||||||
use graphene_std::vector::misc::BooleanOperation;
|
use graphene_std::vector::misc::BooleanOperation;
|
||||||
use graphene_std::vector::misc::{ArcType, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType};
|
use graphene_std::vector::misc::{ArcType, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType};
|
||||||
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientSpreadMethod, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphene_std::vector::style::{Fill, FillChoice, GradientSpreadMethod, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
use graphene_std::vector::{QRCodeErrorCorrectionLevel, VectorModification};
|
use graphene_std::vector::{QRCodeErrorCorrectionLevel, VectorModification};
|
||||||
|
|
||||||
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
|
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
|
||||||
@@ -218,6 +218,7 @@ pub(crate) fn property_from_type(
|
|||||||
Some(x) if x == TypeId::of::<Table<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(),
|
Some(x) if x == TypeId::of::<Table<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(),
|
||||||
Some(x) if x == TypeId::of::<Table<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
|
Some(x) if x == TypeId::of::<Table<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
|
||||||
Some(x) if x == TypeId::of::<Table<GradientStops>>() => color_widget(default_info, ColorInput::default().allow_none(false)),
|
Some(x) if x == TypeId::of::<Table<GradientStops>>() => color_widget(default_info, ColorInput::default().allow_none(false)),
|
||||||
|
Some(x) if x == TypeId::of::<Table<BrushStroke>>() => brush_strokes_widget(default_info).into(),
|
||||||
// ============
|
// ============
|
||||||
// STRUCT TYPES
|
// STRUCT TYPES
|
||||||
// ============
|
// ============
|
||||||
@@ -233,7 +234,6 @@ pub(crate) fn property_from_type(
|
|||||||
// =========================
|
// =========================
|
||||||
// AUTO-GENERATED ENUM TYPES
|
// AUTO-GENERATED ENUM TYPES
|
||||||
// =========================
|
// =========================
|
||||||
Some(x) if x == TypeId::of::<FillType>() => enum_choice::<FillType>().for_socket(default_info).property_row(),
|
|
||||||
Some(x) if x == TypeId::of::<GradientType>() => enum_choice::<GradientType>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<GradientType>() => enum_choice::<GradientType>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<GradientSpreadMethod>() => enum_choice::<GradientSpreadMethod>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<GradientSpreadMethod>() => enum_choice::<GradientSpreadMethod>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<RealTimeMode>() => enum_choice::<RealTimeMode>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<RealTimeMode>() => enum_choice::<RealTimeMode>().for_socket(default_info).property_row(),
|
||||||
@@ -416,6 +416,33 @@ pub fn vector_modification_widget(parameter_widgets_info: ParameterWidgetsInfo)
|
|||||||
widgets
|
widgets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn brush_strokes_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetInstance> {
|
||||||
|
let ParameterWidgetsInfo { document_node, node_id: _, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return widgets };
|
||||||
|
let Some(input) = document_node.inputs.get(index) else { return widgets };
|
||||||
|
|
||||||
|
if let Some(TaggedValue::BrushStrokes(strokes)) = input.as_non_exposed_value() {
|
||||||
|
let stroke_count = strokes.len();
|
||||||
|
let sample_count: usize = strokes.iter().map(|s| s.trace.len()).sum();
|
||||||
|
let label = if stroke_count == 0 {
|
||||||
|
"Empty".to_string()
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"{stroke_count} {} / {sample_count} {}",
|
||||||
|
if stroke_count == 1 { "Stroke" } else { "Strokes" },
|
||||||
|
if sample_count == 1 { "Sample" } else { "Samples" }
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
widgets.extend_from_slice(&[Separator::new(SeparatorStyle::Unrelated).widget_instance(), TextLabel::new(label).widget_instance()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
widgets
|
||||||
|
}
|
||||||
|
|
||||||
pub fn image_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetInstance> {
|
pub fn image_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetInstance> {
|
||||||
let ParameterWidgetsInfo { document_node, node_id: _, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id: _, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
@@ -774,7 +801,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
|
|||||||
.map(str::parse::<f64>)
|
.map(str::parse::<f64>)
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.collect::<Result<Vec<_>, _>>()
|
||||||
.ok()
|
.ok()
|
||||||
.map(|values| TaggedValue::F64Table(values.into_iter().map(graphene_std::table::TableRow::new_from_element).collect()))
|
.map(TaggedValue::F64Array)
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(document_node) = document_node else { return Vec::new() };
|
let Some(document_node) = document_node else { return Vec::new() };
|
||||||
@@ -782,11 +809,11 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
|
|||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return vec![];
|
return vec![];
|
||||||
};
|
};
|
||||||
if let Some(TaggedValue::F64Table(table)) = &input.as_non_exposed_value() {
|
if let Some(TaggedValue::F64Array(values)) = &input.as_non_exposed_value() {
|
||||||
widgets.extend_from_slice(&[
|
widgets.extend_from_slice(&[
|
||||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||||
text_input
|
text_input
|
||||||
.value(table.iter_element_values().map(|v| v.to_string()).collect::<Vec<_>>().join(", "))
|
.value(values.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(", "))
|
||||||
.on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, index))
|
.on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, index))
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
])
|
])
|
||||||
@@ -1077,15 +1104,6 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props:
|
|||||||
.on_commit(commit_value)
|
.on_commit(commit_value)
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
]),
|
]),
|
||||||
Some(&TaggedValue::FVec2(vec2)) => widgets.extend_from_slice(&[
|
|
||||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
|
||||||
number_props
|
|
||||||
// We use an arbitrary `y` instead of an arbitrary `x` here because the "Grid" node's "Spacing" value's height should be used from rectangular mode when transferred to "Y Spacing" in isometric mode
|
|
||||||
.value(Some(vec2.y as f64))
|
|
||||||
.on_update(update_value(move |x: &NumberInput| TaggedValue::F32(x.value.unwrap() as f32), node_id, index))
|
|
||||||
.on_commit(commit_value)
|
|
||||||
.widget_instance(),
|
|
||||||
]),
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1144,47 +1162,27 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
|
|||||||
|
|
||||||
// Add the color input
|
// Add the color input
|
||||||
match &**tagged_value {
|
match &**tagged_value {
|
||||||
TaggedValue::Color(color_table) => widgets.push(
|
TaggedValue::Color(color) => widgets.push(
|
||||||
color_button
|
color_button
|
||||||
.value(match color_table.element(0) {
|
.value(match color {
|
||||||
Some(color) => FillChoice::Solid(*color),
|
Some(color) => FillChoice::Solid(*color),
|
||||||
None => FillChoice::None,
|
None => FillChoice::None,
|
||||||
})
|
})
|
||||||
|
.on_update(update_value(|input: &ColorInput| TaggedValue::Color(input.value.as_solid()), node_id, index))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_instance(),
|
||||||
|
),
|
||||||
|
TaggedValue::Gradient(stops) => widgets.push(
|
||||||
|
color_button
|
||||||
|
.value(FillChoice::Gradient(stops.clone()))
|
||||||
.on_update(update_value(
|
.on_update(update_value(
|
||||||
|input: &ColorInput| TaggedValue::Color(input.value.as_solid().iter().map(|&color| TableRow::new_from_element(color)).collect()),
|
|input: &ColorInput| TaggedValue::Gradient(input.value.as_gradient().cloned().unwrap_or_default()),
|
||||||
node_id,
|
node_id,
|
||||||
index,
|
index,
|
||||||
))
|
))
|
||||||
.on_commit(commit_value)
|
.on_commit(commit_value)
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
),
|
),
|
||||||
TaggedValue::GradientTable(gradient_table) => {
|
|
||||||
let existing_transform: DAffine2 = gradient_table.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
|
||||||
|
|
||||||
widgets.push(
|
|
||||||
color_button
|
|
||||||
.value(match gradient_table.element(0) {
|
|
||||||
Some(gradient) => FillChoice::Gradient(gradient.clone()),
|
|
||||||
None => FillChoice::Gradient(GradientStops::default()),
|
|
||||||
})
|
|
||||||
.on_update(update_value(
|
|
||||||
move |input: &ColorInput| {
|
|
||||||
TaggedValue::GradientTable(
|
|
||||||
input
|
|
||||||
.value
|
|
||||||
.as_gradient()
|
|
||||||
.iter()
|
|
||||||
.map(|&gradient| TableRow::new_from_element(gradient.clone()).with_attribute(ATTR_TRANSFORM, existing_transform))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
node_id,
|
|
||||||
index,
|
|
||||||
))
|
|
||||||
.on_commit(commit_value)
|
|
||||||
.widget_instance(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
x => warn!("Color {x:?}"),
|
x => warn!("Color {x:?}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2212,12 +2210,12 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||||||
};
|
};
|
||||||
let uniform_val = match input.as_non_exposed_value() {
|
let uniform_val = match input.as_non_exposed_value() {
|
||||||
Some(TaggedValue::F64(x)) => *x,
|
Some(TaggedValue::F64(x)) => *x,
|
||||||
Some(TaggedValue::F64Table(table)) => table.iter_element_values().copied().next().unwrap_or(0.),
|
Some(TaggedValue::F64Array(values)) => values.first().copied().unwrap_or(0.),
|
||||||
_ => 0.,
|
_ => 0.,
|
||||||
};
|
};
|
||||||
let individual_val = match input.as_non_exposed_value() {
|
let individual_val = match input.as_non_exposed_value() {
|
||||||
Some(&TaggedValue::F64(x)) => vec![x; 4],
|
Some(&TaggedValue::F64(x)) => vec![x; 4],
|
||||||
Some(TaggedValue::F64Table(table)) => table.iter_element_values().copied().collect(),
|
Some(TaggedValue::F64Array(values)) => values.clone(),
|
||||||
_ => vec![0.; 4],
|
_ => vec![0.; 4],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2255,7 +2253,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||||||
NodeGraphMessage::SetInputValue {
|
NodeGraphMessage::SetInputValue {
|
||||||
node_id,
|
node_id,
|
||||||
input_index: CornerRadiusInput::<f64>::INDEX,
|
input_index: CornerRadiusInput::<f64>::INDEX,
|
||||||
value: TaggedValue::F64Table(individual_val_for_switch.iter().copied().map(graphene_std::table::TableRow::new_from_element).collect()),
|
value: TaggedValue::F64Array(individual_val_for_switch.clone()),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
]),
|
]),
|
||||||
@@ -2273,7 +2271,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||||||
.map(str::parse::<f64>)
|
.map(str::parse::<f64>)
|
||||||
.collect::<Result<Vec<f64>, _>>()
|
.collect::<Result<Vec<f64>, _>>()
|
||||||
.ok()
|
.ok()
|
||||||
.map(|values| TaggedValue::F64Table(values.into_iter().take(4).map(graphene_std::table::TableRow::new_from_element).collect()))
|
.map(|values| TaggedValue::F64Array(values.into_iter().take(4).collect()))
|
||||||
};
|
};
|
||||||
TextInput::default()
|
TextInput::default()
|
||||||
.value(individual_val.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(", "))
|
.value(individual_val.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(", "))
|
||||||
@@ -2444,7 +2442,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), Some(TaggedValue::Color(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (
|
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), Some(TaggedValue::Color(backup_color)), Some(TaggedValue::FillGradient(backup_gradient))) = (
|
||||||
&document_node.inputs[FillInput::<Color>::INDEX].as_value(),
|
&document_node.inputs[FillInput::<Color>::INDEX].as_value(),
|
||||||
&document_node.inputs[BackupColorInput::INDEX].as_value(),
|
&document_node.inputs[BackupColorInput::INDEX].as_value(),
|
||||||
&document_node.inputs[BackupGradientInput::INDEX].as_value(),
|
&document_node.inputs[BackupGradientInput::INDEX].as_value(),
|
||||||
@@ -2454,7 +2452,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
return vec![LayoutGroup::row(widgets_first_row)];
|
return vec![LayoutGroup::row(widgets_first_row)];
|
||||||
};
|
};
|
||||||
let fill2 = fill.clone();
|
let fill2 = fill.clone();
|
||||||
let backup_color_fill: Fill = backup_color.clone().into();
|
let backup_color_fill: Fill = (*backup_color).into();
|
||||||
let backup_gradient_fill: Fill = backup_gradient.clone().into();
|
let backup_gradient_fill: Fill = backup_gradient.clone().into();
|
||||||
|
|
||||||
match fill {
|
match fill {
|
||||||
@@ -2491,19 +2489,19 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
Fill::None => NodeGraphMessage::SetInputValue {
|
Fill::None => NodeGraphMessage::SetInputValue {
|
||||||
node_id,
|
node_id,
|
||||||
input_index: BackupColorInput::INDEX,
|
input_index: BackupColorInput::INDEX,
|
||||||
value: TaggedValue::Color(Table::new()),
|
value: TaggedValue::Color(None),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
Fill::Solid(color) => NodeGraphMessage::SetInputValue {
|
Fill::Solid(color) => NodeGraphMessage::SetInputValue {
|
||||||
node_id,
|
node_id,
|
||||||
input_index: BackupColorInput::INDEX,
|
input_index: BackupColorInput::INDEX,
|
||||||
value: TaggedValue::Color(Table::new_from_element(*color)),
|
value: TaggedValue::Color(Some(*color)),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
Fill::Gradient(gradient) => NodeGraphMessage::SetInputValue {
|
Fill::Gradient(gradient) => NodeGraphMessage::SetInputValue {
|
||||||
node_id,
|
node_id,
|
||||||
input_index: BackupGradientInput::INDEX,
|
input_index: BackupGradientInput::INDEX,
|
||||||
value: TaggedValue::Gradient(gradient.clone()),
|
value: TaggedValue::FillGradient(gradient.clone()),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
},
|
},
|
||||||
@@ -2635,7 +2633,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
move |_: &()| {
|
move |_: &()| {
|
||||||
let mut new_gradient = gradient_for_backup.clone();
|
let mut new_gradient = gradient_for_backup.clone();
|
||||||
new_gradient.spread_method = spread_method;
|
new_gradient.spread_method = spread_method;
|
||||||
TaggedValue::Gradient(new_gradient)
|
TaggedValue::FillGradient(new_gradient)
|
||||||
},
|
},
|
||||||
node_id,
|
node_id,
|
||||||
BackupGradientInput::INDEX,
|
BackupGradientInput::INDEX,
|
||||||
@@ -2684,7 +2682,7 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
|||||||
};
|
};
|
||||||
|
|
||||||
let has_dash_lengths = match &document_node.inputs[DashLengthsInput::<Table<f64>>::INDEX].as_value() {
|
let has_dash_lengths = match &document_node.inputs[DashLengthsInput::<Table<f64>>::INDEX].as_value() {
|
||||||
Some(TaggedValue::F64Table(table)) => table.is_empty(),
|
Some(TaggedValue::F64Array(values)) => values.is_empty(),
|
||||||
_ => true,
|
_ => true,
|
||||||
};
|
};
|
||||||
let miter_limit_disabled = join_value != &StrokeJoin::Miter;
|
let miter_limit_disabled = join_value != &StrokeJoin::Miter;
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graphene_std::Type;
|
use graphene_std::Type;
|
||||||
|
use graphene_std::raster_types::{CPU, Raster};
|
||||||
|
use graphene_std::table::Table;
|
||||||
|
use graphene_std::vector::Vector;
|
||||||
|
use graphene_std::{Artboard, Graphic};
|
||||||
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
@@ -21,14 +25,19 @@ pub enum FrontendGraphDataType {
|
|||||||
impl FrontendGraphDataType {
|
impl FrontendGraphDataType {
|
||||||
pub fn from_type(input: &Type) -> Self {
|
pub fn from_type(input: &Type) -> Self {
|
||||||
match TaggedValue::from_type_or_none(input) {
|
match TaggedValue::from_type_or_none(input) {
|
||||||
TaggedValue::U32(_) | TaggedValue::U64(_) | TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::DVec2(_) | TaggedValue::F64Table(_) | TaggedValue::DAffine2(_) => Self::Number,
|
TaggedValue::U32(_) | TaggedValue::U64(_) | TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::DVec2(_) | TaggedValue::F64Array(_) | TaggedValue::DAffine2(_) => Self::Number,
|
||||||
TaggedValue::Artboard(_) => Self::Artboard,
|
|
||||||
TaggedValue::Graphic(_) => Self::Graphic,
|
|
||||||
TaggedValue::Raster(_) => Self::Raster,
|
|
||||||
TaggedValue::Vector(_) => Self::Vector,
|
|
||||||
TaggedValue::Color(_) => Self::Color,
|
TaggedValue::Color(_) => Self::Color,
|
||||||
TaggedValue::Gradient(_) | TaggedValue::GradientTable(_) => Self::Gradient,
|
TaggedValue::FillGradient(_) | TaggedValue::Gradient(_) => Self::Gradient,
|
||||||
TaggedValue::String(_) | TaggedValue::StringTable(_) => Self::Typography,
|
TaggedValue::String(_) => Self::Typography,
|
||||||
|
// Types whose `TaggedValue` variant has been removed are routed through `TypeDefault` and identified by the descriptor's type name.
|
||||||
|
TaggedValue::TypeDefault(td) => match td.name.as_ref() {
|
||||||
|
n if n == std::any::type_name::<Table<Graphic>>() => Self::Graphic,
|
||||||
|
n if n == std::any::type_name::<Table<Artboard>>() => Self::Artboard,
|
||||||
|
n if n == std::any::type_name::<Table<Raster<CPU>>>() => Self::Raster,
|
||||||
|
n if n == std::any::type_name::<Table<Vector>>() => Self::Vector,
|
||||||
|
n if n == std::any::type_name::<Table<String>>() => Self::Typography,
|
||||||
|
_ => Self::General,
|
||||||
|
},
|
||||||
_ => Self::General,
|
_ => Self::General,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-6
@@ -4,7 +4,11 @@ use graph_craft::document::value::TaggedValue;
|
|||||||
use graph_craft::document::{DocumentNodeImplementation, InlineRust, NodeInput};
|
use graph_craft::document::{DocumentNodeImplementation, InlineRust, NodeInput};
|
||||||
use graph_craft::proto::{GraphErrorType, GraphErrors};
|
use graph_craft::proto::{GraphErrorType, GraphErrors};
|
||||||
use graph_craft::{Type, concrete};
|
use graph_craft::{Type, concrete};
|
||||||
|
use graphene_std::raster_types::{CPU, Raster};
|
||||||
|
use graphene_std::table::Table;
|
||||||
use graphene_std::uuid::NodeId;
|
use graphene_std::uuid::NodeId;
|
||||||
|
use graphene_std::vector::Vector;
|
||||||
|
use graphene_std::{Artboard, Graphic};
|
||||||
use interpreted_executor::dynamic_executor::{NodeTypes, ResolvedDocumentNodeTypesDelta};
|
use interpreted_executor::dynamic_executor::{NodeTypes, ResolvedDocumentNodeTypesDelta};
|
||||||
use interpreted_executor::node_registry::NODE_REGISTRY;
|
use interpreted_executor::node_registry::NODE_REGISTRY;
|
||||||
|
|
||||||
@@ -53,16 +57,21 @@ impl TypeSource {
|
|||||||
};
|
};
|
||||||
match self.compiled_nested_type() {
|
match self.compiled_nested_type() {
|
||||||
Some(nested_type) => match TaggedValue::from_type_or_none(nested_type) {
|
Some(nested_type) => match TaggedValue::from_type_or_none(nested_type) {
|
||||||
TaggedValue::U32(_) | TaggedValue::U64(_) | TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::DVec2(_) | TaggedValue::F64Table(_) | TaggedValue::DAffine2(_) => {
|
TaggedValue::U32(_) | TaggedValue::U64(_) | TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::DVec2(_) | TaggedValue::F64Array(_) | TaggedValue::DAffine2(_) => {
|
||||||
FrontendGraphDataType::Number
|
FrontendGraphDataType::Number
|
||||||
}
|
}
|
||||||
TaggedValue::Artboard(_) => FrontendGraphDataType::Artboard,
|
|
||||||
TaggedValue::Graphic(_) => FrontendGraphDataType::Graphic,
|
|
||||||
TaggedValue::Raster(_) => FrontendGraphDataType::Raster,
|
|
||||||
TaggedValue::Vector(_) => FrontendGraphDataType::Vector,
|
|
||||||
TaggedValue::Color(_) => FrontendGraphDataType::Color,
|
TaggedValue::Color(_) => FrontendGraphDataType::Color,
|
||||||
TaggedValue::Gradient(_) | TaggedValue::GradientTable(_) => FrontendGraphDataType::Gradient,
|
TaggedValue::FillGradient(_) | TaggedValue::Gradient(_) => FrontendGraphDataType::Gradient,
|
||||||
TaggedValue::String(_) => FrontendGraphDataType::Typography,
|
TaggedValue::String(_) => FrontendGraphDataType::Typography,
|
||||||
|
// Types whose `TaggedValue` variant has been removed are routed through `TypeDefault` and identified by the descriptor's type name.
|
||||||
|
TaggedValue::TypeDefault(td) => match td.name.as_ref() {
|
||||||
|
n if n == std::any::type_name::<Table<Graphic>>() => FrontendGraphDataType::Graphic,
|
||||||
|
n if n == std::any::type_name::<Table<Artboard>>() => FrontendGraphDataType::Artboard,
|
||||||
|
n if n == std::any::type_name::<Table<Raster<CPU>>>() => FrontendGraphDataType::Raster,
|
||||||
|
n if n == std::any::type_name::<Table<Vector>>() => FrontendGraphDataType::Vector,
|
||||||
|
n if n == std::any::type_name::<Table<String>>() => FrontendGraphDataType::Typography,
|
||||||
|
_ => FrontendGraphDataType::General,
|
||||||
|
},
|
||||||
_ => FrontendGraphDataType::General,
|
_ => FrontendGraphDataType::General,
|
||||||
},
|
},
|
||||||
None => FrontendGraphDataType::General,
|
None => FrontendGraphDataType::General,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
|||||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
|
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
|
||||||
use crate::messages::prelude::DocumentMessageHandler;
|
use crate::messages::prelude::DocumentMessageHandler;
|
||||||
use glam::{DVec2, IVec2};
|
use glam::{DVec2, IVec2};
|
||||||
|
use graph_craft::descriptor;
|
||||||
use graph_craft::document::DocumentNode;
|
use graph_craft::document::DocumentNode;
|
||||||
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
|
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
|
||||||
use graphene_std::ProtoNodeIdentifier;
|
use graphene_std::ProtoNodeIdentifier;
|
||||||
@@ -2089,42 +2090,30 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||||||
.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(false), false), network_path);
|
.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(false), false), network_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate Path nodes that stored geometry directly in input 0 (as a Table<Vector>) to instead use a VectorModification in input 1
|
// SVG-import legacy Path nodes baked their geometry at non-exposed input 0; move it to input 1 (the modern slot for VectorModification).
|
||||||
|
// For exposed input 0, any baked value is unused at runtime, so just reset it.
|
||||||
if reference == DefinitionIdentifier::Network("Path".into()) {
|
if reference == DefinitionIdentifier::Network("Path".into()) {
|
||||||
let input_0 = node.inputs.first()?;
|
let input_0 = node.inputs.first()?;
|
||||||
if let NodeInput::Value { tagged_value, exposed } = input_0
|
if let NodeInput::Value { tagged_value, exposed } = input_0
|
||||||
&& !exposed
|
&& let TaggedValue::VectorModification(modification) = &**tagged_value
|
||||||
&& let TaggedValue::Vector(vector_table) = &**tagged_value
|
|
||||||
&& !vector_table.is_empty()
|
|
||||||
{
|
{
|
||||||
let vector = vector_table.element(0)?;
|
let modification = modification.clone();
|
||||||
let modification = Box::new(graphene_std::vector::VectorModification::create_from_vector(vector));
|
let was_exposed = *exposed;
|
||||||
|
|
||||||
// Reset input 0 to the default exposed state
|
document.network_interface.set_input(
|
||||||
document
|
&InputConnector::node(*node_id, 0),
|
||||||
.network_interface
|
NodeInput::type_default(descriptor!(graphene_std::table::Table<graphene_std::vector::Vector>), true),
|
||||||
.set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::Vector(Default::default()), true), network_path);
|
network_path,
|
||||||
|
);
|
||||||
|
|
||||||
// Store the converted VectorModification in input 1
|
if !was_exposed {
|
||||||
document
|
document
|
||||||
.network_interface
|
.network_interface
|
||||||
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::VectorModification(modification), false), network_path);
|
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::VectorModification(modification), false), network_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate Image nodes that stored a Table<Raster<CPU>> in input 1 to instead use bare Image<Color> via TaggedValue::ImageData
|
|
||||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::raster_nodes::std_nodes::image::IDENTIFIER)
|
|
||||||
&& let Some(NodeInput::Value { tagged_value, .. }) = node.inputs.get(1)
|
|
||||||
&& let TaggedValue::Raster(raster_table) = &**tagged_value
|
|
||||||
&& let Some(element) = raster_table.element(0)
|
|
||||||
{
|
|
||||||
let image = element.data().clone();
|
|
||||||
|
|
||||||
document
|
|
||||||
.network_interface
|
|
||||||
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::ImageData(image), false), network_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
// ==================================
|
||||||
// PUT ALL MIGRATIONS ABOVE THIS LINE
|
// PUT ALL MIGRATIONS ABOVE THIS LINE
|
||||||
// ==================================
|
// ==================================
|
||||||
|
|||||||
@@ -282,13 +282,13 @@ pub fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkI
|
|||||||
Some(gradient.clone())
|
Some(gradient.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the gradient table of a layer.
|
/// Get the gradient stops of a layer, if any.
|
||||||
pub fn get_gradient_table(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Table<GradientStops>> {
|
pub fn get_gradient_stops(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<GradientStops> {
|
||||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::gradient_value::IDENTIFIER))?;
|
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::gradient_value::IDENTIFIER))?;
|
||||||
let TaggedValue::GradientTable(gradient_table) = inputs.get(graphene_std::math_nodes::gradient_value::GradientInput::INDEX)?.as_value()? else {
|
let TaggedValue::Gradient(stops) = inputs.get(graphene_std::math_nodes::gradient_value::GradientInput::INDEX)?.as_value()? else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
Some(gradient_table.clone())
|
Some(stops.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the transform from a gradient's local space to viewport space for the given layer. For a `Table<GradientStops>`
|
/// Compute the transform from a gradient's local space to viewport space for the given layer. For a `Table<GradientStops>`
|
||||||
|
|||||||
@@ -321,8 +321,8 @@ impl BrushToolData {
|
|||||||
|
|
||||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::brush::brush::brush::IDENTIFIER) && node_id != layer.to_node() {
|
if reference == DefinitionIdentifier::ProtoNode(graphene_std::brush::brush::brush::IDENTIFIER) && node_id != layer.to_node() {
|
||||||
let points_input = node.inputs.get(1)?;
|
let points_input = node.inputs.get(1)?;
|
||||||
let Some(TaggedValue::BrushStrokeTable(strokes)) = points_input.as_value() else { continue };
|
let Some(TaggedValue::BrushStrokes(strokes)) = points_input.as_value() else { continue };
|
||||||
self.strokes = strokes.iter_element_values().cloned().collect();
|
self.strokes = strokes.clone();
|
||||||
|
|
||||||
return Some(layer);
|
return Some(layer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::messages::portfolio::document::overlays::utility_types::{GizmoEmphasi
|
|||||||
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::{FlowType, NodeNetworkInterface};
|
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface};
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer, get_gradient_table};
|
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer, get_gradient_stops};
|
||||||
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graphene_std::raster::color::Color;
|
use graphene_std::raster::color::Color;
|
||||||
@@ -336,10 +336,9 @@ fn gradient_space_transform(layer: LayerNodeIdentifier, document: &DocumentMessa
|
|||||||
graph_modification_utils::gradient_space_transform(layer, &document.network_interface)
|
graph_modification_utils::gradient_space_transform(layer, &document.network_interface)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove this whole function once all gradients are `Table<GradientStops>`
|
// TODO: Remove this whole function once all gradients are stored via the modern `Gradient(GradientStops)` slot
|
||||||
fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Gradient> {
|
fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Gradient> {
|
||||||
if let Some(stops_table) = get_gradient_table(layer, network_interface) {
|
if let Some(stops) = get_gradient_stops(layer, network_interface) {
|
||||||
let stops = stops_table.element(0).cloned().unwrap_or_default();
|
|
||||||
let GradientChainState {
|
let GradientChainState {
|
||||||
transform,
|
transform,
|
||||||
gradient_type,
|
gradient_type,
|
||||||
@@ -505,7 +504,7 @@ fn calculate_insertion(start: DVec2, end: DVec2, stops: &GradientStops, mouse: D
|
|||||||
impl SelectedGradient {
|
impl SelectedGradient {
|
||||||
pub fn new(gradient: Gradient, layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Self {
|
pub fn new(gradient: Gradient, layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Self {
|
||||||
let transform = gradient_space_transform(layer, document);
|
let transform = gradient_space_transform(layer, document);
|
||||||
let is_gradient_table = get_gradient_table(layer, &document.network_interface).is_some();
|
let is_gradient_table = get_gradient_stops(layer, &document.network_interface).is_some();
|
||||||
Self {
|
Self {
|
||||||
layer: Some(layer),
|
layer: Some(layer),
|
||||||
transform,
|
transform,
|
||||||
@@ -1243,7 +1242,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
for layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {
|
for layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {
|
||||||
let Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };
|
let Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };
|
||||||
let transform = gradient_space_transform(layer, document);
|
let transform = gradient_space_transform(layer, document);
|
||||||
let is_gradient_table = get_gradient_table(layer, &document.network_interface).is_some();
|
let is_gradient_table = get_gradient_stops(layer, &document.network_interface).is_some();
|
||||||
|
|
||||||
// Check for dragging a midpoint diamond
|
// Check for dragging a midpoint diamond
|
||||||
if drag_hint.is_none() {
|
if drag_hint.is_none() {
|
||||||
@@ -1385,7 +1384,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
.network_interface
|
.network_interface
|
||||||
.selected_nodes()
|
.selected_nodes()
|
||||||
.selected_visible_layers(&document.network_interface)
|
.selected_visible_layers(&document.network_interface)
|
||||||
.find(|&layer| get_gradient_table(layer, &document.network_interface).is_some())
|
.find(|&layer| get_gradient_stops(layer, &document.network_interface).is_some())
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply the gradient to the selected layer
|
// Apply the gradient to the selected layer
|
||||||
@@ -1750,7 +1749,7 @@ fn apply_gradient_update(
|
|||||||
|
|
||||||
// Only check for the gradient table once we know we'll write back, since this is a graph traversal per layer
|
// Only check for the gradient table once we know we'll write back, since this is a graph traversal per layer
|
||||||
// TODO: Drop the `Fill::Gradient` branch when all gradients become `Table<GradientStops>`
|
// TODO: Drop the `Fill::Gradient` branch when all gradients become `Table<GradientStops>`
|
||||||
if get_gradient_table(layer, &context.document.network_interface).is_some() {
|
if get_gradient_stops(layer, &context.document.network_interface).is_some() {
|
||||||
dispatch_gradient_writes(layer, &gradient, responses);
|
dispatch_gradient_writes(layer, &gradient, responses);
|
||||||
} else {
|
} else {
|
||||||
responses.add(GraphOperationMessage::FillSet {
|
responses.add(GraphOperationMessage::FillSet {
|
||||||
@@ -1791,7 +1790,7 @@ fn apply_stops_update(data: &mut GradientToolData, context: &mut ToolActionMessa
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if get_gradient_table(layer, &context.document.network_interface).is_some() {
|
if get_gradient_stops(layer, &context.document.network_interface).is_some() {
|
||||||
responses.add(GraphOperationMessage::GradientStopsSet { layer, stops: stops.clone() });
|
responses.add(GraphOperationMessage::GradientStopsSet { layer, stops: stops.clone() });
|
||||||
} else if let Some(mut gradient) = get_gradient(layer, &context.document.network_interface) {
|
} else if let Some(mut gradient) = get_gradient(layer, &context.document.network_interface) {
|
||||||
gradient.stops = stops.clone();
|
gradient.stops = stops.clone();
|
||||||
@@ -1887,8 +1886,6 @@ mod test_gradient {
|
|||||||
pub use crate::test_utils::test_prelude::*;
|
pub use crate::test_utils::test_prelude::*;
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graphene_std::ATTR_TRANSFORM;
|
|
||||||
use graphene_std::table::{Table, TableRow};
|
|
||||||
use graphene_std::vector::style::{Fill, Gradient};
|
use graphene_std::vector::style::{Fill, Gradient};
|
||||||
use graphene_std::vector::{GradientStop, GradientStops, fill};
|
use graphene_std::vector::{GradientStop, GradientStops, fill};
|
||||||
|
|
||||||
@@ -1953,21 +1950,18 @@ mod test_gradient {
|
|||||||
.handle_message(NodeGraphMessage::SetInputValue {
|
.handle_message(NodeGraphMessage::SetInputValue {
|
||||||
node_id: gradient_node_id,
|
node_id: gradient_node_id,
|
||||||
input_index: 1,
|
input_index: 1,
|
||||||
value: TaggedValue::GradientTable(Table::new_from_row(
|
value: TaggedValue::Gradient(GradientStops::new([
|
||||||
TableRow::new_from_element(GradientStops::new([
|
GradientStop {
|
||||||
GradientStop {
|
position: 0.,
|
||||||
position: 0.,
|
midpoint: 0.5,
|
||||||
midpoint: 0.5,
|
color: Color::RED,
|
||||||
color: Color::RED,
|
},
|
||||||
},
|
GradientStop {
|
||||||
GradientStop {
|
position: 1.,
|
||||||
position: 1.,
|
midpoint: 0.5,
|
||||||
midpoint: 0.5,
|
color: Color::BLUE,
|
||||||
color: Color::BLUE,
|
},
|
||||||
},
|
])),
|
||||||
]))
|
|
||||||
.with_attribute(ATTR_TRANSFORM, DAffine2::IDENTITY),
|
|
||||||
)),
|
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,11 @@ pub enum NodeInput {
|
|||||||
Node { node_id: NodeId, output_index: usize },
|
Node { node_id: NodeId, output_index: usize },
|
||||||
|
|
||||||
/// A hardcoded value that can't change after the graph is compiled. Gets converted into a value node during graph compilation.
|
/// A hardcoded value that can't change after the graph is compiled. Gets converted into a value node during graph compilation.
|
||||||
Value { tagged_value: MemoHash<TaggedValue>, exposed: bool },
|
Value {
|
||||||
|
#[cfg_attr(feature = "loading", serde(deserialize_with = "crate::document::value::deserialize_tagged_value_with_legacy_migration"))]
|
||||||
|
tagged_value: MemoHash<TaggedValue>,
|
||||||
|
exposed: bool,
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Remove import_type and get type from parent node input
|
// TODO: Remove import_type and get type from parent node input
|
||||||
/// Input that is provided by the import from the parent network to this document node network.
|
/// Input that is provided by the import from the parent network to this document node network.
|
||||||
@@ -231,6 +235,12 @@ impl NodeInput {
|
|||||||
Self::Value { tagged_value, exposed }
|
Self::Value { tagged_value, exposed }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs a `NodeInput::Value` whose tagged value is `TaggedValue::TypeDefault(td)`, recording only the
|
||||||
|
/// type so the runtime materializes its default rather than baking a placeholder value into the saved document.
|
||||||
|
pub fn type_default(td: core_types::TypeDescriptor, exposed: bool) -> Self {
|
||||||
|
Self::value(TaggedValue::TypeDefault(td), exposed)
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn import(import_type: Type, import_index: usize) -> Self {
|
pub const fn import(import_type: Type, import_index: usize) -> Self {
|
||||||
Self::Import { import_type, import_index }
|
Self::Import { import_type, import_index }
|
||||||
}
|
}
|
||||||
@@ -930,10 +940,7 @@ impl NodeNetwork {
|
|||||||
let (tagged_value, exposed) = match previous_export {
|
let (tagged_value, exposed) = match previous_export {
|
||||||
NodeInput::Value { tagged_value, exposed } => (tagged_value, exposed),
|
NodeInput::Value { tagged_value, exposed } => (tagged_value, exposed),
|
||||||
NodeInput::Reflection(reflect) => match reflect {
|
NodeInput::Reflection(reflect) => match reflect {
|
||||||
DocumentNodeMetadata::DocumentNodePath => {
|
DocumentNodeMetadata::DocumentNodePath => (TaggedValue::NodeIdPath(path.to_vec()).into(), false),
|
||||||
let table: core_types::table::Table<NodeId> = path.iter().copied().map(core_types::table::TableRow::new_from_element).collect();
|
|
||||||
(TaggedValue::NodeIdTable(table).into(), false)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
previous_export => {
|
previous_export => {
|
||||||
*export = previous_export;
|
*export = previous_export;
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ use brush_nodes::brush_stroke::BrushStroke;
|
|||||||
use core_types::table::Table;
|
use core_types::table::Table;
|
||||||
use core_types::transform::Footprint;
|
use core_types::transform::Footprint;
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::{CacheHash, Color, ContextFeatures, MemoHash, Node, Type};
|
use core_types::{CacheHash, Color, ContextFeatures, MemoHash, Node, Type, TypeDescriptor};
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
pub use dyn_any::StaticType;
|
pub use dyn_any::StaticType;
|
||||||
use glam::{Affine2, Vec2};
|
|
||||||
pub use glam::{DAffine2, DVec2, IVec2, UVec2};
|
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};
|
||||||
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::{Artboard, Graphic, Vector};
|
||||||
use rendering::RenderMetadata;
|
use rendering::RenderMetadata;
|
||||||
@@ -26,6 +25,19 @@ use vector::VectorModification;
|
|||||||
|
|
||||||
pub struct TaggedValueTypeError;
|
pub struct TaggedValueTypeError;
|
||||||
|
|
||||||
|
/// List of types routed through [`TaggedValue::TypeDefault`] instead of another dedicated variant.
|
||||||
|
/// Consumed by [`TaggedValue::from_type`] (which creates `TypeDefault` values) and [`TaggedValue::to_dynany`]/[`TaggedValue::to_any`] (which unwrap them into real default values).
|
||||||
|
macro_rules! for_each_type_default {
|
||||||
|
($action:ident) => {
|
||||||
|
$action!(Table<Graphic>);
|
||||||
|
$action!(Table<Artboard>);
|
||||||
|
$action!(Table<Raster<CPU>>);
|
||||||
|
$action!(Table<Vector>);
|
||||||
|
$action!(Table<String>);
|
||||||
|
$action!(DocumentNode);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Macro to generate the tagged value enum.
|
/// Macro to generate the tagged value enum.
|
||||||
macro_rules! tagged_value {
|
macro_rules! tagged_value {
|
||||||
($ ($( #[$meta:meta] )* $identifier:ident ($ty:ty) ),* $(,)?) => {
|
($ ($( #[$meta:meta] )* $identifier:ident ($ty:ty) ),* $(,)?) => {
|
||||||
@@ -33,9 +45,48 @@ macro_rules! tagged_value {
|
|||||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
#[allow(clippy::large_enum_variant)] // TODO(TrueDoctor): Properly solve this disparity between the size of the largest and next largest variants
|
#[allow(clippy::large_enum_variant)] // TODO(TrueDoctor): Properly solve this disparity between the size of the largest and next largest variants
|
||||||
pub enum TaggedValue {
|
pub enum TaggedValue {
|
||||||
|
// ===============
|
||||||
|
// MANUAL VARIANTS
|
||||||
|
// ===============
|
||||||
None,
|
None,
|
||||||
|
/// Stores a type, from which its `Default::default()` value can be obtained, rather than storing an actual type's value.
|
||||||
|
/// Example: `TaggedValue::TypeDefault(descriptor!(String))` stores the type `String` but no specific string value.
|
||||||
|
TypeDefault(TypeDescriptor),
|
||||||
|
/// Stored compactly as a `Vec<f64>`, materializes as `Table<f64>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes.
|
||||||
|
#[serde(deserialize_with = "core_types::misc::migrate_to_f64_array")] // TODO: Eventually remove this migration document upgrade code
|
||||||
|
#[serde(alias = "F64Table", alias = "VecF64", alias = "VecF32", alias = "F64Array4")]
|
||||||
|
F64Array(Vec<f64>),
|
||||||
|
/// Stored compactly as an `Option<Color>`, materializes as `Table<Color>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes.
|
||||||
|
#[serde(deserialize_with = "core_types::misc::migrate_to_optional_color")] // TODO: Eventually remove this migration document upgrade code
|
||||||
|
#[serde(alias = "ColorTable", alias = "OptionalColor", alias = "ColorNotInTable")]
|
||||||
|
Color(Option<Color>),
|
||||||
|
/// Stored compactly as a `GradientStops`, materializes as a single-row `Table<GradientStops>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes.
|
||||||
|
/// (Old documents that stored a full `Gradient` struct under this same `"Gradient"` tag are routed to `FillGradient` by `deserialize_tagged_value_with_legacy_migration`.)
|
||||||
|
#[serde(deserialize_with = "graphic_types::vector_types::gradient::migrate_to_gradient_stops")] // TODO: Eventually remove this migration document upgrade code
|
||||||
|
#[serde(alias = "GradientTable", alias = "GradientPositions")]
|
||||||
|
Gradient(GradientStops),
|
||||||
|
/// Stored compactly as a `Vec<BrushStroke>`, materializes as `Table<BrushStroke>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes.
|
||||||
|
#[serde(deserialize_with = "brush_nodes::migrations::migrate_to_brush_strokes")] // TODO: Eventually remove this migration document upgrade code
|
||||||
|
#[serde(alias = "BrushStrokeTable")]
|
||||||
|
BrushStrokes(Vec<BrushStroke>),
|
||||||
|
// =======================
|
||||||
|
// AUTO-GENERATED VARIANTS
|
||||||
|
// =======================
|
||||||
$( $(#[$meta] ) *$identifier( $ty ), )*
|
$( $(#[$meta] ) *$identifier( $ty ), )*
|
||||||
|
// =======================
|
||||||
|
// NON-SERIALIZED VARIANTS
|
||||||
|
// =======================
|
||||||
|
#[serde(skip)]
|
||||||
RenderOutput(RenderOutput),
|
RenderOutput(RenderOutput),
|
||||||
|
/// Path to the consumer of a `NodeInput::Reflection(DocumentNodePath)`. Materializes a `Table<NodeId>` at runtime via `to_dynany`/`to_any` during graph flattening.
|
||||||
|
#[serde(skip)]
|
||||||
|
NodeIdPath(Vec<NodeId>),
|
||||||
|
/// The `DocumentNode` value carried by an `Extract` proto node, populated at flatten time by `resolve_extract_nodes`. The on-disk placeholder uses `TypeDefault(descriptor!(DocumentNode))`.
|
||||||
|
#[serde(skip)]
|
||||||
|
DocumentNode(DocumentNode),
|
||||||
|
/// Carried by context nullification proto nodes constructed at proto node compilation time in `insert_context_nullification_nodes`.
|
||||||
|
#[serde(skip)]
|
||||||
|
ContextFeatures(ContextFeatures),
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
EditorApi(Arc<PlatformEditorApi>),
|
EditorApi(Arc<PlatformEditorApi>),
|
||||||
}
|
}
|
||||||
@@ -44,8 +95,25 @@ macro_rules! tagged_value {
|
|||||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
core::mem::discriminant(self).hash(state);
|
core::mem::discriminant(self).hash(state);
|
||||||
match self {
|
match self {
|
||||||
|
// ===============
|
||||||
|
// MANUAL VARIANTS
|
||||||
|
// ===============
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
|
Self::TypeDefault(td) => td.cache_hash(state),
|
||||||
|
// =======================
|
||||||
|
// AUTO-GENERATED VARIANTS
|
||||||
|
// =======================
|
||||||
$( Self::$identifier(x) => { x.cache_hash(state) }),*
|
$( Self::$identifier(x) => { x.cache_hash(state) }),*
|
||||||
|
Self::F64Array(values) => values.cache_hash(state),
|
||||||
|
Self::Color(color) => color.cache_hash(state),
|
||||||
|
Self::Gradient(stops) => stops.cache_hash(state),
|
||||||
|
Self::BrushStrokes(strokes) => strokes.cache_hash(state),
|
||||||
|
// =======================
|
||||||
|
// NON-SERIALIZED VARIANTS
|
||||||
|
// =======================
|
||||||
|
Self::NodeIdPath(path) => path.hash(state),
|
||||||
|
Self::DocumentNode(node) => node.cache_hash(state),
|
||||||
|
Self::ContextFeatures(features) => features.cache_hash(state),
|
||||||
Self::RenderOutput(x) => x.cache_hash(state),
|
Self::RenderOutput(x) => x.cache_hash(state),
|
||||||
Self::EditorApi(x) => x.cache_hash(state),
|
Self::EditorApi(x) => x.cache_hash(state),
|
||||||
}
|
}
|
||||||
@@ -56,85 +124,231 @@ macro_rules! tagged_value {
|
|||||||
/// Converts to a Box<dyn DynAny>
|
/// Converts to a Box<dyn DynAny>
|
||||||
pub fn to_dynany(self) -> DAny<'a> {
|
pub fn to_dynany(self) -> DAny<'a> {
|
||||||
match self {
|
match self {
|
||||||
|
// ===============
|
||||||
|
// MANUAL VARIANTS
|
||||||
|
// ===============
|
||||||
Self::None => Box::new(()),
|
Self::None => Box::new(()),
|
||||||
|
Self::TypeDefault(td) => {
|
||||||
|
// Construct the actual default for types without a `TaggedValue` variant directly.
|
||||||
|
// Recursion through `from_type_or_none` below is safe only because `for_each_type_default!`
|
||||||
|
// exhaustively handles every type that `from_type` would route back to `TypeDefault`.
|
||||||
|
let name = td.name.as_ref();
|
||||||
|
macro_rules! check {
|
||||||
|
($type_default:ty) => {
|
||||||
|
if name == std::any::type_name::<$type_default>() { return Box::new(<$type_default>::default()); }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for_each_type_default!(check);
|
||||||
|
Self::from_type_or_none(&Type::Concrete(td)).to_dynany()
|
||||||
|
}
|
||||||
|
Self::F64Array(values) => {
|
||||||
|
let table: Table<f64> = values.into_iter().map(core_types::table::TableRow::new_from_element).collect();
|
||||||
|
Box::new(table)
|
||||||
|
}
|
||||||
|
Self::Color(color) => {
|
||||||
|
let table: Table<Color> = color.into_iter().map(core_types::table::TableRow::new_from_element).collect();
|
||||||
|
Box::new(table)
|
||||||
|
}
|
||||||
|
Self::Gradient(stops) => Box::new(Table::<GradientStops>::new_from_element(stops)),
|
||||||
|
Self::BrushStrokes(strokes) => {
|
||||||
|
let table: Table<BrushStroke> = strokes.into_iter().map(core_types::table::TableRow::new_from_element).collect();
|
||||||
|
Box::new(table)
|
||||||
|
}
|
||||||
|
// =======================
|
||||||
|
// AUTO-GENERATED VARIANTS
|
||||||
|
// =======================
|
||||||
$( Self::$identifier(x) => Box::new(x), )*
|
$( Self::$identifier(x) => Box::new(x), )*
|
||||||
|
// =======================
|
||||||
|
// NON-SERIALIZED VARIANTS
|
||||||
|
// =======================
|
||||||
Self::RenderOutput(x) => Box::new(x),
|
Self::RenderOutput(x) => Box::new(x),
|
||||||
|
Self::NodeIdPath(path) => {
|
||||||
|
let table: Table<NodeId> = path.into_iter().map(core_types::table::TableRow::new_from_element).collect();
|
||||||
|
Box::new(table)
|
||||||
|
}
|
||||||
|
Self::DocumentNode(node) => Box::new(node),
|
||||||
|
Self::ContextFeatures(features) => Box::new(features),
|
||||||
Self::EditorApi(x) => Box::new(x),
|
Self::EditorApi(x) => Box::new(x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts to a Arc<dyn Any + Send + Sync + 'static>
|
/// Converts to a Arc<dyn Any + Send + Sync + 'static>
|
||||||
pub fn to_any(self) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
|
pub fn to_any(self) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
|
||||||
match self {
|
match self {
|
||||||
|
// ===============
|
||||||
|
// MANUAL VARIANTS
|
||||||
|
// ===============
|
||||||
Self::None => Arc::new(()),
|
Self::None => Arc::new(()),
|
||||||
|
Self::TypeDefault(td) => {
|
||||||
|
// Same direct-construction path as `to_dynany` for the same reason as in `to_dynany`.
|
||||||
|
let name = td.name.as_ref();
|
||||||
|
macro_rules! check {
|
||||||
|
($type_default:ty) => {
|
||||||
|
if name == std::any::type_name::<$type_default>() { return Arc::new(<$type_default>::default()); }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for_each_type_default!(check);
|
||||||
|
Self::from_type_or_none(&Type::Concrete(td)).to_any()
|
||||||
|
}
|
||||||
|
Self::F64Array(values) => {
|
||||||
|
let table: Table<f64> = values.into_iter().map(core_types::table::TableRow::new_from_element).collect();
|
||||||
|
Arc::new(table)
|
||||||
|
}
|
||||||
|
Self::Color(color) => {
|
||||||
|
let table: Table<Color> = color.into_iter().map(core_types::table::TableRow::new_from_element).collect();
|
||||||
|
Arc::new(table)
|
||||||
|
}
|
||||||
|
Self::Gradient(stops) => Arc::new(Table::<GradientStops>::new_from_element(stops)),
|
||||||
|
Self::BrushStrokes(strokes) => {
|
||||||
|
let table: Table<BrushStroke> = strokes.into_iter().map(core_types::table::TableRow::new_from_element).collect();
|
||||||
|
Arc::new(table)
|
||||||
|
}
|
||||||
|
// =======================
|
||||||
|
// AUTO-GENERATED VARIANTS
|
||||||
|
// =======================
|
||||||
$( Self::$identifier(x) => Arc::new(x), )*
|
$( Self::$identifier(x) => Arc::new(x), )*
|
||||||
|
// =======================
|
||||||
|
// NON-SERIALIZED VARIANTS
|
||||||
|
// =======================
|
||||||
Self::RenderOutput(x) => Arc::new(x),
|
Self::RenderOutput(x) => Arc::new(x),
|
||||||
|
Self::NodeIdPath(path) => {
|
||||||
|
let table: Table<NodeId> = path.into_iter().map(core_types::table::TableRow::new_from_element).collect();
|
||||||
|
Arc::new(table)
|
||||||
|
}
|
||||||
|
Self::DocumentNode(node) => Arc::new(node),
|
||||||
|
Self::ContextFeatures(features) => Arc::new(features),
|
||||||
Self::EditorApi(x) => Arc::new(x),
|
Self::EditorApi(x) => Arc::new(x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a core_types::Type::Concrete(TypeDescriptor { .. }) with the type of the value inside the tagged value
|
/// Creates a core_types::Type::Concrete(TypeDescriptor { .. }) with the type of the value inside the tagged value
|
||||||
pub fn ty(&self) -> Type {
|
pub fn ty(&self) -> Type {
|
||||||
match self {
|
match self {
|
||||||
|
// ===============
|
||||||
|
// MANUAL VARIANTS
|
||||||
|
// ===============
|
||||||
Self::None => concrete!(()),
|
Self::None => concrete!(()),
|
||||||
|
Self::TypeDefault(td) => Type::Concrete(td.clone()),
|
||||||
|
Self::F64Array(_) => concrete!(Table<f64>),
|
||||||
|
Self::Color(_) => concrete!(Table<Color>),
|
||||||
|
Self::Gradient(_) => concrete!(Table<GradientStops>),
|
||||||
|
Self::BrushStrokes(_) => concrete!(Table<BrushStroke>),
|
||||||
|
// =======================
|
||||||
|
// AUTO-GENERATED VARIANTS
|
||||||
|
// =======================
|
||||||
$( Self::$identifier(_) => concrete!($ty), )*
|
$( Self::$identifier(_) => concrete!($ty), )*
|
||||||
|
// =======================
|
||||||
|
// NON-SERIALIZED VARIANTS
|
||||||
|
// =======================
|
||||||
Self::RenderOutput(_) => concrete!(RenderOutput),
|
Self::RenderOutput(_) => concrete!(RenderOutput),
|
||||||
|
Self::NodeIdPath(_) => concrete!(Table<NodeId>),
|
||||||
|
Self::DocumentNode(_) => concrete!(DocumentNode),
|
||||||
|
Self::ContextFeatures(_) => concrete!(ContextFeatures),
|
||||||
Self::EditorApi(_) => concrete!(&PlatformEditorApi),
|
Self::EditorApi(_) => concrete!(&PlatformEditorApi),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to downcast the dynamic type to a tagged value
|
/// Attempts to downcast the dynamic type to a tagged value
|
||||||
pub fn try_from_any(input: Box<dyn DynAny<'a> + 'a>) -> Result<Self, String> {
|
pub fn try_from_any(input: Box<dyn DynAny<'a> + 'a>) -> Result<Self, String> {
|
||||||
use dyn_any::downcast;
|
use dyn_any::downcast;
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
|
|
||||||
match DynAny::type_id(input.as_ref()) {
|
match DynAny::type_id(input.as_ref()) {
|
||||||
|
// ===============
|
||||||
|
// MANUAL VARIANTS
|
||||||
|
// ===============
|
||||||
x if x == TypeId::of::<()>() => Ok(TaggedValue::None),
|
x if x == TypeId::of::<()>() => Ok(TaggedValue::None),
|
||||||
|
// =======================
|
||||||
|
// AUTO-GENERATED VARIANTS
|
||||||
|
// =======================
|
||||||
$( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(*downcast(input).unwrap())), )*
|
$( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(*downcast(input).unwrap())), )*
|
||||||
|
// =======================
|
||||||
|
// NON-SERIALIZED VARIANTS
|
||||||
|
// =======================
|
||||||
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
|
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
|
||||||
|
|
||||||
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
|
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to downcast the dynamic type to a tagged value
|
/// Attempts to downcast the dynamic type to a tagged value
|
||||||
pub fn try_from_std_any_ref(input: &dyn std::any::Any) -> Result<Self, String> {
|
pub fn try_from_std_any_ref(input: &dyn std::any::Any) -> Result<Self, String> {
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
|
|
||||||
match input.type_id() {
|
match input.type_id() {
|
||||||
|
// ===============
|
||||||
|
// MANUAL VARIANTS
|
||||||
|
// ===============
|
||||||
x if x == TypeId::of::<()>() => Ok(TaggedValue::None),
|
x if x == TypeId::of::<()>() => Ok(TaggedValue::None),
|
||||||
|
// =======================
|
||||||
|
// AUTO-GENERATED VARIANTS
|
||||||
|
// =======================
|
||||||
$( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(<$ty as Clone>::clone(input.downcast_ref().unwrap()))), )*
|
$( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(<$ty as Clone>::clone(input.downcast_ref().unwrap()))), )*
|
||||||
|
// =======================
|
||||||
|
// NON-SERIALIZED VARIANTS
|
||||||
|
// =======================
|
||||||
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(RenderOutput::clone(input.downcast_ref().unwrap()))),
|
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(RenderOutput::clone(input.downcast_ref().unwrap()))),
|
||||||
_ => Err(format!("Cannot convert {:?} to TaggedValue", std::any::type_name_of_val(input))),
|
_ => Err(format!("Cannot convert {:?} to TaggedValue", std::any::type_name_of_val(input))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns a TaggedValue from the type, where that value is its type's `Default::default()`
|
|
||||||
|
/// Returns a TaggedValue from the type, where that value is its type's `Default::default()`.
|
||||||
|
/// Dispatches by the type's name (the field that round-trips through serde) so it works for both
|
||||||
|
/// freshly constructed types and types deserialized from disk where the runtime `TypeId` is unavailable.
|
||||||
pub fn from_type(input: &Type) -> Option<Self> {
|
pub fn from_type(input: &Type) -> Option<Self> {
|
||||||
match input {
|
match input {
|
||||||
Type::Generic(_) => None,
|
Type::Generic(_) => None,
|
||||||
Type::Concrete(concrete_type) => {
|
Type::Concrete(concrete_type) => {
|
||||||
use std::any::TypeId;
|
let name = concrete_type.name.as_ref();
|
||||||
// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types
|
// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types
|
||||||
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
|
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
|
||||||
Some(match concrete_type.id? {
|
if name == std::any::type_name::<()>() { return Some(TaggedValue::None) }
|
||||||
x if x == TypeId::of::<()>() => TaggedValue::None,
|
// Table-wrapped types need a single-item default with the element's default, not an empty table
|
||||||
// Table-wrapped types need a single-item default with the element's default, not an empty table
|
if name == std::any::type_name::<Table<Color>>() { return Some(TaggedValue::Color(Some(Color::default()))) }
|
||||||
x if x == TypeId::of::<Table<Color>>() => TaggedValue::Color(Table::new_from_element(Color::default())),
|
if name == std::any::type_name::<Table<GradientStops>>() { return Some(TaggedValue::Gradient(GradientStops::default())) }
|
||||||
x if x == TypeId::of::<Table<GradientStops>>() => TaggedValue::GradientTable(Table::new_from_element(GradientStops::default())),
|
$( if name == std::any::type_name::<$ty>() { return Some(TaggedValue::$identifier(Default::default())) } )*
|
||||||
$( x if x == TypeId::of::<$ty>() => TaggedValue::$identifier(Default::default()), )*
|
if name == std::any::type_name::<Table<f64>>() { return Some(TaggedValue::F64Array(Vec::new())) }
|
||||||
_ => return None,
|
if name == std::any::type_name::<Table<BrushStroke>>() { return Some(TaggedValue::BrushStrokes(Vec::new())) }
|
||||||
})
|
// Types whose `TaggedValue` variant has been removed. They route through `TypeDefault` instead, with `to_dynany`/`to_any` constructing the actual default at execution time.
|
||||||
|
macro_rules! check {
|
||||||
|
($type_default:ty) => {
|
||||||
|
if name == std::any::type_name::<$type_default>() { return Some(TaggedValue::TypeDefault(concrete_type.clone())); }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for_each_type_default!(check);
|
||||||
|
None
|
||||||
}
|
}
|
||||||
Type::Fn(_, output) => TaggedValue::from_type(output),
|
Type::Fn(_, output) => TaggedValue::from_type(output),
|
||||||
Type::Future(output) => {
|
Type::Future(output) => TaggedValue::from_type(output),
|
||||||
TaggedValue::from_type(output)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_type_or_none(input: &Type) -> Self {
|
pub fn from_type_or_none(input: &Type) -> Self {
|
||||||
Self::from_type(input).unwrap_or(TaggedValue::None)
|
Self::from_type(input).unwrap_or(TaggedValue::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_debug_string(&self) -> String {
|
pub fn to_debug_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
|
// ===============
|
||||||
|
// MANUAL VARIANTS
|
||||||
|
// ===============
|
||||||
Self::None => "()".to_string(),
|
Self::None => "()".to_string(),
|
||||||
|
Self::TypeDefault(td) => format!("TypeDefault({})", td.name),
|
||||||
|
Self::F64Array(values) => format!("F64Array({values:?})"),
|
||||||
|
Self::Color(color) => format!("Color({color:?})"),
|
||||||
|
Self::Gradient(stops) => format!("Gradient({stops:?})"),
|
||||||
|
Self::BrushStrokes(strokes) => format!("BrushStrokes({strokes:?})"),
|
||||||
|
// =======================
|
||||||
|
// AUTO-GENERATED VARIANTS
|
||||||
|
// =======================
|
||||||
$( Self::$identifier(x) => format!("{:?}", x), )*
|
$( Self::$identifier(x) => format!("{:?}", x), )*
|
||||||
|
// =======================
|
||||||
|
// NON-SERIALIZED VARIANTS
|
||||||
|
// =======================
|
||||||
Self::RenderOutput(_) => "RenderOutput".to_string(),
|
Self::RenderOutput(_) => "RenderOutput".to_string(),
|
||||||
|
Self::NodeIdPath(path) => format!("NodeIdPath({path:?})"),
|
||||||
|
Self::DocumentNode(node) => format!("DocumentNode({node:?})"),
|
||||||
|
Self::ContextFeatures(features) => format!("ContextFeatures({features:?})"),
|
||||||
Self::EditorApi(_) => "PlatformEditorApi".to_string(),
|
Self::EditorApi(_) => "PlatformEditorApi".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,54 +377,21 @@ macro_rules! tagged_value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tagged_value! {
|
tagged_value! {
|
||||||
// ===========
|
// ===============
|
||||||
// TABLE TYPES
|
// PRIMITIVE TYPES
|
||||||
// ===========
|
// ===============
|
||||||
StringTable(Table<String>),
|
|
||||||
#[serde(deserialize_with = "core_types::misc::migrate_vec_f64_to_table")] // TODO: Eventually remove this migration document upgrade code
|
|
||||||
#[serde(alias = "VecF64", alias = "VecF32", alias = "F64Array4")]
|
|
||||||
F64Table(Table<f64>),
|
|
||||||
NodeIdTable(Table<NodeId>),
|
|
||||||
#[serde(deserialize_with = "graphic_types::migrations::migrate_vector")] // TODO: Eventually remove this migration document upgrade code
|
|
||||||
#[serde(alias = "VectorData")]
|
|
||||||
Vector(Table<Vector>),
|
|
||||||
#[serde(deserialize_with = "graphic_types::raster_types::image::migrate_image_frame")] // TODO: Eventually remove this migration document upgrade code
|
|
||||||
#[serde(alias = "ImageFrame", alias = "RasterData", alias = "Image")]
|
|
||||||
Raster(Table<Raster<CPU>>),
|
|
||||||
#[serde(deserialize_with = "graphic_types::graphic::migrate_graphic")] // TODO: Eventually remove this migration document upgrade code
|
|
||||||
#[serde(alias = "GraphicGroup", alias = "Group")]
|
|
||||||
Graphic(Table<Graphic>),
|
|
||||||
#[serde(deserialize_with = "graphic_types::artboard::migrate_artboard")] // TODO: Eventually remove this migration document upgrade code
|
|
||||||
#[serde(alias = "ArtboardGroup")]
|
|
||||||
Artboard(Table<Artboard>),
|
|
||||||
#[serde(deserialize_with = "core_types::misc::migrate_color")] // TODO: Eventually remove this migration document upgrade code
|
|
||||||
#[serde(alias = "ColorTable", alias = "OptionalColor", alias = "ColorNotInTable")]
|
|
||||||
Color(Table<Color>),
|
|
||||||
#[serde(deserialize_with = "graphic_types::vector_types::gradient::migrate_gradient_stops")] // TODO: Eventually remove this migration document upgrade code
|
|
||||||
#[serde(alias = "GradientPositions", alias = "GradientStops")]
|
|
||||||
GradientTable(Table<GradientStops>),
|
|
||||||
#[serde(deserialize_with = "brush_nodes::migrations::migrate_brush_strokes_to_table")] // TODO: Eventually remove this migration document upgrade code
|
|
||||||
#[serde(alias = "BrushStrokes")]
|
|
||||||
BrushStrokeTable(Table<BrushStroke>),
|
|
||||||
// ============
|
|
||||||
// SCALAR TYPES
|
|
||||||
// ============
|
|
||||||
F32(f32),
|
F32(f32),
|
||||||
F64(f64),
|
F64(f64),
|
||||||
U32(u32),
|
U32(u32),
|
||||||
U64(u64),
|
U64(u64),
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
String(String),
|
String(String),
|
||||||
FVec2(Vec2),
|
#[serde(alias = "IVec2", alias = "UVec2", alias = "Vec2")]
|
||||||
FAffine2(Affine2),
|
|
||||||
#[serde(alias = "IVec2", alias = "UVec2")]
|
|
||||||
DVec2(DVec2),
|
DVec2(DVec2),
|
||||||
|
#[serde(alias = "Affine2")]
|
||||||
DAffine2(DAffine2),
|
DAffine2(DAffine2),
|
||||||
Stroke(Stroke),
|
FillGradient(Gradient),
|
||||||
Gradient(Gradient),
|
|
||||||
Font(Font),
|
Font(Font),
|
||||||
DocumentNode(DocumentNode),
|
|
||||||
ContextFeatures(ContextFeatures),
|
|
||||||
Footprint(Footprint),
|
Footprint(Footprint),
|
||||||
VectorModification(Box<VectorModification>),
|
VectorModification(Box<VectorModification>),
|
||||||
ImageData(Image<Color>),
|
ImageData(Image<Color>),
|
||||||
@@ -247,7 +428,6 @@ tagged_value! {
|
|||||||
StrokeJoin(vector::style::StrokeJoin),
|
StrokeJoin(vector::style::StrokeJoin),
|
||||||
StrokeAlign(vector::style::StrokeAlign),
|
StrokeAlign(vector::style::StrokeAlign),
|
||||||
PaintOrder(vector::style::PaintOrder),
|
PaintOrder(vector::style::PaintOrder),
|
||||||
FillType(vector::style::FillType),
|
|
||||||
GradientType(vector::style::GradientType),
|
GradientType(vector::style::GradientType),
|
||||||
GradientSpreadMethod(vector::style::GradientSpreadMethod),
|
GradientSpreadMethod(vector::style::GradientSpreadMethod),
|
||||||
ReferencePoint(vector::ReferencePoint),
|
ReferencePoint(vector::ReferencePoint),
|
||||||
@@ -388,9 +568,9 @@ impl TaggedValue {
|
|||||||
() if ty == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?,
|
() if ty == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?,
|
||||||
() if ty == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?,
|
() if ty == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?,
|
||||||
// `Color` (not in a table) is still currently needed by `BlackAndWhiteNode` and `ColorOverlayNode` GPU `shader_node(PerPixelAdjust)` variants
|
// `Color` (not in a table) is still currently needed by `BlackAndWhiteNode` and `ColorOverlayNode` GPU `shader_node(PerPixelAdjust)` variants
|
||||||
() if ty == TypeId::of::<Color>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,
|
() if ty == TypeId::of::<Color>() => to_color(string).map(|color| TaggedValue::Color(Some(color)))?,
|
||||||
() if ty == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,
|
() if ty == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Some(color)))?,
|
||||||
() if ty == TypeId::of::<Table<GradientStops>>() => to_gradient(string).map(|color| TaggedValue::GradientTable(Table::new_from_element(color)))?,
|
() if ty == TypeId::of::<Table<GradientStops>>() => to_gradient(string).map(TaggedValue::Gradient)?,
|
||||||
() if ty == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?,
|
() if ty == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?,
|
||||||
() if ty == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
|
() if ty == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
|
||||||
_ => return None,
|
_ => return None,
|
||||||
@@ -408,35 +588,65 @@ impl TaggedValue {
|
|||||||
_ => panic!("Passed value is not of type u32"),
|
_ => panic!("Passed value is not of type u32"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Walks a JSON document tree and replaces any externally-tagged `TaggedValue` whose discriminant is in `REMOVED_VARIANTS` with the unit variant `"None"`.
|
/// Custom deserializer hooked onto `NodeInput::Value::tagged_value` that intercepts removed-variant tags before delegating to `TaggedValue`'s standard derive.
|
||||||
/// Lets documents written before a variant was removed continue to deserialize. The document migration step then removes any orphan node inputs that result.
|
///
|
||||||
#[cfg(feature = "loading")]
|
/// Routes legacy variant names into modern variants, in typed Rust. Each legacy name is also matched against the historical `#[serde(alias = "...")]` spellings the deleted variant accepted, so old-shape inner payloads are caught:
|
||||||
pub fn scrub_removed_variants_from_json(value: &mut serde_json::Value) {
|
///
|
||||||
// Names of `TaggedValue` variants that have been removed since being released. Any object of the form `{"<name>": <payload>}` is rewritten to `"None"` on load.
|
/// - `BrushCache` → `TaggedValue::None` (purely runtime cache; no payload to preserve)
|
||||||
const REMOVED_VARIANTS: &[&str] = &["BrushCache"];
|
/// - `Graphic` (or alias `GraphicGroup`/`Group`) → `TaggedValue::TypeDefault(descriptor!(Table<Graphic>))`
|
||||||
|
/// - `Artboard` (or alias `ArtboardGroup`) → `TaggedValue::TypeDefault(descriptor!(Table<Artboard>))`
|
||||||
|
/// - `Raster` (or alias `ImageFrame`/`RasterData`/`Image`):
|
||||||
|
/// - non-empty (the legacy `image` proto's input 1, where the inner `Raster<CPU>` serializes as the embedded `Image<Color>`) → `TaggedValue::ImageData(<inner Image<Color>>)`
|
||||||
|
/// - empty → `TaggedValue::TypeDefault(descriptor!(Table<Raster<CPU>>))`
|
||||||
|
/// - `Vector` (or alias `VectorData`):
|
||||||
|
/// - non-empty → `TaggedValue::VectorModification(<built from first element>)` (the document_migration's Path pass disambiguates this between SVG-import legacy and a discardable modern baked value via the input's `exposed` flag)
|
||||||
|
/// - empty → `TaggedValue::TypeDefault(descriptor!(Table<Vector>))`
|
||||||
|
///
|
||||||
|
/// All other tags (including ones with the modern shape) fall through to the standard derived `Deserialize` for `TaggedValue`.
|
||||||
|
// TODO: Eventually remove this migration document upgrade code
|
||||||
|
#[cfg(feature = "loading")]
|
||||||
|
pub fn deserialize_tagged_value_with_legacy_migration<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<MemoHash<TaggedValue>, D::Error> {
|
||||||
|
use serde::Deserialize;
|
||||||
|
let value = serde_json::Value::deserialize(deserializer)?;
|
||||||
|
|
||||||
match value {
|
if let Some(map) = value.as_object()
|
||||||
serde_json::Value::Object(map) => {
|
&& map.len() == 1
|
||||||
if map.len() == 1
|
&& let Some((tag, content)) = map.iter().next()
|
||||||
&& let Some(key) = map.keys().next()
|
{
|
||||||
&& REMOVED_VARIANTS.contains(&key.as_str())
|
match tag.as_str() {
|
||||||
{
|
"BrushCache" => return Ok(MemoHash::new(TaggedValue::None)),
|
||||||
*value = serde_json::Value::String("None".to_string());
|
"Graphic" | "GraphicGroup" | "Group" => return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(Table<Graphic>)))),
|
||||||
return;
|
"Artboard" | "ArtboardGroup" => return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(Table<Artboard>)))),
|
||||||
}
|
"Raster" | "ImageFrame" | "RasterData" | "Image" => {
|
||||||
for child in map.values_mut() {
|
let first_element = content.as_object().and_then(|c| c.get("element")).and_then(|e| e.as_array()).and_then(|arr| arr.first());
|
||||||
Self::scrub_removed_variants_from_json(child);
|
if let Some(image_value) = first_element {
|
||||||
|
let image: Image<Color> = serde_json::from_value(image_value.clone()).map_err(serde::de::Error::custom)?;
|
||||||
|
return Ok(MemoHash::new(TaggedValue::ImageData(image)));
|
||||||
}
|
}
|
||||||
|
return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(Table<Raster<CPU>>))));
|
||||||
}
|
}
|
||||||
serde_json::Value::Array(array) => {
|
"Vector" | "VectorData" => {
|
||||||
for child in array {
|
let vector = graphic_types::migrations::migrate_to_optional_vector(content.clone()).map_err(serde::de::Error::custom)?;
|
||||||
Self::scrub_removed_variants_from_json(child);
|
if let Some(vector) = vector {
|
||||||
|
let modification = Box::new(VectorModification::create_from_vector(&vector));
|
||||||
|
return Ok(MemoHash::new(TaggedValue::VectorModification(modification)));
|
||||||
}
|
}
|
||||||
|
return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(Table<Vector>))));
|
||||||
|
}
|
||||||
|
// The `Gradient` tag was reused: it used to carry a full `Gradient` struct (now `FillGradient`), and now carries an `Option<GradientStops>`.
|
||||||
|
// Disambiguate by payload shape: a Gradient struct has `start`/`end` keys; a `GradientStops` has none of those (it has `position`/`midpoint`/`color`).
|
||||||
|
"Gradient" if content.as_object().is_some_and(|c| c.contains_key("start") && c.contains_key("end")) => {
|
||||||
|
let gradient: Gradient = serde_json::from_value(content.clone()).map_err(serde::de::Error::custom)?;
|
||||||
|
return Ok(MemoHash::new(TaggedValue::FillGradient(gradient)));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let tagged_value: TaggedValue = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
|
||||||
|
Ok(MemoHash::new(tagged_value))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for TaggedValue {
|
impl Display for TaggedValue {
|
||||||
@@ -544,34 +754,37 @@ impl CacheHash for RenderOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(test, feature = "loading"))]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod typedefault_dispatch {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use core_types::descriptor;
|
||||||
|
|
||||||
|
/// Round-trips every type listed in [`for_each_type_default`] through `TaggedValue::TypeDefault → to_dynany / to_any` and asserts the resulting concrete type matches the descriptor.
|
||||||
|
///
|
||||||
|
/// This guards against the only way to break the recursion invariant in the unwrap functions: someone hand-rolling a `TypeDefault`-yielding case in `from_type` (or the macro's expansion in one of the unwrap sites silently failing to match a name). If it fails, the message points at the specific type and the structural reason.
|
||||||
#[test]
|
#[test]
|
||||||
fn scrub_replaces_removed_variant_with_none_unit() {
|
fn typedefault_dispatch_terminates() {
|
||||||
let mut value = serde_json::json!({
|
macro_rules! check {
|
||||||
"Value": {
|
($type_default:ty) => {{
|
||||||
"tagged_value": { "BrushCache": { "unique_id": 1, "prev_input": [] } },
|
let descriptor = descriptor!($type_default);
|
||||||
"exposed": false
|
let expected_type_id = std::any::TypeId::of::<$type_default>();
|
||||||
}
|
let dyn_value = TaggedValue::TypeDefault(descriptor.clone()).to_dynany();
|
||||||
});
|
assert_eq!(
|
||||||
TaggedValue::scrub_removed_variants_from_json(&mut value);
|
DynAny::type_id(&*dyn_value),
|
||||||
assert_eq!(value, serde_json::json!({ "Value": { "tagged_value": "None", "exposed": false } }));
|
expected_type_id,
|
||||||
}
|
"`to_dynany(TypeDefault({0}))` did not produce a `{0}` — `for_each_type_default!` lists this type but the unwrap site doesn't handle it. Without a match, `to_dynany` falls back to `from_type_or_none`, which returns `TypeDefault({0})` again and recurses forever.",
|
||||||
|
std::any::type_name::<$type_default>(),
|
||||||
|
);
|
||||||
|
|
||||||
#[test]
|
let arc_value = TaggedValue::TypeDefault(descriptor).to_any();
|
||||||
fn scrub_leaves_live_variants_unchanged() {
|
assert_eq!(
|
||||||
let mut value = serde_json::json!({ "Value": { "tagged_value": { "F64": 1.5 }, "exposed": false } });
|
(*arc_value).type_id(),
|
||||||
let original = value.clone();
|
expected_type_id,
|
||||||
TaggedValue::scrub_removed_variants_from_json(&mut value);
|
"`to_any(TypeDefault({0}))` did not produce a `{0}` — same recursion hazard as above for the `to_any` path.",
|
||||||
assert_eq!(value, original);
|
std::any::type_name::<$type_default>(),
|
||||||
}
|
);
|
||||||
|
}};
|
||||||
#[test]
|
}
|
||||||
fn scrub_recurses_through_arrays_and_nested_objects() {
|
for_each_type_default!(check);
|
||||||
let mut value = serde_json::json!([{ "BrushCache": { "any": "payload" } }, { "F32": 0.5 }]);
|
|
||||||
TaggedValue::scrub_removed_variants_from_json(&mut value);
|
|
||||||
assert_eq!(value, serde_json::json!(["None", { "F32": 0.5 }]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ extern crate log;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate core_types;
|
extern crate core_types;
|
||||||
|
|
||||||
pub use core_types::{ProtoNodeIdentifier, Type, TypeDescriptor, concrete, generic};
|
pub use core_types::{ProtoNodeIdentifier, Type, TypeDescriptor, concrete, descriptor, generic};
|
||||||
|
|
||||||
pub mod application_io;
|
pub mod application_io;
|
||||||
pub mod document;
|
pub mod document;
|
||||||
|
|||||||
@@ -951,7 +951,7 @@ mod test {
|
|||||||
// If this assert fails: These NodeIds seem to be changing when you modify TaggedValue, just update them.
|
// If this assert fails: These NodeIds seem to be changing when you modify TaggedValue, just update them.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ids,
|
ids,
|
||||||
vec![NodeId(12189222519765806511), NodeId(15012204941197567462), NodeId(15525229164021892418), NodeId(1252248957706694248)]
|
vec![NodeId(12815475172301479638), NodeId(13251389748338817266), NodeId(7166921994790432021), NodeId(15318519137317483318)]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,7 +179,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::MergeByDistanceAlgorithm]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::MergeByDistanceAlgorithm]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ExtrudeJoiningAlgorithm]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ExtrudeJoiningAlgorithm]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::FillType]),
|
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
|
||||||
@@ -273,7 +272,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeJoin]),
|
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeJoin]),
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeAlign]),
|
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeAlign]),
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::PaintOrder]),
|
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::PaintOrder]),
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::FillType]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType]),
|
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType]),
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]),
|
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]),
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
|
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
|
||||||
|
|||||||
@@ -61,47 +61,44 @@ impl Clampable for DVec2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct LegacyTable<T> {
|
||||||
|
#[serde(alias = "instances", alias = "instance")]
|
||||||
|
element: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Eventually remove this migration document upgrade code
|
// TODO: Eventually remove this migration document upgrade code
|
||||||
pub fn migrate_color<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<crate::table::Table<no_std_types::color::Color>, D::Error> {
|
pub fn migrate_to_optional_color<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Option<no_std_types::color::Color>, D::Error> {
|
||||||
use crate::table::Table;
|
|
||||||
use no_std_types::color::Color;
|
use no_std_types::color::Color;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||||
enum ColorFormat {
|
enum ColorFormat {
|
||||||
Color(Color),
|
|
||||||
OptionalColor(Option<Color>),
|
OptionalColor(Option<Color>),
|
||||||
ColorTable(Table<Color>),
|
Table(LegacyTable<Color>),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(match ColorFormat::deserialize(deserializer)? {
|
Ok(match ColorFormat::deserialize(deserializer)? {
|
||||||
ColorFormat::Color(color) => Table::new_from_element(color),
|
ColorFormat::OptionalColor(color) => color,
|
||||||
ColorFormat::OptionalColor(color) => {
|
ColorFormat::Table(table) => table.element.into_iter().next(),
|
||||||
if let Some(color) = color {
|
|
||||||
Table::new_from_element(color)
|
|
||||||
} else {
|
|
||||||
Table::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ColorFormat::ColorTable(color_table) => color_table,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Eventually remove this migration document upgrade code
|
// TODO: Eventually remove this migration document upgrade code
|
||||||
pub fn migrate_vec_f64_to_table<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<crate::table::Table<f64>, D::Error> {
|
pub fn migrate_to_f64_array<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Vec<f64>, D::Error> {
|
||||||
use crate::table::{Table, TableRow};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||||
enum F64TableFormat {
|
enum F64ArrayFormat {
|
||||||
VecF64(Vec<f64>),
|
Array(Vec<f64>),
|
||||||
F64Table(Table<f64>),
|
Table(LegacyTable<f64>),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(match F64TableFormat::deserialize(deserializer)? {
|
Ok(match F64ArrayFormat::deserialize(deserializer)? {
|
||||||
F64TableFormat::VecF64(values) => values.into_iter().map(TableRow::new_from_element).collect(),
|
F64ArrayFormat::Array(values) => values,
|
||||||
F64TableFormat::F64Table(table) => table,
|
F64ArrayFormat::Table(table) => table.element,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1063,39 +1063,6 @@ impl<T> Table<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
impl<T: serde::Serialize> serde::Serialize for Table<T> {
|
|
||||||
/// Serializes only the element vec, omitting type-erased attributes which are not serializable.
|
|
||||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
||||||
#[derive(serde::Serialize)]
|
|
||||||
struct TableHelper<'a, T: serde::Serialize> {
|
|
||||||
element: &'a Vec<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
TableHelper { element: &self.element }.serialize(serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for Table<T> {
|
|
||||||
/// Deserializes the element vec and initializes an empty attribute column store with the matching row count.
|
|
||||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
||||||
#[derive(serde::Deserialize)]
|
|
||||||
struct TableHelper<T> {
|
|
||||||
#[serde(alias = "instances", alias = "instance")]
|
|
||||||
element: Vec<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
let helper = TableHelper::deserialize(deserializer)?;
|
|
||||||
let len = helper.element.len();
|
|
||||||
|
|
||||||
Ok(Table {
|
|
||||||
element: helper.element,
|
|
||||||
attributes: AttributeColumns::with_len(len),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: BoundingBox> BoundingBox for Table<T> {
|
impl<T: BoundingBox> BoundingBox for Table<T> {
|
||||||
/// Computes the combined bounding box of all items, composing each item's transform attribute with the given transform.
|
/// Computes the combined bounding box of all items, composing each item's transform attribute with the given transform.
|
||||||
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
|
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
|
||||||
@@ -1254,34 +1221,6 @@ impl<T: PartialEq> PartialEq for TableRow<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
impl<T: serde::Serialize> serde::Serialize for TableRow<T> {
|
|
||||||
/// Serializes only the element, omitting type-erased attributes which are not serializable.
|
|
||||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
||||||
#[derive(serde::Serialize)]
|
|
||||||
struct TableRowHelper<'a, T: serde::Serialize> {
|
|
||||||
element: &'a T,
|
|
||||||
}
|
|
||||||
|
|
||||||
TableRowHelper { element: &self.element }.serialize(serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for TableRow<T> {
|
|
||||||
/// Deserializes the element and initializes an empty set of attributes.
|
|
||||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
||||||
#[derive(serde::Deserialize)]
|
|
||||||
struct TableRowHelper<T> {
|
|
||||||
#[serde(alias = "instance")]
|
|
||||||
element: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
let helper = TableRowHelper::deserialize(deserializer)?;
|
|
||||||
Ok(TableRow::new_from_element(helper.element))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> TableRow<T> {
|
impl<T> TableRow<T> {
|
||||||
/// Constructs a row from a pre-built element and attributes pair.
|
/// Constructs a row from a pre-built element and attributes pair.
|
||||||
pub fn from_parts(element: T, attributes: AttributeValues) -> Self {
|
pub fn from_parts(element: T, attributes: AttributeValues) -> Self {
|
||||||
|
|||||||
@@ -6,22 +6,32 @@ use std::fmt::{Display, Formatter};
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! concrete {
|
macro_rules! concrete {
|
||||||
($type:ty) => {
|
($type:ty) => {
|
||||||
$crate::Type::Concrete($crate::TypeDescriptor {
|
$crate::Type::Concrete($crate::descriptor!($type))
|
||||||
|
};
|
||||||
|
($type:ty, $name:ty) => {
|
||||||
|
$crate::Type::Concrete($crate::descriptor!($type, $name))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! descriptor {
|
||||||
|
($type:ty) => {
|
||||||
|
$crate::TypeDescriptor {
|
||||||
id: Some(std::any::TypeId::of::<$type>()),
|
id: Some(std::any::TypeId::of::<$type>()),
|
||||||
name: $crate::Cow::Borrowed(std::any::type_name::<$type>()),
|
name: $crate::Cow::Borrowed(std::any::type_name::<$type>()),
|
||||||
alias: None,
|
alias: None,
|
||||||
size: std::mem::size_of::<$type>(),
|
size: std::mem::size_of::<$type>(),
|
||||||
align: std::mem::align_of::<$type>(),
|
align: std::mem::align_of::<$type>(),
|
||||||
})
|
}
|
||||||
};
|
};
|
||||||
($type:ty, $name:ty) => {
|
($type:ty, $name:ty) => {
|
||||||
$crate::Type::Concrete($crate::TypeDescriptor {
|
$crate::TypeDescriptor {
|
||||||
id: Some(std::any::TypeId::of::<$type>()),
|
id: Some(std::any::TypeId::of::<$type>()),
|
||||||
name: $crate::Cow::Borrowed(std::any::type_name::<$type>()),
|
name: $crate::Cow::Borrowed(std::any::type_name::<$type>()),
|
||||||
alias: Some($crate::Cow::Borrowed(stringify!($name))),
|
alias: Some($crate::Cow::Borrowed(stringify!($name))),
|
||||||
size: std::mem::size_of::<$type>(),
|
size: std::mem::size_of::<$type>(),
|
||||||
align: std::mem::align_of::<$type>(),
|
align: std::mem::align_of::<$type>(),
|
||||||
})
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +170,7 @@ pub struct TypeDescriptor {
|
|||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
pub id: Option<TypeId>,
|
pub id: Option<TypeId>,
|
||||||
pub name: Cow<'static, str>,
|
pub name: Cow<'static, str>,
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
#[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
|
||||||
pub alias: Option<Cow<'static, str>>,
|
pub alias: Option<Cow<'static, str>>,
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
pub size: usize,
|
pub size: usize,
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
use crate::graphic::Graphic;
|
use crate::graphic::Graphic;
|
||||||
use core_types::blending::BlendMode;
|
|
||||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||||
use core_types::graphene_hash::CacheHash;
|
use core_types::graphene_hash::CacheHash;
|
||||||
use core_types::render_complexity::RenderComplexity;
|
use core_types::render_complexity::RenderComplexity;
|
||||||
use core_types::table::{Table, TableRow};
|
use core_types::table::Table;
|
||||||
use core_types::uuid::NodeId;
|
|
||||||
use core_types::{ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_LOCATION, Color};
|
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::{DAffine2, IVec2};
|
use glam::DAffine2;
|
||||||
|
|
||||||
/// Nominal wrapper around `Table<Graphic>` representing a single artboard's content.
|
/// Nominal wrapper around `Table<Graphic>` representing a single artboard's content.
|
||||||
///
|
///
|
||||||
@@ -15,7 +12,6 @@ use glam::{DAffine2, IVec2};
|
|||||||
/// enclosing `Table<Artboard>`, not as fields here. This keeps `Artboard` a pure type-system boundary
|
/// enclosing `Table<Artboard>`, not as fields here. This keeps `Artboard` a pure type-system boundary
|
||||||
/// that prevents arbitrary `Table<Table<...<Graphic>>>` nesting.
|
/// that prevents arbitrary `Table<Table<...<Graphic>>>` nesting.
|
||||||
#[derive(Clone, Debug, Default, CacheHash, PartialEq, DynAny)]
|
#[derive(Clone, Debug, Default, CacheHash, PartialEq, DynAny)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct Artboard(Table<Graphic>);
|
pub struct Artboard(Table<Graphic>);
|
||||||
|
|
||||||
impl Artboard {
|
impl Artboard {
|
||||||
@@ -63,73 +59,3 @@ impl RenderComplexity for Artboard {
|
|||||||
self.0.render_complexity()
|
self.0.render_complexity()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Eventually remove this migration document upgrade code
|
|
||||||
pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Artboard>, D::Error> {
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
/// Mirrors the removed `AlphaBlending` struct for legacy document deserialization.
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
|
||||||
pub struct LegacyAlphaBlending {
|
|
||||||
pub blend_mode: BlendMode,
|
|
||||||
pub opacity: f32,
|
|
||||||
pub fill: f32,
|
|
||||||
pub clip: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Legacy artboard struct shape, kept for deserializing old documents into `Table<Artboard>`.
|
|
||||||
#[derive(Clone, Debug, DynAny)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct LegacyArtboard {
|
|
||||||
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))]
|
|
||||||
pub struct LegacyArtboardGroup {
|
|
||||||
pub artboards: Vec<(LegacyArtboard, Option<NodeId>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct OldTable<T> {
|
|
||||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
|
||||||
element: Vec<T>,
|
|
||||||
transform: Vec<DAffine2>,
|
|
||||||
alpha_blending: Vec<LegacyAlphaBlending>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
|
||||||
enum ArtboardFormat {
|
|
||||||
ArtboardGroup(LegacyArtboardGroup),
|
|
||||||
OldArtboardTable(OldTable<LegacyArtboard>),
|
|
||||||
LegacyArtboardTable(Table<LegacyArtboard>),
|
|
||||||
// NOTE: Must come last so older tagged formats above are tried first.
|
|
||||||
// Also covers the intermediate `Table<Table<Graphic>>` shape since `Artboard` deserializes transparently.
|
|
||||||
ArtboardTable(Table<Artboard>),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn legacy_to_row(legacy: LegacyArtboard) -> TableRow<Artboard> {
|
|
||||||
// Legacy `label` field is dropped (the artboard's name comes from its parent layer's display name)
|
|
||||||
TableRow::new_from_element(Artboard::new(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)? {
|
|
||||||
ArtboardFormat::ArtboardGroup(group) => group.artboards.into_iter().map(|(artboard, _)| legacy_to_row(artboard)).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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
use core_types::blending::BlendMode;
|
|
||||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||||
use core_types::graphene_hash::CacheHash;
|
use core_types::graphene_hash::CacheHash;
|
||||||
use core_types::ops::TableConvert;
|
use core_types::ops::TableConvert;
|
||||||
use core_types::render_complexity::RenderComplexity;
|
use core_types::render_complexity::RenderComplexity;
|
||||||
use core_types::table::{Table, TableRow};
|
use core_types::table::Table;
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::{ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color};
|
use core_types::{ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color};
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
@@ -16,7 +15,6 @@ pub use vector_types::Vector;
|
|||||||
|
|
||||||
/// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax.
|
/// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax.
|
||||||
#[derive(Clone, Debug, CacheHash, PartialEq, DynAny)]
|
#[derive(Clone, Debug, CacheHash, PartialEq, DynAny)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub enum Graphic {
|
pub enum Graphic {
|
||||||
Graphic(Table<Graphic>),
|
Graphic(Table<Graphic>),
|
||||||
Vector(Table<Vector>),
|
Vector(Table<Vector>),
|
||||||
@@ -486,97 +484,3 @@ impl<T: Clone> OmitIndex for Table<T> {
|
|||||||
self.omit_index(self.len() - index)
|
self.omit_index(self.len() - index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Eventually remove this migration document upgrade code
|
|
||||||
pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Graphic>, D::Error> {
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
/// Mirrors the removed `AlphaBlending` struct for legacy document deserialization.
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
|
||||||
pub struct LegacyAlphaBlending {
|
|
||||||
pub blend_mode: BlendMode,
|
|
||||||
pub opacity: f32,
|
|
||||||
pub fill: f32,
|
|
||||||
pub clip: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct OldGraphicGroup {
|
|
||||||
elements: Vec<(Graphic, Option<NodeId>)>,
|
|
||||||
transform: DAffine2,
|
|
||||||
alpha_blending: LegacyAlphaBlending,
|
|
||||||
}
|
|
||||||
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct GraphicGroup {
|
|
||||||
elements: Vec<(Graphic, Option<NodeId>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct OlderTable<T> {
|
|
||||||
id: Vec<u64>,
|
|
||||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
|
||||||
element: Vec<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct OldTable<T> {
|
|
||||||
id: Vec<u64>,
|
|
||||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
|
||||||
element: Vec<T>,
|
|
||||||
transform: Vec<DAffine2>,
|
|
||||||
alpha_blending: Vec<LegacyAlphaBlending>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
|
||||||
enum GraphicFormat {
|
|
||||||
OldGraphicGroup(OldGraphicGroup),
|
|
||||||
OlderTableOldGraphicGroup(OlderTable<OldGraphicGroup>),
|
|
||||||
OldTableOldGraphicGroup(OldTable<OldGraphicGroup>),
|
|
||||||
OldTableGraphicGroup(OldTable<GraphicGroup>),
|
|
||||||
Table(serde_json::Value),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attributes (transform, alpha_blending, editor:layer_path) are not serialized, so migration only needs
|
|
||||||
// to recover the elements. Per-item attribute values are populated at runtime by the node graph.
|
|
||||||
Ok(match GraphicFormat::deserialize(deserializer)? {
|
|
||||||
GraphicFormat::OldGraphicGroup(old) => old.elements.into_iter().map(|(graphic, _)| TableRow::new_from_element(graphic)).collect(),
|
|
||||||
GraphicFormat::OlderTableOldGraphicGroup(old) => old
|
|
||||||
.element
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|element| element.elements.into_iter().map(|(graphic, _)| TableRow::new_from_element(graphic)))
|
|
||||||
.collect(),
|
|
||||||
GraphicFormat::OldTableOldGraphicGroup(old) => old
|
|
||||||
.element
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|element| element.elements.into_iter().map(|(graphic, _)| TableRow::new_from_element(graphic)))
|
|
||||||
.collect(),
|
|
||||||
GraphicFormat::OldTableGraphicGroup(old) => old
|
|
||||||
.element
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|element| element.elements.into_iter().map(|(graphic, _)| TableRow::new_from_element(graphic)))
|
|
||||||
.collect(),
|
|
||||||
GraphicFormat::Table(value) => {
|
|
||||||
// Try to deserialize as either `Table` format
|
|
||||||
if let Ok(old_table) = serde_json::from_value::<Table<GraphicGroup>>(value.clone()) {
|
|
||||||
let mut graphic_table = Table::new();
|
|
||||||
for index in 0..old_table.len() {
|
|
||||||
for (graphic, _) in &old_table.element(index).unwrap().elements {
|
|
||||||
graphic_table.push(TableRow::new_from_element(graphic.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
graphic_table
|
|
||||||
} else if let Ok(new_table) = serde_json::from_value::<Table<Graphic>>(value) {
|
|
||||||
new_table
|
|
||||||
} else {
|
|
||||||
return Err(serde::de::Error::custom("Failed to deserialize Table<Graphic>"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,88 +11,50 @@ pub use artboard::Artboard;
|
|||||||
pub use graphic::{Graphic, IntoGraphicTable, TryFromGraphic, Vector};
|
pub use graphic::{Graphic, IntoGraphicTable, TryFromGraphic, Vector};
|
||||||
|
|
||||||
pub mod migrations {
|
pub mod migrations {
|
||||||
use core_types::blending::BlendMode;
|
|
||||||
use core_types::table::{Table, TableRow};
|
|
||||||
use dyn_any::DynAny;
|
|
||||||
use glam::DAffine2;
|
|
||||||
use vector_types::vector::{PathStyle, PointDomain, RegionDomain, SegmentDomain, misc::HandleId};
|
use vector_types::vector::{PathStyle, PointDomain, RegionDomain, SegmentDomain, misc::HandleId};
|
||||||
|
|
||||||
use crate::{Graphic, Vector};
|
use crate::Vector;
|
||||||
|
|
||||||
// TODO: Eventually remove this migration document upgrade code
|
// TODO: Eventually remove this migration document upgrade code
|
||||||
pub fn migrate_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Vector>, D::Error> {
|
/// Returns the first `Vector` recovered from any of the legacy on-disk shapes (a single `Vector`, the old `OldVectorData` flat struct, or any of the historical `Table<Vector>` variants).
|
||||||
|
pub fn migrate_to_optional_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Option<Vector>, D::Error> {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
/// Mirrors the removed `AlphaBlending` struct for legacy document deserialization.
|
/// Old documents stored a `Vector` flattened with table attributes (`transform`, `alpha_blending`, `upstream_graphic_group`); only the geometry fields are recovered.
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(serde::Deserialize)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
struct OldVectorData {
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
style: PathStyle,
|
||||||
pub struct LegacyAlphaBlending {
|
colinear_manipulators: Vec<[HandleId; 2]>,
|
||||||
pub blend_mode: BlendMode,
|
point_domain: PointDomain,
|
||||||
pub opacity: f32,
|
segment_domain: SegmentDomain,
|
||||||
pub fill: f32,
|
region_domain: RegionDomain,
|
||||||
pub clip: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DynAny)]
|
#[derive(serde::Deserialize)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
struct LegacyTable {
|
||||||
pub struct OldVectorData {
|
#[serde(alias = "instances", alias = "instance")]
|
||||||
pub transform: DAffine2,
|
element: Vec<Vector>,
|
||||||
pub alpha_blending: LegacyAlphaBlending,
|
|
||||||
|
|
||||||
pub style: PathStyle,
|
|
||||||
|
|
||||||
pub colinear_manipulators: Vec<[HandleId; 2]>,
|
|
||||||
|
|
||||||
pub point_domain: PointDomain,
|
|
||||||
pub segment_domain: SegmentDomain,
|
|
||||||
pub region_domain: RegionDomain,
|
|
||||||
|
|
||||||
pub upstream_graphic_group: Option<Table<Graphic>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(serde::Deserialize)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[serde(untagged)]
|
||||||
pub struct OldTable<T> {
|
|
||||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
|
||||||
element: Vec<T>,
|
|
||||||
transform: Vec<DAffine2>,
|
|
||||||
alpha_blending: Vec<LegacyAlphaBlending>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct OlderTable<T> {
|
|
||||||
id: Vec<u64>,
|
|
||||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
|
||||||
element: Vec<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
enum VectorFormat {
|
enum VectorFormat {
|
||||||
Vector(Vector),
|
Vector(Vector),
|
||||||
OldVectorData(OldVectorData),
|
OldVectorData(OldVectorData),
|
||||||
OldVectorTable(OldTable<Vector>),
|
Table(LegacyTable),
|
||||||
OlderVectorTable(OlderTable<Vector>),
|
|
||||||
VectorTable(Table<Vector>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(match VectorFormat::deserialize(deserializer)? {
|
Ok(match VectorFormat::deserialize(deserializer)? {
|
||||||
VectorFormat::Vector(vector) => Table::new_from_element(vector),
|
VectorFormat::Vector(vector) => Some(vector),
|
||||||
// Attributes (transform, alpha_blending, editor:layer_path) are not serialized, so migration only needs
|
VectorFormat::OldVectorData(old) => Some(Vector {
|
||||||
// to recover the elements. Per-item attribute values are populated at runtime by the node graph.
|
|
||||||
VectorFormat::OldVectorData(old) => Table::new_from_element(Vector {
|
|
||||||
style: old.style,
|
style: old.style,
|
||||||
colinear_manipulators: old.colinear_manipulators,
|
colinear_manipulators: old.colinear_manipulators,
|
||||||
point_domain: old.point_domain,
|
point_domain: old.point_domain,
|
||||||
segment_domain: old.segment_domain,
|
segment_domain: old.segment_domain,
|
||||||
region_domain: old.region_domain,
|
region_domain: old.region_domain,
|
||||||
}),
|
}),
|
||||||
VectorFormat::OlderVectorTable(older_table) => older_table.element.into_iter().map(TableRow::new_from_element).collect(),
|
VectorFormat::Table(table) => table.element.into_iter().next(),
|
||||||
VectorFormat::OldVectorTable(old_table) => old_table.element.into_iter().map(TableRow::new_from_element).collect(),
|
|
||||||
VectorFormat::VectorTable(vector_table) => vector_table,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
use crate::raster_types::{CPU, Raster};
|
|
||||||
use crate::{Bitmap, BitmapMut};
|
use crate::{Bitmap, BitmapMut};
|
||||||
use core_types::blending::BlendMode;
|
use core_types::Color;
|
||||||
use core_types::color::float_to_srgb_u8;
|
use core_types::color::float_to_srgb_u8;
|
||||||
use core_types::table::{Table, TableRow};
|
|
||||||
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color};
|
|
||||||
// use crate::vector::Vector; // TODO: Check if Vector is actually used, if so handle differently
|
// use crate::vector::Vector; // TODO: Check if Vector is actually used, if so handle differently
|
||||||
use core_types::color::*;
|
use core_types::color::*;
|
||||||
use dyn_any::{DynAny, StaticType};
|
use dyn_any::StaticType;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
@@ -15,7 +12,7 @@ mod base64_serde {
|
|||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use core_types::color::*;
|
use core_types::color::*;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
pub fn as_base64<S: Serializer, P: Pixel>(key: &[P], serializer: S) -> Result<S::Ok, S::Error> {
|
pub fn as_base64<S: Serializer, P: Pixel>(key: &[P], serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
let u8_data = bytemuck::cast_slice(key);
|
let u8_data = bytemuck::cast_slice(key);
|
||||||
@@ -25,16 +22,29 @@ mod base64_serde {
|
|||||||
|
|
||||||
pub fn from_base64<'a, D: Deserializer<'a>, P: Pixel>(deserializer: D) -> Result<Vec<P>, D::Error> {
|
pub fn from_base64<'a, D: Deserializer<'a>, P: Pixel>(deserializer: D) -> Result<Vec<P>, D::Error> {
|
||||||
use serde::de::Error;
|
use serde::de::Error;
|
||||||
<(u64, &[u8])>::deserialize(deserializer)
|
// Use a small visitor that accepts both borrowed bytes (from a streaming JSON deserializer) and owned strings (from an intermediate like `serde_json::Value`, which can't preserve the borrow).
|
||||||
.and_then(|(len, str)| {
|
// The migration loader takes the second path, so without this allowance documents containing image base64 data fail with `expected a borrowed byte array`.
|
||||||
|
struct LenAndBase64Visitor<P: Pixel>(std::marker::PhantomData<P>);
|
||||||
|
|
||||||
|
impl<'de, P: Pixel> serde::de::Visitor<'de> for LenAndBase64Visitor<P> {
|
||||||
|
type Value = Vec<P>;
|
||||||
|
|
||||||
|
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
f.write_str("a tuple of (length, base64-encoded data)")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<A: serde::de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
|
||||||
|
let len: u64 = seq.next_element()?.ok_or_else(|| A::Error::missing_field("length"))?;
|
||||||
|
let base64_string: std::borrow::Cow<'de, str> = seq.next_element()?.ok_or_else(|| A::Error::missing_field("base64 data"))?;
|
||||||
let mut output: Vec<P> = vec![P::zeroed(); len as usize];
|
let mut output: Vec<P> = vec![P::zeroed(); len as usize];
|
||||||
base64::engine::general_purpose::STANDARD
|
base64::engine::general_purpose::STANDARD
|
||||||
.decode_slice(str, bytemuck::cast_slice_mut(output.as_mut_slice()))
|
.decode_slice(base64_string.as_bytes(), bytemuck::cast_slice_mut(output.as_mut_slice()))
|
||||||
.map_err(|err| Error::custom(err.to_string()))?;
|
.map_err(|err| A::Error::custom(err.to_string()))?;
|
||||||
|
|
||||||
Ok(output)
|
Ok(output)
|
||||||
})
|
}
|
||||||
.map_err(serde::de::Error::custom)
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_tuple(2, LenAndBase64Visitor::<P>(std::marker::PhantomData))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,253 +229,6 @@ impl<P: Pixel> IntoIterator for Image<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Eventually remove this migration document upgrade code
|
|
||||||
pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Raster<CPU>>, D::Error> {
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
/// Mirrors the removed `AlphaBlending` struct for legacy document deserialization.
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
|
||||||
pub struct LegacyAlphaBlending {
|
|
||||||
pub blend_mode: BlendMode,
|
|
||||||
pub opacity: f32,
|
|
||||||
pub fill: f32,
|
|
||||||
pub clip: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, core_types::CacheHash, PartialEq, DynAny)]
|
|
||||||
enum RasterFrame {
|
|
||||||
ImageFrame(Table<Image<Color>>),
|
|
||||||
}
|
|
||||||
impl<'de> serde::Deserialize<'de> for RasterFrame {
|
|
||||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
||||||
Ok(RasterFrame::ImageFrame(Table::new_from_element(Image::deserialize(deserializer)?)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl serde::Serialize for RasterFrame {
|
|
||||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
||||||
match self {
|
|
||||||
RasterFrame::ImageFrame(table) => table.serialize(serializer),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, core_types::CacheHash, PartialEq, DynAny)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub enum GraphicElement {
|
|
||||||
GraphicGroup(Table<GraphicElement>),
|
|
||||||
RasterFrame(RasterFrame),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct ImageFrame<P: Pixel> {
|
|
||||||
pub image: Image<P>,
|
|
||||||
}
|
|
||||||
impl From<ImageFrame<Color>> for GraphicElement {
|
|
||||||
fn from(image_frame: ImageFrame<Color>) -> Self {
|
|
||||||
GraphicElement::RasterFrame(RasterFrame::ImageFrame(Table::new_from_element(image_frame.image)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<GraphicElement> for ImageFrame<Color> {
|
|
||||||
fn from(element: GraphicElement) -> Self {
|
|
||||||
match element {
|
|
||||||
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {
|
|
||||||
image: image.element(0).unwrap().clone(),
|
|
||||||
},
|
|
||||||
_ => panic!("Expected Image, found {element:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<P> StaticType for ImageFrame<P>
|
|
||||||
where
|
|
||||||
P: dyn_any::StaticTypeSized + Pixel,
|
|
||||||
P::Static: Pixel,
|
|
||||||
{
|
|
||||||
type Static = ImageFrame<P::Static>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct OldImageFrame<P: Pixel> {
|
|
||||||
image: Image<P>,
|
|
||||||
transform: DAffine2,
|
|
||||||
alpha_blending: LegacyAlphaBlending,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
|
||||||
enum FormatVersions {
|
|
||||||
Image(Image<Color>),
|
|
||||||
OldImageFrame(OldImageFrame<Color>),
|
|
||||||
OlderImageFrameTable(OlderTable<ImageFrame<Color>>),
|
|
||||||
OldImageFrameTable(OldTable<ImageFrame<Color>>),
|
|
||||||
OldImageTable(OldTable<Image<Color>>),
|
|
||||||
OldRasterTable(OldTable<Raster<CPU>>),
|
|
||||||
ImageFrameTable(Table<ImageFrame<Color>>),
|
|
||||||
ImageTable(Table<Image<Color>>),
|
|
||||||
RasterTable(Table<Raster<CPU>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct OldTable<T> {
|
|
||||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
|
||||||
element: Vec<T>,
|
|
||||||
transform: Vec<DAffine2>,
|
|
||||||
alpha_blending: Vec<LegacyAlphaBlending>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct OlderTable<T> {
|
|
||||||
id: Vec<u64>,
|
|
||||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
|
||||||
element: Vec<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_image_table(table: Table<Image<Color>>) -> Table<Raster<CPU>> {
|
|
||||||
Table::new_from_element(Raster::new_cpu(table.element(0).unwrap().clone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attributes (transform, alpha_blending, editor:layer_path) are not serialized, so migration only needs
|
|
||||||
// to recover the elements. Per-item attribute values are populated at runtime by the node graph.
|
|
||||||
fn old_table_to_new_table<T>(old_table: OldTable<T>) -> Table<T> {
|
|
||||||
old_table.element.into_iter().map(TableRow::new_from_element).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn older_table_to_new_table<T>(old_table: OlderTable<T>) -> Table<T> {
|
|
||||||
old_table.element.into_iter().map(TableRow::new_from_element).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_image_frame_table(image_frame: Table<ImageFrame<Color>>) -> Table<Raster<CPU>> {
|
|
||||||
let default = ImageFrame::default();
|
|
||||||
let element = image_frame.element(0).unwrap_or(&default);
|
|
||||||
Table::new_from_element(Raster::new_cpu(element.image.clone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(match FormatVersions::deserialize(deserializer)? {
|
|
||||||
FormatVersions::Image(image) => Table::new_from_element(Raster::new_cpu(image)),
|
|
||||||
FormatVersions::OldImageFrame(OldImageFrame { image, transform, alpha_blending }) => {
|
|
||||||
let mut image_frame_table = Table::new_from_element(Raster::new_cpu(image));
|
|
||||||
image_frame_table.set_attribute(ATTR_TRANSFORM, 0, transform);
|
|
||||||
image_frame_table.set_attribute(ATTR_BLEND_MODE, 0, alpha_blending.blend_mode);
|
|
||||||
image_frame_table.set_attribute(ATTR_OPACITY, 0, alpha_blending.opacity as f64);
|
|
||||||
image_frame_table.set_attribute(ATTR_OPACITY_FILL, 0, alpha_blending.fill as f64);
|
|
||||||
image_frame_table.set_attribute(ATTR_CLIPPING_MASK, 0, alpha_blending.clip);
|
|
||||||
image_frame_table
|
|
||||||
}
|
|
||||||
FormatVersions::OlderImageFrameTable(old_table) => from_image_frame_table(older_table_to_new_table(old_table)),
|
|
||||||
FormatVersions::OldImageFrameTable(old_table) => from_image_frame_table(old_table_to_new_table(old_table)),
|
|
||||||
FormatVersions::OldImageTable(old_table) => from_image_table(old_table_to_new_table(old_table)),
|
|
||||||
FormatVersions::OldRasterTable(old_table) => old_table_to_new_table(old_table),
|
|
||||||
FormatVersions::ImageFrameTable(image_frame) => from_image_frame_table(image_frame),
|
|
||||||
FormatVersions::ImageTable(table) => from_image_table(table),
|
|
||||||
FormatVersions::RasterTable(table) => table,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Eventually remove this migration document upgrade code
|
|
||||||
pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<TableRow<Raster<CPU>>, D::Error> {
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
/// Mirrors the removed `AlphaBlending` struct for legacy document deserialization.
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
|
||||||
pub struct LegacyAlphaBlending {
|
|
||||||
pub blend_mode: BlendMode,
|
|
||||||
pub opacity: f32,
|
|
||||||
pub fill: f32,
|
|
||||||
pub clip: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DynAny)]
|
|
||||||
enum RasterFrame {
|
|
||||||
/// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
|
|
||||||
ImageFrame(Table<Image<Color>>),
|
|
||||||
}
|
|
||||||
impl<'de> serde::Deserialize<'de> for RasterFrame {
|
|
||||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
||||||
Ok(RasterFrame::ImageFrame(Table::new_from_element(Image::deserialize(deserializer)?)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl serde::Serialize for RasterFrame {
|
|
||||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
||||||
match self {
|
|
||||||
RasterFrame::ImageFrame(table) => table.serialize(serializer),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DynAny)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub enum GraphicElement {
|
|
||||||
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
|
|
||||||
GraphicGroup(Table<GraphicElement>),
|
|
||||||
RasterFrame(RasterFrame),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct ImageFrame<P: Pixel> {
|
|
||||||
pub image: Image<P>,
|
|
||||||
}
|
|
||||||
impl From<ImageFrame<Color>> for GraphicElement {
|
|
||||||
fn from(image_frame: ImageFrame<Color>) -> Self {
|
|
||||||
GraphicElement::RasterFrame(RasterFrame::ImageFrame(Table::new_from_element(image_frame.image)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<GraphicElement> for ImageFrame<Color> {
|
|
||||||
fn from(element: GraphicElement) -> Self {
|
|
||||||
match element {
|
|
||||||
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {
|
|
||||||
image: image.element(0).unwrap().clone(),
|
|
||||||
},
|
|
||||||
_ => panic!("Expected Image, found {element:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<P> StaticType for ImageFrame<P>
|
|
||||||
where
|
|
||||||
P: dyn_any::StaticTypeSized + Pixel,
|
|
||||||
P::Static: Pixel,
|
|
||||||
{
|
|
||||||
type Static = ImageFrame<P::Static>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct OldImageFrame<P: Pixel> {
|
|
||||||
image: Image<P>,
|
|
||||||
transform: DAffine2,
|
|
||||||
alpha_blending: LegacyAlphaBlending,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
|
||||||
enum FormatVersions {
|
|
||||||
Image(Image<Color>),
|
|
||||||
OldImageFrame(OldImageFrame<Color>),
|
|
||||||
ImageFrameTable(Table<ImageFrame<Color>>),
|
|
||||||
RasterTable(Table<Raster<CPU>>),
|
|
||||||
RasterTableRow(TableRow<Raster<CPU>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attributes (transform, alpha_blending, editor:layer_path) are not serialized, so migration only needs
|
|
||||||
// to recover the element. Per-item attribute values are populated at runtime by the node graph.
|
|
||||||
Ok(match FormatVersions::deserialize(deserializer)? {
|
|
||||||
FormatVersions::Image(image) => TableRow::new_from_element(Raster::new_cpu(image)),
|
|
||||||
FormatVersions::OldImageFrame(old) => TableRow::new_from_element(Raster::new_cpu(old.image)),
|
|
||||||
FormatVersions::ImageFrameTable(image_frame) => TableRow::new_from_element(Raster::new_cpu(image_frame.element(0).unwrap().image.clone())),
|
|
||||||
FormatVersions::RasterTable(image_frame_table) => image_frame_table.into_iter().next().unwrap_or_default(),
|
|
||||||
FormatVersions::RasterTableRow(image_table_row) => image_table_row,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: std::fmt::Debug + Copy + Pixel> Sample for Image<P> {
|
impl<P: std::fmt::Debug + Copy + Pixel> Sample for Image<P> {
|
||||||
type Pixel = P;
|
type Pixel = P;
|
||||||
|
|
||||||
|
|||||||
@@ -526,20 +526,25 @@ impl Gradient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Eventually remove this migration document upgrade code
|
// TODO: Eventually remove this migration document upgrade code
|
||||||
pub fn migrate_gradient_stops<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<core_types::table::Table<GradientStops>, D::Error> {
|
pub fn migrate_to_gradient_stops<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<GradientStops, D::Error> {
|
||||||
use core_types::table::Table;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct LegacyTable {
|
||||||
|
#[serde(alias = "instances", alias = "instance")]
|
||||||
|
element: Vec<GradientStops>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||||
enum GradientStopsFormat {
|
enum GradientStopsFormat {
|
||||||
GradientStops(GradientStops),
|
Stops(GradientStops),
|
||||||
GradientTable(Table<GradientStops>),
|
Table(LegacyTable),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(match GradientStopsFormat::deserialize(deserializer)? {
|
Ok(match GradientStopsFormat::deserialize(deserializer)? {
|
||||||
GradientStopsFormat::GradientStops(stops) => Table::new_from_element(stops),
|
GradientStopsFormat::Stops(stops) => stops,
|
||||||
GradientStopsFormat::GradientTable(table) => table,
|
GradientStopsFormat::Table(table) => table.element.into_iter().next().unwrap_or_default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -222,17 +222,6 @@ impl From<Fill> for FillChoice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
|
||||||
#[derive(Debug, Clone, Copy, Default, PartialEq, DynAny, Hash, graphene_hash::CacheHash, node_macro::ChoiceType)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[widget(Radio)]
|
|
||||||
pub enum FillType {
|
|
||||||
#[default]
|
|
||||||
Solid,
|
|
||||||
Gradient,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The stroke (outline) style of an SVG element.
|
/// The stroke (outline) style of an SVG element.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
|||||||
@@ -4,22 +4,27 @@ pub mod brush_stroke;
|
|||||||
|
|
||||||
pub mod migrations {
|
pub mod migrations {
|
||||||
use crate::brush_stroke::BrushStroke;
|
use crate::brush_stroke::BrushStroke;
|
||||||
use core_types::table::{Table, TableRow};
|
|
||||||
|
|
||||||
// TODO: Eventually remove this migration document upgrade code
|
// TODO: Eventually remove this migration document upgrade code
|
||||||
pub fn migrate_brush_strokes_to_table<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<BrushStroke>, D::Error> {
|
pub fn migrate_to_brush_strokes<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Vec<BrushStroke>, D::Error> {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
#[serde(untagged)]
|
struct LegacyTable {
|
||||||
enum BrushStrokeTableFormat {
|
#[serde(alias = "instances", alias = "instance")]
|
||||||
BrushStrokes(Vec<BrushStroke>),
|
element: Vec<BrushStroke>,
|
||||||
BrushStrokeTable(Table<BrushStroke>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(match BrushStrokeTableFormat::deserialize(deserializer)? {
|
#[derive(serde::Deserialize)]
|
||||||
BrushStrokeTableFormat::BrushStrokes(strokes) => strokes.into_iter().map(TableRow::new_from_element).collect(),
|
#[serde(untagged)]
|
||||||
BrushStrokeTableFormat::BrushStrokeTable(table) => table,
|
enum BrushStrokesFormat {
|
||||||
|
Strokes(Vec<BrushStroke>),
|
||||||
|
Table(LegacyTable),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(match BrushStrokesFormat::deserialize(deserializer)? {
|
||||||
|
BrushStrokesFormat::Strokes(strokes) => strokes,
|
||||||
|
BrushStrokesFormat::Table(table) => table.element,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,12 @@ mod text_context;
|
|||||||
mod to_path;
|
mod to_path;
|
||||||
|
|
||||||
use convert_case::{Boundary, Converter, pattern};
|
use convert_case::{Boundary, Converter, pattern};
|
||||||
use core_types::Color;
|
|
||||||
use core_types::graphene_hash::CacheHash;
|
use core_types::graphene_hash::CacheHash;
|
||||||
use core_types::registry::types::{SignedInteger, TextArea};
|
use core_types::registry::types::{SignedInteger, TextArea};
|
||||||
use core_types::table::{Table, TableRow};
|
use core_types::table::{Table, TableRow};
|
||||||
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractVarArgs, OwnedContextImpl};
|
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractVarArgs, OwnedContextImpl};
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use raster_types::{CPU, Raster};
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
// Re-export for convenience
|
// Re-export for convenience
|
||||||
@@ -797,24 +795,6 @@ fn read_string(ctx: impl Ctx + ExtractVarArgs) -> String {
|
|||||||
|
|
||||||
/// Converts a value to a JSON string representation.
|
/// Converts a value to a JSON string representation.
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"))]
|
||||||
fn serialize<T: serde::Serialize>(
|
fn serialize<T: serde::Serialize>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2)] value: T) -> String {
|
||||||
_: impl Ctx,
|
|
||||||
#[implementations(
|
|
||||||
String,
|
|
||||||
bool,
|
|
||||||
f64,
|
|
||||||
u32,
|
|
||||||
u64,
|
|
||||||
DVec2,
|
|
||||||
DAffine2,
|
|
||||||
// Table<Artboard>,
|
|
||||||
// Table<Graphic>,
|
|
||||||
// Table<Vector>,
|
|
||||||
Table<Raster<CPU>>,
|
|
||||||
Table<Color>,
|
|
||||||
// Table<GradientStops>,
|
|
||||||
)]
|
|
||||||
value: T,
|
|
||||||
) -> String {
|
|
||||||
serde_json::to_string(&value).unwrap_or_else(|_| "Serialization Error".to_string())
|
serde_json::to_string(&value).unwrap_or_else(|_| "Serialization Error".to_string())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user