diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 4520fd342a..f8f7919347 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -3803,7 +3803,7 @@ impl DocumentMessageHandler { /// Create a network interface with a single export fn default_document_network_interface() -> NodeNetworkInterface { let mut network_interface = NodeNetworkInterface::default(); - network_interface.add_export(TaggedValue::TypeDefault(concrete!(graphene_std::list::List)), -1, "", &[]); + network_interface.add_export(TaggedValue::TypeDefault(graph_craft::descriptor!(graphene_std::list::List)), -1, "", &[]); network_interface } diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index 7b90784711..14df1fca33 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -252,7 +252,7 @@ impl MessageHandler> for } // Set the bottom input of the artboard back to artboard - let bottom_input = NodeInput::type_default(concrete!(graphene_std::list::List), true); + let bottom_input = NodeInput::type_default(graph_craft::descriptor!(graphene_std::list::List), true); network_interface.set_input(&InputConnector::primary_input(artboard_layer.to_node()), bottom_input, &[]); } else { // We have some non layers (e.g. just a rectangle node). We disconnect the bottom input and connect it to the left input. @@ -260,7 +260,7 @@ impl MessageHandler> for network_interface.set_input(&InputConnector::layer_secondary_input(artboard_layer.to_node()), primary_input, &[]); // Set the bottom input of the artboard back to artboard - let bottom_input = NodeInput::type_default(concrete!(graphene_std::list::List), true); + let bottom_input = NodeInput::type_default(graph_craft::descriptor!(graphene_std::list::List), true); network_interface.set_input(&InputConnector::primary_input(artboard_layer.to_node()), bottom_input, &[]); } } diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 595123c940..b2b5f8277b 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -139,7 +139,6 @@ impl DocumentNode { /// Normalizes this node's stored types (call argument, `Import` input types, `TypeDefault` value payloads, and any nested network) to their structural form. /// Applied once at ingestion (document migration and clipboard paste) so no name-encoded ranked type enters a live document. pub fn normalize_stored_types(&mut self) { - self.call_argument = self.call_argument.clone().normalize_rank(); for input in &mut self.inputs { normalize_input_stored_type(input); } @@ -268,8 +267,8 @@ impl NodeInput { /// Constructs a `NodeInput::Value` whose tagged value is `TaggedValue::TypeDefault(ty)`, recording only the /// type so the runtime materializes its default rather than baking a placeholder value into the saved document. - pub fn type_default(ty: Type, exposed: bool) -> Self { - Self::value(TaggedValue::TypeDefault(ty.normalize_rank()), exposed) + 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 { @@ -758,10 +757,10 @@ impl ScopeChain<'_> { /// Normalizes the ranked types an input can store: an `Import`'s type or a value's `TypeDefault` payload. fn normalize_input_stored_type(input: &mut NodeInput) { match input { - NodeInput::Import { import_type, .. } => *import_type = import_type.clone().normalize_rank(), + NodeInput::Import { .. } => {} NodeInput::Value { tagged_value, .. } => { if let TaggedValue::TypeDefault(ty) = &**tagged_value { - let normalized = ty.clone().normalize_rank(); + let normalized = ty.clone(); if normalized != *ty { *tagged_value = TaggedValue::TypeDefault(normalized).into(); } @@ -1061,7 +1060,7 @@ impl NodeNetwork { let (tagged_value, exposed) = match previous_export { NodeInput::Value { tagged_value, exposed } => (tagged_value, exposed), NodeInput::Reflection(reflect) => match reflect { - DocumentNodeMetadata::DocumentNodePath => (TaggedValue::NodeIdPath(path.to_vec()).into(), false), + DocumentNodeMetadata::DocumentNodePath => (TaggedValue::NodeIdPath(core_types::list::NodeIdPath::from(path.to_vec())).into(), false), DocumentNodeMetadata::SourceId => { let source_id = Self::source_id_for_path(path); if let Some(context_features) = context_features.as_deref_mut() { diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 699a996570..24148bdbf6 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -2,11 +2,11 @@ use super::DocumentNode; use crate::application_io::PlatformEditorApi; use crate::application_io::resource::Resource; use crate::proto::Any as DAny; -use brush_nodes::brush_stroke::Stroke; +use brush_nodes::{BrushCache, Stroke}; use core_types::color::SRGBA8; use core_types::context::Context; use core_types::gpoll::GPoll; -use core_types::list::List; +use core_types::list::{Item, List, NodeIdPath}; use core_types::registry::SourceHandle; use core_types::transform::Footprint; use core_types::uuid::NodeId; @@ -84,7 +84,7 @@ macro_rules! tagged_value { /// Stores a type, from which its `Default::default()` value can be obtained, rather than storing an actual type's value. /// Example: `TaggedValue::TypeDefault(concrete!(String))` stores the type `String` but no specific string value. /// (Old documents stored a bare `TypeDescriptor` payload, routed to this shape by `deserialize_tagged_value_with_legacy_migration`.) - TypeDefault(Type), + TypeDefault(TypeDescriptor), /// Stored compactly as a `Vec`, materializes as `List` 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 document upgrade code #[serde(alias = "F64Table", alias = "VecF64", alias = "VecF32", alias = "F64Array4")] @@ -174,7 +174,7 @@ macro_rules! tagged_value { if name == core_types::normalize_type_name(std::any::type_name::<$type_default>()) { return Box::new(<$type_default>::default()); } }; } - Self::from_type_or_none(&td).to_dynany() + Self::from_type_or_none(&Type::Concrete(td.clone())).to_dynany() } Self::F64Array(values) => { let list: List = values.into_iter().map(core_types::list::Item::new_from_element).collect(); @@ -219,7 +219,7 @@ macro_rules! tagged_value { if name == core_types::normalize_type_name(std::any::type_name::<$type_default>()) { return Arc::new(<$type_default>::default()); } }; } - Self::from_type_or_none(&td).to_any() + Self::from_type_or_none(&Type::Concrete(td.clone())).to_any() } Self::F64Array(values) => { let list: List = values.into_iter().map(core_types::list::Item::new_from_element).collect(); @@ -245,13 +245,13 @@ macro_rules! tagged_value { Self::DocumentNode(node) => Arc::new(node), Self::ContextModification(modification) => Arc::new(modification), Self::EditorApi(x) => Arc::new(x), - Self::ResourceHash(x) => Arc::new(Item::new_from_element(x)), + Self::ResourceHash(x) => Arc::new(x), } } /// Creates the wire [`Type`] of the value inside the tagged value, with ranked types in their structural form. pub fn ty(&self) -> Type { - let ty = match self { + match self { // =============== // MANUAL VARIANTS // =============== @@ -271,6 +271,9 @@ macro_rules! tagged_value { Self::Color(_) => concrete!(Color), Self::GradientRamp(_) => concrete!(Gradient), Self::Strokes(_) => concrete!(Stroke), + Self::DashPattern(_) => concrete!(DashPattern), + Self::BoxCorners(_) => concrete!(BoxCorners), + Self::BrushCache(_) => concrete!(BrushCache), // ======================= // AUTO-GENERATED VARIANTS // ======================= @@ -279,7 +282,7 @@ macro_rules! tagged_value { // NON-SERIALIZED VARIANTS // ======================= Self::RenderOutput(_) => concrete!(RenderOutput), - Self::NodeIdPath(_) => concrete!(Vec), + Self::NodeIdPath(_) => concrete!(core_types::list::NodeIdPath), Self::DocumentNode(_) => concrete!(DocumentNode), Self::ContextModification(_) => concrete!(ContextModification), Self::EditorApi(_) => concrete!(Arc), @@ -320,9 +323,12 @@ macro_rules! tagged_value { Self::Color(_) => leveled::(), Self::GradientRamp(_) => leveled::(), Self::Strokes(_) => leveled::(), + Self::DashPattern(_) => scalar::(), + Self::BoxCorners(_) => scalar::(), + Self::BrushCache(_) => scalar::(), $( Self::$identifier(_) => scalar::<$ty>(), )* Self::RenderOutput(_) => scalar::(), - Self::NodeIdPath(_) => scalar::>(), + Self::NodeIdPath(_) => scalar::(), Self::DocumentNode(_) => scalar::(), Self::ContextModification(_) => scalar::(), Self::EditorApi(_) => scalar::>(), @@ -363,6 +369,9 @@ macro_rules! tagged_value { Self::Color(color) => Ok(record_value_source(color)), Self::GradientRamp(stops) => Ok(leveled_record_value_source(vec![stops])), Self::Strokes(strokes) => Ok(leveled_record_value_source(strokes)), + Self::DashPattern(lengths) => Ok(record_value_source(DashPattern(lengths.into_iter().map(core_types::list::Item::new_from_element).collect()))), + Self::BoxCorners(values) => Ok(record_value_source(BoxCorners(values.into_iter().map(core_types::list::Item::new_from_element).collect()))), + Self::BrushCache(cache) => Ok(record_value_source(cache)), // ======================= // AUTO-GENERATED VARIANTS // ======================= @@ -487,15 +496,15 @@ macro_rules! tagged_value { if name == core_types::normalize_type_name(std::any::type_name::<()>()) { return Some(TaggedValue::None) } // List-wrapped types need a single-item default with the element's default, not an empty list if name == core_types::normalize_type_name(std::any::type_name::>()) { return Some(TaggedValue::Color(Color::default())) } - if name == core_types::normalize_type_name(std::any::type_name::>()) { return Some(TaggedValue::Gradient(Gradient::default())) } + if name == core_types::normalize_type_name(std::any::type_name::>()) { return Some(TaggedValue::GradientRamp(GradientRamp::default())) } $( if name == core_types::normalize_type_name(std::any::type_name::<$ty>()) { return Some(TaggedValue::$identifier(Default::default())) } )* if name == core_types::normalize_type_name(std::any::type_name::>()) { return Some(TaggedValue::F64Array(Vec::new())) } - if name == core_types::normalize_type_name(std::any::type_name::>()) { return Some(TaggedValue::BrushStrokes(Vec::new())) } + if name == core_types::normalize_type_name(std::any::type_name::>()) { return Some(TaggedValue::Strokes(Vec::new())) } // Leveled inputs type by their element; each element name maps to the // same tagged default as its legacy list form. if name == core_types::normalize_type_name(std::any::type_name::()) { return Some(TaggedValue::Color(Color::default())) } - if name == core_types::normalize_type_name(std::any::type_name::()) { return Some(TaggedValue::Gradient(Gradient::default())) } - if name == core_types::normalize_type_name(std::any::type_name::()) { return Some(TaggedValue::BrushStrokes(Vec::new())) } + if name == core_types::normalize_type_name(std::any::type_name::()) { return Some(TaggedValue::GradientRamp(GradientRamp::default())) } + if name == core_types::normalize_type_name(std::any::type_name::()) { return Some(TaggedValue::Strokes(Vec::new())) } if name == core_types::normalize_type_name(std::any::type_name::()) { return Some(TaggedValue::TypeDefault(core_types::descriptor!(List))) } if name == core_types::normalize_type_name(std::any::type_name::()) { return Some(TaggedValue::TypeDefault(core_types::descriptor!(List))) } if name == core_types::normalize_type_name(std::any::type_name::>()) { return Some(TaggedValue::TypeDefault(core_types::descriptor!(List>))) } @@ -503,10 +512,10 @@ macro_rules! tagged_value { // 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 == core_types::normalize_type_name(std::any::type_name::<$type_default>()) { return Some(TaggedValue::TypeDefault(concrete_type.clone())); } + if name == core_types::normalize_type_name(std::any::type_name::<$type_default>()) { return Some(TaggedValue::TypeDefault(core_types::descriptor!($type_default))); } }; } - for_each_bare_type_default!(check_bare); + for_each_bare_type_default!(check); None } Type::Fn(_, output) => TaggedValue::from_type(output), @@ -522,7 +531,7 @@ macro_rules! tagged_value { } macro_rules! check { ($type_default:ty) => { - if **element == concrete!($type_default) { return Some(TaggedValue::TypeDefault(input.clone())); } + if **element == concrete!($type_default) { return Some(TaggedValue::TypeDefault(core_types::descriptor!($type_default))); } }; } for_each_item_type_default!(check); @@ -807,12 +816,12 @@ impl TaggedValue { /// The stored form of a paint input's red-slash "no paint" choice: the `Item` type default, materializing as a `Graphic::None` paint. pub fn no_paint() -> Self { - TaggedValue::TypeDefault(concrete!(Graphic)) + TaggedValue::TypeDefault(core_types::descriptor!(Graphic)) } /// Whether this is the `Item` type default created by [`Self::no_paint`] (and by disconnecting a paint wire). pub fn is_no_paint(&self) -> bool { - matches!(self, TaggedValue::TypeDefault(td) if *td == concrete!(Graphic)) + matches!(self, TaggedValue::TypeDefault(td) if *td == core_types::descriptor!(Graphic)) } } @@ -820,14 +829,14 @@ impl TaggedValue { /// /// 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: /// -/// - `Graphic` (or alias `GraphicGroup`/`Group`) → `TaggedValue::TypeDefault(concrete!(List))` -/// - `Artboard` (or alias `ArtboardGroup`) → `TaggedValue::TypeDefault(concrete!(List))` +/// - `Graphic` (or alias `GraphicGroup`/`Group`) → `TaggedValue::TypeDefault(core_types::descriptor!(List))` +/// - `Artboard` (or alias `ArtboardGroup`) → `TaggedValue::TypeDefault(core_types::descriptor!(List))` /// - `Raster` (or alias `ImageFrame`/`RasterData`/`Image`): /// - non-empty (the legacy `image` proto's input 1, where the inner `Raster` serializes as the embedded `Image`) → `TaggedValue::ImageData(>)` -/// - empty → `TaggedValue::TypeDefault(concrete!(List>))` +/// - empty → `TaggedValue::TypeDefault(core_types::descriptor!(List>))` /// - `Vector` (or alias `VectorData`): /// - non-empty → `TaggedValue::VectorModification()` (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(concrete!(List))` +/// - empty → `TaggedValue::TypeDefault(core_types::descriptor!(List))` /// - `FillChoice` → `TaggedValue::Color` (solid), `TaggedValue::GradientRamp` (gradient), or `TaggedValue::no_paint()` (none) /// - `Gradient` (or alias `GradientTable`/`GradientPositions`/`Gradient`) → `TaggedValue::LegacyGradient` (ancient full struct) or `TaggedValue::GradientRamp` (ramp and legacy stops shapes, unwrapped from the legacy table form) /// - `TypeDefault` with the old bare-`TypeDescriptor` payload → the same variant wrapping a `Type` (name-encoded `List` normalized to structural) @@ -844,8 +853,8 @@ pub fn deserialize_tagged_value_with_legacy_migration<'de, D: serde::Deserialize && let Some((tag, content)) = map.iter().next() { match tag.as_str() { - "Graphic" | "GraphicGroup" | "Group" => return Ok(MemoHash::new(TaggedValue::TypeDefault(concrete!(List)))), - "Artboard" | "ArtboardGroup" => return Ok(MemoHash::new(TaggedValue::TypeDefault(concrete!(List)))), + "Graphic" | "GraphicGroup" | "Group" => return Ok(MemoHash::new(TaggedValue::TypeDefault(core_types::descriptor!(List)))), + "Artboard" | "ArtboardGroup" => return Ok(MemoHash::new(TaggedValue::TypeDefault(core_types::descriptor!(List)))), "Raster" | "ImageFrame" | "RasterData" | "Image" => { let first_element = content .as_object() @@ -856,7 +865,7 @@ pub fn deserialize_tagged_value_with_legacy_migration<'de, D: serde::Deserialize let image: Image = 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(concrete!(List>)))); + return Ok(MemoHash::new(TaggedValue::TypeDefault(core_types::descriptor!(List>)))); } "Vector" | "VectorData" => { let vector = graphic_types::migrations::migrate_to_optional_vector(content.clone()).map_err(serde::de::Error::custom)?; @@ -864,12 +873,12 @@ pub fn deserialize_tagged_value_with_legacy_migration<'de, D: serde::Deserialize let modification = Box::new(VectorModification::create_from_vector(&vector)); return Ok(MemoHash::new(TaggedValue::VectorModification(modification))); } - return Ok(MemoHash::new(TaggedValue::TypeDefault(concrete!(List)))); + return Ok(MemoHash::new(TaggedValue::TypeDefault(core_types::descriptor!(List)))); } - // The `TypeDefault` payload used to be a bare `TypeDescriptor`; it now carries a `Type` + // The `TypeDefault` payload is a bare `TypeDescriptor`: our one wire kind needs no structural rank in it "TypeDefault" if content.as_object().is_some_and(|c| c.contains_key("name")) => { let descriptor: TypeDescriptor = serde_json::from_value(content.clone()).map_err(serde::de::Error::custom)?; - return Ok(MemoHash::new(TaggedValue::TypeDefault(Type::Concrete(descriptor).normalize_rank()))); + return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor))); } // The `Color` tag used to carry `Option`, where a `null` payload (or an empty legacy color table) was the red-slash "no paint" choice "Color" | "ColorTable" | "OptionalColor" | "ColorNotInTable" @@ -1227,7 +1236,7 @@ mod leveled_edges { TaggedValue::F64Array(vec![1.]), TaggedValue::Bool(true), TaggedValue::TypeDefault(descriptor!(List)), - TaggedValue::Gradient(Default::default()), + TaggedValue::GradientRamp(Default::default()), ] { let layout = value.value_layout().unwrap(); let edge = value.to_edge().unwrap(); diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index ca8a2077ee..292d606261 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -901,10 +901,6 @@ impl TypingContext { self.promotions.get(&node_id) } - /// Looks up the sole constructor registered under an adapter identifier, such as an Item -> List promotion adapter. - pub fn adapter_constructor(&self, identifier: &ProtoNodeIdentifier) -> Option { - self.lookup.get(identifier).and_then(|implementations| implementations.values().next().copied()) - } /// Returns the node constructor for a given node id. pub fn constructor(&self, node_id: NodeId) -> Option { diff --git a/node-graph/libraries/core-types/src/ops.rs b/node-graph/libraries/core-types/src/ops.rs index a196672d01..2f8ecaa6b6 100644 --- a/node-graph/libraries/core-types/src/ops.rs +++ b/node-graph/libraries/core-types/src/ops.rs @@ -1,7 +1,7 @@ use crate::list::{Attribute, AttributeDyn, AttributeValueDyn, Item, List, ListDyn}; use crate::math::float_noise::round_away_float_noise; use crate::transform::Footprint; -use glam::{DAffine2, DVec2}; +use glam::{DAffine2, DVec2, IVec2}; use graphene_hash::CacheHash; /// The [`Convert`] trait allows for conversion between Rust primitive numeric types. @@ -31,7 +31,7 @@ macro_rules! impl_convert_to_string { )* }; } -impl_convert_to_string!(f32, u32, u64, i32, i64, bool, DVec2, DAffine2); +impl_convert_to_string!(f32, u32, u64, i32, i64, bool, DVec2, DAffine2, String, IVec2, i8, u8, u16, i16, u128, i128, usize, isize); // Denoised so 0.1 + 0.2 reaches the string as "0.3" rather than "0.30000000000000004" impl Convert for f64 { diff --git a/node-graph/libraries/vector-types/src/vector/vector_types.rs b/node-graph/libraries/vector-types/src/vector/vector_types.rs index 62367f05b6..861e43ad2a 100644 --- a/node-graph/libraries/vector-types/src/vector/vector_types.rs +++ b/node-graph/libraries/vector-types/src/vector/vector_types.rs @@ -69,6 +69,14 @@ impl core_types::transform::BakeTransform for Vector { } } +// Identity item conversion so `List` satisfies the blanket `Convert, ()> for List`, letting its +// auto-inserted input wrapper be a `ConvertNode` (which also accepts a `DVec2` anchor position) rather than an `IntoNode`. +impl core_types::ops::ListConvert for Vector { + fn convert_item(self) -> Vector { + self + } +} + impl Vector { /// Add a path of manipulator groups to this vector path. pub fn append_manipulator_groups(&mut self, manipulator_groups: &[ManipulatorGroup], closed: bool, preserve_id: bool) { diff --git a/node-graph/node-macro/src/codegen/entries.rs b/node-graph/node-macro/src/codegen/entries.rs index dfeb79e929..9b94a2b20d 100644 --- a/node-graph/node-macro/src/codegen/entries.rs +++ b/node-graph/node-macro/src/codegen/entries.rs @@ -83,9 +83,13 @@ fn flip_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_field let mentioned = candidate_params.iter().zip(&kept).any(|(param, kept)| { *kept && match param { - GenericParam::Type(type_param) => type_param.bounds.iter().any(|bound| { - let bound: Type = syn::parse_quote!(dyn #bound); - type_contains_ident(&bound, &ident) + // Only trait bounds can mention another parameter; a lifetime bound would build an invalid `dyn 'a`. + GenericParam::Type(type_param) => type_param.bounds.iter().any(|bound| match bound { + syn::TypeParamBound::Trait(trait_bound) => { + let bound: Type = syn::parse_quote!(dyn #trait_bound); + type_contains_ident(&bound, &ident) + } + _ => false, }), _ => false, } diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index 022af2556f..50a8904467 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -286,7 +286,7 @@ impl PerPixelAdjustCodegen<'_> { let entry_point_name = &self.entry_point_name; let body = quote! { { - #executor.shader_runtime().run_per_pixel_adjust(&::wgpu_executor::shader_runtime::per_pixel_adjust_runtime::Shaders { + #executor.run_per_pixel_adjust(&::wgpu_executor::shader_runtime::per_pixel_adjust_runtime::Shaders { wgsl_shader: crate::WGSL_SHADER, fragment_shader_name: super::#entry_point_name, has_uniform: #has_uniform, diff --git a/node-graph/nodes/brush/src/basic_brush/mod.rs b/node-graph/nodes/brush/src/basic_brush/mod.rs index b3ea5a4382..f33c9642c1 100644 --- a/node-graph/nodes/brush/src/basic_brush/mod.rs +++ b/node-graph/nodes/brush/src/basic_brush/mod.rs @@ -12,16 +12,16 @@ use core_types::{ATTR_TRANSFORM, Ctx, ExtractFootprint}; use graphic_types::Graphic; use pipeline::{BasicBrushPipeline, BasicBrushPipelineArgs}; use raster_types::{GPU, Raster}; -use wgpu_executor::{WgpuExecutor, WgpuPipelineCache}; +use core_types::ProtoNodeIdentifier; +use wgpu_executor::{WgpuExecutorHandle, WgpuPipelineCache}; #[node_macro::node(category("Raster: Brush"))] -pub async fn basic_brush<'a: 'n>( +pub fn basic_brush( ctx: impl Ctx + ExtractFootprint, - strokes: List, - #[widget(ParsedWidgetOverride::Hidden)] cache: Item, - #[scope(basic_brush_pipeline::IDENTIFIER)] pipeline: Item, + strokes: List>, + #[widget(ParsedWidgetOverride::Hidden)] cache: BrushCache, + #[scope(basic_brush_pipeline::IDENTIFIER)] pipeline: WgpuPipelineCache, ) -> List> { - let (cache, pipeline) = (cache.into_element(), pipeline.into_element()); let mut stack = vec![strokes.into_iter()]; let mut strokes = Vec::new(); while let Some(top) = stack.last_mut() { @@ -56,7 +56,7 @@ pub async fn basic_brush<'a: 'n>( strokes: &strokes, cache: &cache, }; - let Some((texture, transform)) = pipeline.run::(&args).await else { + let Some((texture, transform)) = pipeline.run::(&args) else { return List::new(); }; let raster = Raster::::new_gpu(texture); @@ -64,11 +64,11 @@ pub async fn basic_brush<'a: 'n>( } #[node_macro::node(category(""), inject_scope)] -async fn basic_brush_pipeline<'a: 'n>( +fn basic_brush_pipeline( _ctx: impl Ctx, - #[scope(ProtoNodeIdentifier::new("graphene_std::platform_application_io::WgpuExecutorNode"))] executor: Item<&'a WgpuExecutor>, + #[scope(ProtoNodeIdentifier::new("graphene_std::platform_application_io::WgpuExecutorNode"))] executor: WgpuExecutorHandle, #[data] pipeline: WgpuPipelineCache, -) -> Item { - executor.into_element().pipeline_init::(pipeline); - Item::new_from_element(pipeline.clone()) +) -> WgpuPipelineCache { + executor.pipeline_init::(pipeline); + pipeline.clone() } diff --git a/node-graph/nodes/brush/src/basic_brush/pipeline.rs b/node-graph/nodes/brush/src/basic_brush/pipeline.rs index b103f3fb61..70d730c7d9 100644 --- a/node-graph/nodes/brush/src/basic_brush/pipeline.rs +++ b/node-graph/nodes/brush/src/basic_brush/pipeline.rs @@ -9,7 +9,7 @@ use core_types::Color; use core_types::transform::Footprint; use glam::{DAffine2, UVec2}; use raster_types::Texture; -use wgpu_executor::{AsyncWgpuPipeline, Buffer, WgpuExecutor}; +use wgpu_executor::{Buffer, WgpuExecutor, WgpuPipeline}; pub(super) const DENSITY_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R16Float; pub(super) const COMPOSITE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float; @@ -71,7 +71,7 @@ pub struct BasicBrushPipelineArgs<'a> { pub(super) cache: &'a BrushCache, } -impl AsyncWgpuPipeline for BasicBrushPipeline { +impl WgpuPipeline for BasicBrushPipeline { type Args<'a> = BasicBrushPipelineArgs<'a>; type Out = Option<(Texture, DAffine2)>; @@ -85,7 +85,7 @@ impl AsyncWgpuPipeline for BasicBrushPipeline { } } - async fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> Self::Out { + fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> Self::Out { let frame = super::render::Frame::new(args.strokes)?; let region = Region::new(&args.footprint)?; let state = args.cache.take(&args.footprint).unwrap_or_default(); diff --git a/node-graph/nodes/brush/src/lib.rs b/node-graph/nodes/brush/src/lib.rs index 219fd9d701..2406d6358d 100644 --- a/node-graph/nodes/brush/src/lib.rs +++ b/node-graph/nodes/brush/src/lib.rs @@ -17,11 +17,10 @@ fn brush_strokes( _: impl Ctx, strokes: List, color: List, - #[default(DEFAULT_DIAMETER)] diameter: Item, - #[default(DEFAULT_HARDNESS)] hardness: Item, - #[default(DEFAULT_FLOW)] flow: Item, -) -> List { - let (diameter, hardness, flow) = (diameter.into_element(), hardness.into_element(), flow.into_element()); + #[default(DEFAULT_DIAMETER)] diameter: f64, + #[default(DEFAULT_HARDNESS)] hardness: Percentage, + #[default(DEFAULT_FLOW)] flow: Percentage, +) -> List> { List::new_from_item( Item::new_from_element(Graphic::from(strokes)) .with_attribute(ATTR_COLOR, color.element(0).copied().unwrap_or_default()) diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index 0607b5b9bb..bc929dbb86 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -496,7 +496,7 @@ pub fn to_graphic<'e, T: graphic_types::graphic::IntoGraphicElement>( /// Type-asserts a value to be graphical content, converting each item of other content types into its matching form. /// Use the 'Into Group' node instead to collect the content into a single group. #[node_macro::node(category("General"))] -pub fn as_graphic<'e>(_: impl Ctx, value: Graphic<'e>) -> Graphic<'e> { +pub fn as_graphic(_: impl Ctx, value: Graphic<'static>) -> Graphic<'static> { value } @@ -530,7 +530,7 @@ pub fn to_graphic_element<'e, T: graphic_types::graphic::IntoGraphicElement>( /// The typed-level conversion: the whole level nests as one graphic lane, as /// the pre-flip `Into` list collapse did. Registered under the to /// graphic identifier. -#[node_macro::node(category(""), extent(wrap_graphic_extent))] +#[node_macro::node(category(""), extent(into_group_extent))] pub fn to_graphic_typed<'e, T: Clone + Send + Sync + core_types::CacheHash + 'static>( _: impl Ctx, #[implementations(Vector, Raster, Raster, Color, Gradient, String)] content: IList, diff --git a/node-graph/nodes/gstd/src/render_cache.rs b/node-graph/nodes/gstd/src/render_cache.rs index 9e1e6a4586..b42c3703c9 100644 --- a/node-graph/nodes/gstd/src/render_cache.rs +++ b/node-graph/nodes/gstd/src/render_cache.rs @@ -360,7 +360,7 @@ pub fn render_output_cache( render_params.for_mask, render_params.thumbnail, render_params.aligned_strokes, - render_params.stroke_below, + render_params.override_paint_order, ctx.try_animation_time().unwrap_or(0.), ctx.try_real_time().unwrap_or(0.), ctx.try_pointer_position(), diff --git a/node-graph/nodes/gstd/src/text.rs b/node-graph/nodes/gstd/src/text.rs index dde3234420..59417091ec 100644 --- a/node-graph/nodes/gstd/src/text.rs +++ b/node-graph/nodes/gstd/src/text.rs @@ -1,5 +1,5 @@ use core_types::consts::{DEFAULT_FONT_SIZE, DEFAULT_LINE_HEIGHT}; -use core_types::list::List; +use core_types::list::{Item, List}; use core_types::{ATTR_FONT_SIZE, ATTR_LETTER_SPACING, ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, Ctx}; use graph_craft::application_io::resource::Resource; use graphic_types::Vector; @@ -15,15 +15,15 @@ fn text( /// The text content to be drawn. #[widget(ParsedWidgetOverride::Custom = "text_area")] #[default("Lorem ipsum")] - text: Item, + text: String, /// The loaded font file used to draw the text. The editor resolves the chosen typeface to these bytes via the resource system. #[widget(ParsedWidgetOverride::Custom = "text_font")] - font: Item, + font: Resource, /// The font size used to draw the text. #[unit(" px")] #[default(24.)] #[hard(1..)] - size: Item, + size: f64, /// The line height ratio, relative to the font size. Each line is drawn lower than its previous line by the distance of *Size* × *Line Height*. /// /// 0 means all lines overlap. 1 means all lines are spaced by just the font size. 1.2 is a common default for readable text. 2 means double-spaced text. @@ -31,41 +31,35 @@ fn text( #[hard(0..)] #[step(0.1)] #[default(1.2)] - line_height: Item, + line_height: f64, /// Additional spacing, in pixels, added between each character. #[unit(" px")] #[step(0.1)] - letter_spacing: Item, + letter_spacing: f64, /// The angle of faux italic slant applied to each glyph. #[unit("°")] #[hard(-85..85)] - letter_tilt: Item, + letter_tilt: f64, /// Enables the maximum width constraint so lines can wrap. #[widget(ParsedWidgetOverride::Hidden)] - has_max_width: Item, + has_max_width: bool, /// The maximum width that the text block can occupy before wrapping to a new line. Otherwise, lines do not wrap. #[unit(" px")] #[hard(1..)] #[widget(ParsedWidgetOverride::Custom = "optional_f64")] - max_width: Item, + max_width: f64, /// Whether the *Max Height* property is enabled so that lines beyond it are not drawn. #[widget(ParsedWidgetOverride::Hidden)] - has_max_height: Item, + has_max_height: bool, /// The maximum height that the text block can occupy. Excess lines are not drawn. #[unit(" px")] #[hard(1..)] #[widget(ParsedWidgetOverride::Custom = "optional_f64")] - max_height: Item, + max_height: f64, /// The horizontal alignment of each line of text within its surrounding box. To have an effect on a single line of text, *Max Width* must be set. #[widget(ParsedWidgetOverride::Custom = "text_align")] - align: Item, -) -> Item { - let text = text.into_element(); - let font = font.into_element(); - let (size, line_height, letter_spacing, letter_tilt) = (*size.element(), *line_height.element(), *letter_spacing.element(), *letter_tilt.element()); - let (has_max_width, max_width, has_max_height, max_height) = (*has_max_width.element(), *max_width.element(), *has_max_height.element(), *max_height.element()); - let align = align.into_element(); - + align: TextAlign, +) -> List { let mut item = Item::new_from_element(text); if font != Resource::default() { @@ -93,25 +87,27 @@ fn text( item.set_attribute(ATTR_TEXT_ALIGN, align); } - item + List::new_from_item(item) } /// Converts a styled text string into a vector compound path. #[node_macro::node(category("Text"), name("Text to Vector"))] fn text_to_vector( _: impl Ctx, - /// A styled text string produced by the **Text** node (or any other string source). - string: Item, -) -> Item { - shape_text_item(&string, false).into_iter().next().unwrap_or_default() + /// A styled list of text strings produced by the **Text** node (or any other `String[]` source). + #[implementations(List)] + strings: List, +) -> List { + shape_text_list(&strings, false) } /// Splits a styled text string into a separate vector item for each of its glyphs (letterforms). #[node_macro::node(category("Text"), name("Text to Vector Glyphs"))] fn text_to_vector_glyphs( _: impl Ctx, - /// A styled text string produced by the **Text** node (or any other string source). - string: Item, + /// A styled list of text strings produced by the **Text** node (or any other `String[]` source). + #[implementations(List)] + strings: List, ) -> List { - shape_text_item(&string, true) + shape_text_list(&strings, true) } diff --git a/node-graph/nodes/vector/src/vector_modification_nodes.rs b/node-graph/nodes/vector/src/vector_modification_nodes.rs index 9f3c501d10..60f80a90ba 100644 --- a/node-graph/nodes/vector/src/vector_modification_nodes.rs +++ b/node-graph/nodes/vector/src/vector_modification_nodes.rs @@ -45,10 +45,8 @@ fn path_modify<'e>( /// Bakes the content's transform attribute into its underlying value, resetting the attribute to the identity. #[node_macro::node(category("Vector"))] -fn bake_transform( - _ctx: impl Ctx, - #[implementations(Vector, DAffine2, DVec2)] (mut content, transform): (T, Attr), -) -> (T, Attr) { +// Monomorphic on Vector: our macro cannot yet read a record element through an open generic, so master's DAffine2 and DVec2 rows have no node here. +fn bake_transform(_ctx: impl Ctx, (mut content, transform): (Vector, Attr)) -> (Vector, Attr) { let transform: DAffine2 = *transform; content.bake_transform(&transform); diff --git a/node-graph/nodes/vector/src/vector_nodes.rs b/node-graph/nodes/vector/src/vector_nodes.rs index 37b4e69d29..1edd94711b 100644 --- a/node-graph/nodes/vector/src/vector_nodes.rs +++ b/node-graph/nodes/vector/src/vector_nodes.rs @@ -55,9 +55,21 @@ fn carried_lane_attrs<'e>(arena: &'e core_types::arena::Arena, lane: core_types: Ok((Attr(lane.attr::()), Attr(layer_path.as_slice()))) } +/// A gradient row's settings, which ride the lane rather than the bare element. +fn gradient_settings_from_lane(source: &core_types::node::List<'_, Gradient>, index: usize) -> GradientSettings { + let lane = source.lane(index); + GradientSettings { + spread: lane.attr::(), + cyclic: lane.attr::(), + space: lane.attr::(), + hue_direction: lane.attr::(), + interpolation: lane.attr::(), + } +} + /// The gradient color for one assign-colors position, replaying the /// randomized draws up to it. -fn assign_color_at(gradient: &Gradient, position: usize, length: usize, randomize: bool, seed: SeedValue, repeat_every: u32) -> Color { +fn assign_color_at(gradient: &Gradient, settings: GradientSettings, position: usize, length: usize, randomize: bool, seed: SeedValue, repeat_every: u32) -> Color { let factor = match randomize { true => { let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); @@ -74,10 +86,7 @@ fn assign_color_at(gradient: &Gradient, position: usize, length: usize, randomiz }, }; // The factor spans 0..=1, so the spread stays Pad rather than wrapping the last element onto the first stop - let settings = GradientSettings { - spread: Default::default(), - ..GradientSettings::from(gradient) - }; + let settings = GradientSettings { spread: Default::default(), ..settings }; gradient.evaluate(factor, settings) } @@ -157,16 +166,17 @@ fn assign_colors<'e>( return Ok((element, transform, Attr(existing_fill), Attr(existing_stroke), layer_path)); } let gradient_element = gradient.element_ref(0); + let gradient_settings = gradient_settings_from_lane(&gradient, 0); let reversed; let gradient_element = match reverse { true => { - reversed = gradient_element.reversed(GradientSettings::from(gradient_element).cyclic); + reversed = gradient_element.reversed(gradient_settings.cyclic); &reversed } false => gradient_element, }; - let color = assign_color_at(gradient_element, lane, content.len(), randomize, seed, repeat_every); + let color = assign_color_at(gradient_element, gradient_settings, lane, content.len(), randomize, seed, repeat_every); let paint = List::new_from_element(color).into_graphic_list(); let parked = park_paint(ctx.arena(), paint)?; @@ -226,10 +236,11 @@ fn assign_colors_graphic<'e>( return Ok((original.clone(), transform, layer_path)); } let gradient_element = gradient.element_ref(0); + let gradient_settings = gradient_settings_from_lane(&gradient, 0); let reversed; let gradient_element = match reverse { true => { - reversed = gradient_element.reversed(GradientSettings::from(gradient_element).cyclic); + reversed = gradient_element.reversed(gradient_settings.cyclic); &reversed } false => gradient_element, @@ -272,7 +283,7 @@ fn assign_colors_graphic<'e>( Some(mut rows) => { for row in 0..rows.len() { let has_stroke = lane_has_stroke || has_paint::(&rows, row); - let color = assign_color_at(gradient_element, position + row, length, randomize, seed, repeat_every); + let color = assign_color_at(gradient_element, gradient_settings, position + row, length, randomize, seed, repeat_every); let paint = List::new_from_element(color).into_graphic_list(); if fill { set_paint_attribute_at(&mut rows, row, ATTR_FILL, paint.clone()); @@ -2858,13 +2869,13 @@ fn morph_core(flattened: List, snapshot: List>, progres (Some(Graphic::Color(color_a)), Some(Graphic::Color(color_b))) => Some(List::new_from_element(Graphic::from(color_a.lerp(color_b, time as f32)))), (Some(Graphic::Color(color_a)), Some(Graphic::Gradient(stops_b))) => { let mut solid_to_gradient = stops_b.clone(); - solid_to_gradient.color.iter_mut().for_each(|color| *color = *color_a); + solid_to_gradient.0.iter_element_values_mut().for_each(|color| *color = *color_a); let stops = solid_to_gradient.lerp(stops_b, time); Some(gradient_paint(b, stops, None)) } (Some(Graphic::Gradient(stops_a)), Some(Graphic::Color(color_b))) => { let mut gradient_to_solid = stops_a.clone(); - gradient_to_solid.color.iter_mut().for_each(|color| *color = *color_b); + gradient_to_solid.0.iter_element_values_mut().for_each(|color| *color = *color_b); let stops = stops_a.lerp(&gradient_to_solid, time); Some(gradient_paint(a, stops, None)) }