mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Migrate remaining node graph data types from Vec to Table (#4067)
* Move Vec<String> to Table<String> * Remove old VecDVec2 * Move Vec<u8> to Table<u8> * Move Vec<f64> to Table<f64> * Move [f64; 4] to Table<f64> * Move Vec<NodeId> to Table<NodeId> * Tidy up the TaggedValue variants * Move Vec<BrushStroke> to Table<BrushStroke> * Add missing type implementations * Fix tests ---------
This commit is contained in:
@@ -216,7 +216,7 @@ pub enum DocumentNodeMetadata {
|
||||
impl DocumentNodeMetadata {
|
||||
pub fn ty(&self) -> Type {
|
||||
match self {
|
||||
DocumentNodeMetadata::DocumentNodePath => concrete!(Vec<NodeId>),
|
||||
DocumentNodeMetadata::DocumentNodePath => concrete!(core_types::table::Table<NodeId>),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -930,7 +930,10 @@ impl NodeNetwork {
|
||||
let (tagged_value, exposed) = match previous_export {
|
||||
NodeInput::Value { tagged_value, exposed } => (tagged_value, exposed),
|
||||
NodeInput::Reflection(reflect) => match reflect {
|
||||
DocumentNodeMetadata::DocumentNodePath => (TaggedValue::NodePath(path.to_vec()).into(), false),
|
||||
DocumentNodeMetadata::DocumentNodePath => {
|
||||
let table: core_types::table::Table<NodeId> = path.iter().copied().map(core_types::table::TableRow::new_from_element).collect();
|
||||
(TaggedValue::NodeIdTable(table).into(), false)
|
||||
}
|
||||
},
|
||||
previous_export => {
|
||||
*export = previous_export;
|
||||
|
||||
@@ -4,28 +4,27 @@ use crate::proto::{Any as DAny, FutureAny};
|
||||
use brush_nodes::brush_cache::BrushCache;
|
||||
use brush_nodes::brush_stroke::BrushStroke;
|
||||
use core_types::table::Table;
|
||||
use core_types::transform::Footprint;
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{CacheHash, Color, ContextFeatures, MemoHash, Node, Type};
|
||||
use dyn_any::DynAny;
|
||||
pub use dyn_any::StaticType;
|
||||
use glam::{Affine2, Vec2};
|
||||
pub use glam::{DAffine2, DVec2, IVec2, UVec2};
|
||||
use graphic_types::Artboard;
|
||||
use graphic_types::Graphic;
|
||||
use graphic_types::Vector;
|
||||
use graphic_types::raster_types::Image;
|
||||
use graphic_types::raster_types::{CPU, Raster};
|
||||
use graphic_types::vector_types::vector;
|
||||
use graphic_types::vector_types::vector::ReferencePoint;
|
||||
use graphic_types::vector_types::vector::style::Fill;
|
||||
use graphic_types::vector_types::vector::style::GradientStops;
|
||||
use graphic_types::raster_types::{CPU, Image, Raster};
|
||||
use graphic_types::vector_types::vector::style::{Fill, Gradient, GradientStops, Stroke};
|
||||
use graphic_types::vector_types::vector::{self, ReferencePoint};
|
||||
use graphic_types::{Artboard, Graphic, Vector};
|
||||
use raster_nodes::curve::Curve;
|
||||
use rendering::RenderMetadata;
|
||||
use std::fmt::Display;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use std::str::FromStr;
|
||||
pub use std::sync::Arc;
|
||||
use text_nodes::Font;
|
||||
use text_nodes::vector_types::GradientStop;
|
||||
use vector::VectorModification;
|
||||
|
||||
pub struct TaggedValueTypeError;
|
||||
|
||||
@@ -166,27 +165,14 @@ macro_rules! tagged_value {
|
||||
}
|
||||
|
||||
tagged_value! {
|
||||
// ===============
|
||||
// PRIMITIVE TYPES
|
||||
// ===============
|
||||
F32(f32),
|
||||
F64(f64),
|
||||
U32(u32),
|
||||
U64(u64),
|
||||
Bool(bool),
|
||||
String(String),
|
||||
// ========================
|
||||
// LISTS OF PRIMITIVE TYPES
|
||||
// ========================
|
||||
#[serde(alias = "VecF32")] // TODO: Eventually remove this alias document upgrade code
|
||||
VecF64(Vec<f64>),
|
||||
VecDVec2(Vec<DVec2>),
|
||||
F64Array4([f64; 4]),
|
||||
VecString(Vec<String>),
|
||||
NodePath(Vec<NodeId>),
|
||||
// ===========
|
||||
// TABLE TYPES
|
||||
// ===========
|
||||
StringTable(Table<String>),
|
||||
#[serde(deserialize_with = "core_types::misc::migrate_vec_f64_to_table")] // TODO: Eventually remove this migration document upgrade code
|
||||
#[serde(alias = "VecF64", alias = "VecF32", alias = "F64Array4")]
|
||||
F64Table(Table<f64>),
|
||||
NodeIdTable(Table<NodeId>),
|
||||
#[serde(deserialize_with = "graphic_types::migrations::migrate_vector")] // TODO: Eventually remove this migration document upgrade code
|
||||
#[serde(alias = "VectorData")]
|
||||
Vector(Table<Vector>),
|
||||
@@ -205,24 +191,32 @@ tagged_value! {
|
||||
#[serde(deserialize_with = "graphic_types::vector_types::gradient::migrate_gradient_stops")] // TODO: Eventually remove this migration document upgrade code
|
||||
#[serde(alias = "GradientPositions", alias = "GradientStops")]
|
||||
GradientTable(Table<GradientStops>),
|
||||
#[serde(deserialize_with = "brush_nodes::migrations::migrate_brush_strokes_to_table")] // TODO: Eventually remove this migration document upgrade code
|
||||
#[serde(alias = "BrushStrokes")]
|
||||
BrushStrokeTable(Table<BrushStroke>),
|
||||
// ============
|
||||
// STRUCT TYPES
|
||||
// SCALAR TYPES
|
||||
// ============
|
||||
F32(f32),
|
||||
F64(f64),
|
||||
U32(u32),
|
||||
U64(u64),
|
||||
Bool(bool),
|
||||
String(String),
|
||||
FVec2(Vec2),
|
||||
FAffine2(Affine2),
|
||||
#[serde(alias = "IVec2", alias = "UVec2")]
|
||||
DVec2(DVec2),
|
||||
DAffine2(DAffine2),
|
||||
Stroke(graphic_types::vector_types::vector::style::Stroke),
|
||||
Gradient(graphic_types::vector_types::vector::style::Gradient),
|
||||
Font(text_nodes::Font),
|
||||
BrushStrokes(Vec<BrushStroke>),
|
||||
Stroke(Stroke),
|
||||
Gradient(Gradient),
|
||||
Font(Font),
|
||||
BrushCache(BrushCache),
|
||||
DocumentNode(DocumentNode),
|
||||
ContextFeatures(ContextFeatures),
|
||||
Curve(raster_nodes::curve::Curve),
|
||||
Footprint(core_types::transform::Footprint),
|
||||
VectorModification(Box<vector::VectorModification>),
|
||||
Curve(Curve),
|
||||
Footprint(Footprint),
|
||||
VectorModification(Box<VectorModification>),
|
||||
ImageData(Image<Color>),
|
||||
// ==========
|
||||
// ENUM TYPES
|
||||
|
||||
@@ -951,7 +951,7 @@ mod test {
|
||||
// If this assert fails: These NodeIds seem to be changing when you modify TaggedValue, just update them.
|
||||
assert_eq!(
|
||||
ids,
|
||||
vec![NodeId(2791689253855410677), NodeId(11246167042277902310), NodeId(1014827049498980779), NodeId(4864562752646903491)]
|
||||
vec![NodeId(12189222519765806511), NodeId(15012204941197567462), NodeId(15525229164021892418), NodeId(1252248957706694248)]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => f64]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => u32]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => u64]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<f64>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => BlendMode]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]),
|
||||
@@ -92,19 +91,18 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeAlign]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Gradient]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<graphene_std::uuid::NodeId>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Box<graphene_std::vector::VectorModification>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Option<f64>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Option<NodeId>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<DVec2>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<String>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => [f64; 4]]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<NodeId>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<String>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<NodeId>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<f64>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<u8>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Graphic]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::text::Font]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<BrushStroke>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<BrushStroke>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => BrushCache]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DocumentNode]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::curve::Curve]),
|
||||
@@ -155,11 +153,10 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Color>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<GradientStops>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<DVec2>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<NodeId>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<f64>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<f32>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<String>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<String>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<NodeId>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<f64>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<u8>]),
|
||||
#[cfg(target_family = "wasm")]
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => CanvasHandle]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]),
|
||||
@@ -177,14 +174,13 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<f64>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<Color>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<NodeId>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => [f64; 4]]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Graphic]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Vec2]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Affine2]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Gradient]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::Font]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<BrushStroke>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<BrushStroke>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => BrushCache]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DocumentNode]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextFeatures]),
|
||||
|
||||
@@ -87,3 +87,21 @@ pub fn migrate_color<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Resul
|
||||
ColorFormat::ColorTable(color_table) => color_table,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
pub fn migrate_vec_f64_to_table<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<crate::table::Table<f64>, D::Error> {
|
||||
use crate::table::{Table, TableRow};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||
enum F64TableFormat {
|
||||
VecF64(Vec<f64>),
|
||||
F64Table(Table<f64>),
|
||||
}
|
||||
|
||||
Ok(match F64TableFormat::deserialize(deserializer)? {
|
||||
F64TableFormat::VecF64(values) => values.into_iter().map(TableRow::new_from_element).collect(),
|
||||
F64TableFormat::F64Table(table) => table,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ async fn brush(
|
||||
/// Optional raster content that may be drawn onto.
|
||||
mut image: Table<Raster<CPU>>,
|
||||
/// The list of brush stroke paths drawn by the Brush tool, with each including both its coordinates and styles.
|
||||
strokes: Vec<BrushStroke>,
|
||||
strokes: Table<BrushStroke>,
|
||||
/// Internal cache data used to accelerate rendering of the brush content.
|
||||
cache: BrushCache,
|
||||
) -> Table<Raster<CPU>> {
|
||||
@@ -205,11 +205,15 @@ async fn brush(
|
||||
let bounds = Table::new_from_row(table_row.clone()).bounding_box(DAffine2::IDENTITY, false);
|
||||
let [start, end] = if let RenderBoundingBox::Rectangle(rect) = bounds { rect } else { [DVec2::ZERO, DVec2::ZERO] };
|
||||
let image_bbox = AxisAlignedBbox { start, end };
|
||||
let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
|
||||
let stroke_bbox = strokes.iter_element_values().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
|
||||
let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) };
|
||||
let background_bounds = bbox.to_transform();
|
||||
|
||||
let mut draw_strokes: Vec<_> = strokes.iter().filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect();
|
||||
let mut draw_strokes: Vec<_> = strokes
|
||||
.iter_element_values()
|
||||
.filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let mut brush_plan = cache.compute_brush_plan(table_row, &draw_strokes);
|
||||
|
||||
@@ -273,12 +277,12 @@ async fn brush(
|
||||
actual_image = blend_with_mode(actual_image, stroke_texture, stroke.style.blend_mode, (stroke.style.color.a() * 100.) as f64);
|
||||
}
|
||||
|
||||
let has_erase_or_restore_strokes = strokes.iter().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore));
|
||||
let has_erase_or_restore_strokes = strokes.iter_element_values().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore));
|
||||
if has_erase_or_restore_strokes {
|
||||
let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE);
|
||||
let mut erase_restore_mask = TableRow::new_from_element(Raster::new_cpu(opaque_image)).with_attribute("transform", background_bounds);
|
||||
|
||||
for stroke in strokes {
|
||||
for stroke in strokes.into_iter().map(|row| row.into_element()) {
|
||||
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
||||
if brush_texture.is_none() {
|
||||
let tex = create_brush_texture(&stroke.style).await;
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
pub mod brush;
|
||||
pub mod brush_cache;
|
||||
pub mod brush_stroke;
|
||||
|
||||
pub mod migrations {
|
||||
use crate::brush_stroke::BrushStroke;
|
||||
use core_types::table::{Table, TableRow};
|
||||
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
pub fn migrate_brush_strokes_to_table<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<BrushStroke>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum BrushStrokeTableFormat {
|
||||
BrushStrokes(Vec<BrushStroke>),
|
||||
BrushStrokeTable(Table<BrushStroke>),
|
||||
}
|
||||
|
||||
Ok(match BrushStrokeTableFormat::deserialize(deserializer)? {
|
||||
BrushStrokeTableFormat::BrushStrokes(strokes) => strokes.into_iter().map(TableRow::new_from_element).collect(),
|
||||
BrushStrokeTableFormat::BrushStrokeTable(table) => table,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +73,6 @@ async fn quantize_real_time<T>(
|
||||
Context -> DAffine2,
|
||||
Context -> Footprint,
|
||||
Context -> DVec2,
|
||||
Context -> Vec<f64>,
|
||||
Context -> Vec<String>,
|
||||
Context -> Table<Vector>,
|
||||
Context -> Table<Graphic>,
|
||||
Context -> Table<Raster<CPU>>,
|
||||
@@ -82,6 +80,8 @@ async fn quantize_real_time<T>(
|
||||
Context -> Table<Color>,
|
||||
Context -> Table<Artboard>,
|
||||
Context -> Table<GradientStops>,
|
||||
Context -> Table<String>,
|
||||
Context -> Table<f64>,
|
||||
Context -> (),
|
||||
)]
|
||||
value: impl Node<'n, Context<'static>, Output = T>,
|
||||
@@ -113,8 +113,6 @@ async fn quantize_animation_time<T>(
|
||||
Context -> DAffine2,
|
||||
Context -> Footprint,
|
||||
Context -> DVec2,
|
||||
Context -> Vec<f64>,
|
||||
Context -> Vec<String>,
|
||||
Context -> Table<Vector>,
|
||||
Context -> Table<Graphic>,
|
||||
Context -> Table<Raster<CPU>>,
|
||||
@@ -122,6 +120,8 @@ async fn quantize_animation_time<T>(
|
||||
Context -> Table<Color>,
|
||||
Context -> Table<Artboard>,
|
||||
Context -> Table<GradientStops>,
|
||||
Context -> Table<String>,
|
||||
Context -> Table<f64>,
|
||||
Context -> (),
|
||||
)]
|
||||
value: impl Node<'n, Context<'static>, Output = T>,
|
||||
|
||||
@@ -26,11 +26,11 @@ async fn context_modification<T>(
|
||||
Context -> DAffine2,
|
||||
Context -> Footprint,
|
||||
Context -> DVec2,
|
||||
Context -> Vec<DVec2>,
|
||||
Context -> Option<NodeId>,
|
||||
Context -> Vec<NodeId>,
|
||||
Context -> Vec<f64>,
|
||||
Context -> Vec<String>,
|
||||
Context -> Table<String>,
|
||||
Context -> Table<NodeId>,
|
||||
Context -> Table<f64>,
|
||||
Context -> Table<u8>,
|
||||
Context -> Table<Vector>,
|
||||
Context -> Table<Graphic>,
|
||||
Context -> Table<Raster<CPU>>,
|
||||
|
||||
@@ -16,11 +16,6 @@ pub fn index_elements<T: graphic_types::graphic::AtIndex + Clone + Default>(
|
||||
_: impl Ctx,
|
||||
/// The collection of data, such as a list or table.
|
||||
#[implementations(
|
||||
Vec<f64>,
|
||||
Vec<u32>,
|
||||
Vec<u64>,
|
||||
Vec<DVec2>,
|
||||
Vec<String>,
|
||||
Table<Artboard>,
|
||||
Table<Graphic>,
|
||||
Table<Vector>,
|
||||
@@ -28,6 +23,10 @@ pub fn index_elements<T: graphic_types::graphic::AtIndex + Clone + Default>(
|
||||
Table<Raster<GPU>>,
|
||||
Table<Color>,
|
||||
Table<GradientStops>,
|
||||
Table<String>,
|
||||
Table<f64>,
|
||||
Table<u8>,
|
||||
Table<NodeId>,
|
||||
)]
|
||||
collection: T,
|
||||
/// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the collection, starting from -1 for the last item.
|
||||
@@ -53,11 +52,7 @@ pub fn omit_element<T: graphic_types::graphic::OmitIndex + Clone + Default>(
|
||||
_: impl Ctx,
|
||||
/// The collection of data, such as a list or table.
|
||||
#[implementations(
|
||||
Vec<f64>,
|
||||
Vec<u32>,
|
||||
Vec<u64>,
|
||||
Vec<DVec2>,
|
||||
Vec<String>,
|
||||
Table<String>,
|
||||
Table<Artboard>,
|
||||
Table<Graphic>,
|
||||
Table<Vector>,
|
||||
@@ -181,9 +176,10 @@ where
|
||||
/// Used as the value source for stamping the `editor:layer` attribute on each row of a layer's output,
|
||||
/// which lets editor tools (e.g. selection, click target routing) trace data back to its owning layer.
|
||||
#[node_macro::node(category(""))]
|
||||
pub fn parent_layer(_: impl Ctx, node_path: Vec<NodeId>) -> Option<NodeId> {
|
||||
pub fn parent_layer(_: impl Ctx, node_path: Table<NodeId>) -> Option<NodeId> {
|
||||
// Get the penultimate element of the node path, or None if the path is too short
|
||||
node_path.get(node_path.len().wrapping_sub(2)).copied()
|
||||
let index = node_path.len().wrapping_sub(2);
|
||||
node_path.element(index).copied()
|
||||
}
|
||||
|
||||
/// Writes a per-row attribute column on the input table. The value-producing input is evaluated once per row,
|
||||
@@ -208,13 +204,13 @@ async fn write_attribute<T: AnyHash + Clone + Send + Sync + core_types::CacheHas
|
||||
name: String,
|
||||
/// The node that produces the per-row value. Called once per row with the row index in context.
|
||||
#[implementations(
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
)]
|
||||
value: impl Node<'n, Context<'static>, Output = U>,
|
||||
) -> Table<T> {
|
||||
@@ -255,11 +251,14 @@ pub async fn legacy_layer_extend<T: 'n + Send + Clone>(
|
||||
#[expose]
|
||||
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
|
||||
new: Table<T>,
|
||||
nested_node_path: Vec<NodeId>,
|
||||
nested_node_path: Table<NodeId>,
|
||||
) -> Table<T> {
|
||||
// Get the penultimate element of the node path, or None if the path is too short
|
||||
// This is used to get the ID of the user-facing parent layer-style node (which encapsulates this internal node).
|
||||
let layer = nested_node_path.get(nested_node_path.len().wrapping_sub(2)).copied();
|
||||
let layer = {
|
||||
let index = nested_node_path.len().wrapping_sub(2);
|
||||
nested_node_path.element(index).copied()
|
||||
};
|
||||
|
||||
let mut base = base;
|
||||
for mut row in new.into_iter() {
|
||||
|
||||
@@ -6,7 +6,7 @@ use canvas_utils::{Canvas, CanvasHandle};
|
||||
use core_types::WasmNotSend;
|
||||
#[cfg(target_family = "wasm")]
|
||||
use core_types::math::bbox::Bbox;
|
||||
use core_types::table::Table;
|
||||
use core_types::table::{Table, TableRow};
|
||||
#[cfg(target_family = "wasm")]
|
||||
use core_types::transform::Footprint;
|
||||
use core_types::{Color, Ctx};
|
||||
@@ -85,14 +85,15 @@ async fn post_request(
|
||||
#[name("URL")]
|
||||
url: String,
|
||||
/// The binary data to include in the body of the POST request.
|
||||
body: Vec<u8>,
|
||||
body: Table<u8>,
|
||||
/// Makes the request run in the background without waiting on a response. This is useful for triggering webhooks without blocking the continued execution of the graph.
|
||||
discard_result: bool,
|
||||
#[widget(ParsedWidgetOverride::Custom = "text_area")] headers: String,
|
||||
) -> String {
|
||||
let mut header_map = parse_headers(&headers);
|
||||
header_map.insert("Content-Type", "application/octet-stream".parse().unwrap());
|
||||
let request = reqwest::Client::new().post(url).body(body).headers(header_map);
|
||||
let body_bytes: Vec<u8> = body.iter_element_values().copied().collect();
|
||||
let request = reqwest::Client::new().post(url).body(body_bytes).headers(header_map);
|
||||
|
||||
if discard_result {
|
||||
#[cfg(target_family = "wasm")]
|
||||
@@ -114,15 +115,15 @@ async fn post_request(
|
||||
|
||||
/// Converts a text string to raw binary data. Useful for transmission over HTTP or writing to files.
|
||||
#[node_macro::node(category("Web Request"), name("String to Bytes"))]
|
||||
fn string_to_bytes(_: impl Ctx, string: String) -> Vec<u8> {
|
||||
string.into_bytes()
|
||||
fn string_to_bytes(_: impl Ctx, string: String) -> Table<u8> {
|
||||
string.into_bytes().into_iter().map(TableRow::new_from_element).collect()
|
||||
}
|
||||
|
||||
/// Converts extracted raw RGBA pixel data from an input image. Each pixel becomes 4 sequential bytes. Useful for transmission over HTTP or writing to files.
|
||||
#[node_macro::node(category("Web Request"), name("Image to Bytes"))]
|
||||
fn image_to_bytes(_: impl Ctx, image: Table<Raster<CPU>>) -> Vec<u8> {
|
||||
let Some(image) = image.element(0) else { return vec![] };
|
||||
image.data.iter().flat_map(|color| color.to_rgba8_srgb().into_iter()).collect::<Vec<u8>>()
|
||||
fn image_to_bytes(_: impl Ctx, image: Table<Raster<CPU>>) -> Table<u8> {
|
||||
let Some(image) = image.element(0) else { return Table::new() };
|
||||
image.data.iter().flat_map(|color| color.to_rgba8_srgb()).map(TableRow::new_from_element).collect()
|
||||
}
|
||||
|
||||
/// Loads binary from URLs and local asset paths. Returns a transparent placeholder if the resource fails to load, allowing rendering to continue.
|
||||
@@ -188,7 +189,6 @@ async fn rasterize<T: WasmNotSend + Clone + 'n>(
|
||||
where
|
||||
Table<T>: Render + Clone + graphic_types::IntoGraphicTable,
|
||||
{
|
||||
use core_types::table::TableRow;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
if footprint.transform.matrix2.determinant() == 0. {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use core_types::Ctx;
|
||||
use core_types::table::{Table, TableRow};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::unescape_string;
|
||||
@@ -237,15 +238,15 @@ fn query_json_all(
|
||||
/// Strips the surrounding double quotes from string values, returning the raw text. Other types are never wrapped in quotes.
|
||||
#[default(true)]
|
||||
unquote_strings: bool,
|
||||
) -> Vec<String> {
|
||||
) -> Table<String> {
|
||||
let cleaned = strip_trailing_commas(&json);
|
||||
let Ok(value): Result<Value, _> = serde_json::from_str(&cleaned) else { return Vec::new() };
|
||||
let Some(segments) = parse_json_path(path.trim()) else { return Vec::new() };
|
||||
let Ok(value): Result<Value, _> = serde_json::from_str(&cleaned) else { return Table::new() };
|
||||
let Some(segments) = parse_json_path(path.trim()) else { return Table::new() };
|
||||
|
||||
let mut results = Vec::new();
|
||||
resolve_all(&value, &segments, !unquote_strings, &mut results);
|
||||
|
||||
results
|
||||
results.into_iter().map(TableRow::new_from_element).collect()
|
||||
}
|
||||
|
||||
/// A parsed segment of a JSON access path.
|
||||
|
||||
@@ -9,7 +9,7 @@ use convert_case::{Boundary, Converter, pattern};
|
||||
use core_types::Color;
|
||||
use core_types::graphene_hash::CacheHash;
|
||||
use core_types::registry::types::{SignedInteger, TextArea};
|
||||
use core_types::table::Table;
|
||||
use core_types::table::{Table, TableRow};
|
||||
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractVarArgs, OwnedContextImpl};
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -700,10 +700,10 @@ fn string_split(
|
||||
/// "\n" (newline), "\r" (carriage return), "\t" (tab), "\0" (null), and "\\" (backslash).
|
||||
#[default(true)]
|
||||
delimiter_escaping: bool,
|
||||
) -> Vec<String> {
|
||||
) -> Table<String> {
|
||||
let delimiter = if delimiter_escaping { unescape_string(delimiter) } else { delimiter };
|
||||
|
||||
string.split(&delimiter).map(str::to_string).collect()
|
||||
string.split(&delimiter).map(str::to_string).map(TableRow::new_from_element).collect()
|
||||
}
|
||||
|
||||
/// Joins a list of strings together with a separator between each pair. This is the inverse of the **String Split** node.
|
||||
@@ -713,7 +713,7 @@ fn string_split(
|
||||
fn string_join(
|
||||
_: impl Ctx,
|
||||
/// The list of strings to join together.
|
||||
strings: Vec<String>,
|
||||
strings: Table<String>,
|
||||
/// The text placed between each pair of strings.
|
||||
#[default(", ")]
|
||||
separator: String,
|
||||
@@ -724,26 +724,27 @@ fn string_join(
|
||||
) -> String {
|
||||
let separator = if separator_escaping { unescape_string(separator) } else { separator };
|
||||
|
||||
strings.join(&separator)
|
||||
strings.iter_element_values().map(|s| s.as_str()).collect::<Vec<_>>().join(&separator)
|
||||
}
|
||||
|
||||
/// Iterates over a list of strings, evaluating the mapped operation for each one. Use the **Read String** node to access the current string inside the loop.
|
||||
#[node_macro::node(category("Text"))]
|
||||
async fn map_string(
|
||||
ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
||||
strings: Vec<String>,
|
||||
strings: Table<String>,
|
||||
#[expose]
|
||||
#[implementations(Context -> String)]
|
||||
mapped: impl Node<Context<'static>, Output = String>,
|
||||
) -> Vec<String> {
|
||||
let mut result = Vec::new();
|
||||
) -> Table<String> {
|
||||
let mut result = Table::new();
|
||||
|
||||
for (i, string) in strings.into_iter().enumerate() {
|
||||
for (i, row) in strings.into_iter().enumerate() {
|
||||
let string = row.into_element();
|
||||
let owned_ctx = OwnedContextImpl::from(ctx.clone());
|
||||
let owned_ctx = owned_ctx.with_vararg(Box::new(string)).with_index(i);
|
||||
let mapped_strings = mapped.eval(owned_ctx.into_context()).await;
|
||||
let mapped_string = mapped.eval(owned_ctx.into_context()).await;
|
||||
|
||||
result.push(mapped_strings);
|
||||
result.push(TableRow::new_from_element(mapped_string));
|
||||
}
|
||||
|
||||
result
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use core_types::Ctx;
|
||||
use core_types::registry::types::SignedInteger;
|
||||
use core_types::table::{Table, TableRow};
|
||||
|
||||
/// Checks whether the string contains a match for the given regular expression pattern. Optionally restricts the match to only the start and/or end of the string.
|
||||
#[node_macro::node(category("Text: Regex"))]
|
||||
@@ -92,9 +93,9 @@ fn regex_find(
|
||||
case_insensitive: bool,
|
||||
/// Make `^` and `$` match the start and end of each line, not just the whole string.
|
||||
multiline: bool,
|
||||
) -> Vec<String> {
|
||||
) -> Table<String> {
|
||||
if pattern.is_empty() {
|
||||
return Vec::new();
|
||||
return Table::new();
|
||||
}
|
||||
|
||||
let flags = match (case_insensitive, multiline) {
|
||||
@@ -107,7 +108,7 @@ fn regex_find(
|
||||
|
||||
let Ok(regex) = fancy_regex::Regex::new(&full_pattern) else {
|
||||
log::error!("Invalid regex pattern: {pattern}");
|
||||
return Vec::new();
|
||||
return Table::new();
|
||||
};
|
||||
|
||||
// Collect all matches since we need to support negative indexing
|
||||
@@ -117,7 +118,7 @@ fn regex_find(
|
||||
let resolved_index = if match_index < 0 {
|
||||
let from_end = (-match_index) as usize;
|
||||
if from_end > matches.len() {
|
||||
return Vec::new();
|
||||
return Table::new();
|
||||
}
|
||||
matches.len() - from_end
|
||||
} else {
|
||||
@@ -125,11 +126,14 @@ fn regex_find(
|
||||
};
|
||||
|
||||
let Some(captures) = matches.get(resolved_index) else {
|
||||
return Vec::new();
|
||||
return Table::new();
|
||||
};
|
||||
|
||||
// Index 0 is the whole match, 1+ are capture groups
|
||||
(0..captures.len()).map(|i| captures.get(i).map_or(String::new(), |m| m.as_str().to_string())).collect()
|
||||
(0..captures.len())
|
||||
.map(|i| captures.get(i).map_or(String::new(), |m| m.as_str().to_string()))
|
||||
.map(TableRow::new_from_element)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Finds all non-overlapping matches of a regular expression pattern in the string, returning a list of the matched substrings.
|
||||
@@ -144,9 +148,9 @@ fn regex_find_all(
|
||||
case_insensitive: bool,
|
||||
/// Make `^` and `$` match the start and end of each line, not just the whole string.
|
||||
multiline: bool,
|
||||
) -> Vec<String> {
|
||||
) -> Table<String> {
|
||||
if pattern.is_empty() {
|
||||
return Vec::new();
|
||||
return Table::new();
|
||||
}
|
||||
|
||||
let flags = match (case_insensitive, multiline) {
|
||||
@@ -159,10 +163,15 @@ fn regex_find_all(
|
||||
|
||||
let Ok(regex) = fancy_regex::Regex::new(&full_pattern) else {
|
||||
log::error!("Invalid regex pattern: {pattern}");
|
||||
return Vec::new();
|
||||
return Table::new();
|
||||
};
|
||||
|
||||
regex.find_iter(&string).filter_map(|m| m.ok()).map(|m| m.as_str().to_string()).collect()
|
||||
regex
|
||||
.find_iter(&string)
|
||||
.filter_map(|m| m.ok())
|
||||
.map(|m| m.as_str().to_string())
|
||||
.map(TableRow::new_from_element)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Splits a string into a list of substrings pulled from between separator characters as matched by a regular expression.
|
||||
@@ -179,9 +188,9 @@ fn regex_split(
|
||||
case_insensitive: bool,
|
||||
/// Make `^` and `$` match the start and end of each line, not just the whole string.
|
||||
multiline: bool,
|
||||
) -> Vec<String> {
|
||||
) -> Table<String> {
|
||||
if pattern.is_empty() {
|
||||
return vec![string];
|
||||
return Table::new_from_element(string);
|
||||
}
|
||||
|
||||
let flags = match (case_insensitive, multiline) {
|
||||
@@ -194,8 +203,8 @@ fn regex_split(
|
||||
|
||||
let Ok(regex) = fancy_regex::Regex::new(&full_pattern) else {
|
||||
log::error!("Invalid regex pattern: {pattern}");
|
||||
return vec![string];
|
||||
return Table::new_from_element(string);
|
||||
};
|
||||
|
||||
regex.split(&string).filter_map(|s| s.ok()).map(|s| s.to_string()).collect()
|
||||
regex.split(&string).filter_map(|s| s.ok()).map(|s| s.to_string()).map(TableRow::new_from_element).collect()
|
||||
}
|
||||
|
||||
@@ -18,22 +18,37 @@ impl CornerRadius for f64 {
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rectangle(size / -2., size / 2., [clamped_radius; 4])))
|
||||
}
|
||||
}
|
||||
impl CornerRadius for [f64; 4] {
|
||||
impl CornerRadius for Table<f64> {
|
||||
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector> {
|
||||
// Expand to four corners using the CSS `border-radius` shorthand rules.
|
||||
// - `[a]` → `[a, a, a, a]`
|
||||
// - `[a, b]` → `[a, b, a, b]`
|
||||
// - `[a, b, c]` → `[a, b, c, b]`
|
||||
// - `[a, b, c, d, …]` → `[a, b, c, d]`
|
||||
// - `[]` → `[0, 0, 0, 0]`
|
||||
let values: Vec<f64> = self.iter_element_values().copied().collect();
|
||||
let radii: [f64; 4] = match values.as_slice() {
|
||||
[] => [0., 0., 0., 0.],
|
||||
&[a] => [a, a, a, a],
|
||||
&[a, b] => [a, b, a, b],
|
||||
&[a, b, c] => [a, b, c, b],
|
||||
&[a, b, c, d, ..] => [a, b, c, d],
|
||||
};
|
||||
|
||||
let clamped_radius = if clamped {
|
||||
// Algorithm follows the CSS spec: <https://drafts.csswg.org/css-backgrounds/#corner-overlap>
|
||||
|
||||
let mut scale_factor: f64 = 1.;
|
||||
for i in 0..4 {
|
||||
let side_length = if i % 2 == 0 { size.x } else { size.y };
|
||||
let adjacent_corner_radius_sum = self[i] + self[(i + 1) % 4];
|
||||
let adjacent_corner_radius_sum = radii[i] + radii[(i + 1) % 4];
|
||||
if side_length < adjacent_corner_radius_sum {
|
||||
scale_factor = scale_factor.min(side_length / adjacent_corner_radius_sum);
|
||||
}
|
||||
}
|
||||
self.map(|x| x * scale_factor)
|
||||
radii.map(|x| x * scale_factor)
|
||||
} else {
|
||||
self
|
||||
radii
|
||||
};
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rectangle(size / -2., size / 2., clamped_radius)))
|
||||
}
|
||||
@@ -140,7 +155,7 @@ fn rectangle<T: CornerRadius>(
|
||||
#[default(100)]
|
||||
height: f64,
|
||||
_individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability
|
||||
#[implementations(f64, [f64; 4])] corner_radius: T,
|
||||
#[implementations(f64, Table<f64>)] corner_radius: T,
|
||||
#[default(true)] clamped: bool,
|
||||
) -> Table<Vector> {
|
||||
corner_radius.generate(DVec2::new(width, height), clamped)
|
||||
|
||||
@@ -7,7 +7,7 @@ use vector_types::vector::VectorModification;
|
||||
|
||||
/// Applies a differential modification to a vector path, associating changes made by the Pen and Path tools to indices of edited points and segments.
|
||||
#[node_macro::node(category(""))]
|
||||
async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> Table<Vector> {
|
||||
async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Box<VectorModification>, node_path: Table<NodeId>) -> Table<Vector> {
|
||||
use core_types::table::TableRow;
|
||||
|
||||
if vector.is_empty() {
|
||||
@@ -15,8 +15,11 @@ async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Bo
|
||||
}
|
||||
modification.apply(vector.element_mut(0).expect("push should give one item"));
|
||||
|
||||
// Update the source node id
|
||||
let this_node_path = node_path.iter().rev().nth(1).copied();
|
||||
// Update the source node id (penultimate element in the path, identifying the user-facing layer node)
|
||||
let this_node_path = {
|
||||
let index = node_path.len().wrapping_sub(2);
|
||||
node_path.element(index).copied()
|
||||
};
|
||||
let existing: Option<NodeId> = vector.attribute_cloned_or_default("editor:layer", 0);
|
||||
vector.set_attribute("editor:layer", 0, existing.or(this_node_path));
|
||||
|
||||
|
||||
@@ -179,9 +179,9 @@ impl IntoF64Vec for f64 {
|
||||
vec![self]
|
||||
}
|
||||
}
|
||||
impl IntoF64Vec for Vec<f64> {
|
||||
impl IntoF64Vec for Table<f64> {
|
||||
fn into_vec(self) -> Vec<f64> {
|
||||
self
|
||||
self.into_iter().map(|row| row.into_element()).collect()
|
||||
}
|
||||
}
|
||||
impl IntoF64Vec for String {
|
||||
@@ -217,7 +217,7 @@ async fn stroke<V, L: IntoF64Vec>(
|
||||
/// The order to paint the stroke on top of the fill, or the fill on top of the stroke.
|
||||
paint_order: PaintOrder,
|
||||
/// The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.
|
||||
#[implementations(Vec<f64>, f64, String, Vec<f64>, f64, String)]
|
||||
#[implementations(Table<f64>, f64, String, Table<f64>, f64, String)]
|
||||
dash_lengths: L,
|
||||
/// The phase offset distance from the starting point of the dash pattern.
|
||||
#[unit(" px")]
|
||||
@@ -2850,11 +2850,6 @@ impl<T> Count for Table<T> {
|
||||
self.len()
|
||||
}
|
||||
}
|
||||
impl<T> Count for Vec<T> {
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
}
|
||||
|
||||
// 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.)
|
||||
@@ -2868,9 +2863,10 @@ async fn count_elements<I: Count>(
|
||||
Table<Raster<GPU>>,
|
||||
Table<Color>,
|
||||
Table<GradientStops>,
|
||||
Vec<String>,
|
||||
Vec<f64>,
|
||||
Vec<DVec2>,
|
||||
Table<String>,
|
||||
Table<f64>,
|
||||
Table<u8>,
|
||||
Table<NodeId>,
|
||||
)]
|
||||
content: I,
|
||||
) -> f64 {
|
||||
|
||||
Reference in New Issue
Block a user