mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Reshape the node catalog: rename the list access nodes, split Text to Vector, reinstate Upload Texture as a proto node, and delete the vestigial debug nodes
This commit is contained in:
committed by
Dennis Kobert
parent
1bac1d694f
commit
ead622b969
@@ -2758,7 +2758,7 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
|
||||
let solidify_stroke_definition = document_node_definitions::resolve_proto_node_type(graphene_std::vector::solidify_stroke::IDENTIFIER).expect("Solidify Stroke node should exist");
|
||||
let index_elements_definition = document_node_definitions::resolve_proto_node_type(graphene_std::graphic::index_elements::IDENTIFIER).expect("Index Elements node should exist");
|
||||
let item_at_index_definition = document_node_definitions::resolve_proto_node_type(graphene_std::graphic::item_at_index::IDENTIFIER).expect("Item at Index node should exist");
|
||||
|
||||
let mut resulting_layers: Vec<NodeId> = Vec::new();
|
||||
|
||||
@@ -2791,7 +2791,7 @@ impl DocumentMessageHandler {
|
||||
if has_fill && has_stroke {
|
||||
let (existing_index, new_index) = (0_f64, 1_f64);
|
||||
|
||||
let existing_index_template = index_elements_definition.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(existing_index), false))]);
|
||||
let existing_index_template = item_at_index_definition.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(existing_index), false))]);
|
||||
let existing_index_id = NodeId::new();
|
||||
self.network_interface.insert_node(existing_index_id, existing_index_template, &[]);
|
||||
self.network_interface.move_node_to_chain_start(&existing_index_id, layer, &[], false);
|
||||
@@ -2813,7 +2813,7 @@ impl DocumentMessageHandler {
|
||||
self.network_interface.set_display_name(&new_layer_id, original_name, &[]);
|
||||
}
|
||||
|
||||
let new_index_template = index_elements_definition.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(new_index), false))]);
|
||||
let new_index_template = item_at_index_definition.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(new_index), false))]);
|
||||
let new_index_id = NodeId::new();
|
||||
self.network_interface.insert_node(new_index_id, new_index_template, &[]);
|
||||
self.network_interface.move_node_to_chain_start(&new_index_id, new_layer, &[], false);
|
||||
|
||||
@@ -488,7 +488,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||
},
|
||||
// 1: Count Elements (number of subpaths)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::count_elements::IDENTIFIER),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::list_length::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1289,78 +1289,6 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||
),
|
||||
properties: None,
|
||||
},
|
||||
#[cfg(feature = "gpu")]
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Upload Texture",
|
||||
category: "Debug",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::import(concrete!(List<Raster<CPU>>), 0), NodeInput::scope(platform_application_io::wgpu_executor::IDENTIFIER)],
|
||||
call_argument: generic!(T),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_conversion::upload_texture::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
call_argument: generic!(T),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memoize::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (NodeId(id as u64), node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::type_default(descriptor!(List<Raster<CPU>>), true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
output_names: vec!["Texture".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
node_metadata: [
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, 0)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (NodeId(id as u64), node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("TODO"),
|
||||
properties: None,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Extract",
|
||||
category: "",
|
||||
@@ -1407,13 +1335,13 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||
// Node 1: extract_element at index 0, extracts the whole match as a bare String (drops the item's start/end/name attributes since the unwrapped String can't carry them)
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(0.), false)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::extract_element::IDENTIFIER),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::item_at_index::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
// Node 2: omit_element at index 0, returns the capture group items as a List<String>, preserving each item's start/end/name attributes
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(0.), false)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::omit_element::IDENTIFIER),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::remove_at_index::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
|
||||
@@ -93,10 +93,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
node: graphene_std::animation::animation_time::IDENTIFIER,
|
||||
aliases: &["graphene_core::animation::AnimationTimeNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::debug::clone::IDENTIFIER,
|
||||
aliases: &["graphene_core::ops::CloneNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::extract_xy::extract_xy::IDENTIFIER,
|
||||
aliases: &["graphene_core::ops::ExtractXyNode"],
|
||||
@@ -112,6 +108,14 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
"graphene_core::transform_nodes::FreezeRealTimeNode",
|
||||
"graphene_core::vector::SubpathSegmentLengthsNode",
|
||||
"core_types::vector::SubpathSegmentLengthsNode",
|
||||
// The deleted debug Option trio degrades to a passthrough of its single input (audit resolution 8)
|
||||
"graphene_core::ops::SizeOfNode",
|
||||
"graphene_core::debug::SizeOfNode",
|
||||
"graphene_core::ops::SomeNode",
|
||||
"graphene_core::debug::SomeNode",
|
||||
"graphene_core::ops::UnwrapNode",
|
||||
"graphene_core::debug::UnwrapNode",
|
||||
"graphene_core::debug::UnwrapOptionNode",
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
@@ -126,18 +130,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
node: graphene_std::animation::real_time::IDENTIFIER,
|
||||
aliases: &["graphene_core::animation::RealTimeNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::debug::size_of::IDENTIFIER,
|
||||
aliases: &["graphene_core::ops::SizeOfNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::debug::some::IDENTIFIER,
|
||||
aliases: &["graphene_core::ops::SomeNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::debug::unwrap_option::IDENTIFIER,
|
||||
aliases: &["graphene_core::ops::UnwrapNode", "graphene_core::debug::UnwrapNode"],
|
||||
},
|
||||
// ================================
|
||||
// graphic
|
||||
// ================================
|
||||
@@ -167,13 +159,19 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
aliases: &["graphene_core::graphic::FlattenVectorNode", "graphene_core::graphic_element::FlattenVectorNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic::index_elements::IDENTIFIER,
|
||||
node: graphene_std::graphic::item_at_index::IDENTIFIER,
|
||||
aliases: &[
|
||||
"graphene_core::graphic_element::IndexNode",
|
||||
"graphene_core::graphic::IndexNode",
|
||||
"graphene_core::graphic::IndexElementsNode",
|
||||
"graphic_nodes::graphic::IndexElementsNode",
|
||||
"graphic_nodes::graphic::ExtractElementNode",
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic::remove_at_index::IDENTIFIER,
|
||||
aliases: &["graphic_nodes::graphic::OmitElementNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic::legacy_layer_extend::IDENTIFIER,
|
||||
aliases: &[
|
||||
@@ -777,7 +775,7 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
aliases: &["graphene_core::vector::ClosePathNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::vector::count_elements::IDENTIFIER,
|
||||
node: graphene_std::vector::list_length::IDENTIFIER,
|
||||
aliases: &["graphene_core::vector::CountElementsNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
@@ -1876,6 +1874,25 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
|
||||
}
|
||||
|
||||
// The Text to Vector node's runtime `separate_glyphs` toggle became the dedicated "Text to Vector Glyphs" node, leaving Text to Vector as a plain
|
||||
// string-to-compound-path converter. A 2-input Text to Vector is the old toggled shape: a `true` toggle routes to Text to Vector Glyphs, otherwise
|
||||
// the node stays Text to Vector; either way the toggle input is dropped and the string wire is preserved.
|
||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::text::text_to_vector::IDENTIFIER) && inputs_count == 2 {
|
||||
let separate_glyphs = matches!(node.inputs.get(1).and_then(|input| input.as_value()), Some(TaggedValue::Bool(true)));
|
||||
let target = if separate_glyphs {
|
||||
graphene_std::text::text_to_vector_glyphs::IDENTIFIER
|
||||
} else {
|
||||
graphene_std::text::text_to_vector::IDENTIFIER
|
||||
};
|
||||
|
||||
let mut node_template = resolve_proto_node_type(target)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
if let Some(string_input) = node.inputs.first() {
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), string_input.clone(), network_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
|
||||
if reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new("graphene_std::text::TextNode")) && inputs_count == 8 {
|
||||
let mut template: NodeTemplate = legacy_text_node_template()?;
|
||||
@@ -2406,14 +2423,14 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
return None;
|
||||
};
|
||||
|
||||
// Create Count Elements node: counts content `List` items → N
|
||||
let Some(count_elements_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::count_elements::IDENTIFIER)) else {
|
||||
log::error!("Could not get count_elements node from definition when upgrading morph");
|
||||
// Create List Length node: counts content `List` items → N
|
||||
let Some(list_length_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::list_length::IDENTIFIER)) else {
|
||||
log::error!("Could not get list_length node from definition when upgrading morph");
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
return None;
|
||||
};
|
||||
let count_elements_template = count_elements_def.default_node_template();
|
||||
let count_elements_id = NodeId::new();
|
||||
let list_length_template = list_length_def.default_node_template();
|
||||
let list_length_id = NodeId::new();
|
||||
|
||||
// Create Subtract node: N → N-1
|
||||
let Some(subtract_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::subtract::IDENTIFIER)) else {
|
||||
@@ -2435,10 +2452,10 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
let divide_id = NodeId::new();
|
||||
|
||||
// Insert and position nodes
|
||||
document.network_interface.insert_node(count_elements_id, count_elements_template, network_path);
|
||||
document.network_interface.insert_node(list_length_id, list_length_template, network_path);
|
||||
document
|
||||
.network_interface
|
||||
.shift_absolute_node_position(&count_elements_id, morph_position + IVec2::new(-21, 2), network_path);
|
||||
.shift_absolute_node_position(&list_length_id, morph_position + IVec2::new(-21, 2), network_path);
|
||||
|
||||
document.network_interface.insert_node(subtract_id, subtract_template, network_path);
|
||||
document.network_interface.shift_absolute_node_position(&subtract_id, morph_position + IVec2::new(-14, 2), network_path);
|
||||
@@ -2447,12 +2464,12 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
document.network_interface.shift_absolute_node_position(÷_id, morph_position + IVec2::new(-7, 1), network_path);
|
||||
|
||||
// Wire: content source → Count Elements input 0
|
||||
document.network_interface.set_input(&InputConnector::node(count_elements_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(list_length_id, 0), old_inputs[0].clone(), network_path);
|
||||
|
||||
// Wire: Count Elements output → Subtract input 0 (minuend)
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(subtract_id, 0), NodeInput::node(count_elements_id, 0), network_path);
|
||||
.set_input(&InputConnector::node(subtract_id, 0), NodeInput::node(list_length_id, 0), network_path);
|
||||
|
||||
// Wire: old progression → Divide input 0 (numerator)
|
||||
document.network_interface.set_input(&InputConnector::node(divide_id, 0), old_inputs[1].clone(), network_path);
|
||||
@@ -2745,6 +2762,31 @@ fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, ne
|
||||
}
|
||||
}
|
||||
|
||||
// The removed Attach Attribute node (merged into Write Attribute per audit resolution 6) degrades to a passthrough of its
|
||||
// content: its eager whole-list source input cannot be mechanically rewired as Write Attribute's lazy per-item value producer.
|
||||
if let Some(DefinitionIdentifier::ProtoNode(identifier)) = document.network_interface.reference(node_id, network_path)
|
||||
&& identifier.as_str().ends_with("::AttachAttributeNode")
|
||||
{
|
||||
let mut node_template = resolve_proto_node_type(graphene_std::ops::passthrough::IDENTIFIER)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
if let Some(content) = old_inputs.first() {
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), content.clone(), network_path);
|
||||
}
|
||||
}
|
||||
|
||||
// The Upload Texture node's old wrapper-network form maps onto its proto node form, which draws the executor from scope
|
||||
if let Some(DefinitionIdentifier::Network(name)) = document.network_interface.reference(node_id, network_path)
|
||||
&& name == "Upload Texture"
|
||||
{
|
||||
let mut node_template = resolve_proto_node_type(graphene_std::platform_application_io::upload_texture::IDENTIFIER)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
if let Some(content) = old_inputs.first() {
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), content.clone(), network_path);
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
@@ -2752,6 +2794,13 @@ fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, ne
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// The removed-definition blocks above abort silently via `?` if their swap target ever leaves the catalog
|
||||
#[test]
|
||||
fn removed_definition_swap_targets_resolve() {
|
||||
assert!(resolve_proto_node_type(graphene_std::ops::passthrough::IDENTIFIER).is_some());
|
||||
assert!(resolve_proto_node_type(graphene_std::platform_application_io::upload_texture::IDENTIFIER).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_written_attribute_no_longer_resets_the_layer_definitions() {
|
||||
// The node resolves natively again, so a document carrying it keeps
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::WgpuExecutorHandle;
|
||||
use core_types::Color;
|
||||
use core_types::Ctx;
|
||||
use core_types::color::SRGBA8;
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::ops::{Convert, ConvertAsync};
|
||||
@@ -250,15 +249,3 @@ impl ConvertAsync<Raster<CPU>, WgpuExecutorHandle> for Raster<GPU> {
|
||||
Box::pin(async move { converter.convert(&device).await.expect("Failed to download texture data") })
|
||||
}
|
||||
}
|
||||
|
||||
/// Uploads an raster texture from the CPU to the GPU. This is now deprecated and the Convert node should be used in the future.
|
||||
///
|
||||
/// Accepts either individual raster data or a `List` of raster elements and converts it to the GPU format using the WgpuExecutor's device and queue.
|
||||
#[node_macro::node(category(""))]
|
||||
pub fn upload_texture<T: Convert<List<Raster<GPU>>, WgpuExecutorHandle>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(List<Raster<CPU>>, List<Raster<GPU>>)] input: T,
|
||||
executor: WgpuExecutorHandle,
|
||||
) -> List<Raster<GPU>> {
|
||||
input.convert(Footprint::DEFAULT, executor)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use core_types::Ctx;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use raster_types::{CPU, Raster};
|
||||
|
||||
/// Meant for debugging purposes, not general use. Logs the input value to the console and passes it through unchanged.
|
||||
#[node_macro::node(category("Debug"), name("Log to Console"))]
|
||||
@@ -9,27 +8,3 @@ fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(bool, f64,
|
||||
log::debug!("{value:#?}");
|
||||
value
|
||||
}
|
||||
|
||||
/// Meant for debugging purposes, not general use. Returns the size of the input type in bytes.
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn size_of(_: impl Ctx, ty: core_types::Type) -> Option<usize> {
|
||||
ty.size()
|
||||
}
|
||||
|
||||
/// Meant for debugging purposes, not general use. Wraps the input value in the Some variant of an Option.
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn some<T>(_: impl Ctx, #[implementations(f64, f32, u32, u64, String)] input: T) -> Option<T> {
|
||||
Some(input)
|
||||
}
|
||||
|
||||
/// Meant for debugging purposes, not general use. Unwraps the input value from an Option, returning the default value if the input is None.
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn unwrap_option<T: Default>(_: impl Ctx, #[implementations(Option<f64>, Option<u32>, Option<u64>, Option<String>)] input: Option<T>) -> T {
|
||||
input.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Clones the element out of its record input.
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn clone<T: Clone>(_: impl Ctx, #[implementations(Raster<CPU>, f64)] value: &T) -> T {
|
||||
value.clone()
|
||||
}
|
||||
|
||||
@@ -24,43 +24,10 @@ fn resolve_index(index: f64, total: u64) -> Option<u64> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a one-lane level holding the item at the specified index with its
|
||||
/// attributes, or an empty level when the index is out of range.
|
||||
#[node_macro::node(category("General"), extent(index_elements_extent))]
|
||||
pub fn index_elements<T>(
|
||||
ctx: impl Ctx + ModifyIndex + Copy,
|
||||
/// The list of data.
|
||||
list: impl Node<Context<'_>, Output = T>,
|
||||
/// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the list, starting from -1 for the last item.
|
||||
index: SignedInteger,
|
||||
) -> Result<T, Interrupt> {
|
||||
let total = match list.extent(ctx, Level::Total) {
|
||||
GPoll::Final(Extent::Exactly(count)) => count as u64,
|
||||
GPoll::Pending => return Err(Interrupt::Pending),
|
||||
_ => return Err(GraphError::new("index elements over a non-exact extent").into()),
|
||||
};
|
||||
let Some(source) = resolve_index(index, total) else {
|
||||
return Err(GraphError::new("index elements addressed its empty selection").into());
|
||||
};
|
||||
let mut shifted = *ctx;
|
||||
shifted.set_index(source);
|
||||
list.eval(&shifted)
|
||||
}
|
||||
|
||||
fn index_elements_extent(list: ExtentIn<'_>, index: ValueIn<'_, f64>, level: LevelIn) -> GPoll<Extent> {
|
||||
match level.top() {
|
||||
true => index.get().zip(list.at(level)).map(|(index, extent)| match extent {
|
||||
Extent::Exactly(count) => Extent::Exactly(resolve_index(index, count as u64).is_some() as usize),
|
||||
_ => Extent::Exactly(1),
|
||||
}),
|
||||
false => list.at(level),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list with the element at the specified index removed.
|
||||
/// Returns the list with the item at the specified index removed.
|
||||
/// If no value exists at that index, the list is returned unchanged.
|
||||
#[node_macro::node(category("General"), extent(omit_element_extent))]
|
||||
pub fn omit_element<T>(
|
||||
#[node_macro::node(category("General"), name("Remove at Index"), extent(omit_element_extent))]
|
||||
pub fn remove_at_index<T>(
|
||||
ctx: impl Ctx + ModifyIndex + Copy,
|
||||
/// The list of data.
|
||||
list: impl Node<Context<'_>, Output = T>,
|
||||
@@ -95,8 +62,8 @@ fn omit_element_extent(list: ExtentIn<'_>, index: ValueIn<'_, f64>, level: Level
|
||||
/// Returns the bare element (without the item's attributes) at the specified index in a `List`.
|
||||
/// Use this when downstream nodes want just the inner value rather than a `List` containing a single item.
|
||||
/// If no value exists at that index, the element type's default is returned.
|
||||
#[node_macro::node(category("General"))]
|
||||
pub fn extract_element<T: Clone + Default + Send + Sync + CacheHash + 'static>(
|
||||
#[node_macro::node(category("General"), name("Item at Index"))]
|
||||
pub fn item_at_index<T: Clone + Default + Send + Sync + CacheHash + 'static>(
|
||||
_: impl Ctx,
|
||||
/// The `List` of data to extract from.
|
||||
#[implementations(String, f64, NodeId, Color, Gradient, Vector, Raster<CPU>, Graphic, Artboard)]
|
||||
|
||||
@@ -6,15 +6,14 @@ use canvas_utils::{Canvas, CanvasHandle};
|
||||
use core_types::attribute::{Attr, OwnedAttr, Transform};
|
||||
use core_types::color::SRGBA8;
|
||||
use core_types::gpoll::GPoll;
|
||||
#[cfg(target_family = "wasm")]
|
||||
use core_types::list::List;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
use core_types::ATTR_TRANSFORM;
|
||||
#[cfg(target_family = "wasm")]
|
||||
use core_types::math::bbox::Bbox;
|
||||
use core_types::ops::Convert;
|
||||
use core_types::runtime::SourceFuture;
|
||||
#[cfg(target_family = "wasm")]
|
||||
use core_types::transform::Footprint;
|
||||
use core_types::{Color, Ctx};
|
||||
pub use graph_craft::application_io::resource::{Resource, ResourceHash};
|
||||
@@ -31,7 +30,7 @@ use graphic_types::Vector;
|
||||
#[cfg(target_family = "wasm")]
|
||||
use graphic_types::markers::EditorMergedLayers;
|
||||
use graphic_types::raster_types::Image;
|
||||
use graphic_types::raster_types::{CPU, Raster};
|
||||
use graphic_types::raster_types::{CPU, GPU, Raster};
|
||||
#[cfg(target_family = "wasm")]
|
||||
use graphic_types::vector_types::gradient::Gradient;
|
||||
#[cfg(target_family = "wasm")]
|
||||
@@ -308,3 +307,13 @@ pub fn wgpu_executor(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: A
|
||||
pub fn try_wgpu_executor(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: Arc<PlatformEditorApi>) -> Option<::wgpu_executor::WgpuExecutorHandle> {
|
||||
editor_api.application_io.as_ref()?.gpu_executor_arc().map(::wgpu_executor::WgpuExecutorHandle)
|
||||
}
|
||||
|
||||
/// Uploads image data from CPU memory into a GPU texture so that GPU-based nodes can process it.
|
||||
#[node_macro::node(category("Debug"), memoize)]
|
||||
pub fn upload_texture<T: Convert<List<Raster<GPU>>, ::wgpu_executor::WgpuExecutorHandle>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(List<Raster<CPU>>)] content: T,
|
||||
#[scope(wgpu_executor::IDENTIFIER)] executor: ::wgpu_executor::WgpuExecutorHandle,
|
||||
) -> List<Raster<GPU>> {
|
||||
content.convert(Footprint::DEFAULT, executor)
|
||||
}
|
||||
|
||||
@@ -90,15 +90,24 @@ fn text(
|
||||
list
|
||||
}
|
||||
|
||||
/// Converts a styled `String[]` into vector geometry.
|
||||
/// Converts styled text into vector compound paths.
|
||||
#[node_macro::node(category("Text"), name("Text to Vector"))]
|
||||
fn text_to_vector(
|
||||
_: impl Ctx,
|
||||
/// A styled list of text strings produced by the **Text** node (or any other `String[]` source).
|
||||
#[implementations(List<String>)]
|
||||
strings: List<String>,
|
||||
/// Whether to split every letterform into its own vector item. Otherwise, a single vector compound path is produced.
|
||||
separate_glyphs: bool,
|
||||
) -> List<Vector> {
|
||||
shape_text_list(&strings, separate_glyphs)
|
||||
shape_text_list(&strings, false)
|
||||
}
|
||||
|
||||
/// Splits styled text 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 list of text strings produced by the **Text** node (or any other `String[]` source).
|
||||
#[implementations(List<String>)]
|
||||
strings: List<String>,
|
||||
) -> List<Vector> {
|
||||
shape_text_list(&strings, true)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::TypesettingConfig;
|
||||
use super::text_context::TextContext;
|
||||
use crate::markers::{ATTR_FONT, ATTR_TEXT_ALIGN};
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::list::List;
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{
|
||||
ATTR_BLEND_MODE, ATTR_EDITOR_LAYER_PATH, ATTR_FONT_SIZE, ATTR_LETTER_SPACING, ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM,
|
||||
@@ -23,60 +23,71 @@ pub fn lines_clipping(text: &str, font: &Resource, typesetting: TypesettingConfi
|
||||
TextContext::with_thread_local(|ctx| ctx.lines_clipping(text, font, typesetting))
|
||||
}
|
||||
|
||||
/// Shapes each string item of a styled `List<String>` into vector geometry, reading its font and typesetting
|
||||
/// from the item's attributes (as set by the 'Text' node) and re-applying its transform and blending
|
||||
/// attributes onto the produced paths. With `separate_glyphs`, each glyph becomes its own item.
|
||||
/// Shapes a single styled string item into vector geometry, reading its font and typesetting from the item's
|
||||
/// attributes (as set by the 'Text' node) and re-applying its transform and blending attributes onto the produced
|
||||
/// paths. With `separate_glyphs`, each glyph becomes its own item; otherwise a single compound path is produced.
|
||||
pub fn shape_text_item(item: &Item<String>, separate_glyphs: bool) -> List<Vector> {
|
||||
let text = item.element();
|
||||
if text.is_empty() {
|
||||
return List::new();
|
||||
}
|
||||
|
||||
// Use fallback font when none is explicitly attached.
|
||||
let font: Resource = {
|
||||
let font: Resource = item.attribute_cloned_or_default(ATTR_FONT);
|
||||
if font.is_empty() { super::FALLBACK_FONT_RESOURCE.clone() } else { font }
|
||||
};
|
||||
|
||||
let defaults = TypesettingConfig::default();
|
||||
let typesetting = TypesettingConfig {
|
||||
font_size: item.attribute_cloned_or(ATTR_FONT_SIZE, defaults.font_size),
|
||||
line_height_ratio: item.attribute_cloned_or(ATTR_LINE_HEIGHT, defaults.line_height_ratio),
|
||||
letter_spacing: item.attribute_cloned_or(ATTR_LETTER_SPACING, defaults.letter_spacing),
|
||||
letter_tilt: item.attribute_cloned_or(ATTR_LETTER_TILT, defaults.letter_tilt),
|
||||
max_width: item.attribute_cloned_or::<Option<f64>>(ATTR_MAX_WIDTH, defaults.max_width),
|
||||
max_height: item.attribute_cloned_or::<Option<f64>>(ATTR_MAX_HEIGHT, defaults.max_height),
|
||||
align: item.attribute_cloned_or(ATTR_TEXT_ALIGN, defaults.align),
|
||||
};
|
||||
|
||||
let vectors = to_path(text, &font, typesetting, separate_glyphs);
|
||||
let transform = item.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM);
|
||||
let layer_path = item.attribute::<Vec<NodeId>>(ATTR_EDITOR_LAYER_PATH).cloned();
|
||||
let blend_mode = item.attribute::<BlendMode>(ATTR_BLEND_MODE).copied();
|
||||
let opacity = item.attribute::<f64>(ATTR_OPACITY).copied();
|
||||
let opacity_fill = item.attribute::<f64>(ATTR_OPACITY_FILL).copied();
|
||||
|
||||
let mut result = List::new();
|
||||
for mut produced in vectors.into_iter() {
|
||||
if transform != DAffine2::IDENTITY {
|
||||
let local = produced.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM);
|
||||
produced.set_attribute(ATTR_TRANSFORM, transform * local);
|
||||
}
|
||||
if let Some(layer_path) = &layer_path {
|
||||
produced.set_attribute(ATTR_EDITOR_LAYER_PATH, layer_path.clone());
|
||||
}
|
||||
if let Some(blend_mode) = blend_mode {
|
||||
produced.set_attribute(ATTR_BLEND_MODE, blend_mode);
|
||||
}
|
||||
if let Some(opacity) = opacity {
|
||||
produced.set_attribute(ATTR_OPACITY, opacity);
|
||||
}
|
||||
if let Some(opacity_fill) = opacity_fill {
|
||||
produced.set_attribute(ATTR_OPACITY_FILL, opacity_fill);
|
||||
}
|
||||
result.push(produced);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Shapes each string item of a styled `List<String>` into vector geometry, flattening the per-item results.
|
||||
pub fn shape_text_list(strings: &List<String>, separate_glyphs: bool) -> List<Vector> {
|
||||
let mut result = List::new();
|
||||
|
||||
for index in 0..strings.len() {
|
||||
let Some(text) = strings.element(index) else { continue };
|
||||
if text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use fallback font when none is explicitly attached.
|
||||
let font: Resource = {
|
||||
let f: Resource = strings.attribute_cloned_or_default(ATTR_FONT, index);
|
||||
if f.is_empty() { super::FALLBACK_FONT_RESOURCE.clone() } else { f }
|
||||
};
|
||||
|
||||
let defaults = TypesettingConfig::default();
|
||||
let typesetting = TypesettingConfig {
|
||||
font_size: strings.attribute_cloned_or(ATTR_FONT_SIZE, index, defaults.font_size),
|
||||
line_height_ratio: strings.attribute_cloned_or(ATTR_LINE_HEIGHT, index, defaults.line_height_ratio),
|
||||
letter_spacing: strings.attribute_cloned_or(ATTR_LETTER_SPACING, index, defaults.letter_spacing),
|
||||
letter_tilt: strings.attribute_cloned_or(ATTR_LETTER_TILT, index, defaults.letter_tilt),
|
||||
max_width: strings.attribute_cloned_or::<Option<f64>>(ATTR_MAX_WIDTH, index, defaults.max_width),
|
||||
max_height: strings.attribute_cloned_or::<Option<f64>>(ATTR_MAX_HEIGHT, index, defaults.max_height),
|
||||
align: strings.attribute_cloned_or(ATTR_TEXT_ALIGN, index, defaults.align),
|
||||
};
|
||||
|
||||
let vectors = to_path(text, &font, typesetting, separate_glyphs);
|
||||
let transform = strings.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, index);
|
||||
let layer_path = strings.attribute_cloned_or_default::<Vec<NodeId>>(ATTR_EDITOR_LAYER_PATH, index);
|
||||
let blend_mode = strings.attribute::<BlendMode>(ATTR_BLEND_MODE, index).copied();
|
||||
let opacity = strings.attribute::<f64>(ATTR_OPACITY, index).copied();
|
||||
let opacity_fill = strings.attribute::<f64>(ATTR_OPACITY_FILL, index).copied();
|
||||
|
||||
for mut item in vectors.into_iter() {
|
||||
if transform != DAffine2::IDENTITY {
|
||||
let local = item.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM);
|
||||
item.set_attribute(ATTR_TRANSFORM, transform * local);
|
||||
}
|
||||
if !layer_path.is_empty() {
|
||||
item.set_attribute(ATTR_EDITOR_LAYER_PATH, layer_path.clone());
|
||||
}
|
||||
if let Some(blend_mode) = blend_mode {
|
||||
item.set_attribute(ATTR_BLEND_MODE, blend_mode);
|
||||
}
|
||||
if let Some(opacity) = opacity {
|
||||
item.set_attribute(ATTR_OPACITY, opacity);
|
||||
}
|
||||
if let Some(opacity_fill) = opacity_fill {
|
||||
item.set_attribute(ATTR_OPACITY_FILL, opacity_fill);
|
||||
}
|
||||
result.push(item);
|
||||
let Some(item) = strings.clone_item(index) else { continue };
|
||||
for produced in shape_text_item(&item, separate_glyphs).into_iter() {
|
||||
result.push(produced);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3642,7 +3642,7 @@ fn point_inside(_: impl Ctx, source: IList<Vector>, point: DVec2) -> bool {
|
||||
// TODO: Return u32, u64, or usize instead of f64 after #1621 is resolved and has allowed us to implement automatic type conversion in the node graph for nodes with generic type inputs.
|
||||
// TODO: (Currently automatic type conversion only works for concrete types, via the Graphene preprocessor and not the full Graphene type system.)
|
||||
#[node_macro::node(category("General"), path(graphene_core::vector))]
|
||||
fn count_elements<T: Clone + Send + Sync + CacheHash + 'static>(_: impl Ctx, #[implementations(Graphic, Artboard, Vector, Raster<CPU>, Color, Gradient, String)] content: IList<T>) -> f64 {
|
||||
fn list_length<T: Clone + Send + Sync + CacheHash + 'static>(_: impl Ctx, #[implementations(Graphic, Artboard, Vector, Raster<CPU>, Color, Gradient, String)] content: IList<T>) -> f64 {
|
||||
content.len() as f64
|
||||
}
|
||||
|
||||
|
||||
@@ -178,11 +178,11 @@ fn write_inputs(page: &mut std::fs::File, valid_input_types: &[Vec<core_types::T
|
||||
let default_value = default_value.trim_end_matches('.').trim_end_matches(".0"); // Display whole-number floats as integers
|
||||
|
||||
let render_color = |color| format!(r#"<span style="padding-right: 100px; border: 2px solid var(--color-fog); background: {color}"></span>"#);
|
||||
// Compare against the typed default's debug form so the swatch tracks the `Gradient` representation
|
||||
let black_to_white_gradient = value::TaggedValue::Gradient(Default::default()).to_debug_string();
|
||||
let default_value = match default_value {
|
||||
"Color::BLACK" => render_color("black"),
|
||||
"Gradient([(0.0, Color { red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0 }), (1.0, Color { red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0 })])" => {
|
||||
render_color("linear-gradient(to right, black, white)")
|
||||
}
|
||||
gradient if gradient == black_to_white_gradient => render_color("linear-gradient(to right, black, white)"),
|
||||
_ => format!("`{default_value}{}`", field.unit.unwrap_or_default()),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user