Rename the "Table" type to "List" everywhere (#4133)

* Rename the "Table" type to "List" everywhere

* Fix a few missed ones

* Re-save demo artwork
This commit is contained in:
Keavon Chambers
2026-05-09 01:33:39 -07:00
committed by GitHub
parent 6b3e4757de
commit a28b9437aa
79 changed files with 1571 additions and 1591 deletions

View File

@@ -15,7 +15,7 @@ pub trait BoundingBox {
/// Returns the bounding box to use when sizing this value's thumbnail in the Layers panel.
///
/// Diverges from `bounding_box` for types where the rendering bounds wouldn't make a useful thumbnail frame.
/// For instance, `GradientStops` is `Infinite` for rendering but returns the line's AABB here, so a `Table<Graphic>`
/// For instance, `GradientStops` is `Infinite` for rendering but returns the line's AABB here, so a `List<Graphic>`
/// group of a gradient and a vector frames around the vector's geometry rather than infinity.
/// Types with no meaningful contribution (e.g., `Color`) return `Infinite` from both; the runtime substitutes a
/// small fallback rectangle at the end if no finite bounds remain after combining.

View File

@@ -4,13 +4,13 @@ pub mod bounds;
pub mod consts;
pub mod context;
pub mod generic;
pub mod list;
pub mod math;
pub mod memo;
pub mod misc;
pub mod ops;
pub mod registry;
pub mod render_complexity;
pub mod table;
pub mod transform;
pub mod uuid;
pub mod value;
@@ -23,6 +23,10 @@ pub use ctor;
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
pub use graphene_hash;
pub use graphene_hash::CacheHash;
pub use list::{
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_EDITOR_TEXT_FRAME, ATTR_END,
ATTR_GRADIENT_TYPE, ATTR_LOCATION, ATTR_NAME, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_START, ATTR_TRANSFORM, ATTR_TYPE,
};
pub use memo::MemoHash;
pub use no_std_types::AsU32;
pub use no_std_types::blending;
@@ -33,10 +37,6 @@ pub use num_traits;
use std::any::TypeId;
use std::future::Future;
use std::pin::Pin;
pub use table::{
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_EDITOR_TEXT_FRAME, ATTR_END,
ATTR_GRADIENT_TYPE, ATTR_LOCATION, ATTR_NAME, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_START, ATTR_TRANSFORM, ATTR_TYPE,
};
#[cfg(feature = "wasm")]
pub use tsify;
pub use types::Cow;

View File

@@ -27,11 +27,11 @@ pub const ATTR_OPACITY_FILL: &str = "opacity_fill";
/// `bool` for whether an item inherits the alpha of the content beneath it (clipping mask).
pub const ATTR_CLIPPING_MASK: &str = "clipping_mask";
/// `Table<NodeId>` path from the root network to the layer node owning this item.
/// `List<NodeId>` path from the root network to the layer node owning this item.
/// Used by editor tools to route clicks/selection back to the originating layer.
pub const ATTR_EDITOR_LAYER_PATH: &str = "editor:layer_path";
/// `Table<Graphic>` snapshot of the upstream content that fed into a destructive merge
/// `List<Graphic>` snapshot of the upstream content that fed into a destructive merge
/// (Boolean Operation, Rasterize, etc.), so the editor can still surface click targets for
/// the original child layers after their content has been collapsed.
pub const ATTR_EDITOR_MERGED_LAYERS: &str = "editor:merged_layers";
@@ -147,7 +147,7 @@ impl Clone for Box<dyn AnyAttributeValue> {
// TRAIT: AnyAttribute
// ===================
/// Enables type-erased storage for parallel attribute lists in a [`Table`].
/// Enables type-erased storage for parallel attribute lists in a [`List`].
pub trait AnyAttribute: std::any::Any + Send + Sync {
/// Clones this attribute into a new boxed trait object.
fn clone_box(&self) -> Box<dyn AnyAttribute>;
@@ -224,7 +224,7 @@ impl Clone for Box<dyn AnyAttribute> {
// Attribute<T>
// ============
/// Wraps a Vec<T> for attribute storage in a [`Table`].
/// Wraps a Vec<T> for attribute storage in a [`List`].
pub struct Attribute<T>(pub Vec<T>);
impl<T: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static> AnyAttribute for Attribute<T> {
@@ -329,7 +329,7 @@ impl<T: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static>
// ============
/// Type-erased list of attribute values, used as a node graph parameter type.
/// Lets a node accept any `Table<U>` source via the auto-inserted `Convert<AttributeDyn, ()>`
/// Lets a node accept any `List<U>` source via the auto-inserted `Convert<AttributeDyn, ()>`
/// without monomorphizing over `U` (so the cartesian product of `(content T, source U)` collapses to just `T`).
pub struct AttributeDyn(pub Box<dyn AnyAttribute>);
@@ -439,26 +439,26 @@ unsafe impl StaticType for AttributeValueDyn {
type Static = Self;
}
// ========
// TableDyn
// ========
// =======
// ListDyn
// =======
/// Type-erased view of a `Table<T>` exposing only its attributes and item count, used as a node graph parameter type.
/// Lets a node accept any `Table<U>` source via the auto-inserted `Convert<TableDyn, ()>` without monomorphizing over `U`,
/// for cases where the element type is irrelevant (such as nodes that read out a named attribute regardless of the carrier table).
/// Type-erased view of a `List<T>` exposing only its attributes and item count, used as a node graph parameter type.
/// Lets a node accept any `List<U>` source via the auto-inserted `Convert<ListDyn, ()>` without monomorphizing over `U`,
/// for cases where the element type is irrelevant (such as nodes that read out a named attribute regardless of the carrier `List`).
#[derive(Default)]
pub struct TableDyn {
pub struct ListDyn {
attributes: Vec<(String, Box<dyn AnyAttribute>)>,
len: usize,
}
impl TableDyn {
/// Number of items in the underlying table.
impl ListDyn {
/// Number of items in the underlying `List`.
pub fn len(&self) -> usize {
self.len
}
/// Whether the underlying table has zero items.
/// Whether the underlying `List` has zero items.
pub fn is_empty(&self) -> bool {
self.len == 0
}
@@ -471,16 +471,16 @@ impl TableDyn {
}
}
impl<T> From<Table<T>> for TableDyn {
fn from(table: Table<T>) -> Self {
impl<T> From<List<T>> for ListDyn {
fn from(list: List<T>) -> Self {
Self {
attributes: table.attributes.attributes,
len: table.attributes.len,
attributes: list.attributes.attributes,
len: list.attributes.len,
}
}
}
impl Clone for TableDyn {
impl Clone for ListDyn {
fn clone(&self) -> Self {
Self {
attributes: self.attributes.iter().map(|(key, attribute)| (key.clone(), attribute.clone_box())).collect(),
@@ -489,14 +489,14 @@ impl Clone for TableDyn {
}
}
impl Debug for TableDyn {
impl Debug for ListDyn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let keys: Vec<&str> = self.attributes.iter().map(|(k, _)| k.as_str()).collect();
f.debug_struct("TableDyn").field("keys", &keys).field("len", &self.len).finish()
f.debug_struct("ListDyn").field("keys", &keys).field("len", &self.len).finish()
}
}
impl PartialEq for TableDyn {
impl PartialEq for ListDyn {
fn eq(&self, other: &Self) -> bool {
self.len == other.len
&& self.attributes.len() == other.attributes.len()
@@ -508,7 +508,7 @@ impl PartialEq for TableDyn {
}
}
impl CacheHash for TableDyn {
impl CacheHash for ListDyn {
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.len.cache_hash(state);
for (key, attribute) in &self.attributes {
@@ -518,7 +518,7 @@ impl CacheHash for TableDyn {
}
}
unsafe impl StaticType for TableDyn {
unsafe impl StaticType for ListDyn {
type Static = Self;
}
@@ -631,7 +631,7 @@ impl ItemAttributeValues {
/// The storage data structure for attributes.
///
/// A collection of type-erased parallel attributes, keyed by string name.
/// All access goes through [`Table`] and [`Item`] since internals are private.
/// All access goes through [`List`] and [`Item`] since internals are private.
/// Invariant: every attribute in `attributes` has exactly `len` elements.
#[derive(Clone, Default)]
struct Attributes {
@@ -842,9 +842,9 @@ impl Attributes {
}
}
// ========
// Table<T>
// ========
// =======
// List<T>
// =======
/// A struct-of-arrays collection where each item holds an element of type `T` alongside
/// a set of type-erased, dynamically-typed attributes stored in parallel attributes.
@@ -853,18 +853,18 @@ impl Attributes {
/// [`Attributes`] store that keeps one attribute per attribute key. Items are accessed by
/// index through element/attribute accessor methods, or consumed as owned [`Item`]s via iteration.
#[derive(Clone, Debug)]
pub struct Table<T> {
pub struct List<T> {
element: Vec<T>,
attributes: Attributes,
}
impl<T> Table<T> {
/// Creates an empty table with no items.
impl<T> List<T> {
/// Creates an empty list with no items.
pub fn new() -> Self {
Self::default()
}
/// Creates an empty table with pre-allocated capacity for the given number of items.
/// Creates an empty list with pre-allocated capacity for the given number of items.
pub fn with_capacity(capacity: usize) -> Self {
Self {
element: Vec::with_capacity(capacity),
@@ -872,7 +872,7 @@ impl<T> Table<T> {
}
}
/// Creates a table containing a single item with the given element and no attributes.
/// Creates a list containing a single item with the given element and no attributes.
pub fn new_from_element(element: T) -> Self {
Self {
element: vec![element],
@@ -880,7 +880,7 @@ impl<T> Table<T> {
}
}
/// Creates a table containing a single item from the given [`Item`], preserving its attributes.
/// Creates a list containing a single item from the given [`Item`], preserving its attributes.
pub fn new_from_item(item: Item<T>) -> Self {
let mut attributes = Attributes::new();
attributes.push_item(item.attributes);
@@ -890,29 +890,29 @@ impl<T> Table<T> {
}
}
/// Appends an item to the end of this table.
/// Appends an item to the end of this list.
pub fn push(&mut self, item: Item<T>) {
self.element.push(item.element);
self.attributes.push_item(item.attributes);
}
/// Appends all items from another table into this one.
pub fn extend(&mut self, table: Table<T>) {
self.element.extend(table.element);
self.attributes.extend(table.attributes);
/// Appends all items from another list into this one.
pub fn extend(&mut self, list: List<T>) {
self.element.extend(list.element);
self.attributes.extend(list.attributes);
}
/// Returns the number of items in this table.
/// Returns the number of items in this list.
pub fn len(&self) -> usize {
self.element.len()
}
/// Returns `true` if this table contains no items.
/// Returns `true` if this list contains no items.
pub fn is_empty(&self) -> bool {
self.element.is_empty()
}
/// Returns an iterator over all attribute keys in this table, in insertion order.
/// Returns an iterator over all attribute keys in this list, in insertion order.
pub fn attribute_keys(&self) -> impl Iterator<Item = &str> {
self.attributes.keys()
}
@@ -991,7 +991,7 @@ impl<T> Table<T> {
self.attributes.set_value(key, index, value);
}
/// Replaces (or adds) an attribute from a type-erased source. The source is wrapped or truncated to match this table's item count.
/// Replaces (or adds) an attribute from a type-erased source. The source is wrapped or truncated to match this list's item count.
pub fn set_attribute_dyn(&mut self, key: impl Into<String>, source: AttributeDyn) {
let key = key.into();
self.attributes.attributes.retain(|(k, _)| k != &key);
@@ -999,7 +999,7 @@ impl<T> Table<T> {
self.attributes.attributes.push((key, new_attribute));
}
/// Sets a single type-erased attribute value at the given index, creating the attribute from the value's underlying type if it doesn't exist (padded with defaults to match the table's length).
/// Sets a single type-erased attribute value at the given index, creating the attribute from the value's underlying type if it doesn't exist (padded with defaults to match the list's length).
/// Falls back to default if the value's type doesn't match an existing attribute.
pub fn set_attribute_value_dyn(&mut self, key: impl Into<String>, index: usize, value: AttributeValueDyn) {
let key = key.into();
@@ -1069,7 +1069,7 @@ impl<T> Table<T> {
}
}
impl<T: BoundingBox> BoundingBox for Table<T> {
impl<T: BoundingBox> BoundingBox for List<T> {
/// Computes the combined bounding box of all items, composing each item's transform attribute with the given transform.
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
let mut combined_bounds = None;
@@ -1115,11 +1115,11 @@ impl<T: BoundingBox> BoundingBox for Table<T> {
}
}
impl<T> IntoIterator for Table<T> {
impl<T> IntoIterator for List<T> {
type Item = Item<T>;
type IntoIter = ItemIter<T>;
/// Consumes a [`Table`] and returns an iterator of [`Item`]s, each containing the owned data of the respective item from the original table.
/// Consumes a [`List`] and returns an iterator of [`Item`]s, each containing the owned data of the respective item from the original list.
fn into_iter(self) -> Self::IntoIter {
let attributes = self.attributes.into_item_vec();
ItemIter {
@@ -1129,7 +1129,7 @@ impl<T> IntoIterator for Table<T> {
}
}
impl<T> Default for Table<T> {
impl<T> Default for List<T> {
fn default() -> Self {
Self {
element: Vec::new(),
@@ -1138,7 +1138,7 @@ impl<T> Default for Table<T> {
}
}
impl<T: CacheHash> CacheHash for Table<T> {
impl<T: CacheHash> CacheHash for List<T> {
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.element.cache_hash(state);
@@ -1151,7 +1151,7 @@ impl<T: CacheHash> CacheHash for Table<T> {
}
}
impl<T: PartialEq> PartialEq for Table<T> {
impl<T: PartialEq> PartialEq for List<T> {
fn eq(&self, other: &Self) -> bool {
// Attributes participate in equality so the `a == b` ⇒ `hash(a) == hash(b)` contract holds with `cache_hash`
self.element == other.element
@@ -1165,7 +1165,7 @@ impl<T: PartialEq> PartialEq for Table<T> {
}
}
impl<T> ApplyTransform for Table<T> {
impl<T> ApplyTransform for List<T> {
/// Right-multiplies the modification into each item's transform attribute.
fn apply_transform(&mut self, modification: &DAffine2) {
for transform in self.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
@@ -1181,22 +1181,22 @@ impl<T> ApplyTransform for Table<T> {
}
}
unsafe impl<T: StaticTypeSized> StaticType for Table<T> {
type Static = Table<T::Static>;
unsafe impl<T: StaticTypeSized> StaticType for List<T> {
type Static = List<T::Static>;
}
impl<T> FromIterator<Item<T>> for Table<T> {
/// Collects an iterator of [`Item`]s into a [`Table`], pre-allocating based on the iterator's size hint.
impl<T> FromIterator<Item<T>> for List<T> {
/// Collects an iterator of [`Item`]s into a [`List`], pre-allocating based on the iterator's size hint.
fn from_iter<I: IntoIterator<Item = Item<T>>>(iter: I) -> Self {
let iter = iter.into_iter();
let (lower_bound, _) = iter.size_hint();
let mut table = Self::with_capacity(lower_bound);
let mut list = Self::with_capacity(lower_bound);
for item in iter {
table.push(item);
list.push(item);
}
table
list
}
}
@@ -1206,7 +1206,7 @@ impl<T> FromIterator<Item<T>> for Table<T> {
/// An owned item containing an element of type `T` and a set of type-erased scalar attributes.
///
/// Used to build individual items before pushing them into a [`Table`], or when consuming items out of a table via [`IntoIterator`].
/// Used to build individual items before pushing them into a [`List`], or when consuming items out of a list via [`IntoIterator`].
#[derive(Clone, Debug)]
pub struct Item<T> {
element: T,
@@ -1317,9 +1317,9 @@ impl<T> Item<T> {
// ItemIter<T>
// ===========
/// Owning iterator over the items of a consumed [`Table`], yielding [`Item`]s.
/// Owning iterator over the items of a consumed [`List`], yielding [`Item`]s.
///
/// Created by [`Table::into_iter`]. The table's attributes are converted into per-item
/// Created by [`List::into_iter`]. The list's attributes are converted into per-item
/// scalar [`ItemAttributeValues`] during construction so each yielded item is self-contained.
pub struct ItemIter<T> {
element: std::vec::IntoIter<T>,

View File

@@ -77,12 +77,12 @@ pub fn migrate_to_optional_color<'de, D: serde::Deserializer<'de>>(deserializer:
#[cfg_attr(feature = "serde", serde(untagged))]
enum ColorFormat {
OptionalColor(Option<Color>),
Table(LegacyTable<Color>),
List(LegacyTable<Color>),
}
Ok(match ColorFormat::deserialize(deserializer)? {
ColorFormat::OptionalColor(color) => color,
ColorFormat::Table(table) => table.element.into_iter().next(),
ColorFormat::List(list) => list.element.into_iter().next(),
})
}
@@ -94,11 +94,11 @@ pub fn migrate_to_f64_array<'de, D: serde::Deserializer<'de>>(deserializer: D) -
#[cfg_attr(feature = "serde", serde(untagged))]
enum F64ArrayFormat {
Array(Vec<f64>),
Table(LegacyTable<f64>),
List(LegacyTable<f64>),
}
Ok(match F64ArrayFormat::deserialize(deserializer)? {
F64ArrayFormat::Array(values) => values,
F64ArrayFormat::Table(table) => table.element,
F64ArrayFormat::List(list) => list.element,
})
}

View File

@@ -1,5 +1,5 @@
use crate::Node;
use crate::table::{Attribute, AttributeDyn, AttributeValueDyn, Item, Table, TableDyn};
use crate::list::{Attribute, AttributeDyn, AttributeValueDyn, Item, List, ListDyn};
use crate::transform::Footprint;
use glam::DVec2;
use graphene_hash::CacheHash;
@@ -55,27 +55,27 @@ impl<T: ToString + Send> Convert<String, ()> for T {
}
}
pub trait TableConvert<U> {
pub trait ListConvert<U> {
fn convert_row(self) -> U;
}
impl<U, T: TableConvert<U> + Send> Convert<Table<U>, ()> for Table<T> {
async fn convert(self, _: Footprint, _: ()) -> Table<U> {
let table: Table<U> = self
impl<U, T: ListConvert<U> + Send> Convert<List<U>, ()> for List<T> {
async fn convert(self, _: Footprint, _: ()) -> List<U> {
let list: List<U> = self
.into_iter()
.map(|row| {
let (element, attributes) = row.into_parts();
Item::from_parts(element.convert_row(), attributes)
})
.collect();
table
list
}
}
/// Wraps each row's element into a type-erased column. Lets nodes that accept a source attribute
/// from any `Table<U>` express their signature as `AttributeColumnDyn` and avoid monomorphizing
/// Wraps each row's element into a type-erased attribute. Lets nodes that accept a source attribute
/// from any `List<U>` express their signature as `AttributeDyn` and avoid monomorphizing
/// over `U`; the compiler inserts this convert to bridge concrete-typed graph wires to the dyn input.
impl<T: Clone + Send + Sync + Default + std::fmt::Debug + PartialEq + CacheHash + 'static> Convert<AttributeDyn, ()> for Table<T> {
impl<T: Clone + Send + Sync + Default + std::fmt::Debug + PartialEq + CacheHash + 'static> Convert<AttributeDyn, ()> for List<T> {
async fn convert(self, _: Footprint, _: ()) -> AttributeDyn {
let values: Vec<T> = self.into_iter().map(|row| row.into_element()).collect();
AttributeDyn(Box::new(Attribute(values)))
@@ -83,7 +83,7 @@ impl<T: Clone + Send + Sync + Default + std::fmt::Debug + PartialEq + CacheHash
}
/// Wraps a value into a type-erased attribute value. Lets nodes that take a per-item value source
/// (such as `write_attribute`'s value-producing input) be generic over the destination table type
/// (such as `write_attribute`'s value-producing input) be generic over the destination list type
/// alone, with the compiler-inserted convert handling each concrete value type at the wire level.
impl<T: Clone + Send + Sync + Default + std::fmt::Debug + PartialEq + CacheHash + 'static> Convert<AttributeValueDyn, ()> for T {
async fn convert(self, _: Footprint, _: ()) -> AttributeValueDyn {
@@ -91,11 +91,11 @@ impl<T: Clone + Send + Sync + Default + std::fmt::Debug + PartialEq + CacheHash
}
}
/// Erases a `Table<T>`'s element type, exposing only its attributes and row count. Lets nodes that
/// only need attribute access (such as the `read_attribute_*` family) take a single `TableDyn` input
/// instead of monomorphizing over every possible carrier table type.
impl<T: Send> Convert<TableDyn, ()> for Table<T> {
async fn convert(self, _: Footprint, _: ()) -> TableDyn {
/// Erases a `List<T>`'s element type, exposing only its attributes and row count. Lets nodes that
/// only need attribute access (such as the `read_attribute_*` family) take a single `ListDyn` input
/// instead of monomorphizing over every possible carrier list type.
impl<T: Send> Convert<ListDyn, ()> for List<T> {
async fn convert(self, _: Footprint, _: ()) -> ListDyn {
self.into()
}
}
@@ -106,7 +106,7 @@ impl Convert<DVec2, ()> for DVec2 {
}
}
// TODO: Add a DVec2 to Table<Vector> anchor point conversion implementation to replace the 'Vec2 to Point' node
// TODO: Add a DVec2 to List<Vector> anchor point conversion implementation to replace the 'Vec2 to Point' node
/// Implements the [`Convert`] trait for conversion between the cartesian product of Rust's primitive numeric types.
macro_rules! impl_convert {

View File

@@ -1,6 +1,6 @@
// Raster types moved to raster-types crate
use crate::Color;
use crate::table::Table;
use crate::list::List;
pub trait RenderComplexity {
fn render_complexity(&self) -> usize {
@@ -8,7 +8,7 @@ pub trait RenderComplexity {
}
}
impl<T: RenderComplexity> RenderComplexity for Table<T> {
impl<T: RenderComplexity> RenderComplexity for List<T> {
fn render_complexity(&self) -> usize {
self.iter_element_values().map(|element| element.render_complexity()).fold(0, usize::saturating_add)
}

View File

@@ -1,44 +1,44 @@
use crate::graphic::Graphic;
use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::graphene_hash::CacheHash;
use core_types::list::List;
use core_types::render_complexity::RenderComplexity;
use core_types::table::Table;
use dyn_any::DynAny;
use glam::DAffine2;
/// Nominal wrapper around `Table<Graphic>` representing a single artboard's content.
/// Nominal wrapper around `List<Graphic>` representing a single artboard's content.
///
/// Per-artboard metadata (location, dimensions, background, clip) lives as row attributes on the
/// enclosing `Table<Artboard>`, not as fields here. This keeps `Artboard` a pure type-system boundary
/// that prevents arbitrary `Table<Table<...<Graphic>>>` nesting.
/// enclosing `List<Artboard>`, not as fields here. This keeps `Artboard` a pure type-system boundary
/// that prevents arbitrary `List<List<...<Graphic>>>` nesting.
#[derive(Clone, Debug, Default, CacheHash, PartialEq, DynAny)]
pub struct Artboard(Table<Graphic>);
pub struct Artboard(List<Graphic>);
impl Artboard {
pub fn new(content: Table<Graphic>) -> Self {
pub fn new(content: List<Graphic>) -> Self {
Self(content)
}
pub fn as_graphic_table(&self) -> &Table<Graphic> {
pub fn as_graphic_list(&self) -> &List<Graphic> {
&self.0
}
pub fn as_graphic_table_mut(&mut self) -> &mut Table<Graphic> {
pub fn as_graphic_list_mut(&mut self) -> &mut List<Graphic> {
&mut self.0
}
pub fn into_graphic_table(self) -> Table<Graphic> {
pub fn into_graphic_list(self) -> List<Graphic> {
self.0
}
}
impl From<Table<Graphic>> for Artboard {
fn from(content: Table<Graphic>) -> Self {
impl From<List<Graphic>> for Artboard {
fn from(content: List<Graphic>) -> Self {
Self(content)
}
}
impl From<Artboard> for Table<Graphic> {
impl From<Artboard> for List<Graphic> {
fn from(artboard: Artboard) -> Self {
artboard.0
}

View File

@@ -1,8 +1,8 @@
use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::graphene_hash::CacheHash;
use core_types::ops::TableConvert;
use core_types::list::List;
use core_types::ops::ListConvert;
use core_types::render_complexity::RenderComplexity;
use core_types::table::Table;
use core_types::uuid::NodeId;
use core_types::{ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color};
use dyn_any::DynAny;
@@ -16,45 +16,23 @@ pub use vector_types::Vector;
/// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax.
#[derive(Clone, Debug, CacheHash, PartialEq, DynAny)]
pub enum Graphic {
Graphic(Table<Graphic>),
Vector(Table<Vector>),
RasterCPU(Table<Raster<CPU>>),
RasterGPU(Table<Raster<GPU>>),
Color(Table<Color>),
Gradient(Table<GradientStops>),
Graphic(List<Graphic>),
Vector(List<Vector>),
RasterCPU(List<Raster<CPU>>),
RasterGPU(List<Raster<GPU>>),
Color(List<Color>),
Gradient(List<GradientStops>),
}
impl Default for Graphic {
fn default() -> Self {
Self::Graphic(Table::new())
Self::Graphic(List::new())
}
}
// Explicit `Send`/`Sync` impls. All fields are themselves `Send`/`Sync`, so these would normally
// be inferred, but the type participates in two mutually recursive cycles through `Table<Graphic>`
// and `Table<Vector>` (where `Vector = vector_types::Vector<Option<Table<Graphic>>>`). The second
// path, wrapped in `Option<_>` and a generic type parameter, produces a distinct auto-trait
// obligation that the solver cannot recognize as the same cycle node, causing
// `overflow evaluating the requirement` errors at the workspace's `once_cell::sync::Lazy` statics.
// Providing these impls explicitly anchors the proof and lets the coinductive cache close both cycles.
//
// These can be removed (reverting to auto-derived `Send`/`Sync`) once any of the following holds:
// - We remove the TaggedValue or its variants that contain tables.
// - The `Vector` alias no longer references `Graphic` through a generic type parameter, breaking
// the second cycle so only the direct `Table<Graphic>` self-cycle remains (which the solver
// already handles on its own).
// - `Graphic` stops containing `Table<Graphic>` directly, e.g. by boxing children through a trait
// object or opaque handle so the recursion is no longer structural.
// - A future rustc release improves the auto-trait solver to recognize cycles across generic-
// parameter substitutions. Try deleting these impls and running:
// `cargo check --tests -p graphite-editor`
// If no `overflow evaluating the requirement` errors appear, they're no longer needed).
unsafe impl Send for Graphic {}
unsafe impl Sync for Graphic {}
// Graphic
impl From<Table<Graphic>> for Graphic {
fn from(graphic: Table<Graphic>) -> Self {
impl From<List<Graphic>> for Graphic {
fn from(graphic: List<Graphic>) -> Self {
Graphic::Graphic(graphic)
}
}
@@ -62,113 +40,113 @@ impl From<Table<Graphic>> for Graphic {
// Vector
impl From<Vector> for Graphic {
fn from(vector: Vector) -> Self {
Graphic::Vector(Table::new_from_element(vector))
Graphic::Vector(List::new_from_element(vector))
}
}
impl From<Table<Vector>> for Graphic {
fn from(vector: Table<Vector>) -> Self {
impl From<List<Vector>> for Graphic {
fn from(vector: List<Vector>) -> Self {
Graphic::Vector(vector)
}
}
// Note: Table<Vector> -> Table<Graphic> conversion handled by blanket impl in gcore
// Note: List<Vector> -> List<Graphic> conversion handled by blanket impl in gcore
// Raster<CPU>
impl From<Raster<CPU>> for Graphic {
fn from(raster: Raster<CPU>) -> Self {
Graphic::RasterCPU(Table::new_from_element(raster))
Graphic::RasterCPU(List::new_from_element(raster))
}
}
impl From<Table<Raster<CPU>>> for Graphic {
fn from(raster: Table<Raster<CPU>>) -> Self {
impl From<List<Raster<CPU>>> for Graphic {
fn from(raster: List<Raster<CPU>>) -> Self {
Graphic::RasterCPU(raster)
}
}
// Note: Table conversions handled by blanket impl in gcore
// Note: List conversions handled by blanket impl in gcore
// Raster<GPU>
impl From<Raster<GPU>> for Graphic {
fn from(raster: Raster<GPU>) -> Self {
Graphic::RasterGPU(Table::new_from_element(raster))
Graphic::RasterGPU(List::new_from_element(raster))
}
}
impl From<Table<Raster<GPU>>> for Graphic {
fn from(raster: Table<Raster<GPU>>) -> Self {
impl From<List<Raster<GPU>>> for Graphic {
fn from(raster: List<Raster<GPU>>) -> Self {
Graphic::RasterGPU(raster)
}
}
// Note: Table conversions handled by blanket impl in gcore
// Note: List conversions handled by blanket impl in gcore
// Color
impl From<Color> for Graphic {
fn from(color: Color) -> Self {
Graphic::Color(Table::new_from_element(color))
Graphic::Color(List::new_from_element(color))
}
}
impl From<Table<Color>> for Graphic {
fn from(color: Table<Color>) -> Self {
impl From<List<Color>> for Graphic {
fn from(color: List<Color>) -> Self {
Graphic::Color(color)
}
}
// Note: Table conversions handled by blanket impl in gcore
// Note: Table<Color> -> Option<Color> is in gcore (Color is defined there)
// Note: List conversions handled by blanket impl in gcore
// Note: List<Color> -> Option<Color> is in gcore (Color is defined there)
// GradientStops
impl From<GradientStops> for Graphic {
fn from(gradient: GradientStops) -> Self {
Graphic::Gradient(Table::new_from_element(gradient))
Graphic::Gradient(List::new_from_element(gradient))
}
}
impl From<Table<GradientStops>> for Graphic {
fn from(gradient: Table<GradientStops>) -> Self {
impl From<List<GradientStops>> for Graphic {
fn from(gradient: List<GradientStops>) -> Self {
Graphic::Gradient(gradient)
}
}
/// Deeply flattens a `Table<Graphic>`, collecting only elements matching a specific variant (extracted by `extract_variant`)
/// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-`Table`s composes transforms and opacity.
fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) -> Table<T> {
fn flatten_recursive<T>(output: &mut Table<T>, current_graphic_table: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) {
for current_graphic_row in current_graphic_table.into_iter() {
let layer_path: Table<NodeId> = current_graphic_row.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
/// Deeply flattens a `List<Graphic>`, collecting only elements matching a specific variant (extracted by `extract_variant`)
/// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-`List`s composes transforms and opacity.
fn flatten_graphic_list<T>(content: List<Graphic>, extract_variant: fn(Graphic) -> Option<List<T>>) -> List<T> {
fn flatten_recursive<T>(output: &mut List<T>, current_graphic_list: List<Graphic>, extract_variant: fn(Graphic) -> Option<List<T>>) {
for current_graphic_row in current_graphic_list.into_iter() {
let layer_path: List<NodeId> = current_graphic_row.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
let current_transform: DAffine2 = current_graphic_row.attribute_cloned_or_default(ATTR_TRANSFORM);
let current_opacity: f64 = current_graphic_row.attribute_cloned_or(ATTR_OPACITY, 1.);
let current_fill: f64 = current_graphic_row.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
match current_graphic_row.into_element() {
// Compose the parent's transform, opacity, and fill onto each child row
Graphic::Graphic(mut sub_table) => {
// Identity default means a missing column still composes correctly
for v in sub_table.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
Graphic::Graphic(mut sub_list) => {
// Identity default means a missing attribute still composes correctly
for v in sub_list.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
*v = current_transform * *v;
}
// f64 defaults to 0, but opacity/fill default to 1, so missing columns must be set rather than multiplied
if let Some(values) = sub_table.iter_attribute_values_mut::<f64>(ATTR_OPACITY) {
// f64 defaults to 0, but opacity/fill default to 1, so missing attributes must be set rather than multiplied
if let Some(values) = sub_list.iter_attribute_values_mut::<f64>(ATTR_OPACITY) {
for v in values {
*v *= current_opacity;
}
} else {
for v in sub_table.iter_attribute_values_mut_or_default::<f64>(ATTR_OPACITY) {
for v in sub_list.iter_attribute_values_mut_or_default::<f64>(ATTR_OPACITY) {
*v = current_opacity;
}
}
if let Some(values) = sub_table.iter_attribute_values_mut::<f64>(ATTR_OPACITY_FILL) {
if let Some(values) = sub_list.iter_attribute_values_mut::<f64>(ATTR_OPACITY_FILL) {
for v in values {
*v *= current_fill;
}
} else {
for v in sub_table.iter_attribute_values_mut_or_default::<f64>(ATTR_OPACITY_FILL) {
for v in sub_list.iter_attribute_values_mut_or_default::<f64>(ATTR_OPACITY_FILL) {
*v = current_fill;
}
}
flatten_recursive(output, sub_table, extract_variant);
flatten_recursive(output, sub_list, extract_variant);
}
// Extract the target variant and push its items with composed transform, opacity, and fill
other => {
if let Some(typed_table) = extract_variant(other) {
for mut item in typed_table.into_iter() {
if let Some(typed_list) = extract_variant(other) {
for mut item in typed_list.into_iter() {
let row_transform: DAffine2 = item.attribute_cloned_or_default(ATTR_TRANSFORM);
let row_opacity: f64 = item.attribute_cloned_or(ATTR_OPACITY, 1.);
let row_fill: f64 = item.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
@@ -186,100 +164,100 @@ fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic
}
}
let mut output = Table::new();
let mut output = List::new();
flatten_recursive(&mut output, content, extract_variant);
output
}
/// Maps from a concrete element type to its corresponding `Graphic` enum variant,
/// enabling type-directed casting of typed `Table`s from a `Graphic` value.
/// enabling type-directed casting of typed `List`s from a `Graphic` value.
pub trait TryFromGraphic: Clone + Sized {
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>>;
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>>;
}
impl TryFromGraphic for Vector {
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> {
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
if let Graphic::Vector(t) = graphic { Some(t) } else { None }
}
}
impl TryFromGraphic for Raster<CPU> {
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> {
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
if let Graphic::RasterCPU(t) = graphic { Some(t) } else { None }
}
}
impl TryFromGraphic for Color {
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> {
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
if let Graphic::Color(t) = graphic { Some(t) } else { None }
}
}
impl TryFromGraphic for GradientStops {
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> {
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
if let Graphic::Gradient(t) = graphic { Some(t) } else { None }
}
}
// Local trait to convert types to Table<Graphic> (avoids orphan rule issues)
pub trait IntoGraphicTable {
fn into_graphic_table(self) -> Table<Graphic>;
// Local trait to convert types to List<Graphic> (avoids orphan rule issues)
pub trait IntoGraphicList {
fn into_graphic_list(self) -> List<Graphic>;
/// Deeply flattens any content of type `T` within a `Table<Graphic>`, discarding all other content, and returning a flat `Table<T>`.
fn into_flattened_table<T: TryFromGraphic>(self) -> Table<T>
/// Deeply flattens any content of type `T` within a `List<Graphic>`, discarding all other content, and returning a flat `List<T>`.
fn into_flattened_list<T: TryFromGraphic>(self) -> List<T>
where
Self: std::marker::Sized,
{
flatten_graphic_table(self.into_graphic_table(), T::try_from_graphic)
flatten_graphic_list(self.into_graphic_list(), T::try_from_graphic)
}
}
impl IntoGraphicTable for Table<Graphic> {
fn into_graphic_table(self) -> Table<Graphic> {
impl IntoGraphicList for List<Graphic> {
fn into_graphic_list(self) -> List<Graphic> {
self
}
}
impl IntoGraphicTable for Table<Vector> {
fn into_graphic_table(self) -> Table<Graphic> {
impl IntoGraphicList for List<Vector> {
fn into_graphic_list(self) -> List<Graphic> {
// Propagate `editor:layer_path` from item 0 onto the wrapper Graphic row so a subsequent
// `flatten_graphic_table` doesn't overwrite the inner Vector's stamp with an empty value
let layer_path: Table<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
let mut graphic_table = Table::new_from_element(Graphic::Vector(self));
// `flatten_graphic_list` doesn't overwrite the inner Vector's stamp with an empty value
let layer_path: List<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
let mut graphic_list = List::new_from_element(Graphic::Vector(self));
if !layer_path.is_empty() {
graphic_table.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer_path);
graphic_list.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer_path);
}
graphic_table
graphic_list
}
}
impl IntoGraphicTable for Table<Raster<CPU>> {
fn into_graphic_table(self) -> Table<Graphic> {
Table::new_from_element(Graphic::RasterCPU(self))
impl IntoGraphicList for List<Raster<CPU>> {
fn into_graphic_list(self) -> List<Graphic> {
List::new_from_element(Graphic::RasterCPU(self))
}
}
impl IntoGraphicTable for Table<Raster<GPU>> {
fn into_graphic_table(self) -> Table<Graphic> {
Table::new_from_element(Graphic::RasterGPU(self))
impl IntoGraphicList for List<Raster<GPU>> {
fn into_graphic_list(self) -> List<Graphic> {
List::new_from_element(Graphic::RasterGPU(self))
}
}
impl IntoGraphicTable for Table<Color> {
fn into_graphic_table(self) -> Table<Graphic> {
Table::new_from_element(Graphic::Color(self))
impl IntoGraphicList for List<Color> {
fn into_graphic_list(self) -> List<Graphic> {
List::new_from_element(Graphic::Color(self))
}
}
impl IntoGraphicTable for Table<GradientStops> {
fn into_graphic_table(self) -> Table<Graphic> {
Table::new_from_element(Graphic::Gradient(self))
impl IntoGraphicList for List<GradientStops> {
fn into_graphic_list(self) -> List<Graphic> {
List::new_from_element(Graphic::Gradient(self))
}
}
impl IntoGraphicTable for DAffine2 {
fn into_graphic_table(self) -> Table<Graphic> {
Table::new_from_element(Graphic::default())
impl IntoGraphicList for DAffine2 {
fn into_graphic_list(self) -> List<Graphic> {
List::new_from_element(Graphic::default())
}
}
@@ -289,45 +267,45 @@ impl From<DAffine2> for Graphic {
Graphic::default()
}
}
// Note: Table conversions handled by blanket impl in gcore
// Note: List conversions handled by blanket impl in gcore
impl Graphic {
pub fn as_graphic(&self) -> Option<&Table<Graphic>> {
pub fn as_graphic(&self) -> Option<&List<Graphic>> {
match self {
Graphic::Graphic(graphic) => Some(graphic),
_ => None,
}
}
pub fn as_graphic_mut(&mut self) -> Option<&mut Table<Graphic>> {
pub fn as_graphic_mut(&mut self) -> Option<&mut List<Graphic>> {
match self {
Graphic::Graphic(graphic) => Some(graphic),
_ => None,
}
}
pub fn as_vector(&self) -> Option<&Table<Vector>> {
pub fn as_vector(&self) -> Option<&List<Vector>> {
match self {
Graphic::Vector(vector) => Some(vector),
_ => None,
}
}
pub fn as_vector_mut(&mut self) -> Option<&mut Table<Vector>> {
pub fn as_vector_mut(&mut self) -> Option<&mut List<Vector>> {
match self {
Graphic::Vector(vector) => Some(vector),
_ => None,
}
}
pub fn as_raster(&self) -> Option<&Table<Raster<CPU>>> {
pub fn as_raster(&self) -> Option<&List<Raster<CPU>>> {
match self {
Graphic::RasterCPU(raster) => Some(raster),
_ => None,
}
}
pub fn as_raster_mut(&mut self) -> Option<&mut Table<Raster<CPU>>> {
pub fn as_raster_mut(&mut self) -> Option<&mut List<Raster<CPU>>> {
match self {
Graphic::RasterCPU(raster) => Some(raster),
_ => None,
@@ -335,17 +313,17 @@ impl Graphic {
}
pub fn had_clip_enabled(&self) -> bool {
fn all_clipped<T>(table: &Table<T>) -> bool {
table.iter_attribute_values_or_default::<bool>(ATTR_CLIPPING_MASK).all(|clip| clip)
fn all_clipped<T>(list: &List<T>) -> bool {
list.iter_attribute_values_or_default::<bool>(ATTR_CLIPPING_MASK).all(|clip| clip)
}
match self {
Graphic::Vector(table) => all_clipped(table),
Graphic::Graphic(table) => all_clipped(table),
Graphic::RasterCPU(table) => all_clipped(table),
Graphic::RasterGPU(table) => all_clipped(table),
Graphic::Color(table) => all_clipped(table),
Graphic::Gradient(table) => all_clipped(table),
Graphic::Vector(list) => all_clipped(list),
Graphic::Graphic(list) => all_clipped(list),
Graphic::RasterCPU(list) => all_clipped(list),
Graphic::RasterGPU(list) => all_clipped(list),
Graphic::Color(list) => all_clipped(list),
Graphic::Gradient(list) => all_clipped(list),
}
}
@@ -364,12 +342,12 @@ impl Graphic {
impl BoundingBox for Graphic {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
match self {
Graphic::Vector(table) => table.bounding_box(transform, include_stroke),
Graphic::RasterCPU(table) => table.bounding_box(transform, include_stroke),
Graphic::RasterGPU(table) => table.bounding_box(transform, include_stroke),
Graphic::Graphic(table) => table.bounding_box(transform, include_stroke),
Graphic::Color(table) => table.bounding_box(transform, include_stroke),
Graphic::Gradient(table) => table.bounding_box(transform, include_stroke),
Graphic::Vector(list) => list.bounding_box(transform, include_stroke),
Graphic::RasterCPU(list) => list.bounding_box(transform, include_stroke),
Graphic::RasterGPU(list) => list.bounding_box(transform, include_stroke),
Graphic::Graphic(list) => list.bounding_box(transform, include_stroke),
Graphic::Color(list) => list.bounding_box(transform, include_stroke),
Graphic::Gradient(list) => list.bounding_box(transform, include_stroke),
}
}
@@ -385,31 +363,31 @@ impl BoundingBox for Graphic {
}
}
impl TableConvert<Graphic> for Vector {
impl ListConvert<Graphic> for Vector {
fn convert_row(self) -> Graphic {
Graphic::Vector(Table::new_from_element(self))
Graphic::Vector(List::new_from_element(self))
}
}
impl TableConvert<Graphic> for Raster<CPU> {
impl ListConvert<Graphic> for Raster<CPU> {
fn convert_row(self) -> Graphic {
Graphic::RasterCPU(Table::new_from_element(self))
Graphic::RasterCPU(List::new_from_element(self))
}
}
impl TableConvert<Graphic> for Raster<GPU> {
impl ListConvert<Graphic> for Raster<GPU> {
fn convert_row(self) -> Graphic {
Graphic::RasterGPU(Table::new_from_element(self))
Graphic::RasterGPU(List::new_from_element(self))
}
}
impl RenderComplexity for Graphic {
fn render_complexity(&self) -> usize {
match self {
Self::Graphic(table) => table.render_complexity(),
Self::Vector(table) => table.render_complexity(),
Self::RasterCPU(table) => table.render_complexity(),
Self::RasterGPU(table) => table.render_complexity(),
Self::Color(table) => table.render_complexity(),
Self::Gradient(table) => table.render_complexity(),
Self::Graphic(list) => list.render_complexity(),
Self::Vector(list) => list.render_complexity(),
Self::RasterCPU(list) => list.render_complexity(),
Self::RasterGPU(list) => list.render_complexity(),
Self::Color(list) => list.render_complexity(),
Self::Gradient(list) => list.render_complexity(),
}
}
}
@@ -432,14 +410,14 @@ impl<T: Clone> AtIndex for Vec<T> {
if index == 0 || index > self.len() { None } else { self.get(self.len() - index).cloned() }
}
}
impl<T: Clone> AtIndex for Table<T> {
type Output = Table<T>;
impl<T: Clone> AtIndex for List<T> {
type Output = List<T>;
fn at_index(&self, index: usize) -> Option<Self::Output> {
self.clone_item(index).map(|row| {
let mut result_table = Self::default();
result_table.push(row);
result_table
let mut result_list = Self::default();
result_list.push(row);
result_list
})
}
@@ -464,7 +442,7 @@ impl<T: Clone> OmitIndex for Vec<T> {
self.omit_index(self.len() - index)
}
}
impl<T: Clone> OmitIndex for Table<T> {
impl<T: Clone> OmitIndex for List<T> {
fn omit_index(&self, index: usize) -> Self {
let mut result = Self::default();
for i in 0..self.len() {

View File

@@ -8,7 +8,7 @@ pub use vector_types;
// Re-export commonly used types at the crate root
pub use artboard::Artboard;
pub use graphic::{Graphic, IntoGraphicTable, TryFromGraphic, Vector};
pub use graphic::{Graphic, IntoGraphicList, TryFromGraphic, Vector};
pub mod migrations {
use vector_types::vector::{PathStyle, PointDomain, RegionDomain, SegmentDomain, misc::HandleId};
@@ -16,11 +16,11 @@ pub mod migrations {
use crate::Vector;
// TODO: Eventually remove this migration document upgrade code
/// Returns the first `Vector` recovered from any of the legacy on-disk shapes (a single `Vector`, the old `OldVectorData` flat struct, or any of the historical `Table<Vector>` variants).
/// Returns the first `Vector` recovered from any of the legacy on-disk shapes (a single `Vector`, the old `OldVectorData` flat struct, or any of the historical `List<Vector>` variants).
pub fn migrate_to_optional_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Option<Vector>, D::Error> {
use serde::Deserialize;
/// Old documents stored a `Vector` flattened with table attributes (`transform`, `alpha_blending`, `upstream_graphic_group`); only the geometry fields are recovered.
/// Old documents stored a `Vector` flattened with list attributes (`transform`, `alpha_blending`, `upstream_graphic_group`); only the geometry fields are recovered.
#[derive(serde::Deserialize)]
struct OldVectorData {
style: PathStyle,
@@ -42,7 +42,7 @@ pub mod migrations {
enum VectorFormat {
Vector(Vector),
OldVectorData(OldVectorData),
Table(LegacyTable),
List(LegacyTable),
}
Ok(match VectorFormat::deserialize(deserializer)? {
@@ -54,7 +54,7 @@ pub mod migrations {
segment_domain: old.segment_domain,
region_domain: old.region_domain,
}),
VectorFormat::Table(table) => table.element.into_iter().next(),
VectorFormat::List(list) => list.element.into_iter().next(),
})
}
}

View File

@@ -5,9 +5,9 @@ use core_types::blending::BlendMode;
use core_types::bounds::BoundingBox;
use core_types::bounds::RenderBoundingBox;
use core_types::color::Color;
use core_types::list::{Item, List};
use core_types::math::quad::Quad;
use core_types::render_complexity::RenderComplexity;
use core_types::table::{Item, Table};
use core_types::transform::Footprint;
use core_types::uuid::{NodeId, generate_uuid};
use core_types::{
@@ -402,7 +402,7 @@ pub trait Render: BoundingBox + RenderComplexity {
/// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection.
fn add_upstream_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
/// Like `add_upstream_click_targets` but for visual outlines. `Table<Vector>` overrides this to ignore `editor:click_target` so outlines reflect the actual geometry.
/// Like `add_upstream_click_targets` but for visual outlines. `List<Vector>` overrides this to ignore `editor:click_target` so outlines reflect the actual geometry.
fn add_upstream_outline_targets(&self, outlines: &mut Vec<ClickTarget>) {
self.add_upstream_click_targets(outlines);
}
@@ -423,23 +423,23 @@ pub trait Render: BoundingBox + RenderComplexity {
impl Render for Graphic {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
match self {
Graphic::Graphic(table) => table.render_svg(render, render_params),
Graphic::Vector(table) => table.render_svg(render, render_params),
Graphic::RasterCPU(table) => table.render_svg(render, render_params),
Graphic::Graphic(list) => list.render_svg(render, render_params),
Graphic::Vector(list) => list.render_svg(render, render_params),
Graphic::RasterCPU(list) => list.render_svg(render, render_params),
Graphic::RasterGPU(_) => (),
Graphic::Color(table) => table.render_svg(render, render_params),
Graphic::Gradient(table) => table.render_svg(render, render_params),
Graphic::Color(list) => list.render_svg(render, render_params),
Graphic::Gradient(list) => list.render_svg(render, render_params),
}
}
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
match self {
Graphic::Graphic(table) => table.render_to_vello(scene, transform, context, render_params),
Graphic::Vector(table) => table.render_to_vello(scene, transform, context, render_params),
Graphic::RasterCPU(table) => table.render_to_vello(scene, transform, context, render_params),
Graphic::RasterGPU(table) => table.render_to_vello(scene, transform, context, render_params),
Graphic::Color(table) => table.render_to_vello(scene, transform, context, render_params),
Graphic::Gradient(table) => table.render_to_vello(scene, transform, context, render_params),
Graphic::Graphic(list) => list.render_to_vello(scene, transform, context, render_params),
Graphic::Vector(list) => list.render_to_vello(scene, transform, context, render_params),
Graphic::RasterCPU(list) => list.render_to_vello(scene, transform, context, render_params),
Graphic::RasterGPU(list) => list.render_to_vello(scene, transform, context, render_params),
Graphic::Color(list) => list.render_to_vello(scene, transform, context, render_params),
Graphic::Gradient(list) => list.render_to_vello(scene, transform, context, render_params),
}
}
@@ -449,100 +449,100 @@ impl Render for Graphic {
Graphic::Graphic(_) => {
metadata.upstream_footprints.insert(element_id, footprint);
}
Graphic::Vector(table) => {
Graphic::Vector(list) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than the first item
if !table.is_empty() {
let layer_path: Table<NodeId> = table.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
if !list.is_empty() {
let layer_path: List<NodeId> = list.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
let layer = layer_path.iter_element_values().next_back().copied();
let transform: DAffine2 = table.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
let transform: DAffine2 = list.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
metadata.first_element_source_id.insert(element_id, layer);
metadata.local_transforms.insert(element_id, transform);
}
}
Graphic::RasterCPU(table) => {
Graphic::RasterCPU(list) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than the first item
if !table.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
if !list.is_empty() {
metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
}
}
Graphic::RasterGPU(table) => {
Graphic::RasterGPU(list) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than the first item
if !table.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
if !list.is_empty() {
metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
}
}
Graphic::Color(table) => {
Graphic::Color(list) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than the first item
if !table.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
if !list.is_empty() {
metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
}
}
Graphic::Gradient(table) => {
Graphic::Gradient(list) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than the first item
if !table.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
if !list.is_empty() {
metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
}
}
}
}
match self {
Graphic::Graphic(table) => table.collect_metadata(metadata, footprint, element_id),
Graphic::Vector(table) => table.collect_metadata(metadata, footprint, element_id),
Graphic::RasterCPU(table) => table.collect_metadata(metadata, footprint, element_id),
Graphic::RasterGPU(table) => table.collect_metadata(metadata, footprint, element_id),
Graphic::Color(table) => table.collect_metadata(metadata, footprint, element_id),
Graphic::Gradient(table) => table.collect_metadata(metadata, footprint, element_id),
Graphic::Graphic(list) => list.collect_metadata(metadata, footprint, element_id),
Graphic::Vector(list) => list.collect_metadata(metadata, footprint, element_id),
Graphic::RasterCPU(list) => list.collect_metadata(metadata, footprint, element_id),
Graphic::RasterGPU(list) => list.collect_metadata(metadata, footprint, element_id),
Graphic::Color(list) => list.collect_metadata(metadata, footprint, element_id),
Graphic::Gradient(list) => list.collect_metadata(metadata, footprint, element_id),
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
match self {
Graphic::Graphic(table) => table.add_upstream_click_targets(click_targets),
Graphic::Vector(table) => table.add_upstream_click_targets(click_targets),
Graphic::RasterCPU(table) => table.add_upstream_click_targets(click_targets),
Graphic::RasterGPU(table) => table.add_upstream_click_targets(click_targets),
Graphic::Color(table) => table.add_upstream_click_targets(click_targets),
Graphic::Gradient(table) => table.add_upstream_click_targets(click_targets),
Graphic::Graphic(list) => list.add_upstream_click_targets(click_targets),
Graphic::Vector(list) => list.add_upstream_click_targets(click_targets),
Graphic::RasterCPU(list) => list.add_upstream_click_targets(click_targets),
Graphic::RasterGPU(list) => list.add_upstream_click_targets(click_targets),
Graphic::Color(list) => list.add_upstream_click_targets(click_targets),
Graphic::Gradient(list) => list.add_upstream_click_targets(click_targets),
}
}
fn add_upstream_outline_targets(&self, outlines: &mut Vec<ClickTarget>) {
match self {
Graphic::Graphic(table) => table.add_upstream_outline_targets(outlines),
Graphic::Vector(table) => table.add_upstream_outline_targets(outlines),
Graphic::RasterCPU(table) => table.add_upstream_outline_targets(outlines),
Graphic::RasterGPU(table) => table.add_upstream_outline_targets(outlines),
Graphic::Color(table) => table.add_upstream_outline_targets(outlines),
Graphic::Gradient(table) => table.add_upstream_outline_targets(outlines),
Graphic::Graphic(list) => list.add_upstream_outline_targets(outlines),
Graphic::Vector(list) => list.add_upstream_outline_targets(outlines),
Graphic::RasterCPU(list) => list.add_upstream_outline_targets(outlines),
Graphic::RasterGPU(list) => list.add_upstream_outline_targets(outlines),
Graphic::Color(list) => list.add_upstream_outline_targets(outlines),
Graphic::Gradient(list) => list.add_upstream_outline_targets(outlines),
}
}
fn contains_artboard(&self) -> bool {
match self {
Graphic::Graphic(table) => table.contains_artboard(),
Graphic::Vector(table) => table.contains_artboard(),
Graphic::RasterCPU(table) => table.contains_artboard(),
Graphic::RasterGPU(table) => table.contains_artboard(),
Graphic::Color(table) => table.contains_artboard(),
Graphic::Gradient(table) => table.contains_artboard(),
Graphic::Graphic(list) => list.contains_artboard(),
Graphic::Vector(list) => list.contains_artboard(),
Graphic::RasterCPU(list) => list.contains_artboard(),
Graphic::RasterGPU(list) => list.contains_artboard(),
Graphic::Color(list) => list.contains_artboard(),
Graphic::Gradient(list) => list.contains_artboard(),
}
}
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
match self {
Graphic::Graphic(table) => table.new_ids_from_hash(reference),
Graphic::Vector(table) => table.new_ids_from_hash(reference),
Graphic::Graphic(list) => list.new_ids_from_hash(reference),
Graphic::Vector(list) => list.new_ids_from_hash(reference),
Graphic::RasterCPU(_) => (),
Graphic::RasterGPU(_) => (),
Graphic::Color(_) => (),
@@ -551,19 +551,19 @@ impl Render for Graphic {
}
}
/// Reads the artboard metadata for the item at `index` from a `Table<Artboard>`.
fn read_artboard_attributes(table: &Table<Artboard>, index: usize) -> (DVec2, DVec2, Color, bool) {
let location: DVec2 = table.attribute_cloned_or_default(ATTR_LOCATION, index);
let dimensions: DVec2 = table.attribute_cloned_or_default(ATTR_DIMENSIONS, index);
let background: Color = table.attribute_cloned_or_default(ATTR_BACKGROUND, index);
let clip: bool = table.attribute_cloned_or_default(ATTR_CLIP, index);
/// Reads the artboard metadata for the item at `index` from a `List<Artboard>`.
fn read_artboard_attributes(list: &List<Artboard>, index: usize) -> (DVec2, DVec2, Color, bool) {
let location: DVec2 = list.attribute_cloned_or_default(ATTR_LOCATION, index);
let dimensions: DVec2 = list.attribute_cloned_or_default(ATTR_DIMENSIONS, index);
let background: Color = list.attribute_cloned_or_default(ATTR_BACKGROUND, index);
let clip: bool = list.attribute_cloned_or_default(ATTR_CLIP, index);
(location, dimensions, background, clip)
}
impl Render for Table<Artboard> {
impl Render for List<Artboard> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for index in 0..self.len() {
let Some(content) = self.element(index).map(Artboard::as_graphic_table) else { continue };
let Some(content) = self.element(index).map(Artboard::as_graphic_list) else { continue };
let (location, dimensions, background, clip) = read_artboard_attributes(self, index);
let x = location.x.min(location.x + dimensions.x);
@@ -621,7 +621,7 @@ impl Render for Table<Artboard> {
use vello::peniko;
for index in 0..self.len() {
let Some(content) = self.element(index).map(Artboard::as_graphic_table) else { continue };
let Some(content) = self.element(index).map(Artboard::as_graphic_list) else { continue };
let (location, dimensions, background, clip) = read_artboard_attributes(self, index);
let [a, b] = [location, location + dimensions];
@@ -651,10 +651,10 @@ impl Render for Table<Artboard> {
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
for index in 0..self.len() {
let Some(content) = self.element(index).map(Artboard::as_graphic_table) else { continue };
let Some(content) = self.element(index).map(Artboard::as_graphic_list) else { continue };
let (location, dimensions, _background, clip) = read_artboard_attributes(self, index);
let layer_path: Table<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
let layer_path: List<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
let element_id = layer_path.iter_element_values().next_back().copied();
if let Some(element_id) = element_id {
@@ -688,7 +688,7 @@ impl Render for Table<Artboard> {
}
}
impl Render for Table<Graphic> {
impl Render for List<Graphic> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
let mut mask_state = None;
@@ -826,7 +826,7 @@ impl Render for Table<Graphic> {
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
for index in 0..self.len() {
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let layer_path: Table<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
let layer_path: List<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
let layer = layer_path.iter_element_values().next_back().copied();
let element = self.element(index).unwrap();
@@ -908,14 +908,14 @@ impl Render for Table<Graphic> {
}
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
let (elements, layers) = self.element_and_attribute_slices_mut::<Table<NodeId>>(ATTR_EDITOR_LAYER_PATH);
let (elements, layers) = self.element_and_attribute_slices_mut::<List<NodeId>>(ATTR_EDITOR_LAYER_PATH);
for (element, layer) in elements.iter_mut().zip(layers.iter()) {
element.new_ids_from_hash(layer.iter_element_values().next_back().copied());
}
}
}
impl Render for Table<Vector> {
impl Render for List<Vector> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for index in 0..self.len() {
let Some(vector) = self.element(index) else { continue };
@@ -987,7 +987,7 @@ impl Render for Table<Vector> {
// The mask must draw at full alpha so the SVG `<mask>`/`<clipPath>` fully zeroes the path interior.
// The wrapping SVG group (above) handles the user-set opacity.
let vector_item = Table::new_from_item(Item::new_from_element(cloned_vector).with_attribute(ATTR_TRANSFORM, multiplied_transform));
let vector_item = List::new_from_item(Item::new_from_element(cloned_vector).with_attribute(ATTR_TRANSFORM, multiplied_transform));
(id, mask_type, vector_item)
});
@@ -1312,7 +1312,7 @@ impl Render for Table<Vector> {
// The mask must draw at full alpha so `SrcOut` fully zeroes the path interior.
// The outer opacity/blend layer (above) handles the user-set opacity.
let vector_table = Table::new_from_item(Item::new_from_element(cloned_element).with_attribute(ATTR_TRANSFORM, item_transform));
let vector_list = List::new_from_item(Item::new_from_element(cloned_element).with_attribute(ATTR_TRANSFORM, item_transform));
let bounds = element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);
// This branch is gated on `can_draw_aligned_stroke`, which already requires every subpath is closed
@@ -1330,7 +1330,7 @@ impl Render for Table<Vector> {
if wants_stroke_below {
scene.push_layer(peniko::Fill::NonZero, peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect);
vector_table.render_to_vello(scene, parent_transform, _context, &render_params.for_alignment(applied_stroke_transform));
vector_list.render_to_vello(scene, parent_transform, _context, &render_params.for_alignment(applied_stroke_transform));
scene.push_layer(peniko::Fill::NonZero, peniko::BlendMode::new(peniko::Mix::Normal, compose), 1., kurbo::Affine::IDENTITY, &rect);
do_stroke(scene, 2.);
@@ -1344,7 +1344,7 @@ impl Render for Table<Vector> {
do_fill(scene);
scene.push_layer(peniko::Fill::NonZero, peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect);
vector_table.render_to_vello(scene, parent_transform, _context, &render_params.for_alignment(applied_stroke_transform));
vector_list.render_to_vello(scene, parent_transform, _context, &render_params.for_alignment(applied_stroke_transform));
scene.push_layer(peniko::Fill::NonZero, peniko::BlendMode::new(peniko::Mix::Normal, compose), 1., kurbo::Affine::IDENTITY, &rect);
do_stroke(scene, 2.);
@@ -1382,7 +1382,7 @@ impl Render for Table<Vector> {
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) {
// Aggregate all items' targets per element_id so multi-item tables (e.g. 'Text' node with "Separate Glyphs" active) produce hit areas for every glyph.
// Aggregate all items' targets per element_id so multi-item lists (e.g. 'Text' node with "Separate Glyphs" active) produce hit areas for every glyph.
// Targets are baked relative to item 0's transform since `Graphic::collect_metadata` records that as `local_transforms[element_id]`.
let item_zero_transform: DAffine2 = if !self.is_empty() {
self.attribute_cloned_or_default(ATTR_TRANSFORM, 0)
@@ -1401,7 +1401,7 @@ impl Render for Table<Vector> {
for index in 0..self.len() {
let Some(source) = self.element(index) else { continue };
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let layer_path: Table<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
let layer_path: List<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
let layer = layer_path.iter_element_values().next_back().copied();
if let Some(element_id) = caller_element_id.or(layer) {
@@ -1440,7 +1440,7 @@ impl Render for Table<Vector> {
// If this item carries a snapshot of upstream graphic content (e.g. it was produced by Boolean Operation,
// Flatten Path, Morph, or any other destructive merge), recurse into that snapshot so the editor can
// surface the original child layers' click targets.
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, index);
let upstream_nested_layers = self.attribute_cloned_or_default::<List<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, index);
if !upstream_nested_layers.is_empty() {
let mut upstream_footprint = footprint;
upstream_footprint.transform *= transform;
@@ -1524,7 +1524,7 @@ fn extend_free_point_targets(vector: &Vector, transform: DAffine2) -> impl Itera
})
}
impl Render for Table<Raster<CPU>> {
impl Render for List<Raster<CPU>> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for index in 0..self.len() {
let Some(image) = self.element(index) else { continue };
@@ -1673,7 +1673,7 @@ impl Render for Table<Raster<CPU>> {
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one item of the `Table<Raster<...>>`
// TODO: Find a way to handle more than one item of the `List<Raster<...>>`
if !self.is_empty() {
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
metadata.local_transforms.insert(element_id, transform);
@@ -1684,7 +1684,7 @@ impl Render for Table<Raster<CPU>> {
// The snapshot was captured before Rasterize shifted its input transforms to align with the rasterization
// area, so the children are already in the coordinate space matching `footprint` here — we must NOT
// multiply in `transform` (which is the rasterization area, not a layer-stack transform).
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, 0);
let upstream_nested_layers = self.attribute_cloned_or_default::<List<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, 0);
if !upstream_nested_layers.is_empty() {
upstream_nested_layers.collect_metadata(metadata, footprint, None);
}
@@ -1699,7 +1699,7 @@ impl Render for Table<Raster<CPU>> {
static LAZY_ARC_VEC_ZERO_U8: LazyLock<Arc<Vec<u8>>> = LazyLock::new(|| Arc::new(Vec::new()));
impl Render for Table<Raster<GPU>> {
impl Render for List<Raster<GPU>> {
fn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) {
log::warn!("tried to render texture as an svg");
}
@@ -1768,7 +1768,7 @@ impl Render for Table<Raster<GPU>> {
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one item of the `Table<Raster<...>>`
// TODO: Find a way to handle more than one item of the `List<Raster<...>>`
if !self.is_empty() {
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
metadata.local_transforms.insert(element_id, transform);
@@ -1779,7 +1779,7 @@ impl Render for Table<Raster<GPU>> {
// The snapshot was captured before Rasterize shifted its input transforms to align with the rasterization
// area, so the children are already in the coordinate space matching `footprint` here — we must NOT
// multiply in `transform` (which is the rasterization area, not a layer-stack transform).
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, 0);
let upstream_nested_layers = self.attribute_cloned_or_default::<List<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, 0);
if !upstream_nested_layers.is_empty() {
upstream_nested_layers.collect_metadata(metadata, footprint, None);
}
@@ -1798,7 +1798,7 @@ impl Render for Table<Raster<GPU>> {
// For SVG, this is is achived by creating a truly giant rectangle.
// For Vello, we create a layer with a placeholder transform which we
// later replace with the current viewport transform before each render.
impl Render for Table<Color> {
impl Render for List<Color> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for (index, color) in self.iter_element_values().enumerate() {
let blend_mode: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
@@ -1858,7 +1858,7 @@ impl Render for Table<Color> {
}
}
impl Render for Table<GradientStops> {
impl Render for List<GradientStops> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
// For thumbnails the gradient fills a finite rect at the footprint's document space bounds, with a 1-unit margin to cover the `as u32` truncation of `Footprint::resolution`.
// The viewBox crops the overshoot. Canvas rendering keeps the polyline path since Chrome rejects rects larger than ~20 million.
@@ -2017,7 +2017,7 @@ impl Render for Table<GradientStops> {
let mut layer = false;
if opacity < 1. || blend_mode_attr != BlendMode::default() {
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
// See implementation in `Table<Color>` for more detail
// See implementation in `List<Color>` for more detail
scene.push_layer(peniko::Fill::NonZero, blending, opacity, kurbo::Affine::scale(f64::INFINITY), &rect);
layer = true;
}

View File

@@ -539,12 +539,12 @@ pub fn migrate_to_gradient_stops<'de, D: serde::Deserializer<'de>>(deserializer:
#[cfg_attr(feature = "serde", serde(untagged))]
enum GradientStopsFormat {
Stops(GradientStops),
Table(LegacyTable),
List(LegacyTable),
}
Ok(match GradientStopsFormat::deserialize(deserializer)? {
GradientStopsFormat::Stops(stops) => stops,
GradientStopsFormat::Table(table) => table.element.into_iter().next().unwrap_or_default(),
GradientStopsFormat::List(list) => list.element.into_iter().next().unwrap_or_default(),
})
}

View File

@@ -4,7 +4,7 @@ pub use crate::gradient::*;
use core_types::ATTR_OPACITY;
use core_types::Color;
use core_types::color::Alpha;
use core_types::table::Table;
use core_types::list::List;
use core_types::transform::Transform;
use dyn_any::DynAny;
use glam::DAffine2;
@@ -133,16 +133,16 @@ impl From<Option<Color>> for Fill {
}
}
impl From<Table<Color>> for Fill {
fn from(color: Table<Color>) -> Fill {
impl From<List<Color>> for Fill {
fn from(color: List<Color>) -> Fill {
let alpha: f64 = color.attribute_cloned_or(ATTR_OPACITY, 0, 1.);
let color = color.element(0).copied();
Fill::solid_or_none(color.map(|c| c.with_alpha(c.alpha() * alpha as f32)))
}
}
impl From<Table<GradientStops>> for Fill {
fn from(gradient: Table<GradientStops>) -> Fill {
impl From<List<GradientStops>> for Fill {
fn from(gradient: List<GradientStops>) -> Fill {
Fill::Gradient(Gradient {
stops: gradient.element(0).cloned().unwrap_or_default(),
..Default::default()

View File

@@ -556,7 +556,7 @@ impl RenderComplexity for Vector {
}
}
// Note: BoundingBox for Table<Vector> is handled by blanket impl in gcore
// Note: BoundingBox for List<Vector> is handled by blanket impl in gcore
#[cfg(test)]
mod tests {

View File

@@ -1,7 +1,7 @@
use crate::WgpuContext;
use crate::shader_runtime::{FULLSCREEN_VERTEX_SHADER_NAME, ShaderRuntime};
use core_types::list::{Item, List};
use core_types::shaders::buffer_struct::BufferStruct;
use core_types::table::{Item, Table};
use futures::lock::Mutex;
use raster_types::{GPU, Raster};
use std::borrow::Cow;
@@ -33,7 +33,7 @@ impl PerPixelAdjustShaderRuntime {
}
impl ShaderRuntime {
pub async fn run_per_pixel_adjust<T: BufferStruct>(&self, shaders: &Shaders<'_>, textures: Table<Raster<GPU>>, args: Option<&T>) -> Table<Raster<GPU>> {
pub async fn run_per_pixel_adjust<T: BufferStruct>(&self, shaders: &Shaders<'_>, textures: List<Raster<GPU>>, args: Option<&T>) -> List<Raster<GPU>> {
let mut cache = self.per_pixel_adjust.pipeline_cache.lock().await;
let pipeline = cache
.entry(shaders.fragment_shader_name.to_owned())
@@ -160,7 +160,7 @@ impl PerPixelAdjustGraphicsPipeline {
}
}
pub fn dispatch(&self, context: &WgpuContext, textures: Table<Raster<GPU>>, arg_buffer: Option<Buffer>) -> Table<Raster<GPU>> {
pub fn dispatch(&self, context: &WgpuContext, textures: List<Raster<GPU>>, arg_buffer: Option<Buffer>) -> List<Raster<GPU>> {
assert_eq!(self.has_uniform, arg_buffer.is_some());
let device = &context.device;
let name = self.name.as_str();
@@ -236,7 +236,7 @@ impl PerPixelAdjustGraphicsPipeline {
let attributes = textures.clone_item_attributes(index);
Item::from_parts(Raster::new(GPU { texture: tex_out }), attributes)
})
.collect::<Table<_>>();
.collect::<List<_>>();
context.queue.submit([cmd.finish()]);
out
}

View File

@@ -2,8 +2,8 @@ use crate::WgpuExecutor;
use core_types::Color;
use core_types::Ctx;
use core_types::color::SRGBA8;
use core_types::list::{Item, List};
use core_types::ops::Convert;
use core_types::table::{Item, Table};
use core_types::transform::Footprint;
use raster_types::Image;
use raster_types::{CPU, GPU, Raster};
@@ -137,19 +137,19 @@ impl RasterGpuToRasterCpuConverter {
}
}
/// Passthrough conversion for GPU `Table`s - no conversion needed
impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> Table<Raster<GPU>> {
/// Passthrough conversion for GPU `List`s - no conversion needed
impl<'i> Convert<List<Raster<GPU>>, &'i WgpuExecutor> for List<Raster<GPU>> {
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> List<Raster<GPU>> {
self
}
}
/// Converts a `Table<Raster<CPU>>` to `Table<Raster<GPU>>` by uploading each image to a texture
impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Table<Raster<GPU>> {
/// Converts a `List<Raster<CPU>>` to `List<Raster<GPU>>` by uploading each image to a texture
impl<'i> Convert<List<Raster<GPU>>, &'i WgpuExecutor> for List<Raster<CPU>> {
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> List<Raster<GPU>> {
let device = &executor.context.device;
let queue = &executor.context.queue;
let table = self
let list = self
.into_iter()
.map(|row| {
let (image, attributes) = row.into_parts();
@@ -160,7 +160,7 @@ impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {
.collect();
queue.submit([]);
table
list
}
}
@@ -176,16 +176,16 @@ impl<'i> Convert<Raster<GPU>, &'i WgpuExecutor> for Raster<CPU> {
}
}
/// Passthrough conversion for CPU `Table`s - no conversion needed
impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> Table<Raster<CPU>> {
/// Passthrough conversion for CPU `List`s - no conversion needed
impl<'i> Convert<List<Raster<CPU>>, &'i WgpuExecutor> for List<Raster<CPU>> {
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> List<Raster<CPU>> {
self
}
}
/// Converts a `Table<Raster<GPU>>` to `Table<Raster<CPU>>` by downloading texture data in one go then asynchronously maps all buffers and processes the results.
impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Table<Raster<CPU>> {
/// Converts a `List<Raster<GPU>>` to `List<Raster<CPU>>` by downloading texture data in one go then asynchronously maps all buffers and processes the results.
impl<'i> Convert<List<Raster<CPU>>, &'i WgpuExecutor> for List<Raster<GPU>> {
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> List<Raster<CPU>> {
let device = &executor.context.device;
let queue = &executor.context.queue;
@@ -245,12 +245,12 @@ impl<'i> Convert<Raster<CPU>, &'i WgpuExecutor> for Raster<GPU> {
/// 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 `Table` of raster elements and converts it to the GPU format using the WgpuExecutor's device and queue.
/// 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 async fn upload_texture<'a: 'n, T: Convert<Table<Raster<GPU>>, &'a WgpuExecutor>>(
pub async fn upload_texture<'a: 'n, T: Convert<List<Raster<GPU>>, &'a WgpuExecutor>>(
_: impl Ctx,
#[implementations(Table<Raster<CPU>>, Table<Raster<GPU>>)] input: T,
#[implementations(List<Raster<CPU>>, List<Raster<GPU>>)] input: T,
executor: &'a WgpuExecutor,
) -> Table<Raster<GPU>> {
) -> List<Raster<GPU>> {
input.convert(Footprint::DEFAULT, executor).await
}