mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-12-23 10:11:54 +00:00
Polish user-created subgraph nodes: imports in the Properties panel; reorder/delete/rename imports/exports (#2105)
* Remove imports/exports * WIP: Autogenerated properties * WIP: Input based properties * WIP: Hashmap based input overrides * Migrate noise pattern node to input properties * Reorder exports * Continue migrating properties * WIP: Improve reorder exports * Automatically populate all input properties for sub networks * Complete reorder import and export * Add widget override to node macro * Migrate assign colors to input based properties * WIP: Full node property override * Node based properties override for proto nodes * Migrate all node properties to be input based * Rename imports/exports * improve UI * Protonode input valid implementations * Valid type list * Small formatting fixes * Polishing small issues * Document upgrade * fix tests * Upgrade noise pattern node * remove console log * Fix upgrade script for Noise Pattern * Improve the Properties panel representation for graphical data * Re-export demo art * Code review * code review improvements * Cleanup for node properties overrides * Reexport demo art * Fix clippy lints --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
ad68b1e5c8
commit
eec0ef761c
38 changed files with 3660 additions and 2006 deletions
|
|
@ -37,7 +37,7 @@ impl ValueProvider for MathNodeContext {
|
|||
}
|
||||
|
||||
/// Calculates a mathematical expression with input values "A" and "B"
|
||||
#[node_macro::node(category("Math"))]
|
||||
#[node_macro::node(category("General"), properties("math_properties"))]
|
||||
fn math<U: num_traits::float::Float>(
|
||||
_: (),
|
||||
/// The value of "A" when calculating the expression
|
||||
|
|
|
|||
|
|
@ -1185,7 +1185,7 @@ impl DomainWarpType {
|
|||
// Aims for interoperable compatibility with:
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27mixr%27%20%3D%20Channel%20Mixer
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
#[node_macro::node(category("Raster: Adjustment"), properties("channel_mixer_properties"))]
|
||||
async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
|
||||
#[implementations(
|
||||
(),
|
||||
|
|
@ -1555,7 +1555,7 @@ async fn posterize<F: 'n + Send, T: Adjust<Color>>(
|
|||
//
|
||||
// Algorithm based on:
|
||||
// https://geraldbakker.nl/psnumbers/exposure.html
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
#[node_macro::node(category("Raster: Adjustment"), properties("exposure_properties"))]
|
||||
async fn exposure<F: 'n + Send, T: Adjust<Color>>(
|
||||
#[implementations(
|
||||
(),
|
||||
|
|
@ -1683,7 +1683,13 @@ mod index_node {
|
|||
use crate::raster::{Color, ImageFrame};
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
pub fn index<T: Default + Clone>(_: (), #[implementations(Vec<ImageFrame<Color>>, Vec<Color>)] input: Vec<T>, index: u32) -> T {
|
||||
pub fn index<T: Default + Clone>(
|
||||
_: (),
|
||||
#[implementations(Vec<ImageFrame<Color>>, Vec<Color>)]
|
||||
#[widget(ParsedWidgetOverride::Hidden)]
|
||||
input: Vec<T>,
|
||||
index: u32,
|
||||
) -> T {
|
||||
if (index as usize) < input.len() {
|
||||
input[index as usize].clone()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -29,27 +29,39 @@ pub mod types {
|
|||
pub type Resolution = glam::UVec2;
|
||||
}
|
||||
|
||||
// Translation struct between macro and definition
|
||||
#[derive(Clone)]
|
||||
pub struct NodeMetadata {
|
||||
pub display_name: &'static str,
|
||||
pub category: Option<&'static str>,
|
||||
pub fields: Vec<FieldMetadata>,
|
||||
pub description: &'static str,
|
||||
pub properties: Option<&'static str>,
|
||||
}
|
||||
|
||||
// Translation struct between macro and definition
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FieldMetadata {
|
||||
pub name: &'static str,
|
||||
pub description: &'static str,
|
||||
pub exposed: bool,
|
||||
pub value_source: ValueSource,
|
||||
pub widget_override: RegistryWidgetOverride,
|
||||
pub value_source: RegistryValueSource,
|
||||
pub number_min: Option<f64>,
|
||||
pub number_max: Option<f64>,
|
||||
pub number_mode_range: Option<(f64, f64)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ValueSource {
|
||||
pub enum RegistryWidgetOverride {
|
||||
None,
|
||||
Hidden,
|
||||
String(&'static str),
|
||||
Custom(&'static str),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RegistryValueSource {
|
||||
None,
|
||||
Default(&'static str),
|
||||
Scope(&'static str),
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ fn ellipse<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _prima
|
|||
ellipse
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
#[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))]
|
||||
fn rectangle<F: 'n + Send, T: CornerRadius>(
|
||||
#[implementations((), Footprint)] _footprint: F,
|
||||
_primary: (),
|
||||
|
|
|
|||
|
|
@ -50,14 +50,15 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
|
|||
Footprint -> GraphicGroup,
|
||||
Footprint -> VectorData,
|
||||
)]
|
||||
#[widget(ParsedWidgetOverride::Hidden)]
|
||||
vector_group: impl Node<F, Output = T>,
|
||||
#[default(true)] fill: bool,
|
||||
stroke: bool,
|
||||
gradient: GradientStops,
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")] gradient: GradientStops,
|
||||
reverse: bool,
|
||||
randomize: bool,
|
||||
seed: SeedValue,
|
||||
repeat_every: u32,
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_randomize")] randomize: bool,
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] seed: SeedValue,
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] repeat_every: u32,
|
||||
) -> T {
|
||||
let mut input = vector_group.eval(footprint).await;
|
||||
let length = input.vector_iter_mut().count();
|
||||
|
|
@ -89,7 +90,7 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
|
|||
input
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
|
||||
async fn fill<F: 'n + Send, FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
|
|
@ -161,7 +162,7 @@ async fn fill<F: 'n + Send, FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIter
|
|||
target
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))]
|
||||
async fn stroke<F: 'n + Send, ColorTy: Into<Option<Color>> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
|
|
@ -430,7 +431,7 @@ async fn bounding_box<F: 'n + Send>(
|
|||
result
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))]
|
||||
async fn offset_path<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
|
|
|
|||
|
|
@ -762,7 +762,7 @@ pub struct NodeNetwork {
|
|||
/// Each export is a reference to a node within this network, paired with its output index, that is the source of the network's exported data.
|
||||
#[cfg_attr(feature = "serde", serde(alias = "outputs", deserialize_with = "deserialize_exports"))] // TODO: Eventually remove this alias document upgrade code
|
||||
pub exports: Vec<NodeInput>,
|
||||
/// TODO: Instead of storing import types in each NodeInput::Network connection, the types are stored here. This is similar to how types need to be defined for parameters when creating a function in Rust.
|
||||
// TODO: Instead of storing import types in each NodeInput::Network connection, the types are stored here. This is similar to how types need to be defined for parameters when creating a function in Rust.
|
||||
// pub import_types: Vec<Type>,
|
||||
/// The list of all nodes in this network.
|
||||
#[cfg_attr(
|
||||
|
|
|
|||
|
|
@ -430,24 +430,11 @@ generate_imaginate_node! {
|
|||
tiling: Tiling: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ImageFrameNode<P, Transform> {
|
||||
transform: Transform,
|
||||
_p: PhantomData<P>,
|
||||
}
|
||||
#[node_macro::old_node_fn(ImageFrameNode<_P>)]
|
||||
fn image_frame<_P: Pixel>(image: Image<_P>, transform: DAffine2) -> ImageFrame<_P> {
|
||||
ImageFrame {
|
||||
image,
|
||||
transform,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Generator"))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn noise_pattern(
|
||||
footprint: Footprint,
|
||||
_primary: (),
|
||||
clip: bool,
|
||||
seed: u32,
|
||||
scale: f64,
|
||||
|
|
|
|||
|
|
@ -55,12 +55,22 @@ async fn draw_image_frame(_: (), image: ImageFrame<graphene_core::raster::SRGBA8
|
|||
|
||||
#[node_macro::node(category("Network"))]
|
||||
async fn load_resource<'a: 'n>(_: (), _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, url: String) -> Arc<[u8]> {
|
||||
editor.application_io.as_ref().unwrap().load_resource(url).unwrap().await.unwrap()
|
||||
let Some(api) = editor.application_io.as_ref() else {
|
||||
return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
|
||||
};
|
||||
let Ok(data) = api.load_resource(url) else {
|
||||
return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
|
||||
};
|
||||
let Ok(data) = data.await else {
|
||||
return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
|
||||
};
|
||||
|
||||
data
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
fn decode_image(_: (), data: Arc<[u8]>) -> ImageFrame<Color> {
|
||||
let image = image::load_from_memory(data.as_ref()).expect("Failed to decode image");
|
||||
let Some(image) = image::load_from_memory(data.as_ref()).ok() else { return ImageFrame::default() };
|
||||
let image = image.to_rgba32f();
|
||||
let image = ImageFrame {
|
||||
image: Image {
|
||||
|
|
|
|||
|
|
@ -361,7 +361,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => Option<WgpuSurface>]),
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => TextureFrame]),
|
||||
register_node!(graphene_core::structural::ConsNode<_, _>, input: Image<Color>, params: [&str]),
|
||||
register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image<Color>, params: [DAffine2]),
|
||||
];
|
||||
let mut map: HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
|
||||
for (id, entry) in graphene_core::registry::NODE_REGISTRY.lock().unwrap().iter() {
|
||||
|
|
|
|||
|
|
@ -84,21 +84,36 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
|||
ParsedField::Regular { ty, .. } => ty.clone(),
|
||||
ParsedField::Node { output_type, input_type, .. } => match parsed.is_async {
|
||||
true => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output: core::future::Future<Output=#output_type> + #graphene_core::WasmNotSend>),
|
||||
|
||||
false => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = #output_type>),
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
let widget_override: Vec<_> = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let parsed_widget_override = match field {
|
||||
ParsedField::Regular { widget_override, .. } => widget_override,
|
||||
ParsedField::Node { widget_override, .. } => widget_override,
|
||||
};
|
||||
match parsed_widget_override {
|
||||
ParsedWidgetOverride::None => quote!(RegistryWidgetOverride::None),
|
||||
ParsedWidgetOverride::Hidden => quote!(RegistryWidgetOverride::Hidden),
|
||||
ParsedWidgetOverride::String(lit_str) => quote!(RegistryWidgetOverride::String(#lit_str)),
|
||||
ParsedWidgetOverride::Custom(lit_str) => quote!(RegistryWidgetOverride::Custom(#lit_str)),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let value_sources: Vec<_> = fields
|
||||
.iter()
|
||||
.map(|field| match field {
|
||||
ParsedField::Regular { value_source, .. } => match value_source {
|
||||
ValueSource::Default(data) => quote!(ValueSource::Default(stringify!(#data))),
|
||||
ValueSource::Scope(data) => quote!(ValueSource::Scope(#data)),
|
||||
_ => quote!(ValueSource::None),
|
||||
ParsedValueSource::Default(data) => quote!(RegistryValueSource::Default(stringify!(#data))),
|
||||
ParsedValueSource::Scope(data) => quote!(RegistryValueSource::Scope(#data)),
|
||||
_ => quote!(RegistryValueSource::None),
|
||||
},
|
||||
_ => quote!(ValueSource::None),
|
||||
_ => quote!(RegistryValueSource::None),
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
|
@ -213,6 +228,8 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
|||
let register_node_impl = generate_register_node_impl(parsed, &field_names, &struct_name, &identifier)?;
|
||||
let import_name = format_ident!("_IMPORT_STUB_{}", mod_name.to_string().to_case(Case::UpperSnake));
|
||||
|
||||
let properties = &attributes.properties_string.as_ref().map(|value| quote!(Some(#value))).unwrap_or(quote!(None));
|
||||
|
||||
Ok(quote! {
|
||||
/// Underlying implementation for [#struct_name]
|
||||
#[inline]
|
||||
|
|
@ -235,7 +252,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
|||
use gcore::{Node, NodeIOTypes, concrete, fn_type, future, ProtoNodeIdentifier, WasmNotSync, NodeIO};
|
||||
use gcore::value::ClonedNode;
|
||||
use gcore::ops::TypeNode;
|
||||
use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, ValueSource};
|
||||
use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, RegistryValueSource, RegistryWidgetOverride};
|
||||
use gcore::ctor::ctor;
|
||||
|
||||
// Use the types specified in the implementation
|
||||
|
|
@ -266,10 +283,12 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
|||
display_name: #display_name,
|
||||
category: #category,
|
||||
description: #description,
|
||||
properties: #properties,
|
||||
fields: vec![
|
||||
#(
|
||||
FieldMetadata {
|
||||
name: #input_names,
|
||||
widget_override: #widget_override,
|
||||
description: #input_descriptions,
|
||||
exposed: #exposed,
|
||||
value_source: #value_sources,
|
||||
|
|
|
|||
|
|
@ -39,26 +39,69 @@ pub(crate) struct NodeFnAttributes {
|
|||
pub(crate) display_name: Option<LitStr>,
|
||||
pub(crate) path: Option<Path>,
|
||||
pub(crate) skip_impl: bool,
|
||||
pub(crate) properties_string: Option<LitStr>,
|
||||
// Add more attributes as needed
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub enum ValueSource {
|
||||
pub enum ParsedValueSource {
|
||||
#[default]
|
||||
None,
|
||||
Default(TokenStream2),
|
||||
Scope(LitStr),
|
||||
}
|
||||
|
||||
// #[widget(ParsedWidgetOverride::Hidden)]
|
||||
// #[widget(ParsedWidgetOverride::String = "Some string")]
|
||||
// #[widget(ParsedWidgetOverride::Custom = "Custom string")]
|
||||
#[derive(Debug, Default)]
|
||||
pub enum ParsedWidgetOverride {
|
||||
#[default]
|
||||
None,
|
||||
Hidden,
|
||||
String(LitStr),
|
||||
Custom(LitStr),
|
||||
}
|
||||
|
||||
impl Parse for ParsedWidgetOverride {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
// Parse the full path (e.g., ParsedWidgetOverride::Hidden)
|
||||
let path: Path = input.parse()?;
|
||||
|
||||
// Ensure the path starts with `ParsedWidgetOverride`
|
||||
if path.segments.len() == 2 && path.segments[0].ident == "ParsedWidgetOverride" {
|
||||
let variant = &path.segments[1].ident;
|
||||
|
||||
match variant.to_string().as_str() {
|
||||
"Hidden" => Ok(ParsedWidgetOverride::Hidden),
|
||||
"String" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
let lit: LitStr = input.parse()?;
|
||||
Ok(ParsedWidgetOverride::String(lit))
|
||||
}
|
||||
"Custom" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
let lit: LitStr = input.parse()?;
|
||||
Ok(ParsedWidgetOverride::Custom(lit))
|
||||
}
|
||||
_ => Err(syn::Error::new(variant.span(), "Unknown ParsedWidgetOverride variant")),
|
||||
}
|
||||
} else {
|
||||
Err(syn::Error::new(input.span(), "Expected ParsedWidgetOverride::<variant>"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ParsedField {
|
||||
Regular {
|
||||
pat_ident: PatIdent,
|
||||
name: Option<LitStr>,
|
||||
description: String,
|
||||
widget_override: ParsedWidgetOverride,
|
||||
ty: Type,
|
||||
exposed: bool,
|
||||
value_source: ValueSource,
|
||||
value_source: ParsedValueSource,
|
||||
number_min: Option<LitFloat>,
|
||||
number_max: Option<LitFloat>,
|
||||
number_mode_range: Option<ExprTuple>,
|
||||
|
|
@ -68,6 +111,7 @@ pub(crate) enum ParsedField {
|
|||
pat_ident: PatIdent,
|
||||
name: Option<LitStr>,
|
||||
description: String,
|
||||
widget_override: ParsedWidgetOverride,
|
||||
input_type: Type,
|
||||
output_type: Type,
|
||||
implementations: Punctuated<Implementation, Comma>,
|
||||
|
|
@ -126,6 +170,7 @@ impl Parse for NodeFnAttributes {
|
|||
let mut display_name = None;
|
||||
let mut path = None;
|
||||
let mut skip_impl = false;
|
||||
let mut properties_string = None;
|
||||
|
||||
let content = input;
|
||||
// let content;
|
||||
|
|
@ -165,6 +210,16 @@ impl Parse for NodeFnAttributes {
|
|||
}
|
||||
skip_impl = true;
|
||||
}
|
||||
Meta::List(meta) if meta.path.is_ident("properties") => {
|
||||
if properties_string.is_some() {
|
||||
return Err(Error::new_spanned(path, "Multiple 'properties_string' attributes are not allowed"));
|
||||
}
|
||||
let parsed_properties_string: LitStr = meta
|
||||
.parse_args()
|
||||
.map_err(|_| Error::new_spanned(meta, "Expected a string for 'properties', e.g., name(\"channel_mixer_properties\")"))?;
|
||||
|
||||
properties_string = Some(parsed_properties_string);
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::new_spanned(
|
||||
meta,
|
||||
|
|
@ -187,6 +242,7 @@ impl Parse for NodeFnAttributes {
|
|||
display_name,
|
||||
path,
|
||||
skip_impl,
|
||||
properties_string,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -343,13 +399,21 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
|||
.map(|attr| attr.parse_args().map_err(|e| Error::new_spanned(attr, format!("Invalid `name` value for argument '{}': {}", ident, e))))
|
||||
.transpose()?;
|
||||
|
||||
let widget_override = extract_attribute(attrs, "widget")
|
||||
.map(|attr| {
|
||||
attr.parse_args()
|
||||
.map_err(|e| Error::new_spanned(attr, format!("Invalid `widget override` value for argument '{}': {}", ident, e)))
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
let exposed = extract_attribute(attrs, "expose").is_some();
|
||||
|
||||
let value_source = match (default_value, scope) {
|
||||
(Some(_), Some(_)) => return Err(Error::new_spanned(&pat_ident, "Cannot have both `default` and `scope` attributes")),
|
||||
(Some(default_value), _) => ValueSource::Default(default_value),
|
||||
(_, Some(scope)) => ValueSource::Scope(scope),
|
||||
_ => ValueSource::None,
|
||||
(Some(default_value), _) => ParsedValueSource::Default(default_value),
|
||||
(_, Some(scope)) => ParsedValueSource::Scope(scope),
|
||||
_ => ParsedValueSource::None,
|
||||
};
|
||||
|
||||
let number_min = extract_attribute(attrs, "min")
|
||||
|
|
@ -405,7 +469,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
|||
let (input_type, output_type) = node_input_type
|
||||
.zip(node_output_type)
|
||||
.ok_or_else(|| Error::new_spanned(&ty, "Invalid Node type. Expected `impl Node<Input, Output = OutputType>`"))?;
|
||||
if !matches!(&value_source, ValueSource::None) {
|
||||
if !matches!(&value_source, ParsedValueSource::None) {
|
||||
return Err(Error::new_spanned(&ty, "No default values for `impl Node` allowed"));
|
||||
}
|
||||
let implementations = extract_attribute(attrs, "implementations")
|
||||
|
|
@ -417,6 +481,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
|||
pat_ident,
|
||||
name,
|
||||
description,
|
||||
widget_override,
|
||||
input_type,
|
||||
output_type,
|
||||
implementations,
|
||||
|
|
@ -430,6 +495,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
|||
pat_ident,
|
||||
name,
|
||||
description,
|
||||
widget_override,
|
||||
exposed,
|
||||
number_min,
|
||||
number_max,
|
||||
|
|
@ -550,11 +616,11 @@ mod tests {
|
|||
assert_eq!(p_name, e_name);
|
||||
assert_eq!(p_exp, e_exp);
|
||||
match (p_default, e_default) {
|
||||
(ValueSource::None, ValueSource::None) => {}
|
||||
(ValueSource::Default(p), ValueSource::Default(e)) => {
|
||||
(ParsedValueSource::None, ParsedValueSource::None) => {}
|
||||
(ParsedValueSource::Default(p), ParsedValueSource::Default(e)) => {
|
||||
assert_eq!(p.to_token_stream().to_string(), e.to_token_stream().to_string());
|
||||
}
|
||||
(ValueSource::Scope(p), ValueSource::Scope(e)) => {
|
||||
(ParsedValueSource::Scope(p), ParsedValueSource::Scope(e)) => {
|
||||
assert_eq!(p.value(), e.value());
|
||||
}
|
||||
_ => panic!("Mismatched default values"),
|
||||
|
|
@ -602,6 +668,7 @@ mod tests {
|
|||
display_name: None,
|
||||
path: Some(parse_quote!(graphene_core::TestNode)),
|
||||
skip_impl: true,
|
||||
properties_string: None,
|
||||
},
|
||||
fn_name: Ident::new("add", Span::call_site()),
|
||||
struct_name: Ident::new("Add", Span::call_site()),
|
||||
|
|
@ -619,9 +686,10 @@ mod tests {
|
|||
pat_ident: pat_ident("b"),
|
||||
name: None,
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: parse_quote!(f64),
|
||||
exposed: false,
|
||||
value_source: ValueSource::None,
|
||||
value_source: ParsedValueSource::None,
|
||||
number_min: None,
|
||||
number_max: None,
|
||||
number_mode_range: None,
|
||||
|
|
@ -655,6 +723,7 @@ mod tests {
|
|||
display_name: None,
|
||||
path: None,
|
||||
skip_impl: false,
|
||||
properties_string: None,
|
||||
},
|
||||
fn_name: Ident::new("transform", Span::call_site()),
|
||||
struct_name: Ident::new("Transform", Span::call_site()),
|
||||
|
|
@ -673,6 +742,7 @@ mod tests {
|
|||
pat_ident: pat_ident("transform_target"),
|
||||
name: None,
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
input_type: parse_quote!(Footprint),
|
||||
output_type: parse_quote!(T),
|
||||
implementations: Punctuated::new(),
|
||||
|
|
@ -681,9 +751,10 @@ mod tests {
|
|||
pat_ident: pat_ident("translate"),
|
||||
name: None,
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: parse_quote!(DVec2),
|
||||
exposed: false,
|
||||
value_source: ValueSource::None,
|
||||
value_source: ParsedValueSource::None,
|
||||
number_min: None,
|
||||
number_max: None,
|
||||
number_mode_range: None,
|
||||
|
|
@ -715,6 +786,7 @@ mod tests {
|
|||
display_name: None,
|
||||
path: None,
|
||||
skip_impl: false,
|
||||
properties_string: None,
|
||||
},
|
||||
fn_name: Ident::new("circle", Span::call_site()),
|
||||
struct_name: Ident::new("Circle", Span::call_site()),
|
||||
|
|
@ -732,9 +804,10 @@ mod tests {
|
|||
pat_ident: pat_ident("radius"),
|
||||
name: None,
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: parse_quote!(f64),
|
||||
exposed: false,
|
||||
value_source: ValueSource::Default(quote!(50.)),
|
||||
value_source: ParsedValueSource::Default(quote!(50.)),
|
||||
number_min: None,
|
||||
number_max: None,
|
||||
number_mode_range: None,
|
||||
|
|
@ -764,6 +837,7 @@ mod tests {
|
|||
display_name: None,
|
||||
path: None,
|
||||
skip_impl: false,
|
||||
properties_string: None,
|
||||
},
|
||||
fn_name: Ident::new("levels", Span::call_site()),
|
||||
struct_name: Ident::new("Levels", Span::call_site()),
|
||||
|
|
@ -781,9 +855,10 @@ mod tests {
|
|||
pat_ident: pat_ident("shadows"),
|
||||
name: None,
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: parse_quote!(f64),
|
||||
exposed: false,
|
||||
value_source: ValueSource::None,
|
||||
value_source: ParsedValueSource::None,
|
||||
number_min: None,
|
||||
number_max: None,
|
||||
number_mode_range: None,
|
||||
|
|
@ -825,6 +900,7 @@ mod tests {
|
|||
display_name: None,
|
||||
path: Some(parse_quote!(graphene_core::TestNode)),
|
||||
skip_impl: false,
|
||||
properties_string: None,
|
||||
},
|
||||
fn_name: Ident::new("add", Span::call_site()),
|
||||
struct_name: Ident::new("Add", Span::call_site()),
|
||||
|
|
@ -842,9 +918,10 @@ mod tests {
|
|||
pat_ident: pat_ident("b"),
|
||||
name: None,
|
||||
description: String::from("b"),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: parse_quote!(f64),
|
||||
exposed: false,
|
||||
value_source: ValueSource::None,
|
||||
value_source: ParsedValueSource::None,
|
||||
number_min: Some(parse_quote!(-500.)),
|
||||
number_max: Some(parse_quote!(500.)),
|
||||
number_mode_range: Some(parse_quote!((0., 100.))),
|
||||
|
|
@ -874,6 +951,7 @@ mod tests {
|
|||
display_name: None,
|
||||
path: None,
|
||||
skip_impl: false,
|
||||
properties_string: None,
|
||||
},
|
||||
fn_name: Ident::new("load_image", Span::call_site()),
|
||||
struct_name: Ident::new("LoadImage", Span::call_site()),
|
||||
|
|
@ -892,8 +970,9 @@ mod tests {
|
|||
name: None,
|
||||
ty: parse_quote!(String),
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
exposed: true,
|
||||
value_source: ValueSource::None,
|
||||
value_source: ParsedValueSource::None,
|
||||
number_min: None,
|
||||
number_max: None,
|
||||
number_mode_range: None,
|
||||
|
|
@ -923,6 +1002,7 @@ mod tests {
|
|||
display_name: Some(parse_quote!("CustomNode2")),
|
||||
path: None,
|
||||
skip_impl: false,
|
||||
properties_string: None,
|
||||
},
|
||||
fn_name: Ident::new("custom_node", Span::call_site()),
|
||||
struct_name: Ident::new("CustomNode", Span::call_site()),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue