Skip to content

Commit dbaea08

Browse files
committed
Replace Table<Table<Graphic>> with Table<Artboard> with Artboard as a type boundary newtype
1 parent 9943af5 commit dbaea08

16 files changed

Lines changed: 134 additions & 86 deletions

File tree

editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,14 @@ use crate::messages::prelude::*;
66
use crate::messages::tool::tool_messages::tool_prelude::*;
77
use glam::{Affine2, DAffine2, Vec2};
88
use graph_craft::document::NodeId;
9-
use graphene_std::Color;
10-
use graphene_std::Context;
11-
use graphene_std::Graphic;
129
use graphene_std::blending::BlendMode;
1310
use graphene_std::gradient::GradientStops;
1411
use graphene_std::memo::IORecord;
1512
use graphene_std::raster_types::{CPU, GPU, Raster};
1613
use graphene_std::table::Table;
1714
use graphene_std::vector::Vector;
1815
use graphene_std::vector::style::{Fill, FillChoice};
16+
use graphene_std::{Artboard, Color, Context, Graphic};
1917
use std::any::Any;
2018
use std::sync::Arc;
2119

@@ -183,7 +181,7 @@ fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'st
183181
return Some(table_node_id_path_layout_with_breadcrumb(&io.output, data));
184182
}
185183
generate_layout_downcast!(introspected_data, data, [
186-
Table<Table<Graphic>>,
184+
Table<Artboard>,
187185
Table<Graphic>,
188186
Table<Vector>,
189187
Table<Raster<CPU>>,
@@ -301,6 +299,22 @@ impl<T: TableRowLayout> TableRowLayout for Table<T> {
301299
}
302300
}
303301

302+
impl TableRowLayout for Artboard {
303+
fn type_name() -> &'static str {
304+
"Artboard"
305+
}
306+
fn identifier(&self) -> String {
307+
self.as_graphic_table().identifier()
308+
}
309+
// Don't put a breadcrumb for Artboard
310+
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
311+
self.value_page(data)
312+
}
313+
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
314+
self.as_graphic_table().layout_with_breadcrumb(data)
315+
}
316+
}
317+
304318
impl TableRowLayout for Graphic {
305319
fn type_name() -> &'static str {
306320
"Graphic"
@@ -871,7 +885,7 @@ impl TableRowLayout for NodeId {
871885
macro_rules! known_table_row_types {
872886
($apply:ident) => {
873887
$apply!(
874-
Table<Table<Graphic>>,
888+
Table<Artboard>,
875889
Table<Graphic>,
876890
Table<Vector>,
877891
Table<Raster<CPU>>,
@@ -900,6 +914,7 @@ macro_rules! known_table_row_types {
900914
Raster<CPU>,
901915
Raster<GPU>,
902916
Graphic,
917+
Artboard,
903918
);
904919
};
905920
}

editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
394394
},
395395
DocumentNode {
396396
inputs: vec![
397-
NodeInput::import(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Table<Graphic>>))), 0),
397+
NodeInput::import(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),
398398
NodeInput::node(NodeId(3), 0),
399399
],
400400
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),

