Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2774,7 +2774,7 @@ impl DocumentMessageHandler {

let has_fill = fill_graphic_list.is_some_and(|list| is_paint_present(list));
// `Vector.stroke` captures stroke geometry, even with weight 0 or transparent paint.
// So stroke visibility must be checked from `ATTR_STROKE`, the paint source of truth.
// So stroke visibility must be checked from `graphic_types::attr::Stroke`, the paint source of truth.
let stroke_visible = stroke_graphic_list.is_some_and(|list| list.element(0).is_some_and(|g| !g.is_fully_transparent()));
let has_stroke = stroke.as_ref().is_some_and(|s| s.has_renderable_stroke()) && stroke_visible;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
DocumentNode {
inputs: vec![
NodeInput::node(NodeId(1), 0),
NodeInput::value(TaggedValue::String(graphene_std::ATTR_EDITOR_LAYER_PATH.to_string()), false),
NodeInput::value(TaggedValue::String(attr::editor::LayerPath::name().to_string()), false),
NodeInput::node(NodeId(2), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(graphic::write_attribute::IDENTIFIER),
Expand Down Expand Up @@ -310,7 +310,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
DocumentNode {
inputs: vec![
NodeInput::node(NodeId(0), 0),
NodeInput::value(TaggedValue::String(graphene_std::ATTR_EDITOR_LAYER_PATH.to_string()), false),
NodeInput::value(TaggedValue::String(attr::editor::LayerPath::name().to_string()), false),
NodeInput::node(NodeId(1), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(graphic::write_attribute::IDENTIFIER),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::messages::prelude::ViewportMessageHandler;
use core::borrow::Borrow;
use core::f64::consts::{FRAC_PI_2, PI, TAU};
use glam::{DAffine2, DVec2};
use graphene_std::ATTR_TRANSFORM;
use graphene_std::attr;
use graphene_std::list::List;
use graphene_std::math::quad::Quad;
use graphene_std::subpath::{self, Subpath};
Expand Down Expand Up @@ -1168,7 +1168,7 @@ impl OverlayContextInternal {
// Use the existing bezier_to_path infrastructure to convert Vector to BezPath
let mut path = BezPath::new();
let mut last_point = None;
let transform: DAffine2 = text_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let transform = text_list.attr_cloned_or_default::<attr::Transform>(index);

let Some(element) = text_list.element(index) else { continue };
for (_, bezier, start_id, end_id) in element.segment_iter() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ pub struct DocumentMetadata {
/// Vector data keyed by layer ID, used as fallback when no Path node exists.
/// This provides accurate SegmentIds for layers without explicit Path nodes.
pub layer_vector_data: HashMap<LayerNodeIdentifier, Arc<Vector>>,
/// Per-layer `ATTR_FILL` attribute, exposed so message handlers can read paint
/// Per-layer `graphic_types::attr::Fill` attribute, exposed so message handlers can read paint
/// information that lives on the list.
pub layer_fill_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic>>>,
/// Per-layer `ATTR_STROKE` attribute, exposed so message handlers can read
/// Per-layer `graphic_types::attr::Stroke` attribute, exposed so message handlers can read
/// stroke paint information that lives on the list.
pub layer_stroke_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic>>>,
/// Transform from document space to viewport space.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3454,12 +3454,12 @@ impl NodeNetworkInterface {
self.document_metadata.layer_vector_data = new_layer_vector_data;
}

/// Update the per-layer `ATTR_FILL` snapshot.
/// Update the per-layer `graphic_types::attr::Fill` snapshot.
pub fn update_fill_attributes(&mut self, new_layer_fill_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic>>>) {
self.document_metadata.layer_fill_attributes = new_layer_fill_attributes;
}

/// Update the per-layer `ATTR_STROKE` snapshot.
/// Update the per-layer `graphic_types::attr::Stroke` snapshot.
pub fn update_stroke_attributes(&mut self, new_layer_stroke_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic>>>) {
self.document_metadata.layer_stroke_attributes = new_layer_stroke_attributes;
}
Expand Down
5 changes: 3 additions & 2 deletions editor/src/messages/tool/tool_messages/artboard_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ impl Fsm for ArtboardToolFsmState {
mod test_artboard {
pub use crate::test_utils::test_prelude::*;
use graphene_std::Artboard;
use graphene_std::attr;
use graphene_std::list::List;

async fn get_artboards(editor: &mut EditorTestUtils) -> List<Artboard> {
Expand Down Expand Up @@ -601,8 +602,8 @@ mod test_artboard {
let artboards = get_artboards(editor).await;
let artboards = (0..artboards.len())
.map(|index| {
let location: DVec2 = artboards.attribute_cloned_or_default(graphene_std::ATTR_LOCATION, index);
let dimensions: DVec2 = artboards.attribute_cloned_or_default(graphene_std::ATTR_DIMENSIONS, index);
let location = artboards.attr_cloned_or_default::<attr::Location>(index);
let dimensions = artboards.attr_cloned_or_default::<attr::Dimensions>(index);
ArtboardLayoutDocument::new(location, dimensions)
})
.collect::<Vec<_>>();
Expand Down
4 changes: 2 additions & 2 deletions editor/src/node_graph_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use graphene_std::raster::{CPU, Raster};
use graphene_std::renderer::{RenderMetadata, graphic_list_bounding_box};
use graphene_std::transform::Footprint;
use graphene_std::vector::{Vector, graphic_types};
use graphene_std::{ATTR_TRANSFORM, Context, Graphic, NodeInputDecleration};
use graphene_std::{Context, Graphic, NodeInputDecleration, attr};
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
use std::any::Any;
use std::sync::Arc;
Expand Down Expand Up @@ -870,7 +870,7 @@ fn redirect_export_chain(network: &mut NodeNetwork, full_path: &[NodeId]) -> boo
fn measure_fill_geometry(data: &Arc<dyn Any + Send + Sync>) -> Option<(DAffine2, DAffine2)> {
if let Some(list) = introspected_output::<List<Vector>>(data) {
let vector = list.element(0)?;
let item_transform: DAffine2 = list.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
let item_transform = list.attr_cloned_or_default::<attr::Transform>(0);
let bounds = vector.nonzero_bounding_box();
let bounding_box_affine = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
return Some((bounding_box_affine, item_transform));
Expand Down
5 changes: 3 additions & 2 deletions editor/src/node_graph_executor/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use graph_craft::document::{NodeId, NodeNetwork};
use graph_craft::graphene_compiler::Compiler;
use graph_craft::proto::GraphErrors;
use graphene_std::application_io::{ApplicationIo, ExportFormat, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig, Texture};
use graphene_std::attr;
use graphene_std::bounds::RenderBoundingBox;
use graphene_std::list::{Item, List};
use graphene_std::memo::IORecord;
Expand Down Expand Up @@ -536,8 +537,8 @@ impl NodeRuntime {
fn artboard_clip_bounds(artboards: &List<Artboard>) -> RenderBoundingBox {
let mut combined: Option<[DVec2; 2]> = None;
for index in 0..artboards.len() {
let location: DVec2 = artboards.attribute_cloned_or_default(graphene_std::ATTR_LOCATION, index);
let dimensions: DVec2 = artboards.attribute_cloned_or_default(graphene_std::ATTR_DIMENSIONS, index);
let location = artboards.attr_cloned_or_default::<attr::Location>(index);
let dimensions = artboards.attr_cloned_or_default::<attr::Dimensions>(index);
let bounds = [location, location + dimensions];
combined = Some(match combined {
Some(existing) => [existing[0].min(bounds[0]), existing[1].max(bounds[1])],
Expand Down
10 changes: 5 additions & 5 deletions node-graph/interpreted-executor/src/dynamic_executor/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ fn transform_network(content: TaggedValue, rotation: TaggedValue) -> ProtoNetwor

#[test]
fn transform_composes_onto_item_wire() {
use glam::{DAffine2, DVec2};
use glam::DVec2;

let network = transform_network(TaggedValue::TypeDefault(item!(Vector)), TaggedValue::F64(0.));
let output = network.output;
Expand All @@ -197,7 +197,7 @@ fn transform_composes_onto_item_wire() {
let context: Context = None;
let result: Option<Item<Vector>> = futures::executor::block_on(tree.eval(output, context));
let item = result.expect("A rank-0 chain through Transform should stay rank 0");
let transform = item.attribute_cloned_or_default::<DAffine2>(core_types::ATTR_TRANSFORM);
let transform = item.attr_cloned_or_default::<core_types::attr::Transform>();
assert_eq!(transform.translation, DVec2::new(5., 0.), "The translation should compose onto the item's transform attribute");
}

Expand All @@ -219,8 +219,8 @@ fn transform_broadcasts_item_content_across_a_framed_parameter() {
let list = result.expect("The broadcast should produce a List");
assert_eq!(list.len(), 2, "One output item per frame slot");

let first: DAffine2 = list.attribute_cloned_or_default(core_types::ATTR_TRANSFORM, 0);
let second: DAffine2 = list.attribute_cloned_or_default(core_types::ATTR_TRANSFORM, 1);
let first: DAffine2 = list.attr_cloned_or_default::<core_types::attr::Transform>(0);
let second: DAffine2 = list.attr_cloned_or_default::<core_types::attr::Transform>(1);
assert!((first.matrix2.col(0).y - 0.).abs() < 1e-10, "Slot 0 should be unrotated");
assert!((second.matrix2.col(0).y - 1.).abs() < 1e-10, "Slot 1 should be rotated 90 degrees");
}
Expand Down Expand Up @@ -320,7 +320,7 @@ fn value_wires_materialize_as_items_at_resolution() {
let context: Context = None;
let result: Option<Item<DAffine2>> = futures::executor::block_on(tree.eval(NodeId(5), context));
let item = result.expect("A value matrix should flow through Transform as an Item");
let transform = item.attribute_cloned_or_default::<DAffine2>(core_types::ATTR_TRANSFORM);
let transform = item.attr_cloned_or_default::<core_types::attr::Transform>();
assert_eq!(transform.translation, DVec2::new(7., 0.), "The translation should compose onto the gained transform attribute");
}

Expand Down
70 changes: 70 additions & 0 deletions node-graph/libraries/core-types/src/attr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//! Typed attribute keys.
//!
//! Each key is a zero-sized marker implementing [`Attr`], which ties the name (the string
//! stored in the attribute store) to the Rust value type.
//!
//! Keys are declared with the [`node_macro::attrs!`] macro: `Name: Type` entries, where
//! `namespace { ... }` blocks contribute a `namespace:` name prefix. The key name is
//! derived mechanically from the ident (UpperCamel -> snake_case).

use crate::Color;
use crate::list::NodeIdPath;
use glam::{DAffine2, DVec2};
use graphene_hash::CacheHash;
use std::fmt::Debug;

pub trait Attr {
type Value: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static;
fn name() -> &'static str;
}

node_macro::attrs! {
/// Item's `DAffine2` transformation, composed multiplicatively through nested groups.
Transform: DAffine2,
/// Item's `BlendMode`, controlling how it composites with content beneath it.
BlendMode: crate::blending::BlendMode,
/// Item's opacity multiplier, composed multiplicatively through nested groups. Affects content clipped to the item.
Opacity: f64,
/// Item's fill opacity multiplier. Like opacity but does not affect content clipped to the item.
OpacityFill: f64,
/// Whether an item inherits the alpha of the content beneath it (clipping mask).
ClippingMask: bool,
/// Byte offset where a regex match begins ('Regex Find All', 'Regex Capture' text nodes).
Start: u64,
/// Byte offset where a regex match ends ('Regex Find All', 'Regex Capture' text nodes).
End: u64,
/// A regex named-capture-group's name, or empty for unnamed groups ('Regex Capture' text node).
Name: String,
/// A JSON value's type (`"string"`, `"number"`, `"object"`, etc.) from 'JSON Query All'.
Type: String,
/// Artboard's top-left corner in document coordinates.
Location: DVec2,
/// Artboard's width and height.
Dimensions: DVec2,
/// Artboard's background fill.
Background: Color,
/// Whether an artboard clips content to its bounds.
Clip: bool,
/// Text item's font size in document-space units.
FontSize: f64,
/// Text item's line height as a ratio of the font size.
LineHeight: f64,
/// Text item's extra spacing between letters in document-space units.
LetterSpacing: f64,
/// Text item's maximum line-wrap width in document-space units.
MaxWidth: Option<f64>,
/// Text item's maximum block height in document-space units, past which lines are not drawn.
MaxHeight: Option<f64>,
/// Text item's faux-italic letter tilt angle in degrees.
LetterTilt: f64,
editor {
/// Path from the root network to the layer node owning this item.
/// Used by editor tools to route clicks/selection back to the originating layer.
LayerPath: NodeIdPath,
/// Affine mapping the unit square `[(0, 0), (1, 1)]` (top-left convention) onto the 'Text'
/// node's text frame in this item's local space. Each item carries the frame relative to its own
/// glyph origin so it survives `Index Elements` filtering. The Text tool reads this to position
/// its drag cage. Stored as an affine to allow non-axis-aligned frames in the future.
TextFrame: DAffine2,
},
}
7 changes: 2 additions & 5 deletions node-graph/libraries/core-types/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
extern crate log;

pub mod attr;
pub mod bounds;
pub mod consts;
pub mod context;
Expand All @@ -16,18 +17,14 @@ pub mod uuid;
pub mod value;

pub use crate as core_types;
pub use attr::Attr;
pub use blending::*;
pub use color::Color;
pub use context::*;
pub use ctor;
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
pub use graphene_hash;
pub use graphene_hash::CacheHash;
pub use list::{
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_EDITOR_TEXT_FRAME, ATTR_END,
ATTR_FONT, ATTR_FONT_SIZE, ATTR_GRADIENT_TYPE, ATTR_LETTER_SPACING, ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_LOCATION, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_NAME, ATTR_OPACITY, ATTR_OPACITY_FILL,
ATTR_SPREAD_METHOD, ATTR_START, ATTR_TEXT_ALIGN, ATTR_TRANSFORM, ATTR_TYPE,
};
pub use memo::MemoHash;
pub use no_std_types::AsU32;
pub use no_std_types::blending;
Expand Down
Loading
Loading