mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
New nodes: 'Attach Attribute' and 'Read Attribute *' (#4100)
* New nodes: 'Zip Attribute' and 'Read Attribute {Vector, Number, Bool, String, Transform, Color, Blend Mode, Gradient Type, Spread Method}
* Cleanup
* Reduce type explosion
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use core::f64;
|
||||
use core_types::context::{CloneVarArgs, Context, ContextFeatures, Ctx, ExtractAll};
|
||||
use core_types::table::Table;
|
||||
use core_types::table::{AttributeColumnDyn, AttributeValueDyn, Table, TableDyn};
|
||||
use core_types::transform::Footprint;
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{Color, OwnedContextImpl};
|
||||
@@ -37,6 +37,9 @@ async fn context_modification<T>(
|
||||
Context -> Table<Color>,
|
||||
Context -> Table<Artboard>,
|
||||
Context -> Table<GradientStops>,
|
||||
Context -> AttributeColumnDyn,
|
||||
Context -> AttributeValueDyn,
|
||||
Context -> TableDyn,
|
||||
)]
|
||||
value: impl Node<Context<'static>, Output = T>,
|
||||
/// The parts of the context to keep when evaluating the input value. All other parts are nullified.
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use core_types::registry::types::{Angle, SignedInteger};
|
||||
use core_types::table::{Table, TableRow};
|
||||
use core_types::table::{AttributeColumnDyn, AttributeValueDyn, Table, TableDyn, TableRow};
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, AnyHash, CacheHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||
use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, AnyHash, BlendMode, CacheHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphic_types::graphic::{Graphic, IntoGraphicTable};
|
||||
use graphic_types::{Artboard, Vector};
|
||||
use raster_types::{CPU, GPU, Raster};
|
||||
use vector_types::gradient::{GradientSpreadMethod, GradientType};
|
||||
use vector_types::{GradientStop, GradientStops, ReferencePoint};
|
||||
|
||||
/// Returns the value at the specified index in the list.
|
||||
@@ -216,47 +217,279 @@ pub fn path_of_subgraph(_: impl Ctx, node_path: Table<NodeId>) -> Table<NodeId>
|
||||
node_path.into_iter().take(len.saturating_sub(1)).collect()
|
||||
}
|
||||
|
||||
/// Writes a named attribute on each item of the input `Table`. The value-producing input is evaluated once per item,
|
||||
/// with the item's index and the item itself (as a `Table` containing only that item, passed as a vararg) provided via
|
||||
/// context, so the upstream pipeline can return a different value per item that may be derived from the item's own data.
|
||||
/// If the attribute already exists, its values are replaced; if not, the attribute is added.
|
||||
#[node_macro::node(category("General"))]
|
||||
async fn write_attribute<T: AnyHash + Clone + Send + Sync + CacheHash, U: Clone + Send + Sync + Default + std::fmt::Debug + PartialEq + CacheHash + 'static>(
|
||||
/// Sets a named attribute on the input `Table`, computing one value per item via the value-producing input. That input
|
||||
/// is evaluated once per item, with the item's index and the item itself (as a `Table` containing only that item,
|
||||
/// passed as a vararg) provided via context, so the upstream pipeline can return a different value per item that may
|
||||
/// be derived from the item's own data. If the attribute already exists, its values are replaced; if not, it's added.
|
||||
/// The value is type-erased into an `AttributeValueDyn` by an auto-inserted convert node, so this node only
|
||||
/// monomorphizes over `T` instead of the cartesian product `(T, U)`.
|
||||
#[node_macro::node(category("Attributes: Write"))]
|
||||
async fn write_attribute<T: AnyHash + Clone + Send + Sync + CacheHash>(
|
||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||
/// The `Table` whose items will gain or have replaced the named attribute.
|
||||
/// The `Table` to set the named attribute on (one value per item).
|
||||
#[implementations(
|
||||
Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>,
|
||||
Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>,
|
||||
Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>,
|
||||
Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>,
|
||||
Table<Raster<GPU>>, Table<Raster<GPU>>, Table<Raster<GPU>>, Table<Raster<GPU>>, Table<Raster<GPU>>, Table<Raster<GPU>>, Table<Raster<GPU>>, Table<Raster<GPU>>, Table<Raster<GPU>>, Table<Raster<GPU>>,
|
||||
Table<Color>, Table<Color>, Table<Color>, Table<Color>, Table<Color>, Table<Color>, Table<Color>, Table<Color>, Table<Color>, Table<Color>,
|
||||
Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>,
|
||||
Table<Artboard>,
|
||||
Table<Graphic>,
|
||||
Table<Vector>,
|
||||
Table<Raster<CPU>>,
|
||||
Table<Color>,
|
||||
Table<GradientStops>,
|
||||
Table<f64>,
|
||||
Table<bool>,
|
||||
Table<String>,
|
||||
Table<DAffine2>,
|
||||
Table<BlendMode>,
|
||||
Table<GradientType>,
|
||||
Table<GradientSpreadMethod>,
|
||||
)]
|
||||
mut content: Table<T>,
|
||||
/// The attribute name (key) to write or replace.
|
||||
name: String,
|
||||
/// The node that produces the attribute value for each item. Called once per item with the item's index in context.
|
||||
#[implementations(
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||
)]
|
||||
value: impl Node<'n, Context<'static>, Output = U>,
|
||||
#[implementations(Context -> AttributeValueDyn)]
|
||||
value: impl Node<'n, Context<'static>, Output = AttributeValueDyn>,
|
||||
) -> Table<T> {
|
||||
for index in 0..content.len() {
|
||||
let row = content.clone_row(index).expect("index is within bounds");
|
||||
let owned_ctx = OwnedContextImpl::from(ctx.clone()).with_vararg(Box::new(Table::new_from_row(row))).with_index(index);
|
||||
let v = value.eval(owned_ctx.into_context()).await;
|
||||
content.set_attribute(&name, index, v);
|
||||
content.set_attribute_dyn(&name, index, v);
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
/// Sets a named attribute on the primary table, with each value taken from the corresponding item's element in the source table (paired by index, wrapping if the source has fewer items).
|
||||
/// The source is type-erased into an `AttributeColumnDyn` by an auto-inserted convert node, so this node only monomorphizes over `T` instead of the cartesian product `(T, U)`.
|
||||
#[node_macro::node(category("Attributes: Write"))]
|
||||
fn attach_attribute<T: AnyHash + Clone + Send + Sync + CacheHash>(
|
||||
_: impl Ctx,
|
||||
/// The `Table` to attach the new attribute to.
|
||||
#[implementations(
|
||||
Table<Artboard>,
|
||||
Table<Graphic>,
|
||||
Table<Vector>,
|
||||
Table<Raster<CPU>>,
|
||||
Table<Color>,
|
||||
Table<GradientStops>,
|
||||
Table<f64>,
|
||||
Table<bool>,
|
||||
Table<String>,
|
||||
Table<DAffine2>,
|
||||
Table<BlendMode>,
|
||||
Table<GradientType>,
|
||||
Table<GradientSpreadMethod>,
|
||||
)]
|
||||
mut content: Table<T>,
|
||||
/// The source values to attach. Any `Table<U>` wired here is type-erased via an auto-inserted convert.
|
||||
#[expose]
|
||||
source: AttributeColumnDyn,
|
||||
/// The name to assign to the new destination attribute.
|
||||
name: String,
|
||||
) -> Table<T> {
|
||||
if source.is_empty() {
|
||||
return content;
|
||||
}
|
||||
content.set_column_dyn(name, source);
|
||||
content
|
||||
}
|
||||
|
||||
/// Reads a named `Vector` attribute from the input table, outputting each value as an element of a new `Table<Vector>`.
|
||||
#[node_macro::node(category("Attributes: Read"))]
|
||||
fn read_attribute_vector(
|
||||
_: impl Ctx,
|
||||
content: TableDyn,
|
||||
/// The attribute name (key) to read.
|
||||
name: String,
|
||||
) -> Table<Vector> {
|
||||
let mut result = Table::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<Vector>(&name, index) else { continue };
|
||||
result.push(TableRow::new_from_element(value.clone()));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Reads a named numeric attribute (`f64`, `u64`, or `u32`) from the input table, outputting each value as an element of a new `Table<f64>`. Integer values are converted to `f64`.
|
||||
#[node_macro::node(category("Attributes: Read"))]
|
||||
fn read_attribute_number(
|
||||
_: impl Ctx,
|
||||
content: TableDyn,
|
||||
/// The attribute name (key) to read.
|
||||
name: String,
|
||||
) -> Table<f64> {
|
||||
let mut result = Table::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let value = content
|
||||
.attribute::<f64>(&name, index)
|
||||
.copied()
|
||||
.or_else(|| content.attribute::<u64>(&name, index).map(|v| *v as f64))
|
||||
.or_else(|| content.attribute::<u32>(&name, index).map(|v| *v as f64));
|
||||
let Some(value) = value else { continue };
|
||||
result.push(TableRow::new_from_element(value));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Reads a named `bool` attribute from the input table, outputting each value as an element of a new `Table<bool>`.
|
||||
#[node_macro::node(category("Attributes: Read"))]
|
||||
fn read_attribute_bool(
|
||||
_: impl Ctx,
|
||||
content: TableDyn,
|
||||
/// The attribute name (key) to read.
|
||||
name: String,
|
||||
) -> Table<bool> {
|
||||
let mut result = Table::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<bool>(&name, index) else { continue };
|
||||
result.push(TableRow::new_from_element(*value));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Reads a named `String` attribute from the input table, outputting each value as an element of a new `Table<String>`.
|
||||
#[node_macro::node(category("Attributes: Read"))]
|
||||
fn read_attribute_string(
|
||||
_: impl Ctx,
|
||||
content: TableDyn,
|
||||
/// The attribute name (key) to read.
|
||||
name: String,
|
||||
) -> Table<String> {
|
||||
let mut result = Table::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<String>(&name, index) else { continue };
|
||||
result.push(TableRow::new_from_element(value.clone()));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Reads a named `DAffine2` transform attribute from the input table, outputting each value as an element of a new `Table<DAffine2>`.
|
||||
#[node_macro::node(category("Attributes: Read"))]
|
||||
fn read_attribute_transform(
|
||||
_: impl Ctx,
|
||||
content: TableDyn,
|
||||
/// The attribute name (key) to read.
|
||||
name: String,
|
||||
) -> Table<DAffine2> {
|
||||
let mut result = Table::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<DAffine2>(&name, index) else { continue };
|
||||
result.push(TableRow::new_from_element(*value));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Reads a named `Color` attribute from the input table, outputting each value as an element of a new `Table<Color>`.
|
||||
#[node_macro::node(category("Attributes: Read"))]
|
||||
fn read_attribute_color(
|
||||
_: impl Ctx,
|
||||
content: TableDyn,
|
||||
/// The attribute name (key) to read.
|
||||
name: String,
|
||||
) -> Table<Color> {
|
||||
let mut result = Table::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<Color>(&name, index) else { continue };
|
||||
result.push(TableRow::new_from_element(*value));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Reads a named `BlendMode` attribute from the input table, outputting each value as an element of a new `Table<BlendMode>`.
|
||||
#[node_macro::node(category("Attributes: Read"))]
|
||||
fn read_attribute_blend_mode(
|
||||
_: impl Ctx,
|
||||
content: TableDyn,
|
||||
/// The attribute name (key) to read.
|
||||
name: String,
|
||||
) -> Table<BlendMode> {
|
||||
let mut result = Table::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<BlendMode>(&name, index) else { continue };
|
||||
result.push(TableRow::new_from_element(*value));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Reads a named `GradientType` attribute from the input table, outputting each value as an element of a new `Table<GradientType>`.
|
||||
#[node_macro::node(category("Attributes: Read"))]
|
||||
fn read_attribute_gradient_type(
|
||||
_: impl Ctx,
|
||||
content: TableDyn,
|
||||
/// The attribute name (key) to read.
|
||||
name: String,
|
||||
) -> Table<GradientType> {
|
||||
let mut result = Table::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<GradientType>(&name, index) else { continue };
|
||||
result.push(TableRow::new_from_element(*value));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Reads a named `GradientSpreadMethod` attribute from the input table, outputting each value as an element of a new `Table<GradientSpreadMethod>`.
|
||||
#[node_macro::node(category("Attributes: Read"))]
|
||||
fn read_attribute_spread_method(
|
||||
_: impl Ctx,
|
||||
content: TableDyn,
|
||||
/// The attribute name (key) to read.
|
||||
name: String,
|
||||
) -> Table<GradientSpreadMethod> {
|
||||
let mut result = Table::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<GradientSpreadMethod>(&name, index) else { continue };
|
||||
result.push(TableRow::new_from_element(*value));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Reads a named `GradientStops` attribute from the input table, outputting each value as an element of a new `Table<GradientStops>`.
|
||||
#[node_macro::node(category("Attributes: Read"))]
|
||||
fn read_attribute_gradient_stops(
|
||||
_: impl Ctx,
|
||||
content: TableDyn,
|
||||
/// The attribute name (key) to read.
|
||||
name: String,
|
||||
) -> Table<GradientStops> {
|
||||
let mut result = Table::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<GradientStops>(&name, index) else { continue };
|
||||
result.push(TableRow::new_from_element(value.clone()));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Reads a named `Artboard` attribute from the input table, outputting each value as an element of a new `Table<Artboard>`.
|
||||
#[node_macro::node(category("Attributes: Read"))]
|
||||
fn read_attribute_artboard(
|
||||
_: impl Ctx,
|
||||
content: TableDyn,
|
||||
/// The attribute name (key) to read.
|
||||
name: String,
|
||||
) -> Table<Artboard> {
|
||||
let mut result = Table::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<Artboard>(&name, index) else { continue };
|
||||
result.push(TableRow::new_from_element(value.clone()));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Reads a named `Raster<CPU>` attribute from the input table, outputting each value as an element of a new `Table<Raster<CPU>>`.
|
||||
#[node_macro::node(category("Attributes: Read"))]
|
||||
fn read_attribute_raster(
|
||||
_: impl Ctx,
|
||||
content: TableDyn,
|
||||
/// The attribute name (key) to read.
|
||||
name: String,
|
||||
) -> Table<Raster<CPU>> {
|
||||
let mut result = Table::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<Raster<CPU>>(&name, index) else { continue };
|
||||
result.push(TableRow::new_from_element(value.clone()));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Joins two `Table`s of the same type, extending the base `Table` with the items from the new `Table`.
|
||||
#[node_macro::node(category("General"))]
|
||||
pub async fn extend<T: 'n + Send + Clone>(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use core::f64;
|
||||
use core_types::color::Color;
|
||||
use core_types::table::Table;
|
||||
use core_types::table::{Table, TableDyn};
|
||||
use core_types::transform::{ApplyTransform, ScaleType, Transform};
|
||||
use core_types::{ATTR_TRANSFORM, CloneVarArgs, Context, Ctx, ExtractAll, InjectFootprint, ModifyFootprint, OwnedContextImpl};
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
@@ -113,19 +113,8 @@ fn replace_transform<T>(
|
||||
// TODO: Figure out how this node should behave once #2982 is implemented.
|
||||
/// Obtains the transform of the first item in the input `Table`, if present.
|
||||
#[node_macro::node(category("Math: Transform"), path(core_types::vector))]
|
||||
async fn extract_transform<T>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
Table<Graphic>,
|
||||
Table<Vector>,
|
||||
Table<Raster<CPU>>,
|
||||
Table<Raster<GPU>>,
|
||||
Table<Color>,
|
||||
Table<GradientStops>,
|
||||
)]
|
||||
content: Table<T>,
|
||||
) -> DAffine2 {
|
||||
content.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0)
|
||||
async fn extract_transform(_: impl Ctx, content: TableDyn) -> DAffine2 {
|
||||
content.attribute::<DAffine2>(ATTR_TRANSFORM, 0).copied().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Produces the inverse of the input transform, which is the transform that undoes the effect of the original transform.
|
||||
|
||||
@@ -4,7 +4,7 @@ use core::hash::{Hash, Hasher};
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
|
||||
use core_types::table::{Table, TableRow};
|
||||
use core_types::table::{Table, TableDyn, TableRow};
|
||||
use core_types::transform::{Footprint, Transform};
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{
|
||||
@@ -2883,35 +2883,11 @@ fn point_inside(_: impl Ctx, source: Table<Vector>, point: DVec2) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
trait Count {
|
||||
fn count(&self) -> usize;
|
||||
}
|
||||
impl<T> Count for Table<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.)
|
||||
#[node_macro::node(category("General"), path(graphene_core::vector))]
|
||||
async fn count_elements<I: Count>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
Table<Graphic>,
|
||||
Table<Vector>,
|
||||
Table<Raster<CPU>>,
|
||||
Table<Raster<GPU>>,
|
||||
Table<Color>,
|
||||
Table<GradientStops>,
|
||||
Table<String>,
|
||||
Table<f64>,
|
||||
Table<u8>,
|
||||
Table<NodeId>,
|
||||
)]
|
||||
content: I,
|
||||
) -> f64 {
|
||||
content.count() as f64
|
||||
async fn count_elements(_: impl Ctx, content: TableDyn) -> f64 {
|
||||
content.len() as f64
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))]
|
||||
|
||||
Reference in New Issue
Block a user