editor/src/messages/tool/tool_messages/artboard_tool.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -611,18 +611,15 @@ impl Fsm for ArtboardToolFsmState {
611611
#[cfg(test)]
612612
mod test_artboard {
613613
pub use crate::test_utils::test_prelude::*;
614-
use graphene_std::Graphic;
614+
use graphene_std::Artboard;
615615
use graphene_std::table::Table;
616616

617-
async fn get_artboards(editor: &mut EditorTestUtils) -> Table<Table<Graphic>> {
617+
async fn get_artboards(editor: &mut EditorTestUtils) -> Table<Artboard> {
618618
let instrumented = match editor.eval_graph().await {
619619
Ok(instrumented) => instrumented,
620620
Err(e) => panic!("Failed to evaluate graph: {e}"),
621621
};
622-
instrumented
623-
.grab_all_input::<graphene_std::graphic::extend::NewInput<Table<graphene_std::Graphic>>>(&editor.runtime)
624-
.flatten()
625-
.collect()
622+
instrumented.grab_all_input::<graphene_std::graphic::extend::NewInput<Artboard>>(&editor.runtime).flatten().collect()
626623
}
627624

628625
#[derive(Debug, PartialEq)]

editor/src/node_graph_executor/runtime.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use graphene_std::text::FontCache;
2020
use graphene_std::transform::RenderQuality;
2121
use graphene_std::vector::Vector;
2222
use graphene_std::vector::style::RenderMode;
23-
use graphene_std::{Context, Graphic};
23+
use graphene_std::{Artboard, Context, Graphic};
2424
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
2525
use interpreted_executor::util::wrap_network_in_scope;
2626
use spin::Mutex;
@@ -441,7 +441,7 @@ impl NodeRuntime {
441441
}
442442
// Artboard thumbnail bounds come from the clipping rectangles, not the content union, since the renderer
443443
// clips content to those rectangles so anything outside isn't visible
444-
else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Table<Graphic>>>>() {
444+
else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
445445
if update_thumbnails {
446446
let bounds = artboard_clip_bounds(&io.output);
447447
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, bounds, responses)
@@ -522,7 +522,7 @@ impl NodeRuntime {
522522

523523
/// Returns the union of the artboards' clipping rectangles, used as the thumbnail bounds for an artboard layer so the
524524
/// framing matches what's actually visible after clipping rather than the unclipped content extents.
525-
fn artboard_clip_bounds(artboards: &Table<Table<Graphic>>) -> RenderBoundingBox {
525+
fn artboard_clip_bounds(artboards: &Table<Artboard>) -> RenderBoundingBox {
526526
let mut combined: Option<[DVec2; 2]> = None;
527527
for index in 0..artboards.len() {
528528
let location: DVec2 = artboards.attribute_cloned_or_default(graphene_std::ATTR_LOCATION, index);

node-graph/graph-craft/src/document/value.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub use glam::{DAffine2, DVec2, IVec2, UVec2};
1414
use graphic_types::raster_types::{CPU, Image, Raster};
1515
use graphic_types::vector_types::vector::style::{Fill, Gradient, GradientStops, Stroke};
1616
use graphic_types::vector_types::vector::{self, ReferencePoint};
17-
use graphic_types::{Graphic, Vector};
17+
use graphic_types::{Artboard, Graphic, Vector};
1818
use raster_nodes::curve::Curve;
1919
use rendering::RenderMetadata;
2020
use std::fmt::Display;
@@ -184,7 +184,7 @@ tagged_value! {
184184
Graphic(Table<Graphic>),
185185
#[serde(deserialize_with = "graphic_types::artboard::migrate_artboard")] // TODO: Eventually remove this migration document upgrade code
186186
#[serde(alias = "ArtboardGroup")]
187-
Artboard(Table<Table<Graphic>>),
187+
Artboard(Table<Artboard>),
188188
#[serde(deserialize_with = "core_types::misc::migrate_color")] // TODO: Eventually remove this migration document upgrade code
189189
#[serde(alias = "ColorTable", alias = "OptionalColor", alias = "ColorNotInTable")]
190190
Color(Table<Color>),

node-graph/interpreted-executor/src/node_registry.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use graphene_std::table::Table;
2121
use graphene_std::transform::Footprint;
2222
use graphene_std::uuid::NodeId;
2323
use graphene_std::vector::Vector;
24-
use graphene_std::{Context, Graphic, NodeIO, NodeIOTypes, ProtoNodeIdentifier, concrete, fn_type_fut, future};
24+
use graphene_std::{Artboard, Context, Graphic, NodeIO, NodeIOTypes, ProtoNodeIdentifier, concrete, fn_type_fut, future};
2525
use node_registry_macros::{async_node, convert_node, into_node};
2626
use std::collections::HashMap;
2727
#[cfg(feature = "gpu")]
@@ -63,7 +63,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
6363
// MONITOR NODES
6464
// =============
6565
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ()]),
66-
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Table<Graphic>>]),
66+
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Artboard>]),
6767
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Graphic>]),
6868
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Vector>]),
6969
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
@@ -145,7 +145,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
145145
// ==========
146146
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ()]),
147147
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => bool]),
148-
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Table<Graphic>>]),
148+
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Artboard>]),
149149
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Graphic>]),
150150
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Vector>]),
151151
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),

node-graph/libraries/graphic-types/src/artboard.rs

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,71 @@
11
use crate::graphic::Graphic;
22
use core_types::blending::BlendMode;
3+
use core_types::bounds::{BoundingBox, RenderBoundingBox};
4+
use core_types::graphene_hash::CacheHash;
5+
use core_types::render_complexity::RenderComplexity;
36
use core_types::table::{Table, TableRow};
47
use core_types::uuid::NodeId;
58
use core_types::{ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_LOCATION, Color};
69
use dyn_any::DynAny;
710
use glam::{DAffine2, IVec2};
811

