diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index bfd3c2607be..fb5b184fb16 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -618,21 +618,13 @@ impl TaggedValue { } fn to_color(input: &str) -> Option { - // String syntax (e.g. "000000ff") - if input.starts_with('"') && input.ends_with('"') { - let hex = input.trim().trim_matches('"').trim().trim_start_matches('#'); - let color = SRGBA8::from_hex_str(hex).map(Color::from); - if color.is_none() { - log::error!("Invalid default value color string: {input}"); - } - return color; - } - // Color constant syntax (e.g. Color::BLACK) - let mut choices = input.split("::"); - let (first, second) = (choices.next()?.trim(), choices.next()?.trim()); - if first == "Color" { - return Some(match second { + if let Some((first, second)) = input.split_once("::") { + if first.trim() != "Color" { + log::error!("Invalid default value color: {input}"); + return None; + } + return Some(match second.trim() { "BLACK" => Color::BLACK, "WHITE" => Color::WHITE, "RED" => Color::RED, @@ -649,8 +641,13 @@ impl TaggedValue { }); } - log::error!("Invalid default value color: {input}"); - None + // Hex syntax (e.g. "000000ff"), which a string literal default reaches here without its quotes + let hex = input.trim().trim_matches('"').trim().trim_start_matches('#'); + let color = SRGBA8::from_hex_str(hex).map(Color::from); + if color.is_none() { + log::error!("Invalid default value color string: {input}"); + } + color } fn to_gradient(input: &str) -> Option { @@ -1039,6 +1036,18 @@ mod paint_default_parsing { ); } + /// A hex string default reaches the parser without the quotes its literal had in the node signature, and must still parse. + #[test] + fn hex_string_color_default_parses_without_quotes() { + let tint = Some(TaggedValue::Color(Color::from(SRGBA8::new(225, 211, 179, 255)))); + assert_eq!(TaggedValue::from_primitive_string("e1d3b3", &item!(Color)), tint, "a bare hex default should resolve"); + assert_eq!( + TaggedValue::from_primitive_string("\"#e1d3b3\"", &item!(Color)), + tint, + "a quoted, hash-prefixed hex default should resolve" + ); + } + /// Table-era documents stored the red-slash "no paint" fill as an empty color table, which must keep /// deserializing to [`TaggedValue::no_paint`] rather than collapsing to a transparent color. #[test] diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index aacec0d8006..6485d058c05 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -228,8 +228,9 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn quote!(Some(concrete!(#implementation_ty))) } } - // A concrete ranked `Item` param's scalar `#[default]` parses as a bare `T` literal (unranked, promoted at resolution); - // without one it keeps the structural `Type::Item` wire type with the element's alias on its descriptor (so the rank-0 Properties widget still dispatches, e.g. `Progression`), and `node_inputs` peels to `T` if no `Item` type default exists + // A concrete ranked `Item` param's scalar `#[default]` parses as a bare `T` literal (unranked, promoted at resolution); without one it keeps + // the structural `Type::Item` wire type, and `node_inputs` peels to `T` if no `Item` type default exists. Either way the element's alias stays + // on its descriptor so the rank-0 Properties widget still dispatches, e.g. `Progression`. None => match &field.ty { ParsedFieldType::Item { field: RegularParsedField { value_source, .. }, @@ -241,7 +242,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn // The fn's lifetimes are elided since the metadata registration fn declares none of them let element = substitute_lifetimes(element.clone(), "_"); match value_source { - ParsedValueSource::Default(_) => quote!(Some(concrete!(#element))), + ParsedValueSource::Default(_) => quote!(Some(concrete!(#element, #element))), _ => quote!(Some(#core_types::item!(#element, #element))), } }