From 1c9c19a69785bb16fd7487194217f16c0b592495 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 14 May 2026 00:56:43 -0700 Subject: [PATCH] Simplify and standardize how data types are presented in user-facing strings (#4147) * User-facing type formatting * Parse unicode not ASCII chars --- .../document/node_graph/node_properties.rs | 2 +- .../network_interface/resolved_types.rs | 12 +-- frontend/src/components/views/Graph.svelte | 22 +++-- node-graph/libraries/core-types/src/types.rs | 80 ++++++++++++++++--- 4 files changed, 84 insertions(+), 32 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 4e2db76d..1e6383c9 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -276,7 +276,7 @@ pub(crate) fn property_from_type( widgets.extend_from_slice(&[ Separator::new(SeparatorStyle::Unrelated).widget_instance(), TextLabel::new("-") - .tooltip_label(format!("Data Type: {concrete_type}")) + .tooltip_label(concrete_type.to_string()) .tooltip_description("This data can only be supplied through the node graph because no widget exists for its type.") .widget_instance(), ]); diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs index 24df7334..685d32e5 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs @@ -94,8 +94,8 @@ impl TypeSource { /// The type to display in the tooltip label. pub fn resolved_type_tooltip_string(&self) -> String { match self { - TypeSource::Compiled(compiled_type) => format!("Data Type: {}", compiled_type.nested_type()), - TypeSource::TaggedValue(value_type) => format!("Data Type: {}", value_type.nested_type()), + TypeSource::Compiled(compiled_type) => compiled_type.nested_type().to_string(), + TypeSource::TaggedValue(value_type) => value_type.nested_type().to_string(), TypeSource::Unknown => "Unknown Data Type".to_string(), TypeSource::Invalid => "Invalid Type Combination".to_string(), TypeSource::Error(_) => "Error Getting Data Type".to_string(), @@ -253,8 +253,8 @@ impl NodeNetworkInterface { }; let number_of_inputs = self.number_of_inputs(node_id, network_path); implementations - .iter() - .filter_map(|(node_io, _)| { + .keys() + .filter_map(|node_io| { // Check if this NodeIOTypes implementation is valid for the other inputs let valid_implementation = (0..number_of_inputs).filter(|iterator_index| iterator_index != input_index).all(|iterator_index| { let input_type = self.input_type_not_invalid(&InputConnector::node(*node_id, iterator_index), network_path); @@ -293,8 +293,8 @@ impl NodeNetworkInterface { let valid_output_types = self.valid_output_types(&OutputConnector::node(*node_id, 0), network_path); implementations - .iter() - .filter_map(|(node_io, _)| { + .keys() + .filter_map(|node_io| { if !valid_output_types.iter().any(|output_type| output_type.nested_type() == node_io.return_value.nested_type()) { return None; } diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index c34d06a0..7d84645c 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -188,10 +188,6 @@ return `M-2,-2 L${nodeWidth + 2},-2 L${nodeWidth + 2},${nodeHeight + 2} L-2,${nodeHeight + 2}z ${rectangles.join(" ")}`; } - function dataTypeTooltipLabel(value: FrontendGraphInput | FrontendGraphOutput): string { - return `Data Type: ${value.resolvedType}`; - } - function nodeNameTooltipLabel(node: FrontendNode): string { return node.displayName === node.implementationName ? node.displayName : `${node.displayName} (${node.implementationName})`; } @@ -349,7 +345,7 @@ viewBox="0 0 8 8" class="connector" data-connector="output" - data-tooltip-label={dataTypeTooltipLabel(frontendOutput)} + data-tooltip-label={frontendOutput.resolvedType} data-tooltip-description={outputConnectedToText(frontendOutput)} data-datatype={frontendOutput.dataType} style:--data-color={`var(--color-data-${frontendOutput.dataType.toLowerCase()})`} @@ -418,7 +414,7 @@ viewBox="0 0 8 8" class="connector" data-connector="input" - data-tooltip-label={dataTypeTooltipLabel(frontendInput)} + data-tooltip-label={frontendInput.resolvedType} data-tooltip-description={inputConnectedToText(frontendInput)} data-datatype={frontendInput.dataType} style:--data-color={`var(--color-data-${frontendInput.dataType.toLowerCase()})`} @@ -556,7 +552,7 @@ viewBox="0 0 8 12" class="connector top" data-connector="output" - data-tooltip-label={dataTypeTooltipLabel(node.primaryOutput)} + data-tooltip-label={node.primaryOutput.resolvedType} data-tooltip-description={outputConnectedToText(node.primaryOutput)} data-datatype={node.primaryOutput.dataType} style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`} @@ -578,7 +574,7 @@ viewBox="0 0 8 12" class="connector bottom" data-connector="input" - data-tooltip-label={node.primaryInput ? dataTypeTooltipLabel(node.primaryInput) : ""} + data-tooltip-label={node.primaryInput ? node.primaryInput.resolvedType : ""} data-tooltip-description={node.primaryInput ? `${validTypesText(node.primaryInput).trim()}\n\n${inputConnectedToText(node.primaryInput)}` : ""} data-datatype={node.primaryInput?.dataType} style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`} @@ -601,7 +597,7 @@ xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" class="connector" - data-tooltip-label={dataTypeTooltipLabel(stackDataInput)} + data-tooltip-label={stackDataInput.resolvedType} data-tooltip-description={`${validTypesText(stackDataInput).trim()}\n\n${inputConnectedToText(stackDataInput)}`} data-connector="input" data-datatype={stackDataInput.dataType} @@ -755,7 +751,7 @@ viewBox="0 0 8 8" class="connector primary-connector" data-connector="input" - data-tooltip-label={dataTypeTooltipLabel(node.primaryInput)} + data-tooltip-label={node.primaryInput.resolvedType} data-tooltip-description={`${validTypesText(node.primaryInput).trim()}\n\n${inputConnectedToText(node.primaryInput)}`} data-datatype={node.primaryInput?.dataType} style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`} @@ -775,7 +771,7 @@ viewBox="0 0 8 8" class="connector" data-connector="input" - data-tooltip-label={dataTypeTooltipLabel(secondary)} + data-tooltip-label={secondary.resolvedType} data-tooltip-description={`${validTypesText(secondary).trim()}\n\n${inputConnectedToText(secondary)}`} data-datatype={secondary.dataType} style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`} @@ -798,7 +794,7 @@ viewBox="0 0 8 8" class="connector primary-connector" data-connector="output" - data-tooltip-label={dataTypeTooltipLabel(node.primaryOutput)} + data-tooltip-label={node.primaryOutput.resolvedType} data-tooltip-description={`${outputConnectedToText(node.primaryOutput)}`} data-datatype={node.primaryOutput.dataType} style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`} @@ -817,7 +813,7 @@ viewBox="0 0 8 8" class="connector" data-connector="output" - data-tooltip-label={dataTypeTooltipLabel(secondary)} + data-tooltip-label={secondary.resolvedType} data-tooltip-description={`${outputConnectedToText(secondary)}`} data-datatype={secondary.dataType} style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`} diff --git a/node-graph/libraries/core-types/src/types.rs b/node-graph/libraries/core-types/src/types.rs index 8d46b435..cd4f6700 100644 --- a/node-graph/libraries/core-types/src/types.rs +++ b/node-graph/libraries/core-types/src/types.rs @@ -1,4 +1,3 @@ -use crate::transform::Footprint; use std::any::TypeId; pub use std::borrow::Cow; use std::fmt::{Display, Formatter}; @@ -346,8 +345,73 @@ pub fn simplify_identifier_name(ty: &str) -> String { .join("<") } +/// Converts a Rust-internal type name to its user-facing form. pub fn make_type_user_readable(ty: &str) -> String { - ty.replace("Option>", "Context").replace("Raster", "Raster").replace("Raster", "Raster") + let ty = ty + .replace("Option>", "Context") + .replace("Raster", "Raster") + .replace("Raster", "Raster") + .replace("DAffine2", "Transform") + .replace("Affine2", "Transform") + .replace("DVec2", "Vec2") + .replace("IVec2", "Vec2") + .replace("UVec2", "Vec2") + .replace("&str", "String"); + + rewrite_list_as_array_brackets(&ty) +} + +/// Rewrites `List` as `T[]`. Handles nesting (e.g. `List>` becomes `Vector[][]`). +/// Respects word boundaries so unrelated identifiers that happen to end in `List` are not affected. +fn rewrite_list_as_array_brackets(input: &str) -> String { + let bytes = input.as_bytes(); + let mut result = String::with_capacity(input.len()); + let mut i = 0; + + while i < bytes.len() { + let at_word_boundary = i == 0 || !is_identifier_byte(bytes[i - 1]); + if at_word_boundary && bytes[i..].starts_with(b"List<") { + let inner_start = i + b"List<".len(); + if let Some(close) = find_matching_angle_bracket(bytes, inner_start) { + let inner = &input[inner_start..close]; + result.push_str(&rewrite_list_as_array_brackets(inner)); + result.push_str("[]"); + i = close + 1; + continue; + } + } + if bytes[i].is_ascii() { + result.push(bytes[i] as char); + i += 1; + } else { + let ch = input[i..].chars().next().unwrap(); + result.push(ch); + i += ch.len_utf8(); + } + } + + result +} + +fn is_identifier_byte(byte: u8) -> bool { + byte.is_ascii_alphanumeric() || byte == b'_' +} + +fn find_matching_angle_bracket(bytes: &[u8], start: usize) -> Option { + let mut depth = 1_usize; + for (offset, &byte) in bytes[start..].iter().enumerate() { + match byte { + b'<' => depth += 1, + b'>' => { + depth -= 1; + if depth == 0 { + return Some(start + offset); + } + } + _ => {} + } + } + None } impl std::fmt::Debug for Type { @@ -359,18 +423,10 @@ impl std::fmt::Debug for Type { // Display impl std::fmt::Display for Type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use glam::*; - match self { Type::Generic(name) => write!(f, "{}", make_type_user_readable(name)), - Type::Concrete(ty) => match () { - () if self == &concrete!(DVec2) || self == &concrete!(Vec2) || self == &concrete!(IVec2) || self == &concrete!(UVec2) => write!(f, "Vec2"), - () if self == &concrete!(glam::DAffine2) => write!(f, "Transform"), - () if self == &concrete!(Footprint) => write!(f, "Footprint"), - () if self == &concrete!(&str) || self == &concrete!(String) => write!(f, "String"), - _ => write!(f, "{}", make_type_user_readable(&simplify_identifier_name(&ty.name))), - }, - Type::Fn(call_arg, return_value) => write!(f, "{return_value} called with {call_arg}"), + Type::Concrete(ty) => write!(f, "{ty}"), + Type::Fn(_, return_value) => write!(f, "{return_value}"), Type::Future(ty) => write!(f, "{ty}"), } }