9-
// An artboard table is `Table<Table<Graphic>>`: each row's element is the artboard's content
10-
// (a `Table<Graphic>`), with the artboard's metadata stored alongside on the row as attributes
11-
// (see `ATTR_LOCATION`, `ATTR_DIMENSIONS`, `ATTR_BACKGROUND`, `ATTR_CLIP`).
12-
//
13-
// The artboard's user-visible name is the parent layer's display name (resolved live from the
14-
// network interface via the row's `ATTR_EDITOR_LAYER_PATH` attribute) — not stored here, so it
15-
// can never go stale.
16-
//
17-
// These metadata attributes are populated at runtime by the `Artboard` proto node from its
18-
// inputs and therefore aren't persisted in document files; the proto node's input values are
19-
// what get serialized.
12+
/// Nominal wrapper around `Table<Graphic>` representing a single artboard's content.
13+
///
14+
/// Per-artboard metadata (location, dimensions, background, clip) lives as row attributes on the
15+
/// enclosing `Table<Artboard>`, not as fields here. This keeps `Artboard` a pure type-system boundary
16+
/// that prevents arbitrary `Table<Table<...<Graphic>>>` nesting.
17+
#[derive(Clone, Debug, Default, CacheHash, PartialEq, DynAny)]
18+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19+
pub struct Artboard(Table<Graphic>);
20+
21+
impl Artboard {
22+
pub fn new(content: Table<Graphic>) -> Self {
23+
Self(content)
24+
}
25+
26+
pub fn as_graphic_table(&self) -> &Table<Graphic> {
27+
&self.0
28+
}
29+
30+
pub fn as_graphic_table_mut(&mut self) -> &mut Table<Graphic> {
31+
&mut self.0
32+
}
33+
34+
pub fn into_graphic_table(self) -> Table<Graphic> {
35+
self.0
36+
}
37+
}
38+
39+
impl From<Table<Graphic>> for Artboard {
40+
fn from(content: Table<Graphic>) -> Self {
41+
Self(content)
42+
}
43+
}
44+
45+
impl From<Artboard> for Table<Graphic> {
46+
fn from(artboard: Artboard) -> Self {
47+
artboard.0
48+
}
49+
}
50+
51+
impl BoundingBox for Artboard {
52+
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
53+
self.0.bounding_box(transform, include_stroke)
54+
}
55+
56+
fn thumbnail_bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
57+
self.0.thumbnail_bounding_box(transform, include_stroke)
58+
}
59+
}
60+
61+
impl RenderComplexity for Artboard {
62+
fn render_complexity(&self) -> usize {
63+
self.0.render_complexity()
64+
}
65+
}
2066

