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
7ddab438d1
commit
5bc41c4c58
@@ -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()
|
||||
}
|
||||
|
||||
@@ -25,43 +25,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>,
|
||||
@@ -96,8 +63,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3668,7 +3668,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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user