mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 23:38:06 +08:00
Merge branch 'master' into ir/ref-centroid
This commit is contained in:
149
node-graph/gcore/src/artboard.rs
Normal file
149
node-graph/gcore/src/artboard.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
use crate::blending::AlphaBlending;
|
||||
use crate::bounds::BoundingBox;
|
||||
use crate::math::quad::Quad;
|
||||
use crate::raster_types::{CPU, GPU, Raster};
|
||||
use crate::table::{Table, TableRow};
|
||||
use crate::transform::TransformMut;
|
||||
use crate::uuid::NodeId;
|
||||
use crate::vector::VectorData;
|
||||
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, Graphic, OwnedContextImpl};
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Artboard {
|
||||
pub graphic_group: Table<Graphic>,
|
||||
pub label: String,
|
||||
pub location: IVec2,
|
||||
pub dimensions: IVec2,
|
||||
pub background: Color,
|
||||
pub clip: bool,
|
||||
}
|
||||
|
||||
impl Default for Artboard {
|
||||
fn default() -> Self {
|
||||
Self::new(IVec2::ZERO, IVec2::new(1920, 1080))
|
||||
}
|
||||
}
|
||||
|
||||
impl Artboard {
|
||||
pub fn new(location: IVec2, dimensions: IVec2) -> Self {
|
||||
Self {
|
||||
graphic_group: Table::new(),
|
||||
label: "Artboard".to_string(),
|
||||
location: location.min(location + dimensions),
|
||||
dimensions: dimensions.abs(),
|
||||
background: Color::WHITE,
|
||||
clip: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundingBox for Artboard {
|
||||
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
|
||||
let artboard_bounds = (transform * Quad::from_box([self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()])).bounding_box();
|
||||
if self.clip {
|
||||
Some(artboard_bounds)
|
||||
} else {
|
||||
[self.graphic_group.bounding_box(transform, include_stroke), Some(artboard_bounds)]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Artboard>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Default, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ArtboardGroup {
|
||||
pub artboards: Vec<(Artboard, Option<NodeId>)>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum EitherFormat {
|
||||
ArtboardGroup(ArtboardGroup),
|
||||
ArtboardGroupTable(Table<Artboard>),
|
||||
}
|
||||
|
||||
Ok(match EitherFormat::deserialize(deserializer)? {
|
||||
EitherFormat::ArtboardGroup(artboard_group) => {
|
||||
let mut table = Table::new();
|
||||
for (artboard, source_node_id) in artboard_group.artboards {
|
||||
table.push(TableRow {
|
||||
element: artboard,
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
source_node_id,
|
||||
});
|
||||
}
|
||||
table
|
||||
}
|
||||
EitherFormat::ArtboardGroupTable(artboard_group_table) => artboard_group_table,
|
||||
})
|
||||
}
|
||||
|
||||
impl BoundingBox for Table<Artboard> {
|
||||
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
|
||||
self.iter_ref().filter_map(|row| row.element.bounding_box(transform, include_stroke)).reduce(Quad::combine_bounds)
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn to_artboard<Data: Into<Table<Graphic>> + 'n>(
|
||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||
#[implementations(
|
||||
Context -> Table<Graphic>,
|
||||
Context -> Table<VectorData>,
|
||||
Context -> Table<Raster<CPU>>,
|
||||
Context -> Table<Raster<GPU>>,
|
||||
Context -> DAffine2,
|
||||
)]
|
||||
contents: impl Node<Context<'static>, Output = Data>,
|
||||
label: String,
|
||||
location: DVec2,
|
||||
dimensions: DVec2,
|
||||
background: Color,
|
||||
clip: bool,
|
||||
) -> Artboard {
|
||||
let location = location.as_ivec2();
|
||||
let dimensions = dimensions.as_ivec2().max(IVec2::ONE);
|
||||
|
||||
let footprint = ctx.try_footprint().copied();
|
||||
let mut new_ctx = OwnedContextImpl::from(ctx);
|
||||
if let Some(mut footprint) = footprint {
|
||||
footprint.translate(location.as_dvec2());
|
||||
new_ctx = new_ctx.with_footprint(footprint);
|
||||
}
|
||||
let graphic_group = contents.eval(new_ctx.into_context()).await;
|
||||
|
||||
Artboard {
|
||||
graphic_group: graphic_group.into(),
|
||||
label,
|
||||
location: location.min(location + dimensions),
|
||||
dimensions: dimensions.abs(),
|
||||
background,
|
||||
clip,
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
pub async fn append_artboard(_ctx: impl Ctx, mut artboards: Table<Artboard>, artboard: Artboard, node_path: Vec<NodeId>) -> Table<Artboard> {
|
||||
// Get the penultimate element of the node path, or None if the path is too short.
|
||||
// This is used to get the ID of the user-facing "Artboard" node (which encapsulates this internal "Append Artboard" node).
|
||||
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
|
||||
|
||||
artboards.push(TableRow {
|
||||
element: artboard,
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
source_node_id: encapsulating_node_id,
|
||||
});
|
||||
|
||||
artboards
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::raster::Image;
|
||||
use crate::raster_types::{CPU, RasterDataTable};
|
||||
use crate::raster_types::{CPU, Raster};
|
||||
use crate::registry::types::Percentage;
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::{BlendMode, Color, Ctx, GraphicElement, GraphicGroupTable};
|
||||
use crate::table::Table;
|
||||
use crate::vector::VectorData;
|
||||
use crate::{BlendMode, Color, Ctx, Graphic};
|
||||
|
||||
pub(super) trait MultiplyAlpha {
|
||||
fn multiply_alpha(&mut self, factor: f64);
|
||||
@@ -13,27 +13,24 @@ impl MultiplyAlpha for Color {
|
||||
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
|
||||
}
|
||||
}
|
||||
impl MultiplyAlpha for VectorDataTable {
|
||||
impl MultiplyAlpha for Table<VectorData> {
|
||||
fn multiply_alpha(&mut self, factor: f64) {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.opacity *= factor as f32;
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.opacity *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MultiplyAlpha for GraphicGroupTable {
|
||||
impl MultiplyAlpha for Table<Graphic> {
|
||||
fn multiply_alpha(&mut self, factor: f64) {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.opacity *= factor as f32;
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.opacity *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MultiplyAlpha for RasterDataTable<CPU>
|
||||
where
|
||||
GraphicElement: From<Image<Color>>,
|
||||
{
|
||||
impl MultiplyAlpha for Table<Raster<CPU>> {
|
||||
fn multiply_alpha(&mut self, factor: f64) {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.opacity *= factor as f32;
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.opacity *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,24 +43,24 @@ impl MultiplyFill for Color {
|
||||
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
|
||||
}
|
||||
}
|
||||
impl MultiplyFill for VectorDataTable {
|
||||
impl MultiplyFill for Table<VectorData> {
|
||||
fn multiply_fill(&mut self, factor: f64) {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.fill *= factor as f32;
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.fill *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MultiplyFill for GraphicGroupTable {
|
||||
impl MultiplyFill for Table<Graphic> {
|
||||
fn multiply_fill(&mut self, factor: f64) {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.fill *= factor as f32;
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.fill *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MultiplyFill for RasterDataTable<CPU> {
|
||||
impl MultiplyFill for Table<Raster<CPU>> {
|
||||
fn multiply_fill(&mut self, factor: f64) {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.fill *= factor as f32;
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.fill *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,24 +69,24 @@ trait SetBlendMode {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode);
|
||||
}
|
||||
|
||||
impl SetBlendMode for VectorDataTable {
|
||||
impl SetBlendMode for Table<VectorData> {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.blend_mode = blend_mode;
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.blend_mode = blend_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SetBlendMode for GraphicGroupTable {
|
||||
impl SetBlendMode for Table<Graphic> {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.blend_mode = blend_mode;
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.blend_mode = blend_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SetBlendMode for RasterDataTable<CPU> {
|
||||
impl SetBlendMode for Table<Raster<CPU>> {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.blend_mode = blend_mode;
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.blend_mode = blend_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,24 +95,24 @@ trait SetClip {
|
||||
fn set_clip(&mut self, clip: bool);
|
||||
}
|
||||
|
||||
impl SetClip for VectorDataTable {
|
||||
impl SetClip for Table<VectorData> {
|
||||
fn set_clip(&mut self, clip: bool) {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.clip = clip;
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.clip = clip;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SetClip for GraphicGroupTable {
|
||||
impl SetClip for Table<Graphic> {
|
||||
fn set_clip(&mut self, clip: bool) {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.clip = clip;
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.clip = clip;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SetClip for RasterDataTable<CPU> {
|
||||
impl SetClip for Table<Raster<CPU>> {
|
||||
fn set_clip(&mut self, clip: bool) {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.clip = clip;
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.clip = clip;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,14 +121,14 @@ impl SetClip for RasterDataTable<CPU> {
|
||||
fn blend_mode<T: SetBlendMode>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
RasterDataTable<CPU>,
|
||||
Table<Graphic>,
|
||||
Table<VectorData>,
|
||||
Table<Raster<CPU>>,
|
||||
)]
|
||||
mut value: T,
|
||||
blend_mode: BlendMode,
|
||||
) -> T {
|
||||
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or Instance<T>) rather than applying to each row in its own table, which produces the undesired result
|
||||
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result
|
||||
value.set_blend_mode(blend_mode);
|
||||
value
|
||||
}
|
||||
@@ -140,14 +137,14 @@ fn blend_mode<T: SetBlendMode>(
|
||||
fn opacity<T: MultiplyAlpha>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
RasterDataTable<CPU>,
|
||||
Table<Graphic>,
|
||||
Table<VectorData>,
|
||||
Table<Raster<CPU>>,
|
||||
)]
|
||||
mut value: T,
|
||||
#[default(100.)] opacity: Percentage,
|
||||
) -> T {
|
||||
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or Instance<T>) rather than applying to each row in its own table, which produces the undesired result
|
||||
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result
|
||||
value.multiply_alpha(opacity / 100.);
|
||||
value
|
||||
}
|
||||
@@ -156,9 +153,9 @@ fn opacity<T: MultiplyAlpha>(
|
||||
fn blending<T: SetBlendMode + MultiplyAlpha + MultiplyFill + SetClip>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
RasterDataTable<CPU>,
|
||||
Table<Graphic>,
|
||||
Table<VectorData>,
|
||||
Table<Raster<CPU>>,
|
||||
)]
|
||||
mut value: T,
|
||||
blend_mode: BlendMode,
|
||||
@@ -166,7 +163,7 @@ fn blending<T: SetBlendMode + MultiplyAlpha + MultiplyFill + SetClip>(
|
||||
#[default(100.)] fill: Percentage,
|
||||
#[default(false)] clip: bool,
|
||||
) -> T {
|
||||
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or Instance<T>) rather than applying to each row in its own table, which produces the undesired result
|
||||
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result
|
||||
value.set_blend_mode(blend_mode);
|
||||
value.multiply_alpha(opacity / 100.);
|
||||
value.multiply_fill(fill / 100.);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::raster_types::{CPU, RasterDataTable};
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::raster_types::{CPU, Raster};
|
||||
use crate::table::Table;
|
||||
use crate::vector::VectorData;
|
||||
use crate::{Color, Ctx};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[node_macro::node(category("Debug"), name("Log to Console"))]
|
||||
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
|
||||
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, Table<VectorData>, DAffine2, Color, Option<Color>)] value: T) -> T {
|
||||
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
||||
log::debug!("{:#?}", value);
|
||||
value
|
||||
@@ -30,6 +31,6 @@ fn unwrap<T: Default>(_: impl Ctx, #[implementations(Option<f64>, Option<f32>, O
|
||||
|
||||
/// Meant for debugging purposes, not general use. Clones the input value.
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&RasterDataTable<CPU>)] value: &'i T) -> T {
|
||||
fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&Table<Raster<CPU>>)] value: &'i T) -> T {
|
||||
value.clone()
|
||||
}
|
||||
|
||||
@@ -1,191 +1,174 @@
|
||||
use crate::blending::AlphaBlending;
|
||||
use crate::bounds::BoundingBox;
|
||||
use crate::instances::{Instance, Instances};
|
||||
use crate::math::quad::Quad;
|
||||
use crate::raster::image::Image;
|
||||
use crate::raster_types::{CPU, GPU, Raster, RasterDataTable};
|
||||
use crate::transform::TransformMut;
|
||||
use crate::raster_types::{CPU, GPU, Raster};
|
||||
use crate::table::{Table, TableRow};
|
||||
use crate::uuid::NodeId;
|
||||
use crate::vector::{VectorData, VectorDataTable};
|
||||
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||
use crate::vector::VectorData;
|
||||
use crate::{Color, Ctx};
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::hash::Hash;
|
||||
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<GraphicGroupTable, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct OldGraphicGroup {
|
||||
elements: Vec<(GraphicElement, Option<NodeId>)>,
|
||||
transform: DAffine2,
|
||||
alpha_blending: AlphaBlending,
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GraphicGroup {
|
||||
elements: Vec<(GraphicElement, Option<NodeId>)>,
|
||||
}
|
||||
pub type OldGraphicGroupTable = Instances<GraphicGroup>;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum EitherFormat {
|
||||
OldGraphicGroup(OldGraphicGroup),
|
||||
InstanceTable(serde_json::Value),
|
||||
}
|
||||
|
||||
Ok(match EitherFormat::deserialize(deserializer)? {
|
||||
EitherFormat::OldGraphicGroup(old) => {
|
||||
let mut graphic_group_table = GraphicGroupTable::default();
|
||||
for (graphic_element, source_node_id) in old.elements {
|
||||
graphic_group_table.push(Instance {
|
||||
instance: graphic_element,
|
||||
transform: old.transform,
|
||||
alpha_blending: old.alpha_blending,
|
||||
source_node_id,
|
||||
});
|
||||
}
|
||||
graphic_group_table
|
||||
}
|
||||
EitherFormat::InstanceTable(value) => {
|
||||
// Try to deserialize as either table format
|
||||
if let Ok(old_table) = serde_json::from_value::<OldGraphicGroupTable>(value.clone()) {
|
||||
let mut graphic_group_table = GraphicGroupTable::default();
|
||||
for instance in old_table.instance_ref_iter() {
|
||||
for (graphic_element, source_node_id) in &instance.instance.elements {
|
||||
graphic_group_table.push(Instance {
|
||||
instance: graphic_element.clone(),
|
||||
transform: *instance.transform,
|
||||
alpha_blending: *instance.alpha_blending,
|
||||
source_node_id: *source_node_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
graphic_group_table
|
||||
} else if let Ok(new_table) = serde_json::from_value::<GraphicGroupTable>(value) {
|
||||
new_table
|
||||
} else {
|
||||
return Err(serde::de::Error::custom("Failed to deserialize GraphicGroupTable"));
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Rename to GraphicElementTable
|
||||
pub type GraphicGroupTable = Instances<GraphicElement>;
|
||||
|
||||
impl From<VectorData> for GraphicGroupTable {
|
||||
fn from(vector_data: VectorData) -> Self {
|
||||
Self::new(GraphicElement::VectorData(VectorDataTable::new(vector_data)))
|
||||
}
|
||||
}
|
||||
impl From<VectorDataTable> for GraphicGroupTable {
|
||||
fn from(vector_data: VectorDataTable) -> Self {
|
||||
Self::new(GraphicElement::VectorData(vector_data))
|
||||
}
|
||||
}
|
||||
impl From<Image<Color>> for GraphicGroupTable {
|
||||
fn from(image: Image<Color>) -> Self {
|
||||
Self::new(GraphicElement::RasterDataCPU(RasterDataTable::<CPU>::new(Raster::new_cpu(image))))
|
||||
}
|
||||
}
|
||||
impl From<RasterDataTable<CPU>> for GraphicGroupTable {
|
||||
fn from(raster_data_table: RasterDataTable<CPU>) -> Self {
|
||||
Self::new(GraphicElement::RasterDataCPU(raster_data_table))
|
||||
}
|
||||
}
|
||||
impl From<RasterDataTable<GPU>> for GraphicGroupTable {
|
||||
fn from(raster_data_table: RasterDataTable<GPU>) -> Self {
|
||||
Self::new(GraphicElement::RasterDataGPU(raster_data_table))
|
||||
}
|
||||
}
|
||||
impl From<DAffine2> for GraphicGroupTable {
|
||||
fn from(_: DAffine2) -> Self {
|
||||
GraphicGroupTable::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// The possible forms of graphical content held in a Vec by the `elements` field of [`GraphicElement`].
|
||||
/// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax.
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub enum GraphicElement {
|
||||
pub enum Graphic {
|
||||
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
|
||||
GraphicGroup(GraphicGroupTable),
|
||||
GraphicGroup(Table<Graphic>),
|
||||
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
|
||||
VectorData(VectorDataTable),
|
||||
RasterDataCPU(RasterDataTable<CPU>),
|
||||
RasterDataGPU(RasterDataTable<GPU>),
|
||||
VectorData(Table<VectorData>),
|
||||
RasterDataCPU(Table<Raster<CPU>>),
|
||||
RasterDataGPU(Table<Raster<GPU>>),
|
||||
}
|
||||
|
||||
impl Default for GraphicElement {
|
||||
impl Default for Graphic {
|
||||
fn default() -> Self {
|
||||
Self::GraphicGroup(GraphicGroupTable::default())
|
||||
Self::GraphicGroup(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DAffine2> for GraphicElement {
|
||||
// GraphicGroup
|
||||
impl From<Table<Graphic>> for Graphic {
|
||||
fn from(graphic_group: Table<Graphic>) -> Self {
|
||||
Graphic::GraphicGroup(graphic_group)
|
||||
}
|
||||
}
|
||||
|
||||
// VectorData
|
||||
impl From<VectorData> for Graphic {
|
||||
fn from(vector_data: VectorData) -> Self {
|
||||
Graphic::VectorData(Table::new_from_element(vector_data))
|
||||
}
|
||||
}
|
||||
impl From<Table<VectorData>> for Graphic {
|
||||
fn from(vector_data: Table<VectorData>) -> Self {
|
||||
Graphic::VectorData(vector_data)
|
||||
}
|
||||
}
|
||||
impl From<VectorData> for Table<Graphic> {
|
||||
fn from(vector_data: VectorData) -> Self {
|
||||
Table::new_from_element(Graphic::VectorData(Table::new_from_element(vector_data)))
|
||||
}
|
||||
}
|
||||
impl From<Table<VectorData>> for Table<Graphic> {
|
||||
fn from(vector_data: Table<VectorData>) -> Self {
|
||||
Table::new_from_element(Graphic::VectorData(vector_data))
|
||||
}
|
||||
}
|
||||
|
||||
// Raster<CPU>
|
||||
impl From<Raster<CPU>> for Graphic {
|
||||
fn from(raster_data: Raster<CPU>) -> Self {
|
||||
Graphic::RasterDataCPU(Table::new_from_element(raster_data))
|
||||
}
|
||||
}
|
||||
impl From<Table<Raster<CPU>>> for Graphic {
|
||||
fn from(raster_data: Table<Raster<CPU>>) -> Self {
|
||||
Graphic::RasterDataCPU(raster_data)
|
||||
}
|
||||
}
|
||||
impl From<Raster<CPU>> for Table<Graphic> {
|
||||
fn from(raster_data: Raster<CPU>) -> Self {
|
||||
Table::new_from_element(Graphic::RasterDataCPU(Table::new_from_element(raster_data)))
|
||||
}
|
||||
}
|
||||
impl From<Table<Raster<CPU>>> for Table<Graphic> {
|
||||
fn from(raster_data_table: Table<Raster<CPU>>) -> Self {
|
||||
Table::new_from_element(Graphic::RasterDataCPU(raster_data_table))
|
||||
}
|
||||
}
|
||||
|
||||
// Raster<GPU>
|
||||
impl From<Raster<GPU>> for Graphic {
|
||||
fn from(raster_data: Raster<GPU>) -> Self {
|
||||
Graphic::RasterDataGPU(Table::new_from_element(raster_data))
|
||||
}
|
||||
}
|
||||
impl From<Table<Raster<GPU>>> for Graphic {
|
||||
fn from(raster_data: Table<Raster<GPU>>) -> Self {
|
||||
Graphic::RasterDataGPU(raster_data)
|
||||
}
|
||||
}
|
||||
impl From<Raster<GPU>> for Table<Graphic> {
|
||||
fn from(raster_data: Raster<GPU>) -> Self {
|
||||
Table::new_from_element(Graphic::RasterDataGPU(Table::new_from_element(raster_data)))
|
||||
}
|
||||
}
|
||||
impl From<Table<Raster<GPU>>> for Table<Graphic> {
|
||||
fn from(raster_data_table: Table<Raster<GPU>>) -> Self {
|
||||
Table::new_from_element(Graphic::RasterDataGPU(raster_data_table))
|
||||
}
|
||||
}
|
||||
|
||||
// DAffine2
|
||||
impl From<DAffine2> for Graphic {
|
||||
fn from(_: DAffine2) -> Self {
|
||||
GraphicElement::default()
|
||||
Graphic::default()
|
||||
}
|
||||
}
|
||||
impl From<DAffine2> for Table<Graphic> {
|
||||
fn from(_: DAffine2) -> Self {
|
||||
Table::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphicElement {
|
||||
pub fn as_group(&self) -> Option<&GraphicGroupTable> {
|
||||
impl Graphic {
|
||||
pub fn as_group(&self) -> Option<&Table<Graphic>> {
|
||||
match self {
|
||||
GraphicElement::GraphicGroup(group) => Some(group),
|
||||
Graphic::GraphicGroup(group) => Some(group),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_group_mut(&mut self) -> Option<&mut GraphicGroupTable> {
|
||||
pub fn as_group_mut(&mut self) -> Option<&mut Table<Graphic>> {
|
||||
match self {
|
||||
GraphicElement::GraphicGroup(group) => Some(group),
|
||||
Graphic::GraphicGroup(group) => Some(group),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vector_data(&self) -> Option<&VectorDataTable> {
|
||||
pub fn as_vector_data(&self) -> Option<&Table<VectorData>> {
|
||||
match self {
|
||||
GraphicElement::VectorData(data) => Some(data),
|
||||
Graphic::VectorData(data) => Some(data),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vector_data_mut(&mut self) -> Option<&mut VectorDataTable> {
|
||||
pub fn as_vector_data_mut(&mut self) -> Option<&mut Table<VectorData>> {
|
||||
match self {
|
||||
GraphicElement::VectorData(data) => Some(data),
|
||||
Graphic::VectorData(data) => Some(data),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_raster(&self) -> Option<&RasterDataTable<CPU>> {
|
||||
pub fn as_raster(&self) -> Option<&Table<Raster<CPU>>> {
|
||||
match self {
|
||||
GraphicElement::RasterDataCPU(raster) => Some(raster),
|
||||
Graphic::RasterDataCPU(raster) => Some(raster),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_raster_mut(&mut self) -> Option<&mut RasterDataTable<CPU>> {
|
||||
pub fn as_raster_mut(&mut self) -> Option<&mut Table<Raster<CPU>>> {
|
||||
match self {
|
||||
GraphicElement::RasterDataCPU(raster) => Some(raster),
|
||||
Graphic::RasterDataCPU(raster) => Some(raster),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn had_clip_enabled(&self) -> bool {
|
||||
match self {
|
||||
GraphicElement::VectorData(data) => data.instance_ref_iter().all(|instance| instance.alpha_blending.clip),
|
||||
GraphicElement::GraphicGroup(data) => data.instance_ref_iter().all(|instance| instance.alpha_blending.clip),
|
||||
GraphicElement::RasterDataCPU(data) => data.instance_ref_iter().all(|instance| instance.alpha_blending.clip),
|
||||
GraphicElement::RasterDataGPU(data) => data.instance_ref_iter().all(|instance| instance.alpha_blending.clip),
|
||||
Graphic::VectorData(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
|
||||
Graphic::GraphicGroup(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
|
||||
Graphic::RasterDataCPU(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
|
||||
Graphic::RasterDataGPU(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_reduce_to_clip_path(&self) -> bool {
|
||||
match self {
|
||||
GraphicElement::VectorData(vector_data_table) => vector_data_table.instance_ref_iter().all(|instance_data| {
|
||||
let style = &instance_data.instance.style;
|
||||
let alpha_blending = &instance_data.alpha_blending;
|
||||
Graphic::VectorData(vector_data_table) => vector_data_table.iter_ref().all(|row| {
|
||||
let style = &row.element.style;
|
||||
let alpha_blending = &row.alpha_blending;
|
||||
(alpha_blending.opacity > 1. - f32::EPSILON) && style.fill().is_opaque() && style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke())
|
||||
}),
|
||||
_ => false,
|
||||
@@ -193,108 +176,21 @@ impl GraphicElement {
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundingBox for GraphicElement {
|
||||
impl BoundingBox for Graphic {
|
||||
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
|
||||
match self {
|
||||
GraphicElement::VectorData(vector_data) => vector_data.bounding_box(transform, include_stroke),
|
||||
GraphicElement::RasterDataCPU(raster) => raster.bounding_box(transform, include_stroke),
|
||||
GraphicElement::RasterDataGPU(raster) => raster.bounding_box(transform, include_stroke),
|
||||
GraphicElement::GraphicGroup(graphic_group) => graphic_group.bounding_box(transform, include_stroke),
|
||||
Graphic::VectorData(vector_data) => vector_data.bounding_box(transform, include_stroke),
|
||||
Graphic::RasterDataCPU(raster) => raster.bounding_box(transform, include_stroke),
|
||||
Graphic::RasterDataGPU(raster) => raster.bounding_box(transform, include_stroke),
|
||||
Graphic::GraphicGroup(graphic_group) => graphic_group.bounding_box(transform, include_stroke),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundingBox for GraphicGroupTable {
|
||||
impl BoundingBox for Table<Graphic> {
|
||||
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
|
||||
self.instance_ref_iter()
|
||||
.filter_map(|element| element.instance.bounding_box(transform * *element.transform, include_stroke))
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
}
|
||||
|
||||
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Artboard {
|
||||
pub graphic_group: GraphicGroupTable,
|
||||
pub label: String,
|
||||
pub location: IVec2,
|
||||
pub dimensions: IVec2,
|
||||
pub background: Color,
|
||||
pub clip: bool,
|
||||
}
|
||||
|
||||
impl Default for Artboard {
|
||||
fn default() -> Self {
|
||||
Self::new(IVec2::ZERO, IVec2::new(1920, 1080))
|
||||
}
|
||||
}
|
||||
|
||||
impl Artboard {
|
||||
pub fn new(location: IVec2, dimensions: IVec2) -> Self {
|
||||
Self {
|
||||
graphic_group: GraphicGroupTable::default(),
|
||||
label: "Artboard".to_string(),
|
||||
location: location.min(location + dimensions),
|
||||
dimensions: dimensions.abs(),
|
||||
background: Color::WHITE,
|
||||
clip: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundingBox for Artboard {
|
||||
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
|
||||
let artboard_bounds = (transform * Quad::from_box([self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()])).bounding_box();
|
||||
if self.clip {
|
||||
Some(artboard_bounds)
|
||||
} else {
|
||||
[self.graphic_group.bounding_box(transform, include_stroke), Some(artboard_bounds)]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<ArtboardGroupTable, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Default, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ArtboardGroup {
|
||||
pub artboards: Vec<(Artboard, Option<NodeId>)>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum EitherFormat {
|
||||
ArtboardGroup(ArtboardGroup),
|
||||
ArtboardGroupTable(ArtboardGroupTable),
|
||||
}
|
||||
|
||||
Ok(match EitherFormat::deserialize(deserializer)? {
|
||||
EitherFormat::ArtboardGroup(artboard_group) => {
|
||||
let mut table = ArtboardGroupTable::default();
|
||||
for (artboard, source_node_id) in artboard_group.artboards {
|
||||
table.push(Instance {
|
||||
instance: artboard,
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
source_node_id,
|
||||
});
|
||||
}
|
||||
table
|
||||
}
|
||||
EitherFormat::ArtboardGroupTable(artboard_group_table) => artboard_group_table,
|
||||
})
|
||||
}
|
||||
|
||||
pub type ArtboardGroupTable = Instances<Artboard>;
|
||||
|
||||
impl BoundingBox for ArtboardGroupTable {
|
||||
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
|
||||
self.instance_ref_iter()
|
||||
.filter_map(|instance| instance.instance.bounding_box(transform, include_stroke))
|
||||
self.iter_ref()
|
||||
.filter_map(|element| element.element.bounding_box(transform * *element.transform, include_stroke))
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
}
|
||||
@@ -302,15 +198,15 @@ impl BoundingBox for ArtboardGroupTable {
|
||||
#[node_macro::node(category(""))]
|
||||
async fn layer<I: 'n + Send + Clone>(
|
||||
_: impl Ctx,
|
||||
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>, RasterDataTable<GPU>)] mut stack: Instances<I>,
|
||||
#[implementations(GraphicElement, VectorData, Raster<CPU>, Raster<GPU>)] element: I,
|
||||
#[implementations(Table<Graphic>, Table<VectorData>, Table<Raster<CPU>>, Table<Raster<GPU>>)] mut stack: Table<I>,
|
||||
#[implementations(Graphic, VectorData, Raster<CPU>, Raster<GPU>)] element: I,
|
||||
node_path: Vec<NodeId>,
|
||||
) -> Instances<I> {
|
||||
) -> Table<I> {
|
||||
// Get the penultimate element of the node path, or None if the path is too short
|
||||
let source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
|
||||
|
||||
stack.push(Instance {
|
||||
instance: element,
|
||||
stack.push(TableRow {
|
||||
element,
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
source_node_id,
|
||||
@@ -320,60 +216,60 @@ async fn layer<I: 'n + Send + Clone>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
async fn to_element<Data: Into<GraphicElement> + 'n>(
|
||||
async fn to_element<Data: Into<Graphic> + 'n>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
RasterDataTable<CPU>,
|
||||
RasterDataTable<GPU>,
|
||||
Table<Graphic>,
|
||||
Table<VectorData>,
|
||||
Table<Raster<CPU>>,
|
||||
Table<Raster<GPU>>,
|
||||
DAffine2,
|
||||
)]
|
||||
data: Data,
|
||||
) -> GraphicElement {
|
||||
) -> Graphic {
|
||||
data.into()
|
||||
}
|
||||
|
||||
#[node_macro::node(category("General"))]
|
||||
async fn to_group<Data: Into<GraphicGroupTable> + 'n>(
|
||||
async fn to_group<Data: Into<Table<Graphic>> + 'n>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
RasterDataTable<CPU>,
|
||||
RasterDataTable<GPU>,
|
||||
Table<Graphic>,
|
||||
Table<VectorData>,
|
||||
Table<Raster<CPU>>,
|
||||
Table<Raster<GPU>>,
|
||||
)]
|
||||
element: Data,
|
||||
) -> GraphicGroupTable {
|
||||
) -> Table<Graphic> {
|
||||
element.into()
|
||||
}
|
||||
|
||||
#[node_macro::node(category("General"))]
|
||||
async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable {
|
||||
// TODO: Avoid mutable reference, instead return a new GraphicGroupTable?
|
||||
fn flatten_group(output_group_table: &mut GraphicGroupTable, current_group_table: GraphicGroupTable, fully_flatten: bool, recursion_depth: usize) {
|
||||
for current_instance in current_group_table.instance_ref_iter() {
|
||||
let current_element = current_instance.instance.clone();
|
||||
let reference = *current_instance.source_node_id;
|
||||
async fn flatten_group(_: impl Ctx, group: Table<Graphic>, fully_flatten: bool) -> Table<Graphic> {
|
||||
// TODO: Avoid mutable reference, instead return a new Table<Graphic>?
|
||||
fn flatten_group(output_group_table: &mut Table<Graphic>, current_group_table: Table<Graphic>, fully_flatten: bool, recursion_depth: usize) {
|
||||
for current_row in current_group_table.iter_ref() {
|
||||
let current_element = current_row.element.clone();
|
||||
let reference = *current_row.source_node_id;
|
||||
|
||||
let recurse = fully_flatten || recursion_depth == 0;
|
||||
|
||||
match current_element {
|
||||
// If we're allowed to recurse, flatten any GraphicGroups we encounter
|
||||
GraphicElement::GraphicGroup(mut current_element) if recurse => {
|
||||
Graphic::GraphicGroup(mut current_element) if recurse => {
|
||||
// Apply the parent group's transform to all child elements
|
||||
for graphic_element in current_element.instance_mut_iter() {
|
||||
*graphic_element.transform = *current_instance.transform * *graphic_element.transform;
|
||||
for graphic_element in current_element.iter_mut() {
|
||||
*graphic_element.transform = *current_row.transform * *graphic_element.transform;
|
||||
}
|
||||
|
||||
flatten_group(output_group_table, current_element, fully_flatten, recursion_depth + 1);
|
||||
}
|
||||
// Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten
|
||||
_ => {
|
||||
output_group_table.push(Instance {
|
||||
instance: current_element,
|
||||
transform: *current_instance.transform,
|
||||
alpha_blending: *current_instance.alpha_blending,
|
||||
output_group_table.push(TableRow {
|
||||
element: current_element,
|
||||
transform: *current_row.transform,
|
||||
alpha_blending: *current_row.alpha_blending,
|
||||
source_node_id: reference,
|
||||
});
|
||||
}
|
||||
@@ -381,41 +277,41 @@ async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: boo
|
||||
}
|
||||
}
|
||||
|
||||
let mut output = GraphicGroupTable::default();
|
||||
let mut output = Table::new();
|
||||
flatten_group(&mut output, group, fully_flatten, 0);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"))]
|
||||
async fn flatten_vector(_: impl Ctx, group: GraphicGroupTable) -> VectorDataTable {
|
||||
// TODO: Avoid mutable reference, instead return a new GraphicGroupTable?
|
||||
fn flatten_group(output_group_table: &mut VectorDataTable, current_group_table: GraphicGroupTable) {
|
||||
for current_instance in current_group_table.instance_ref_iter() {
|
||||
let current_element = current_instance.instance.clone();
|
||||
let reference = *current_instance.source_node_id;
|
||||
async fn flatten_vector(_: impl Ctx, group: Table<Graphic>) -> Table<VectorData> {
|
||||
// TODO: Avoid mutable reference, instead return a new Table<Graphic>?
|
||||
fn flatten_group(output_group_table: &mut Table<VectorData>, current_group_table: Table<Graphic>) {
|
||||
for current_graphic_element_row in current_group_table.iter_ref() {
|
||||
let current_element = current_graphic_element_row.element.clone();
|
||||
let reference = *current_graphic_element_row.source_node_id;
|
||||
|
||||
match current_element {
|
||||
// If we're allowed to recurse, flatten any GraphicGroups we encounter
|
||||
GraphicElement::GraphicGroup(mut current_element) => {
|
||||
Graphic::GraphicGroup(mut current_element) => {
|
||||
// Apply the parent group's transform to all child elements
|
||||
for graphic_element in current_element.instance_mut_iter() {
|
||||
*graphic_element.transform = *current_instance.transform * *graphic_element.transform;
|
||||
for graphic_element in current_element.iter_mut() {
|
||||
*graphic_element.transform = *current_graphic_element_row.transform * *graphic_element.transform;
|
||||
}
|
||||
|
||||
flatten_group(output_group_table, current_element);
|
||||
}
|
||||
// Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten
|
||||
GraphicElement::VectorData(vector_instance) => {
|
||||
for current_element in vector_instance.instance_ref_iter() {
|
||||
output_group_table.push(Instance {
|
||||
instance: current_element.instance.clone(),
|
||||
transform: *current_instance.transform * *current_element.transform,
|
||||
Graphic::VectorData(vector_table) => {
|
||||
for current_vector_row in vector_table.iter_ref() {
|
||||
output_group_table.push(TableRow {
|
||||
element: current_vector_row.element.clone(),
|
||||
transform: *current_graphic_element_row.transform * *current_vector_row.transform,
|
||||
alpha_blending: AlphaBlending {
|
||||
blend_mode: current_element.alpha_blending.blend_mode,
|
||||
opacity: current_instance.alpha_blending.opacity * current_element.alpha_blending.opacity,
|
||||
fill: current_element.alpha_blending.fill,
|
||||
clip: current_element.alpha_blending.clip,
|
||||
blend_mode: current_vector_row.alpha_blending.blend_mode,
|
||||
opacity: current_graphic_element_row.alpha_blending.opacity * current_vector_row.alpha_blending.opacity,
|
||||
fill: current_vector_row.alpha_blending.fill,
|
||||
clip: current_vector_row.alpha_blending.clip,
|
||||
},
|
||||
source_node_id: reference,
|
||||
});
|
||||
@@ -426,113 +322,12 @@ async fn flatten_vector(_: impl Ctx, group: GraphicGroupTable) -> VectorDataTabl
|
||||
}
|
||||
}
|
||||
|
||||
let mut output = VectorDataTable::default();
|
||||
let mut output = Table::new();
|
||||
flatten_group(&mut output, group);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn to_artboard<Data: Into<GraphicGroupTable> + 'n>(
|
||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||
#[implementations(
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> VectorDataTable,
|
||||
Context -> RasterDataTable<CPU>,
|
||||
Context -> RasterDataTable<GPU>,
|
||||
Context -> DAffine2,
|
||||
)]
|
||||
contents: impl Node<Context<'static>, Output = Data>,
|
||||
label: String,
|
||||
location: DVec2,
|
||||
dimensions: DVec2,
|
||||
background: Color,
|
||||
clip: bool,
|
||||
) -> Artboard {
|
||||
let location = location.as_ivec2();
|
||||
let dimensions = dimensions.as_ivec2().max(IVec2::ONE);
|
||||
|
||||
let footprint = ctx.try_footprint().copied();
|
||||
let mut new_ctx = OwnedContextImpl::from(ctx);
|
||||
if let Some(mut footprint) = footprint {
|
||||
footprint.translate(location.as_dvec2());
|
||||
new_ctx = new_ctx.with_footprint(footprint);
|
||||
}
|
||||
let graphic_group = contents.eval(new_ctx.into_context()).await;
|
||||
|
||||
Artboard {
|
||||
graphic_group: graphic_group.into(),
|
||||
label,
|
||||
location: location.min(location + dimensions),
|
||||
dimensions: dimensions.abs(),
|
||||
background,
|
||||
clip,
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
pub async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroupTable, artboard: Artboard, node_path: Vec<NodeId>) -> ArtboardGroupTable {
|
||||
// Get the penultimate element of the node path, or None if the path is too short.
|
||||
// This is used to get the ID of the user-facing "Artboard" node (which encapsulates this internal "Append Artboard" node).
|
||||
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
|
||||
|
||||
artboards.push(Instance {
|
||||
instance: artboard,
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
source_node_id: encapsulating_node_id,
|
||||
});
|
||||
|
||||
artboards
|
||||
}
|
||||
|
||||
// TODO: Remove this one
|
||||
impl From<Image<Color>> for GraphicElement {
|
||||
fn from(raster_data: Image<Color>) -> Self {
|
||||
GraphicElement::RasterDataCPU(RasterDataTable::<CPU>::new(Raster::new_cpu(raster_data)))
|
||||
}
|
||||
}
|
||||
impl From<RasterDataTable<CPU>> for GraphicElement {
|
||||
fn from(raster_data: RasterDataTable<CPU>) -> Self {
|
||||
GraphicElement::RasterDataCPU(raster_data)
|
||||
}
|
||||
}
|
||||
impl From<RasterDataTable<GPU>> for GraphicElement {
|
||||
fn from(raster_data: RasterDataTable<GPU>) -> Self {
|
||||
GraphicElement::RasterDataGPU(raster_data)
|
||||
}
|
||||
}
|
||||
impl From<Raster<CPU>> for GraphicElement {
|
||||
fn from(raster_data: Raster<CPU>) -> Self {
|
||||
GraphicElement::RasterDataCPU(RasterDataTable::new(raster_data))
|
||||
}
|
||||
}
|
||||
impl From<Raster<GPU>> for GraphicElement {
|
||||
fn from(raster_data: Raster<GPU>) -> Self {
|
||||
GraphicElement::RasterDataGPU(RasterDataTable::new(raster_data))
|
||||
}
|
||||
}
|
||||
// TODO: Remove this one
|
||||
impl From<VectorData> for GraphicElement {
|
||||
fn from(vector_data: VectorData) -> Self {
|
||||
GraphicElement::VectorData(VectorDataTable::new(vector_data))
|
||||
}
|
||||
}
|
||||
impl From<VectorDataTable> for GraphicElement {
|
||||
fn from(vector_data: VectorDataTable) -> Self {
|
||||
GraphicElement::VectorData(vector_data)
|
||||
}
|
||||
}
|
||||
impl From<GraphicGroupTable> for GraphicElement {
|
||||
fn from(graphic_group: GraphicGroupTable) -> Self {
|
||||
GraphicElement::GraphicGroup(graphic_group)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToGraphicElement {
|
||||
fn to_graphic_element(&self) -> GraphicElement;
|
||||
}
|
||||
|
||||
/// Returns the value at the specified index in the collection.
|
||||
/// If that index has no value, the type's default value is returned.
|
||||
#[node_macro::node(category("General"))]
|
||||
@@ -544,9 +339,9 @@ fn index<T: AtIndex + Clone + Default>(
|
||||
Vec<Option<Color>>,
|
||||
Vec<f64>, Vec<u64>,
|
||||
Vec<DVec2>,
|
||||
VectorDataTable,
|
||||
RasterDataTable<CPU>,
|
||||
GraphicGroupTable,
|
||||
Table<VectorData>,
|
||||
Table<Raster<CPU>>,
|
||||
Table<Graphic>,
|
||||
)]
|
||||
collection: T,
|
||||
/// The index of the item to retrieve, starting from 0 for the first item.
|
||||
@@ -569,16 +364,75 @@ impl<T: Clone> AtIndex for Vec<T> {
|
||||
self.get(index).cloned()
|
||||
}
|
||||
}
|
||||
impl<T: Clone> AtIndex for Instances<T> {
|
||||
type Output = Instances<T>;
|
||||
impl<T: Clone> AtIndex for Table<T> {
|
||||
type Output = Table<T>;
|
||||
|
||||
fn at_index(&self, index: usize) -> Option<Self::Output> {
|
||||
let mut result_table = Self::default();
|
||||
if let Some(row) = self.instance_ref_iter().nth(index) {
|
||||
result_table.push(row.to_instance_cloned());
|
||||
if let Some(row) = self.iter_ref().nth(index) {
|
||||
result_table.push(row.into_cloned());
|
||||
Some(result_table)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Graphic>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct OldGraphicGroup {
|
||||
elements: Vec<(Graphic, Option<NodeId>)>,
|
||||
transform: DAffine2,
|
||||
alpha_blending: AlphaBlending,
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GraphicGroup {
|
||||
elements: Vec<(Graphic, Option<NodeId>)>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum EitherFormat {
|
||||
OldGraphicGroup(OldGraphicGroup),
|
||||
Table(serde_json::Value),
|
||||
}
|
||||
|
||||
Ok(match EitherFormat::deserialize(deserializer)? {
|
||||
EitherFormat::OldGraphicGroup(old) => {
|
||||
let mut graphic_group_table = Table::new();
|
||||
for (graphic_element, source_node_id) in old.elements {
|
||||
graphic_group_table.push(TableRow {
|
||||
element: graphic_element,
|
||||
transform: old.transform,
|
||||
alpha_blending: old.alpha_blending,
|
||||
source_node_id,
|
||||
});
|
||||
}
|
||||
graphic_group_table
|
||||
}
|
||||
EitherFormat::Table(value) => {
|
||||
// Try to deserialize as either table format
|
||||
if let Ok(old_table) = serde_json::from_value::<Table<GraphicGroup>>(value.clone()) {
|
||||
let mut graphic_group_table = Table::new();
|
||||
for row in old_table.iter_ref() {
|
||||
for (graphic_element, source_node_id) in &row.element.elements {
|
||||
graphic_group_table.push(TableRow {
|
||||
element: graphic_element.clone(),
|
||||
transform: *row.transform,
|
||||
alpha_blending: *row.alpha_blending,
|
||||
source_node_id: *source_node_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
graphic_group_table
|
||||
} else if let Ok(new_table) = serde_json::from_value::<Table<Graphic>>(value) {
|
||||
new_table
|
||||
} else {
|
||||
return Err(serde::de::Error::custom("Failed to deserialize Table<Graphic>"));
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
use crate::AlphaBlending;
|
||||
use crate::transform::ApplyTransform;
|
||||
use crate::uuid::NodeId;
|
||||
use dyn_any::StaticType;
|
||||
use glam::DAffine2;
|
||||
use std::hash::Hash;
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Instances<T> {
|
||||
#[serde(alias = "instances")]
|
||||
instance: Vec<T>,
|
||||
#[serde(default = "one_daffine2_default")]
|
||||
transform: Vec<DAffine2>,
|
||||
#[serde(default = "one_alpha_blending_default")]
|
||||
alpha_blending: Vec<AlphaBlending>,
|
||||
#[serde(default = "one_source_node_id_default")]
|
||||
source_node_id: Vec<Option<NodeId>>,
|
||||
}
|
||||
|
||||
impl<T> Instances<T> {
|
||||
pub fn new(instance: T) -> Self {
|
||||
Self {
|
||||
instance: vec![instance],
|
||||
transform: vec![DAffine2::IDENTITY],
|
||||
alpha_blending: vec![AlphaBlending::default()],
|
||||
source_node_id: vec![None],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_instance(instance: Instance<T>) -> Self {
|
||||
Self {
|
||||
instance: vec![instance.instance],
|
||||
transform: vec![instance.transform],
|
||||
alpha_blending: vec![instance.alpha_blending],
|
||||
source_node_id: vec![instance.source_node_id],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
instance: Vec::with_capacity(capacity),
|
||||
transform: Vec::with_capacity(capacity),
|
||||
alpha_blending: Vec::with_capacity(capacity),
|
||||
source_node_id: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, instance: Instance<T>) {
|
||||
self.instance.push(instance.instance);
|
||||
self.transform.push(instance.transform);
|
||||
self.alpha_blending.push(instance.alpha_blending);
|
||||
self.source_node_id.push(instance.source_node_id);
|
||||
}
|
||||
|
||||
pub fn extend(&mut self, instances: Instances<T>) {
|
||||
self.instance.extend(instances.instance);
|
||||
self.transform.extend(instances.transform);
|
||||
self.alpha_blending.extend(instances.alpha_blending);
|
||||
self.source_node_id.extend(instances.source_node_id);
|
||||
}
|
||||
|
||||
pub fn instance_iter(self) -> impl DoubleEndedIterator<Item = Instance<T>> {
|
||||
self.instance
|
||||
.into_iter()
|
||||
.zip(self.transform)
|
||||
.zip(self.alpha_blending)
|
||||
.zip(self.source_node_id)
|
||||
.map(|(((instance, transform), alpha_blending), source_node_id)| Instance {
|
||||
instance,
|
||||
transform,
|
||||
alpha_blending,
|
||||
source_node_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn instance_ref_iter(&self) -> impl DoubleEndedIterator<Item = InstanceRef<'_, T>> + Clone {
|
||||
self.instance
|
||||
.iter()
|
||||
.zip(self.transform.iter())
|
||||
.zip(self.alpha_blending.iter())
|
||||
.zip(self.source_node_id.iter())
|
||||
.map(|(((instance, transform), alpha_blending), source_node_id)| InstanceRef {
|
||||
instance,
|
||||
transform,
|
||||
alpha_blending,
|
||||
source_node_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn instance_mut_iter(&mut self) -> impl DoubleEndedIterator<Item = InstanceMut<'_, T>> {
|
||||
self.instance
|
||||
.iter_mut()
|
||||
.zip(self.transform.iter_mut())
|
||||
.zip(self.alpha_blending.iter_mut())
|
||||
.zip(self.source_node_id.iter_mut())
|
||||
.map(|(((instance, transform), alpha_blending), source_node_id)| InstanceMut {
|
||||
instance,
|
||||
transform,
|
||||
alpha_blending,
|
||||
source_node_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(&self, index: usize) -> Option<InstanceRef<'_, T>> {
|
||||
if index >= self.instance.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(InstanceRef {
|
||||
instance: &self.instance[index],
|
||||
transform: &self.transform[index],
|
||||
alpha_blending: &self.alpha_blending[index],
|
||||
source_node_id: &self.source_node_id[index],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, index: usize) -> Option<InstanceMut<'_, T>> {
|
||||
if index >= self.instance.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(InstanceMut {
|
||||
instance: &mut self.instance[index],
|
||||
transform: &mut self.transform[index],
|
||||
alpha_blending: &mut self.alpha_blending[index],
|
||||
source_node_id: &mut self.source_node_id[index],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.instance.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.instance.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Instances<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
instance: Vec::new(),
|
||||
transform: Vec::new(),
|
||||
alpha_blending: Vec::new(),
|
||||
source_node_id: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash> Hash for Instances<T> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
for instance in &self.instance {
|
||||
instance.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ApplyTransform for Instances<T> {
|
||||
fn apply_transform(&mut self, modification: &DAffine2) {
|
||||
for transform in &mut self.transform {
|
||||
*transform *= *modification;
|
||||
}
|
||||
}
|
||||
|
||||
fn left_apply_transform(&mut self, modification: &DAffine2) {
|
||||
for transform in &mut self.transform {
|
||||
*transform = *modification * *transform;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> PartialEq for Instances<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.instance.len() == other.instance.len() && { self.instance.iter().zip(other.instance.iter()).all(|(a, b)| a == b) }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: StaticType + 'static> StaticType for Instances<T> {
|
||||
type Static = Instances<T>;
|
||||
}
|
||||
|
||||
impl<T> FromIterator<Instance<T>> for Instances<T> {
|
||||
fn from_iter<I: IntoIterator<Item = Instance<T>>>(iter: I) -> Self {
|
||||
let iter = iter.into_iter();
|
||||
let (lower, _) = iter.size_hint();
|
||||
let mut instances = Self::with_capacity(lower);
|
||||
for instance in iter {
|
||||
instances.push(instance);
|
||||
}
|
||||
instances
|
||||
}
|
||||
}
|
||||
|
||||
fn one_daffine2_default() -> Vec<DAffine2> {
|
||||
vec![DAffine2::IDENTITY]
|
||||
}
|
||||
fn one_alpha_blending_default() -> Vec<AlphaBlending> {
|
||||
vec![AlphaBlending::default()]
|
||||
}
|
||||
fn one_source_node_id_default() -> Vec<Option<NodeId>> {
|
||||
vec![None]
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct InstanceRef<'a, T> {
|
||||
pub instance: &'a T,
|
||||
pub transform: &'a DAffine2,
|
||||
pub alpha_blending: &'a AlphaBlending,
|
||||
pub source_node_id: &'a Option<NodeId>,
|
||||
}
|
||||
|
||||
impl<T> InstanceRef<'_, T> {
|
||||
pub fn to_instance_cloned(self) -> Instance<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
Instance {
|
||||
instance: self.instance.clone(),
|
||||
transform: *self.transform,
|
||||
alpha_blending: *self.alpha_blending,
|
||||
source_node_id: *self.source_node_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InstanceMut<'a, T> {
|
||||
pub instance: &'a mut T,
|
||||
pub transform: &'a mut DAffine2,
|
||||
pub alpha_blending: &'a mut AlphaBlending,
|
||||
pub source_node_id: &'a mut Option<NodeId>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Instance<T> {
|
||||
pub instance: T,
|
||||
pub transform: DAffine2,
|
||||
pub alpha_blending: AlphaBlending,
|
||||
pub source_node_id: Option<NodeId>,
|
||||
}
|
||||
|
||||
impl<T> Instance<T> {
|
||||
pub fn to_graphic_element<U>(self) -> Instance<U>
|
||||
where
|
||||
T: Into<U>,
|
||||
{
|
||||
Instance {
|
||||
instance: self.instance.into(),
|
||||
transform: self.transform,
|
||||
alpha_blending: self.alpha_blending,
|
||||
source_node_id: self.source_node_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_instance_ref(&self) -> InstanceRef<'_, T> {
|
||||
InstanceRef {
|
||||
instance: &self.instance,
|
||||
transform: &self.transform,
|
||||
alpha_blending: &self.alpha_blending,
|
||||
source_node_id: &self.source_node_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_instance_mut(&mut self) -> InstanceMut<'_, T> {
|
||||
InstanceMut {
|
||||
instance: &mut self.instance,
|
||||
transform: &mut self.transform,
|
||||
alpha_blending: &mut self.alpha_blending,
|
||||
source_node_id: &mut self.source_node_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_table(self) -> Instances<T> {
|
||||
Instances {
|
||||
instance: vec![self.instance],
|
||||
transform: vec![self.transform],
|
||||
alpha_blending: vec![self.alpha_blending],
|
||||
source_node_id: vec![self.source_node_id],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
extern crate log;
|
||||
|
||||
pub mod animation;
|
||||
pub mod artboard;
|
||||
pub mod blending_nodes;
|
||||
pub mod bounds;
|
||||
pub mod consts;
|
||||
@@ -11,7 +12,6 @@ pub mod extract_xy;
|
||||
pub mod generic;
|
||||
pub mod gradient;
|
||||
pub mod graphic_element;
|
||||
pub mod instances;
|
||||
pub mod logic;
|
||||
pub mod math;
|
||||
pub mod memo;
|
||||
@@ -22,6 +22,7 @@ pub mod raster_types;
|
||||
pub mod registry;
|
||||
pub mod render_complexity;
|
||||
pub mod structural;
|
||||
pub mod table;
|
||||
pub mod text;
|
||||
pub mod transform;
|
||||
pub mod transform_nodes;
|
||||
@@ -30,6 +31,7 @@ pub mod value;
|
||||
pub mod vector;
|
||||
|
||||
pub use crate as graphene_core;
|
||||
pub use artboard::Artboard;
|
||||
pub use blending::*;
|
||||
pub use color::Color;
|
||||
pub use context::*;
|
||||
@@ -39,7 +41,7 @@ pub use graphene_core_shaders::AsU32;
|
||||
pub use graphene_core_shaders::blending;
|
||||
pub use graphene_core_shaders::choice_type;
|
||||
pub use graphene_core_shaders::color;
|
||||
pub use graphic_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable};
|
||||
pub use graphic_element::Graphic;
|
||||
pub use memo::MemoHash;
|
||||
pub use num_traits;
|
||||
use std::any::TypeId;
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
use crate::ArtboardGroupTable;
|
||||
use crate::Artboard;
|
||||
use crate::Color;
|
||||
use crate::GraphicElement;
|
||||
use crate::GraphicGroupTable;
|
||||
use crate::Graphic;
|
||||
use crate::gradient::GradientStops;
|
||||
use crate::graphene_core::registry::types::TextArea;
|
||||
use crate::raster_types::{CPU, GPU, RasterDataTable};
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::raster_types::{CPU, GPU, Raster};
|
||||
use crate::table::Table;
|
||||
use crate::vector::VectorData;
|
||||
use crate::{Context, Ctx};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[node_macro::node(category("Text"))]
|
||||
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, VectorDataTable)] value: T) -> String {
|
||||
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Table<VectorData>)] value: T) -> String {
|
||||
format!("{:?}", value)
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Text"))]
|
||||
fn serialize<T: serde::Serialize>(
|
||||
_: impl Ctx,
|
||||
#[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Color, Option<Color>, GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] value: T,
|
||||
#[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Color, Option<Color>, Table<Graphic>, Table<VectorData>, Table<Raster<CPU>>)] value: T,
|
||||
) -> String {
|
||||
serde_json::to_string(&value).unwrap_or_else(|_| "Serialization Error".to_string())
|
||||
}
|
||||
@@ -59,12 +59,12 @@ async fn switch<T, C: Send + 'n + Clone>(
|
||||
Context -> u64,
|
||||
Context -> DVec2,
|
||||
Context -> DAffine2,
|
||||
Context -> ArtboardGroupTable,
|
||||
Context -> VectorDataTable,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> RasterDataTable<CPU>,
|
||||
Context -> RasterDataTable<GPU>,
|
||||
Context -> GraphicElement,
|
||||
Context -> Table<Artboard>,
|
||||
Context -> Table<VectorData>,
|
||||
Context -> Table<Graphic>,
|
||||
Context -> Table<Raster<CPU>>,
|
||||
Context -> Table<Raster<GPU>>,
|
||||
Context -> Graphic,
|
||||
Context -> Color,
|
||||
Context -> Option<Color>,
|
||||
Context -> GradientStops,
|
||||
@@ -80,12 +80,12 @@ async fn switch<T, C: Send + 'n + Clone>(
|
||||
Context -> u64,
|
||||
Context -> DVec2,
|
||||
Context -> DAffine2,
|
||||
Context -> ArtboardGroupTable,
|
||||
Context -> VectorDataTable,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> RasterDataTable<CPU>,
|
||||
Context -> RasterDataTable<GPU>,
|
||||
Context -> GraphicElement,
|
||||
Context -> Table<Artboard>,
|
||||
Context -> Table<VectorData>,
|
||||
Context -> Table<Graphic>,
|
||||
Context -> Table<Raster<CPU>>,
|
||||
Context -> Table<Raster<GPU>>,
|
||||
Context -> Graphic,
|
||||
Context -> Color,
|
||||
Context -> Option<Color>,
|
||||
Context -> GradientStops,
|
||||
|
||||
@@ -6,10 +6,8 @@ pub mod color {
|
||||
pub mod image;
|
||||
|
||||
pub use self::image::Image;
|
||||
use crate::GraphicGroupTable;
|
||||
pub use crate::color::*;
|
||||
use crate::raster_types::{CPU, RasterDataTable};
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::raster_types::CPU;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait Bitmap {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use super::Color;
|
||||
use crate::AlphaBlending;
|
||||
use crate::color::float_to_srgb_u8;
|
||||
use crate::instances::{Instance, Instances};
|
||||
use crate::raster_types::Raster;
|
||||
use crate::table::{Table, TableRow};
|
||||
use crate::vector::VectorData;
|
||||
use core::hash::{Hash, Hasher};
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -212,25 +213,23 @@ impl<P: Pixel> IntoIterator for Image<P> {
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<RasterDataTable<CPU>, D::Error> {
|
||||
pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Raster<CPU>>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
type ImageFrameTable<P> = Instances<Image<P>>;
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
|
||||
enum RasterFrame {
|
||||
/// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
|
||||
ImageFrame(ImageFrameTable<Color>),
|
||||
ImageFrame(Table<Image<Color>>),
|
||||
}
|
||||
impl<'de> serde::Deserialize<'de> for RasterFrame {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
Ok(RasterFrame::ImageFrame(ImageFrameTable::new(Image::deserialize(deserializer)?)))
|
||||
Ok(RasterFrame::ImageFrame(Table::new_from_element(Image::deserialize(deserializer)?)))
|
||||
}
|
||||
}
|
||||
impl serde::Serialize for RasterFrame {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
match self {
|
||||
RasterFrame::ImageFrame(image_instances) => image_instances.serialize(serializer),
|
||||
RasterFrame::ImageFrame(table) => table.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,9 +237,9 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub enum GraphicElement {
|
||||
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
|
||||
GraphicGroup(GraphicGroupTable),
|
||||
GraphicGroup(Table<GraphicElement>),
|
||||
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
|
||||
VectorData(VectorDataTable),
|
||||
VectorData(Table<VectorData>),
|
||||
RasterFrame(RasterFrame),
|
||||
}
|
||||
|
||||
@@ -250,14 +249,14 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
||||
}
|
||||
impl From<ImageFrame<Color>> for GraphicElement {
|
||||
fn from(image_frame: ImageFrame<Color>) -> Self {
|
||||
GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame.image)))
|
||||
GraphicElement::RasterFrame(RasterFrame::ImageFrame(Table::new_from_element(image_frame.image)))
|
||||
}
|
||||
}
|
||||
impl From<GraphicElement> for ImageFrame<Color> {
|
||||
fn from(element: GraphicElement) -> Self {
|
||||
match element {
|
||||
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {
|
||||
image: image.instance_ref_iter().next().unwrap().instance.clone(),
|
||||
image: image.iter_ref().next().unwrap().element.clone(),
|
||||
},
|
||||
_ => panic!("Expected Image, found {:?}", element),
|
||||
}
|
||||
@@ -284,54 +283,52 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
||||
enum FormatVersions {
|
||||
Image(Image<Color>),
|
||||
OldImageFrame(OldImageFrame<Color>),
|
||||
ImageFrame(Instances<ImageFrame<Color>>),
|
||||
ImageFrameTable(ImageFrameTable<Color>),
|
||||
RasterDataTable(RasterDataTable<CPU>),
|
||||
ImageFrame(Table<ImageFrame<Color>>),
|
||||
ImageFrameTable(Table<Image<Color>>),
|
||||
RasterDataTable(Table<Raster<CPU>>),
|
||||
}
|
||||
|
||||
Ok(match FormatVersions::deserialize(deserializer)? {
|
||||
FormatVersions::Image(image) => RasterDataTable::new(Raster::new_cpu(image)),
|
||||
FormatVersions::Image(image) => Table::new_from_element(Raster::new_cpu(image)),
|
||||
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => {
|
||||
let OldImageFrame { image, transform, alpha_blending } = image_frame_with_transform_and_blending;
|
||||
let mut image_frame_table = RasterDataTable::new(Raster::new_cpu(image));
|
||||
*image_frame_table.instance_mut_iter().next().unwrap().transform = transform;
|
||||
*image_frame_table.instance_mut_iter().next().unwrap().alpha_blending = alpha_blending;
|
||||
let mut image_frame_table = Table::new_from_element(Raster::new_cpu(image));
|
||||
*image_frame_table.iter_mut().next().unwrap().transform = transform;
|
||||
*image_frame_table.iter_mut().next().unwrap().alpha_blending = alpha_blending;
|
||||
image_frame_table
|
||||
}
|
||||
FormatVersions::ImageFrame(image_frame) => RasterDataTable::new(Raster::new_cpu(
|
||||
FormatVersions::ImageFrame(image_frame) => Table::new_from_element(Raster::new_cpu(
|
||||
image_frame
|
||||
.instance_ref_iter()
|
||||
.iter_ref()
|
||||
.next()
|
||||
.unwrap_or(Instances::new(ImageFrame::default()).instance_ref_iter().next().unwrap())
|
||||
.instance
|
||||
.unwrap_or(Table::new_from_element(ImageFrame::default()).iter_ref().next().unwrap())
|
||||
.element
|
||||
.image
|
||||
.clone(),
|
||||
)),
|
||||
FormatVersions::ImageFrameTable(image_frame_table) => RasterDataTable::new(Raster::new_cpu(image_frame_table.instance_ref_iter().next().unwrap().instance.clone())),
|
||||
FormatVersions::ImageFrameTable(image_frame_table) => Table::new_from_element(Raster::new_cpu(image_frame_table.iter_ref().next().unwrap().element.clone())),
|
||||
FormatVersions::RasterDataTable(raster_data_table) => raster_data_table,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Instance<Raster<CPU>>, D::Error> {
|
||||
pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<TableRow<Raster<CPU>>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
type ImageFrameTable<P> = Instances<Image<P>>;
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
|
||||
enum RasterFrame {
|
||||
/// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
|
||||
ImageFrame(ImageFrameTable<Color>),
|
||||
ImageFrame(Table<Image<Color>>),
|
||||
}
|
||||
impl<'de> serde::Deserialize<'de> for RasterFrame {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
Ok(RasterFrame::ImageFrame(ImageFrameTable::new(Image::deserialize(deserializer)?)))
|
||||
Ok(RasterFrame::ImageFrame(Table::new_from_element(Image::deserialize(deserializer)?)))
|
||||
}
|
||||
}
|
||||
impl serde::Serialize for RasterFrame {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
match self {
|
||||
RasterFrame::ImageFrame(image_instances) => image_instances.serialize(serializer),
|
||||
RasterFrame::ImageFrame(table) => table.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,9 +336,9 @@ pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializ
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub enum GraphicElement {
|
||||
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
|
||||
GraphicGroup(GraphicGroupTable),
|
||||
GraphicGroup(Table<GraphicElement>),
|
||||
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
|
||||
VectorData(VectorDataTable),
|
||||
VectorData(Table<VectorData>),
|
||||
RasterFrame(RasterFrame),
|
||||
}
|
||||
|
||||
@@ -351,14 +348,14 @@ pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializ
|
||||
}
|
||||
impl From<ImageFrame<Color>> for GraphicElement {
|
||||
fn from(image_frame: ImageFrame<Color>) -> Self {
|
||||
GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame.image)))
|
||||
GraphicElement::RasterFrame(RasterFrame::ImageFrame(Table::new_from_element(image_frame.image)))
|
||||
}
|
||||
}
|
||||
impl From<GraphicElement> for ImageFrame<Color> {
|
||||
fn from(element: GraphicElement) -> Self {
|
||||
match element {
|
||||
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {
|
||||
image: image.instance_ref_iter().next().unwrap().instance.clone(),
|
||||
image: image.iter_ref().next().unwrap().element.clone(),
|
||||
},
|
||||
_ => panic!("Expected Image, found {:?}", element),
|
||||
}
|
||||
@@ -385,33 +382,31 @@ pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializ
|
||||
enum FormatVersions {
|
||||
Image(Image<Color>),
|
||||
OldImageFrame(OldImageFrame<Color>),
|
||||
ImageFrame(Instances<ImageFrame<Color>>),
|
||||
RasterDataTable(RasterDataTable<CPU>),
|
||||
ImageInstance(Instance<Raster<CPU>>),
|
||||
ImageFrame(Table<ImageFrame<Color>>),
|
||||
RasterDataTable(Table<Raster<CPU>>),
|
||||
ImageTableRow(TableRow<Raster<CPU>>),
|
||||
}
|
||||
|
||||
Ok(match FormatVersions::deserialize(deserializer)? {
|
||||
FormatVersions::Image(image) => Instance {
|
||||
instance: Raster::new_cpu(image),
|
||||
FormatVersions::Image(image) => TableRow {
|
||||
element: Raster::new_cpu(image),
|
||||
..Default::default()
|
||||
},
|
||||
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => Instance {
|
||||
instance: Raster::new_cpu(image_frame_with_transform_and_blending.image),
|
||||
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => TableRow {
|
||||
element: Raster::new_cpu(image_frame_with_transform_and_blending.image),
|
||||
transform: image_frame_with_transform_and_blending.transform,
|
||||
alpha_blending: image_frame_with_transform_and_blending.alpha_blending,
|
||||
source_node_id: None,
|
||||
},
|
||||
FormatVersions::ImageFrame(image_frame) => Instance {
|
||||
instance: Raster::new_cpu(image_frame.instance_ref_iter().next().unwrap().instance.image.clone()),
|
||||
FormatVersions::ImageFrame(image_frame) => TableRow {
|
||||
element: Raster::new_cpu(image_frame.iter_ref().next().unwrap().element.image.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
FormatVersions::RasterDataTable(image_frame_table) => image_frame_table.instance_iter().next().unwrap_or_default(),
|
||||
FormatVersions::ImageInstance(image_instance) => image_instance,
|
||||
FormatVersions::RasterDataTable(image_frame_table) => image_frame_table.iter().next().unwrap_or_default(),
|
||||
FormatVersions::ImageTableRow(image_table_row) => image_table_row,
|
||||
})
|
||||
}
|
||||
|
||||
// pub type RasterDataTable<P> = Instances<Image<P>>;
|
||||
|
||||
impl<P: Debug + Copy + Pixel> Sample for Image<P> {
|
||||
type Pixel = P;
|
||||
|
||||
@@ -458,23 +453,6 @@ impl From<Image<Color>> for Image<SRGBA8> {
|
||||
}
|
||||
}
|
||||
|
||||
// impl From<RasterDataTable<CPU>> for RasterDataTable<SRGBA8> {
|
||||
// fn from(image_frame_table: RasterDataTable<CPU>) -> Self {
|
||||
// let mut result_table = RasterDataTable::<SRGBA8>::default();
|
||||
|
||||
// for image_frame_instance in image_frame_table.instance_iter() {
|
||||
// result_table.push(Instance {
|
||||
// instance: image_frame_instance.instance,
|
||||
// transform: image_frame_instance.transform,
|
||||
// alpha_blending: image_frame_instance.alpha_blending,
|
||||
// source_node_id: image_frame_instance.source_node_id,
|
||||
// });
|
||||
// }
|
||||
|
||||
// result_table
|
||||
// }
|
||||
// }
|
||||
|
||||
impl From<Image<SRGBA8>> for Image<Color> {
|
||||
fn from(image: Image<SRGBA8>) -> Self {
|
||||
let data = image.data.into_iter().map(|x| x.into()).collect();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::Color;
|
||||
use crate::bounds::BoundingBox;
|
||||
use crate::instances::Instances;
|
||||
use crate::math::quad::Quad;
|
||||
use crate::raster::Image;
|
||||
use crate::table::Table;
|
||||
use core::ops::Deref;
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -61,8 +61,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub type RasterDataTable<Storage> = Instances<Raster<Storage>>;
|
||||
|
||||
pub use cpu::CPU;
|
||||
|
||||
mod cpu {
|
||||
@@ -165,10 +163,13 @@ mod gpu {
|
||||
#[cfg(not(feature = "wgpu"))]
|
||||
mod gpu {
|
||||
use super::*;
|
||||
use crate::raster_types::__private::Sealed;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Hash)]
|
||||
pub struct GPU;
|
||||
|
||||
impl Sealed for Raster<GPU> {}
|
||||
|
||||
impl Storage for Raster<GPU> {
|
||||
fn is_empty(&self) -> bool {
|
||||
true
|
||||
@@ -198,15 +199,15 @@ mod gpu_common {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> BoundingBox for RasterDataTable<T>
|
||||
impl<T> BoundingBox for Table<Raster<T>>
|
||||
where
|
||||
Raster<T>: Storage,
|
||||
{
|
||||
fn bounding_box(&self, transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
|
||||
self.instance_ref_iter()
|
||||
.filter(|instance| !instance.instance.is_empty()) // Eliminate empty images
|
||||
.flat_map(|instance| {
|
||||
let transform = transform * *instance.transform;
|
||||
self.iter_ref()
|
||||
.filter(|row| !row.element.is_empty()) // Eliminate empty images
|
||||
.flat_map(|row| {
|
||||
let transform = transform * *row.transform;
|
||||
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
|
||||
})
|
||||
.reduce(Quad::combine_bounds)
|
||||
|
||||
@@ -56,20 +56,20 @@ pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::ne
|
||||
|
||||
pub static NODE_METADATA: LazyLock<Mutex<HashMap<ProtoNodeIdentifier, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub type DynFuture<'n, T> = Pin<Box<dyn std::future::Future<Output = T> + 'n>>;
|
||||
pub type LocalFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n>>;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub type Any<'n> = Box<dyn DynAny<'n> + 'n + Send>;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub type Any<'n> = Box<dyn DynAny<'n> + 'n>;
|
||||
pub type FutureAny<'n> = DynFuture<'n, Any<'n>>;
|
||||
// TODO: is this safe? This is assumed to be send+sync.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n + Send + Sync;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n;
|
||||
pub type TypeErasedPinnedRef<'n> = Pin<&'n TypeErasedNode<'n>>;
|
||||
pub type TypeErasedRef<'n> = &'n TypeErasedNode<'n>;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::instances::Instances;
|
||||
use crate::raster_types::{CPU, GPU, Raster};
|
||||
use crate::table::Table;
|
||||
use crate::vector::VectorData;
|
||||
use crate::{Artboard, Color, GraphicElement};
|
||||
use crate::{Artboard, Color, Graphic};
|
||||
use glam::DVec2;
|
||||
|
||||
pub trait RenderComplexity {
|
||||
@@ -10,9 +10,9 @@ pub trait RenderComplexity {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RenderComplexity> RenderComplexity for Instances<T> {
|
||||
impl<T: RenderComplexity> RenderComplexity for Table<T> {
|
||||
fn render_complexity(&self) -> usize {
|
||||
self.instance_ref_iter().map(|instance| instance.instance.render_complexity()).fold(0, usize::saturating_add)
|
||||
self.iter_ref().map(|row| row.element.render_complexity()).fold(0, usize::saturating_add)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@ impl RenderComplexity for Artboard {
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderComplexity for GraphicElement {
|
||||
impl RenderComplexity for Graphic {
|
||||
fn render_complexity(&self) -> usize {
|
||||
match self {
|
||||
Self::GraphicGroup(instances) => instances.render_complexity(),
|
||||
Self::VectorData(instances) => instances.render_complexity(),
|
||||
Self::RasterDataCPU(instances) => instances.render_complexity(),
|
||||
Self::RasterDataGPU(instances) => instances.render_complexity(),
|
||||
Self::GraphicGroup(table) => table.render_complexity(),
|
||||
Self::VectorData(table) => table.render_complexity(),
|
||||
Self::RasterDataCPU(table) => table.render_complexity(),
|
||||
Self::RasterDataGPU(table) => table.render_complexity(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
252
node-graph/gcore/src/table.rs
Normal file
252
node-graph/gcore/src/table.rs
Normal file
@@ -0,0 +1,252 @@
|
||||
use crate::AlphaBlending;
|
||||
use crate::transform::ApplyTransform;
|
||||
use crate::uuid::NodeId;
|
||||
use dyn_any::StaticType;
|
||||
use glam::DAffine2;
|
||||
use std::hash::Hash;
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Table<T> {
|
||||
#[serde(alias = "instances", alias = "instance")]
|
||||
element: Vec<T>,
|
||||
transform: Vec<DAffine2>,
|
||||
alpha_blending: Vec<AlphaBlending>,
|
||||
source_node_id: Vec<Option<NodeId>>,
|
||||
}
|
||||
|
||||
impl<T> Table<T> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
element: Vec::with_capacity(capacity),
|
||||
transform: Vec::with_capacity(capacity),
|
||||
alpha_blending: Vec::with_capacity(capacity),
|
||||
source_node_id: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_element(element: T) -> Self {
|
||||
Self {
|
||||
element: vec![element],
|
||||
transform: vec![DAffine2::IDENTITY],
|
||||
alpha_blending: vec![AlphaBlending::default()],
|
||||
source_node_id: vec![None],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_row(row: TableRow<T>) -> Self {
|
||||
Self {
|
||||
element: vec![row.element],
|
||||
transform: vec![row.transform],
|
||||
alpha_blending: vec![row.alpha_blending],
|
||||
source_node_id: vec![row.source_node_id],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, row: TableRow<T>) {
|
||||
self.element.push(row.element);
|
||||
self.transform.push(row.transform);
|
||||
self.alpha_blending.push(row.alpha_blending);
|
||||
self.source_node_id.push(row.source_node_id);
|
||||
}
|
||||
|
||||
pub fn extend(&mut self, table: Table<T>) {
|
||||
self.element.extend(table.element);
|
||||
self.transform.extend(table.transform);
|
||||
self.alpha_blending.extend(table.alpha_blending);
|
||||
self.source_node_id.extend(table.source_node_id);
|
||||
}
|
||||
|
||||
pub fn iter(self) -> impl DoubleEndedIterator<Item = TableRow<T>> {
|
||||
self.element
|
||||
.into_iter()
|
||||
.zip(self.transform)
|
||||
.zip(self.alpha_blending)
|
||||
.zip(self.source_node_id)
|
||||
.map(|(((element, transform), alpha_blending), source_node_id)| TableRow {
|
||||
element,
|
||||
transform,
|
||||
alpha_blending,
|
||||
source_node_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter_ref(&self) -> impl DoubleEndedIterator<Item = TableRowRef<'_, T>> + Clone {
|
||||
self.element
|
||||
.iter()
|
||||
.zip(self.transform.iter())
|
||||
.zip(self.alpha_blending.iter())
|
||||
.zip(self.source_node_id.iter())
|
||||
.map(|(((element, transform), alpha_blending), source_node_id)| TableRowRef {
|
||||
element,
|
||||
transform,
|
||||
alpha_blending,
|
||||
source_node_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = TableRowMut<'_, T>> {
|
||||
self.element
|
||||
.iter_mut()
|
||||
.zip(self.transform.iter_mut())
|
||||
.zip(self.alpha_blending.iter_mut())
|
||||
.zip(self.source_node_id.iter_mut())
|
||||
.map(|(((element, transform), alpha_blending), source_node_id)| TableRowMut {
|
||||
element,
|
||||
transform,
|
||||
alpha_blending,
|
||||
source_node_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(&self, index: usize) -> Option<TableRowRef<'_, T>> {
|
||||
if index >= self.element.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(TableRowRef {
|
||||
element: &self.element[index],
|
||||
transform: &self.transform[index],
|
||||
alpha_blending: &self.alpha_blending[index],
|
||||
source_node_id: &self.source_node_id[index],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, index: usize) -> Option<TableRowMut<'_, T>> {
|
||||
if index >= self.element.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(TableRowMut {
|
||||
element: &mut self.element[index],
|
||||
transform: &mut self.transform[index],
|
||||
alpha_blending: &mut self.alpha_blending[index],
|
||||
source_node_id: &mut self.source_node_id[index],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.element.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.element.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Table<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
element: Vec::new(),
|
||||
transform: Vec::new(),
|
||||
alpha_blending: Vec::new(),
|
||||
source_node_id: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash> Hash for Table<T> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
for element in &self.element {
|
||||
element.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ApplyTransform for Table<T> {
|
||||
fn apply_transform(&mut self, modification: &DAffine2) {
|
||||
for transform in &mut self.transform {
|
||||
*transform *= *modification;
|
||||
}
|
||||
}
|
||||
|
||||
fn left_apply_transform(&mut self, modification: &DAffine2) {
|
||||
for transform in &mut self.transform {
|
||||
*transform = *modification * *transform;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> PartialEq for Table<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.element.len() == other.element.len() && { self.element.iter().zip(other.element.iter()).all(|(a, b)| a == b) }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: StaticType + 'static> StaticType for Table<T> {
|
||||
type Static = Table<T>;
|
||||
}
|
||||
|
||||
impl<T> FromIterator<TableRow<T>> for Table<T> {
|
||||
fn from_iter<I: IntoIterator<Item = TableRow<T>>>(iter: I) -> Self {
|
||||
let iter = iter.into_iter();
|
||||
let (lower, _) = iter.size_hint();
|
||||
let mut table = Self::with_capacity(lower);
|
||||
for row in iter {
|
||||
table.push(row);
|
||||
}
|
||||
table
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TableRow<T> {
|
||||
#[serde(alias = "instance")]
|
||||
pub element: T,
|
||||
pub transform: DAffine2,
|
||||
pub alpha_blending: AlphaBlending,
|
||||
pub source_node_id: Option<NodeId>,
|
||||
}
|
||||
|
||||
impl<T> TableRow<T> {
|
||||
pub fn as_ref(&self) -> TableRowRef<'_, T> {
|
||||
TableRowRef {
|
||||
element: &self.element,
|
||||
transform: &self.transform,
|
||||
alpha_blending: &self.alpha_blending,
|
||||
source_node_id: &self.source_node_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_mut(&mut self) -> TableRowMut<'_, T> {
|
||||
TableRowMut {
|
||||
element: &mut self.element,
|
||||
transform: &mut self.transform,
|
||||
alpha_blending: &mut self.alpha_blending,
|
||||
source_node_id: &mut self.source_node_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct TableRowRef<'a, T> {
|
||||
pub element: &'a T,
|
||||
pub transform: &'a DAffine2,
|
||||
pub alpha_blending: &'a AlphaBlending,
|
||||
pub source_node_id: &'a Option<NodeId>,
|
||||
}
|
||||
|
||||
impl<T> TableRowRef<'_, T> {
|
||||
pub fn into_cloned(self) -> TableRow<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
TableRow {
|
||||
element: self.element.clone(),
|
||||
transform: *self.transform,
|
||||
alpha_blending: *self.alpha_blending,
|
||||
source_node_id: *self.source_node_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TableRowMut<'a, T> {
|
||||
pub element: &'a mut T,
|
||||
pub transform: &'a mut DAffine2,
|
||||
pub alpha_blending: &'a mut AlphaBlending,
|
||||
pub source_node_id: &'a mut Option<NodeId>,
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::TextAlign;
|
||||
use crate::instances::Instance;
|
||||
use crate::vector::{PointId, VectorData, VectorDataTable};
|
||||
use crate::table::{Table, TableRow};
|
||||
use crate::vector::{PointId, VectorData};
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use core::cell::RefCell;
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -24,7 +24,7 @@ struct PathBuilder {
|
||||
current_subpath: Subpath<PointId>,
|
||||
origin: DVec2,
|
||||
glyph_subpaths: Vec<Subpath<PointId>>,
|
||||
vector_table: VectorDataTable,
|
||||
vector_table: Table<VectorData>,
|
||||
scale: f64,
|
||||
id: PointId,
|
||||
}
|
||||
@@ -51,15 +51,15 @@ impl PathBuilder {
|
||||
}
|
||||
|
||||
if per_glyph_instances {
|
||||
self.vector_table.push(Instance {
|
||||
instance: VectorData::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false),
|
||||
self.vector_table.push(TableRow {
|
||||
element: VectorData::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false),
|
||||
transform: DAffine2::from_translation(glyph_offset),
|
||||
..Default::default()
|
||||
});
|
||||
} else {
|
||||
for subpath in self.glyph_subpaths.drain(..) {
|
||||
// Unwrapping here is ok because `self.vector_table` is initialized with a single `VectorData`
|
||||
self.vector_table.get_mut(0).unwrap().instance.append_subpath(subpath, false);
|
||||
self.vector_table.get_mut(0).unwrap().element.append_subpath(subpath, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,19 +205,15 @@ fn layout_text(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingC
|
||||
Some(layout)
|
||||
}
|
||||
|
||||
pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig, per_glyph_instances: bool) -> VectorDataTable {
|
||||
pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<VectorData> {
|
||||
let Some(layout) = layout_text(str, font_data, typesetting) else {
|
||||
return VectorDataTable::new(VectorData::default());
|
||||
return Table::new_from_element(VectorData::default());
|
||||
};
|
||||
|
||||
let mut path_builder = PathBuilder {
|
||||
current_subpath: Subpath::new(Vec::new(), false),
|
||||
glyph_subpaths: Vec::new(),
|
||||
vector_table: if per_glyph_instances {
|
||||
VectorDataTable::default()
|
||||
} else {
|
||||
VectorDataTable::new(VectorData::default())
|
||||
},
|
||||
vector_table: if per_glyph_instances { Table::new() } else { Table::new_from_element(VectorData::default()) },
|
||||
scale: layout.scale() as f64,
|
||||
id: PointId::ZERO,
|
||||
origin: DVec2::default(),
|
||||
@@ -232,7 +228,7 @@ pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingC
|
||||
}
|
||||
|
||||
if path_builder.vector_table.is_empty() {
|
||||
path_builder.vector_table = VectorDataTable::new(VectorData::default());
|
||||
path_builder.vector_table = Table::new_from_element(VectorData::default());
|
||||
}
|
||||
|
||||
path_builder.vector_table
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::instances::Instances;
|
||||
use crate::raster_types::{CPU, GPU, RasterDataTable};
|
||||
use crate::raster_types::{CPU, GPU, Raster};
|
||||
use crate::table::Table;
|
||||
use crate::transform::{ApplyTransform, Footprint, Transform};
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, GraphicGroupTable, OwnedContextImpl};
|
||||
use crate::vector::VectorData;
|
||||
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, Graphic, OwnedContextImpl};
|
||||
use core::f64;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
@@ -12,10 +12,10 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
|
||||
#[implementations(
|
||||
Context -> DAffine2,
|
||||
Context -> DVec2,
|
||||
Context -> VectorDataTable,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> RasterDataTable<CPU>,
|
||||
Context -> RasterDataTable<GPU>,
|
||||
Context -> Table<VectorData>,
|
||||
Context -> Table<Graphic>,
|
||||
Context -> Table<Raster<CPU>>,
|
||||
Context -> Table<Raster<GPU>>,
|
||||
)]
|
||||
value: impl Node<Context<'static>, Output = T>,
|
||||
translate: DVec2,
|
||||
@@ -43,10 +43,10 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
|
||||
#[node_macro::node(category(""))]
|
||||
fn replace_transform<Data, TransformInput: Transform>(
|
||||
_: impl Ctx,
|
||||
#[implementations(VectorDataTable, RasterDataTable<CPU>, GraphicGroupTable)] mut data: Instances<Data>,
|
||||
#[implementations(Table<VectorData>, Table<Raster<CPU>>, Table<Graphic>)] mut data: Table<Data>,
|
||||
#[implementations(DAffine2)] transform: TransformInput,
|
||||
) -> Instances<Data> {
|
||||
for data_transform in data.instance_mut_iter() {
|
||||
) -> Table<Data> {
|
||||
for data_transform in data.iter_mut() {
|
||||
*data_transform.transform = transform.transform();
|
||||
}
|
||||
data
|
||||
@@ -56,14 +56,14 @@ fn replace_transform<Data, TransformInput: Transform>(
|
||||
async fn extract_transform<T>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
GraphicGroupTable,
|
||||
VectorDataTable,
|
||||
RasterDataTable<CPU>,
|
||||
RasterDataTable<GPU>,
|
||||
Table<Graphic>,
|
||||
Table<VectorData>,
|
||||
Table<Raster<CPU>>,
|
||||
Table<Raster<GPU>>,
|
||||
)]
|
||||
vector_data: Instances<T>,
|
||||
vector_data: Table<T>,
|
||||
) -> DAffine2 {
|
||||
vector_data.instance_ref_iter().next().map(|vector_data| *vector_data.transform).unwrap_or_default()
|
||||
vector_data.iter_ref().next().map(|vector_data| *vector_data.transform).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Math: Transform"))]
|
||||
@@ -90,10 +90,10 @@ fn decompose_scale(_: impl Ctx, transform: DAffine2) -> DVec2 {
|
||||
async fn boundless_footprint<T: 'n + 'static>(
|
||||
ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
||||
#[implementations(
|
||||
Context -> VectorDataTable,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> RasterDataTable<CPU>,
|
||||
Context -> RasterDataTable<GPU>,
|
||||
Context -> Table<VectorData>,
|
||||
Context -> Table<Graphic>,
|
||||
Context -> Table<Raster<CPU>>,
|
||||
Context -> Table<Raster<GPU>>,
|
||||
Context -> String,
|
||||
Context -> f64,
|
||||
)]
|
||||
@@ -108,10 +108,10 @@ async fn boundless_footprint<T: 'n + 'static>(
|
||||
async fn freeze_real_time<T: 'n + 'static>(
|
||||
ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
||||
#[implementations(
|
||||
Context -> VectorDataTable,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> RasterDataTable<CPU>,
|
||||
Context -> RasterDataTable<GPU>,
|
||||
Context -> Table<VectorData>,
|
||||
Context -> Table<Graphic>,
|
||||
Context -> Table<Raster<CPU>>,
|
||||
Context -> Table<Raster<GPU>>,
|
||||
Context -> String,
|
||||
Context -> f64,
|
||||
)]
|
||||
|
||||
@@ -167,14 +167,17 @@ fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer:
|
||||
let name = match name.as_str() {
|
||||
"f32" => "f64".to_string(),
|
||||
"graphene_core::transform::Footprint" => "std::option::Option<std::sync::Arc<graphene_core::context::OwnedContextImpl>>".to_string(),
|
||||
"graphene_core::graphic_element::GraphicGroup" => "graphene_core::instances::Instances<graphene_core::graphic_element::GraphicGroup>".to_string(),
|
||||
"graphene_core::vector::vector_data::VectorData" => "graphene_core::instances::Instances<graphene_core::vector::vector_data::VectorData>".to_string(),
|
||||
"graphene_core::graphic_element::GraphicGroup" => "graphene_core::table::Table<graphene_core::graphic_element::GraphicGroup>".to_string(),
|
||||
"graphene_core::vector::vector_data::VectorData" => "graphene_core::table::Table<graphene_core::vector::vector_data::VectorData>".to_string(),
|
||||
"graphene_core::raster::image::ImageFrame<Color>"
|
||||
| "graphene_core::raster::image::ImageFrame<graphene_core::raster::color::Color>"
|
||||
| "graphene_core::instances::Instances<graphene_core::raster::image::ImageFrame<Color>>"
|
||||
| "graphene_core::instances::Instances<graphene_core::raster::image::ImageFrame<graphene_core::raster::color::Color>>" => {
|
||||
"graphene_core::instances::Instances<graphene_core::raster::image::Image<graphene_core::raster::color::Color>>".to_string()
|
||||
| "graphene_core::instances::Instances<graphene_core::raster::image::ImageFrame<graphene_core::raster::color::Color>>"
|
||||
| "graphene_core::instances::Instances<graphene_core::raster::image::Image<graphene_core::raster::color::Color>>" => {
|
||||
"graphene_core::table::Table<graphene_core::raster::image::Image<graphene_core::raster::color::Color>>".to_string()
|
||||
}
|
||||
"graphene_core::instances::Instances<graphene_core::vector::vector_data::VectorData>" => "graphene_core::table::Table<graphene_core::vector::vector_data::VectorData>".to_string(),
|
||||
"graphene_core::instances::Instances<graphene_core::graphic_element::Artboard>" => "graphene_core::table::Table<graphene_core::artboard::Artboard>".to_string(),
|
||||
_ => name,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
use crate::instances::{InstanceRef, Instances};
|
||||
use crate::raster_types::{CPU, RasterDataTable};
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, GraphicElement, GraphicGroupTable, OwnedContextImpl};
|
||||
use crate::raster_types::{CPU, Raster};
|
||||
use crate::table::{Table, TableRowRef};
|
||||
use crate::vector::VectorData;
|
||||
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, Graphic, OwnedContextImpl};
|
||||
use glam::DVec2;
|
||||
|
||||
#[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))]
|
||||
async fn instance_on_points<T: Into<GraphicElement> + Default + Send + Clone + 'static>(
|
||||
async fn instance_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
||||
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx,
|
||||
points: VectorDataTable,
|
||||
points: Table<VectorData>,
|
||||
#[implementations(
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> VectorDataTable,
|
||||
Context -> RasterDataTable<CPU>
|
||||
Context -> Table<Graphic>,
|
||||
Context -> Table<VectorData>,
|
||||
Context -> Table<Raster<CPU>>
|
||||
)]
|
||||
instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
|
||||
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||
reverse: bool,
|
||||
) -> Instances<T> {
|
||||
let mut result_table = Instances::<T>::default();
|
||||
) -> Table<T> {
|
||||
let mut result_table = Table::new();
|
||||
|
||||
for InstanceRef { instance: points, transform, .. } in points.instance_ref_iter() {
|
||||
for TableRowRef { element: points, transform, .. } in points.iter_ref() {
|
||||
let mut iteration = async |index, point| {
|
||||
let transformed_point = transform.transform_point2(point);
|
||||
|
||||
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(Box::new(transformed_point));
|
||||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
||||
|
||||
for mut instanced in generated_instance.instance_iter() {
|
||||
instanced.transform.translation = transformed_point;
|
||||
result_table.push(instanced);
|
||||
for mut generated_row in generated_instance.iter() {
|
||||
generated_row.transform.translation = transformed_point;
|
||||
result_table.push(generated_row);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -47,20 +47,20 @@ async fn instance_on_points<T: Into<GraphicElement> + Default + Send + Clone + '
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
|
||||
async fn instance_repeat<T: Into<GraphicElement> + Default + Send + Clone + 'static>(
|
||||
async fn instance_repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||
#[implementations(
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> VectorDataTable,
|
||||
Context -> RasterDataTable<CPU>
|
||||
Context -> Table<Graphic>,
|
||||
Context -> Table<VectorData>,
|
||||
Context -> Table<Raster<CPU>>
|
||||
)]
|
||||
instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
|
||||
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||
#[default(1)] count: u64,
|
||||
reverse: bool,
|
||||
) -> Instances<T> {
|
||||
) -> Table<T> {
|
||||
let count = count.max(1) as usize;
|
||||
|
||||
let mut result_table = Instances::<T>::default();
|
||||
let mut result_table = Table::new();
|
||||
|
||||
for index in 0..count {
|
||||
let index = if reverse { count - index - 1 } else { index };
|
||||
@@ -68,8 +68,8 @@ async fn instance_repeat<T: Into<GraphicElement> + Default + Send + Clone + 'sta
|
||||
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index);
|
||||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
||||
|
||||
for instanced in generated_instance.instance_iter() {
|
||||
result_table.push(instanced);
|
||||
for generated_row in generated_instance.iter() {
|
||||
result_table.push(generated_row);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,11 +128,11 @@ mod test {
|
||||
);
|
||||
|
||||
let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];
|
||||
let points = VectorDataTable::new(VectorData::from_subpath(Subpath::from_anchors_linear(positions, false)));
|
||||
let repeated = super::instance_on_points(owned, points, &rect, false).await;
|
||||
assert_eq!(repeated.len(), positions.len());
|
||||
for (position, instanced) in positions.into_iter().zip(repeated.instance_ref_iter()) {
|
||||
let bounds = instanced.instance.bounding_box_with_transform(*instanced.transform).unwrap();
|
||||
let points = Table::new_from_element(VectorData::from_subpath(Subpath::from_anchors_linear(positions, false)));
|
||||
let generated = super::instance_on_points(owned, points, &rect, false).await;
|
||||
assert_eq!(generated.len(), positions.len());
|
||||
for (position, generated_row) in positions.into_iter().zip(generated.iter_ref()) {
|
||||
let bounds = generated_row.element.bounding_box_with_transform(*generated_row.transform).unwrap();
|
||||
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
|
||||
assert_eq!((bounds[1] - bounds[0]).x, position.y);
|
||||
}
|
||||
|
||||
@@ -2,21 +2,22 @@ use super::misc::{ArcType, AsU64, GridType};
|
||||
use super::{PointId, SegmentId, StrokeId};
|
||||
use crate::Ctx;
|
||||
use crate::registry::types::{Angle, PixelSize};
|
||||
use crate::vector::{HandleId, VectorData, VectorDataTable};
|
||||
use crate::table::Table;
|
||||
use crate::vector::{HandleId, VectorData};
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DVec2;
|
||||
|
||||
trait CornerRadius {
|
||||
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable;
|
||||
fn generate(self, size: DVec2, clamped: bool) -> Table<VectorData>;
|
||||
}
|
||||
impl CornerRadius for f64 {
|
||||
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable {
|
||||
fn generate(self, size: DVec2, clamped: bool) -> Table<VectorData> {
|
||||
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
|
||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
|
||||
Table::new_from_element(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
|
||||
}
|
||||
}
|
||||
impl CornerRadius for [f64; 4] {
|
||||
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable {
|
||||
fn generate(self, size: DVec2, clamped: bool) -> Table<VectorData> {
|
||||
let clamped_radius = if clamped {
|
||||
// Algorithm follows the CSS spec: <https://drafts.csswg.org/css-backgrounds/#corner-overlap>
|
||||
|
||||
@@ -32,7 +33,7 @@ impl CornerRadius for [f64; 4] {
|
||||
} else {
|
||||
self
|
||||
};
|
||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
|
||||
Table::new_from_element(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,9 +44,9 @@ fn circle(
|
||||
#[unit(" px")]
|
||||
#[default(50.)]
|
||||
radius: f64,
|
||||
) -> VectorDataTable {
|
||||
) -> Table<VectorData> {
|
||||
let radius = radius.abs();
|
||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
|
||||
Table::new_from_element(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
@@ -60,8 +61,8 @@ fn arc(
|
||||
#[range((0., 360.))]
|
||||
sweep_angle: Angle,
|
||||
arc_type: ArcType,
|
||||
) -> VectorDataTable {
|
||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_arc(
|
||||
) -> Table<VectorData> {
|
||||
Table::new_from_element(VectorData::from_subpath(Subpath::new_arc(
|
||||
radius,
|
||||
start_angle / 360. * std::f64::consts::TAU,
|
||||
sweep_angle / 360. * std::f64::consts::TAU,
|
||||
@@ -83,7 +84,7 @@ fn ellipse(
|
||||
#[unit(" px")]
|
||||
#[default(25)]
|
||||
radius_y: f64,
|
||||
) -> VectorDataTable {
|
||||
) -> Table<VectorData> {
|
||||
let radius = DVec2::new(radius_x, radius_y);
|
||||
let corner1 = -radius;
|
||||
let corner2 = radius;
|
||||
@@ -97,7 +98,7 @@ fn ellipse(
|
||||
.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);
|
||||
}
|
||||
|
||||
VectorDataTable::new(ellipse)
|
||||
Table::new_from_element(ellipse)
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))]
|
||||
@@ -113,7 +114,7 @@ fn rectangle<T: CornerRadius>(
|
||||
_individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability
|
||||
#[implementations(f64, [f64; 4])] corner_radius: T,
|
||||
#[default(true)] clamped: bool,
|
||||
) -> VectorDataTable {
|
||||
) -> Table<VectorData> {
|
||||
corner_radius.generate(DVec2::new(width, height), clamped)
|
||||
}
|
||||
|
||||
@@ -128,10 +129,10 @@ fn regular_polygon<T: AsU64>(
|
||||
#[unit(" px")]
|
||||
#[default(50)]
|
||||
radius: f64,
|
||||
) -> VectorDataTable {
|
||||
) -> Table<VectorData> {
|
||||
let points = sides.as_u64();
|
||||
let radius: f64 = radius * 2.;
|
||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
|
||||
Table::new_from_element(VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
@@ -148,17 +149,17 @@ fn star<T: AsU64>(
|
||||
#[unit(" px")]
|
||||
#[default(25)]
|
||||
radius_2: f64,
|
||||
) -> VectorDataTable {
|
||||
) -> Table<VectorData> {
|
||||
let points = sides.as_u64();
|
||||
let diameter: f64 = radius_1 * 2.;
|
||||
let inner_diameter = radius_2 * 2.;
|
||||
|
||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
|
||||
Table::new_from_element(VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> VectorDataTable {
|
||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
|
||||
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> Table<VectorData> {
|
||||
Table::new_from_element(VectorData::from_subpath(Subpath::new_line(start, end)))
|
||||
}
|
||||
|
||||
trait GridSpacing {
|
||||
@@ -188,7 +189,7 @@ fn grid<T: GridSpacing>(
|
||||
#[default(10)] columns: u32,
|
||||
#[default(10)] rows: u32,
|
||||
#[default(30., 30.)] angles: DVec2,
|
||||
) -> VectorDataTable {
|
||||
) -> Table<VectorData> {
|
||||
let (x_spacing, y_spacing) = spacing.as_dvec2().into();
|
||||
let (angle_a, angle_b) = angles.into();
|
||||
|
||||
@@ -270,7 +271,7 @@ fn grid<T: GridSpacing>(
|
||||
}
|
||||
}
|
||||
|
||||
VectorDataTable::new(vector_data)
|
||||
Table::new_from_element(vector_data)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -284,9 +285,9 @@ mod tests {
|
||||
|
||||
// Works properly
|
||||
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (30., 30.).into());
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
|
||||
assert_eq!(grid.iter_ref().next().unwrap().element.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.iter_ref().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.iter_ref().next().unwrap().element.segment_bezier_iter() {
|
||||
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
|
||||
assert!(
|
||||
((bezier.start - bezier.end).length() - 10.).abs() < 1e-5,
|
||||
@@ -299,9 +300,9 @@ mod tests {
|
||||
#[test]
|
||||
fn skew_isometric_grid_test() {
|
||||
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (40., 30.).into());
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
|
||||
assert_eq!(grid.iter_ref().next().unwrap().element.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.iter_ref().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.iter_ref().next().unwrap().element.segment_bezier_iter() {
|
||||
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
|
||||
let vector = bezier.start - bezier.end;
|
||||
let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.;
|
||||
|
||||
@@ -5,11 +5,11 @@ mod modification;
|
||||
use super::misc::{dvec2_to_point, point_to_dvec2};
|
||||
use super::style::{PathStyle, Stroke};
|
||||
use crate::bounds::BoundingBox;
|
||||
use crate::instances::Instances;
|
||||
use crate::math::quad::Quad;
|
||||
use crate::table::Table;
|
||||
use crate::transform::Transform;
|
||||
use crate::vector::click_target::{ClickTargetType, FreePoint};
|
||||
use crate::{AlphaBlending, Color, GraphicGroupTable};
|
||||
use crate::{AlphaBlending, Color, Graphic};
|
||||
pub use attributes::*;
|
||||
use bezier_rs::{BezierHandles, ManipulatorGroup};
|
||||
use core::borrow::Borrow;
|
||||
@@ -22,7 +22,7 @@ pub use modification::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
// TODO: Eventually remove this migration document upgrade code
|
||||
pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<VectorDataTable, D::Error> {
|
||||
pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<VectorData>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
@@ -41,7 +41,7 @@ pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
||||
pub region_domain: RegionDomain,
|
||||
|
||||
// Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
|
||||
pub upstream_graphic_group: Option<GraphicGroupTable>,
|
||||
pub upstream_graphic_group: Option<Table<Graphic>>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
@@ -50,13 +50,13 @@ pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
||||
enum EitherFormat {
|
||||
VectorData(VectorData),
|
||||
OldVectorData(OldVectorData),
|
||||
VectorDataTable(VectorDataTable),
|
||||
VectorDataTable(Table<VectorData>),
|
||||
}
|
||||
|
||||
Ok(match EitherFormat::deserialize(deserializer)? {
|
||||
EitherFormat::VectorData(vector_data) => VectorDataTable::new(vector_data),
|
||||
EitherFormat::VectorData(vector_data) => Table::new_from_element(vector_data),
|
||||
EitherFormat::OldVectorData(old) => {
|
||||
let mut vector_data_table = VectorDataTable::new(VectorData {
|
||||
let mut vector_data_table = Table::new_from_element(VectorData {
|
||||
style: old.style,
|
||||
colinear_manipulators: old.colinear_manipulators,
|
||||
point_domain: old.point_domain,
|
||||
@@ -64,16 +64,14 @@ pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
||||
region_domain: old.region_domain,
|
||||
upstream_graphic_group: old.upstream_graphic_group,
|
||||
});
|
||||
*vector_data_table.instance_mut_iter().next().unwrap().transform = old.transform;
|
||||
*vector_data_table.instance_mut_iter().next().unwrap().alpha_blending = old.alpha_blending;
|
||||
*vector_data_table.iter_mut().next().unwrap().transform = old.transform;
|
||||
*vector_data_table.iter_mut().next().unwrap().alpha_blending = old.alpha_blending;
|
||||
vector_data_table
|
||||
}
|
||||
EitherFormat::VectorDataTable(vector_data_table) => vector_data_table,
|
||||
})
|
||||
}
|
||||
|
||||
pub type VectorDataTable = Instances<VectorData>;
|
||||
|
||||
/// [VectorData] is passed between nodes.
|
||||
/// It contains a list of subpaths (that may be open or closed), a transform, and some style information.
|
||||
///
|
||||
@@ -91,7 +89,7 @@ pub struct VectorData {
|
||||
pub region_domain: RegionDomain,
|
||||
|
||||
// Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
|
||||
pub upstream_graphic_group: Option<GraphicGroupTable>,
|
||||
pub upstream_graphic_group: Option<Table<Graphic>>,
|
||||
}
|
||||
|
||||
impl Default for VectorData {
|
||||
@@ -495,24 +493,24 @@ impl VectorData {
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundingBox for VectorDataTable {
|
||||
impl BoundingBox for Table<VectorData> {
|
||||
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
|
||||
self.instance_ref_iter()
|
||||
.flat_map(|instance| {
|
||||
self.iter_ref()
|
||||
.flat_map(|row| {
|
||||
if !include_stroke {
|
||||
return instance.instance.bounding_box_with_transform(transform * *instance.transform);
|
||||
return row.element.bounding_box_with_transform(transform * *row.transform);
|
||||
}
|
||||
|
||||
let stroke_width = instance.instance.style.stroke().map(|s| s.weight()).unwrap_or_default();
|
||||
let stroke_width = row.element.style.stroke().map(|s| s.weight()).unwrap_or_default();
|
||||
|
||||
let miter_limit = instance.instance.style.stroke().map(|s| s.join_miter_limit).unwrap_or(1.);
|
||||
let miter_limit = row.element.style.stroke().map(|s| s.join_miter_limit).unwrap_or(1.);
|
||||
|
||||
let scale = transform.decompose_scale();
|
||||
|
||||
// We use the full line width here to account for different styles of stroke caps
|
||||
let offset = DVec2::splat(stroke_width * scale.x.max(scale.y) * miter_limit);
|
||||
|
||||
instance.instance.bounding_box_with_transform(transform * *instance.transform).map(|[a, b]| [a - offset, b + offset])
|
||||
row.element.bounding_box_with_transform(transform * *row.transform).map(|[a, b]| [a - offset, b + offset])
|
||||
})
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ macro_rules! create_ids {
|
||||
};
|
||||
}
|
||||
|
||||
create_ids! { InstanceId, PointId, SegmentId, RegionId, StrokeId, FillId }
|
||||
create_ids! { PointId, SegmentId, RegionId, StrokeId, FillId }
|
||||
|
||||
/// A no-op hasher that allows writing u64s (the id type).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
@@ -305,7 +305,7 @@ impl SegmentDomain {
|
||||
&self.stroke
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, id: SegmentId, start: usize, end: usize, handles: BezierHandles, stroke: StrokeId) {
|
||||
pub fn push(&mut self, id: SegmentId, start: usize, end: usize, handles: BezierHandles, stroke: StrokeId) {
|
||||
debug_assert!(!self.id.contains(&id), "Tried to push an existing point to a point domain");
|
||||
|
||||
self.id.push(id);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
use crate::Ctx;
|
||||
use crate::instances::Instance;
|
||||
use crate::table::TableRow;
|
||||
use crate::uuid::{NodeId, generate_uuid};
|
||||
use bezier_rs::BezierHandles;
|
||||
use dyn_any::DynAny;
|
||||
@@ -420,35 +420,35 @@ impl Hash for VectorModification {
|
||||
|
||||
/// Applies a diff modification to a vector path.
|
||||
#[node_macro::node(category(""))]
|
||||
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> VectorDataTable {
|
||||
async fn path_modify(_ctx: impl Ctx, mut vector_data: Table<VectorData>, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> Table<VectorData> {
|
||||
if vector_data.is_empty() {
|
||||
vector_data.push(Instance::default());
|
||||
vector_data.push(TableRow::default());
|
||||
}
|
||||
let vector_data_instance = vector_data.get_mut(0).expect("push should give one item");
|
||||
modification.apply(vector_data_instance.instance);
|
||||
let row = vector_data.get_mut(0).expect("push should give one item");
|
||||
modification.apply(row.element);
|
||||
|
||||
// Update the source node id
|
||||
let this_node_path = node_path.iter().rev().nth(1).copied();
|
||||
*vector_data_instance.source_node_id = vector_data_instance.source_node_id.or(this_node_path);
|
||||
*row.source_node_id = row.source_node_id.or(this_node_path);
|
||||
|
||||
if vector_data.len() > 1 {
|
||||
warn!("The path modify ran on {} instances of vector data. Only the first can be modified.", vector_data.len());
|
||||
warn!("The path modify ran on {} rows of vector data. Only the first can be modified.", vector_data.len());
|
||||
}
|
||||
vector_data
|
||||
}
|
||||
|
||||
/// Applies the vector path's local transformation to its geometry and resets it to the identity.
|
||||
#[node_macro::node(category("Vector"))]
|
||||
async fn apply_transform(_ctx: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable {
|
||||
for vector_data_instance in vector_data.instance_mut_iter() {
|
||||
let vector_data = vector_data_instance.instance;
|
||||
let transform = *vector_data_instance.transform;
|
||||
async fn apply_transform(_ctx: impl Ctx, mut vector_data: Table<VectorData>) -> Table<VectorData> {
|
||||
for row in vector_data.iter_mut() {
|
||||
let vector_data = row.element;
|
||||
let transform = *row.transform;
|
||||
|
||||
for (_, point) in vector_data.point_domain.positions_mut() {
|
||||
*point = transform.transform_point2(*point);
|
||||
}
|
||||
|
||||
*vector_data_instance.transform = DAffine2::IDENTITY;
|
||||
*row.transform = DAffine2::IDENTITY;
|
||||
}
|
||||
|
||||
vector_data
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user