2167
// TODO: Eventually remove this migration document upgrade code
22-
pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Table<Graphic>>, D::Error> {
68+
pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Artboard>, D::Error> {
2369
use serde::Deserialize;
2470

2571
/// Mirrors the removed `AlphaBlending` struct for legacy document deserialization.
@@ -33,9 +79,7 @@ pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re
3379
pub clip: bool,
3480
}
3581

36-
/// Pre-migration shape of the artboard's stored data: the struct that used to live as the element
37-
/// of `Table<Artboard>`. Kept as a private type so we can deserialize legacy documents into the new
38-
/// `Table<Table<Graphic>>` (element = `content`, other fields → row attributes).
82+
/// Legacy artboard struct shape, kept for deserializing old documents into `Table<Artboard>`.
3983
#[derive(Clone, Debug, DynAny)]
4084
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
4185
pub struct LegacyArtboard {
@@ -68,14 +112,14 @@ pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re
68112
ArtboardGroup(LegacyArtboardGroup),
69113
OldArtboardTable(OldTable<LegacyArtboard>),
70114
LegacyArtboardTable(Table<LegacyArtboard>),
71-
// Note: this variant must come last so older formats above are tried first; an empty
72-
// `Table<Table<Graphic>>` would otherwise match (since `Table<T>` has the same shell across `T`).
73-
ArtboardTable(Table<Table<Graphic>>),
115+
// NOTE: Must come last so older tagged formats above are tried first.
116+
// Also covers the intermediate `Table<Table<Graphic>>` shape since `Artboard` deserializes transparently.
117+
ArtboardTable(Table<Artboard>),
74118
}
75119

76-
fn legacy_to_row(legacy: LegacyArtboard) -> TableRow<Table<Graphic>> {
77-
// Legacy `label` field is dropped the artboard's name now comes from its parent layer's display name.
78-
TableRow::new_from_element(legacy.content)
120+
fn legacy_to_row(legacy: LegacyArtboard) -> TableRow<Artboard> {
121+
// Legacy `label` field is dropped (the artboard's name comes from its parent layer's display name)
122+
TableRow::new_from_element(Artboard::new(legacy.content))
79123
.with_attribute(ATTR_LOCATION, legacy.location.as_dvec2())
80124
.with_attribute(ATTR_DIMENSIONS, legacy.dimensions.as_dvec2())
81125
.with_attribute(ATTR_BACKGROUND, legacy.background)

node-graph/libraries/graphic-types/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub use raster_types;
77
pub use vector_types;
88

99
// Re-export commonly used types at the crate root
10+
pub use artboard::Artboard;
1011
pub use graphic::{Graphic, IntoGraphicTable, TryFromGraphic, Vector};
1112

1213
pub mod migrations {

node-graph/libraries/rendering/src/renderer.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@ use core_types::{
1717
use dyn_any::DynAny;
1818
use glam::{DAffine2, DVec2};
1919
use graphene_hash::CacheHashWrapper;
20-
use graphic_types::Graphic;
21-
use graphic_types::Vector;
2220
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster};
2321
use graphic_types::vector_types::gradient::{GradientStops, GradientType};
2422
use graphic_types::vector_types::subpath::Subpath;
2523
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
2624
use graphic_types::vector_types::vector::style::{Fill, PaintOrder, RenderMode, Stroke, StrokeAlign};
25+
use graphic_types::{Artboard, Graphic, Vector};
2726
use kurbo::{Affine, Cap, Join, Shape};
2827
use num_traits::Zero;
2928
use std::collections::{HashMap, HashSet};
@@ -514,19 +513,19 @@ impl Render for Graphic {
514513
}
515514
}
516515

517-
/// Reads the artboard metadata for the row at `index` from a `Table<Table<Graphic>>` of artboards.
518-
fn read_artboard_attributes(table: &Table<Table<Graphic>>, index: usize) -> (DVec2, DVec2, Color, bool) {
516+
/// Reads the artboard metadata for the row at `index` from a `Table<Artboard>`.
517+
fn read_artboard_attributes(table: &Table<Artboard>, index: usize) -> (DVec2, DVec2, Color, bool) {
519518
let location: DVec2 = table.attribute_cloned_or_default(ATTR_LOCATION, index);
520519
let dimensions: DVec2 = table.attribute_cloned_or_default(ATTR_DIMENSIONS, index);
521520
let background: Color = table.attribute_cloned_or_default(ATTR_BACKGROUND, index);
522521
let clip: bool = table.attribute_cloned_or_default(ATTR_CLIP, index);
523522
(location, dimensions, background, clip)
524523
}
525524

526-
impl Render for Table<Table<Graphic>> {
525+
impl Render for Table<Artboard> {
527526
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
528527
for index in 0..self.len() {
529-
let Some(content) = self.element(index) else { continue };
528+
let Some(content) = self.element(index).map(Artboard::as_graphic_table) else { continue };
530529
let (location, dimensions, background, clip) = read_artboard_attributes(self, index);
531530

532531
let x = location.x.min(location.x + dimensions.x);
@@ -584,7 +583,7 @@ impl Render for Table<Table<Graphic>> {
584583
use vello::peniko;
585584

586585
for index in 0..self.len() {
587-
let Some(content) = self.element(index) else { continue };
586+
let Some(content) = self.element(index).map(Artboard::as_graphic_table) else { continue };
588587
let (location, dimensions, background, clip) = read_artboard_attributes(self, index);
589588

590589
let [a, b] = [location, location + dimensions];
@@ -614,7 +613,7 @@ impl Render for Table<Table<Graphic>> {
614613

615614
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
616615
for index in 0..self.len() {
617-
let Some(content) = self.element(index) else { continue };
616+
let Some(content) = self.element(index).map(Artboard::as_graphic_table) else { continue };
618617
let (location, dimensions, _background, clip) = read_artboard_attributes(self, index);
619618

620619
let layer_path: Table<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);

node-graph/nodes/gcore/src/animation.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use core_types::transform::Footprint;
33
use core_types::{CacheHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl};
44
use glam::{DAffine2, DVec2};
55
use graphic_types::vector_types::GradientStops;
6-
use graphic_types::{Graphic, Vector};
6+
use graphic_types::{Artboard, Graphic, Vector};
77
use raster_types::{CPU, GPU, Raster};
88

99
const DAY: f64 = 1000. * 3600. * 24.;
@@ -78,7 +78,7 @@ async fn quantize_real_time<T>(
7878
Context -> Table<Raster<CPU>>,
7979
Context -> Table<Raster<GPU>>,
8080
Context -> Table<Color>,
81-
Context -> Table<Table<Graphic>>,
81+
Context -> Table<Artboard>,
8282
Context -> Table<GradientStops>,
8383
Context -> Table<String>,
8484
Context -> Table<f64>,
@@ -118,7 +118,7 @@ async fn quantize_animation_time<T>(
118118
Context -> Table<Raster<CPU>>,
119119
Context -> Table<Raster<GPU>>,
120120
Context -> Table<Color>,
121-
Context -> Table<Table<Graphic>>,
121+
Context -> Table<Artboard>,
122122
Context -> Table<GradientStops>,
123123
Context -> Table<String>,
124124
Context -> Table<f64>,

0 commit comments

Comments
 (0)