diff --git a/node-graph/libraries/core-types/src/lane.rs b/node-graph/libraries/core-types/src/lane.rs index 5c220db367..c1537fdfd9 100644 --- a/node-graph/libraries/core-types/src/lane.rs +++ b/node-graph/libraries/core-types/src/lane.rs @@ -6,9 +6,13 @@ use crate::attribute::Attribute; /// One marker's column on a source, resolved once so lane reads skip the key /// lookup. pub trait LaneColumn<'a, A: Attribute> { - /// The lane's value, or the marker's census default where the column is - /// absent. - fn get(&self, lane: usize) -> A::Value<'a>; + /// The lane's value, `None` where the source carries no such column. + fn try_get(&self, lane: usize) -> Option>; + + /// The lane's value, falling back to the marker's census default. + fn get(&self, lane: usize) -> A::Value<'a> { + self.try_get(lane).unwrap_or_else(A::default) + } } /// A source of lanes carrying an element and census attributes. @@ -27,6 +31,11 @@ pub trait LaneSource { fn attr(&self, lane: usize) -> A::Value<'_> { self.column::().get(lane) } + + /// Distinguishes an absent column from one holding the census default. + fn try_attr(&self, lane: usize) -> Option> { + self.column::().try_get(lane) + } } #[cfg(test)] diff --git a/node-graph/libraries/core-types/src/list.rs b/node-graph/libraries/core-types/src/list.rs index 43ff7467d3..d685c29fad 100644 --- a/node-graph/libraries/core-types/src/list.rs +++ b/node-graph/libraries/core-types/src/list.rs @@ -1180,8 +1180,8 @@ pub struct ListColumn<'a, A: crate::attribute::Attribute> { } impl<'a, A: crate::attribute::Attribute> crate::lane::LaneColumn<'a, A> for ListColumn<'a, A> { - fn get(&self, lane: usize) -> A::Value<'a> { - self.stored.and_then(|column| column.get_any(lane)).and_then(A::from_stored).unwrap_or_else(A::default) + fn try_get(&self, lane: usize) -> Option> { + self.stored.and_then(|column| column.get_any(lane)).and_then(A::from_stored) } } diff --git a/node-graph/libraries/core-types/src/record.rs b/node-graph/libraries/core-types/src/record.rs index 2dfcde7f82..54cbb1986b 100644 --- a/node-graph/libraries/core-types/src/record.rs +++ b/node-graph/libraries/core-types/src/record.rs @@ -1888,13 +1888,10 @@ pub struct RunColumn<'a, A: crate::attribute::Attribute> { } impl<'a, A: crate::attribute::Attribute> crate::lane::LaneColumn<'a, A> for RunColumn<'a, A> { - fn get(&self, lane: usize) -> A::Value<'a> { - match self.offset { - // SAFETY: the offset comes from the item's own layout, whose field at - // this name holds this marker's value type by census registration. - Some(offset) => unsafe { self.item.lanes().get(lane).rec().ptr().add(offset).cast::>().read() }, - None => A::default(), - } + fn try_get(&self, lane: usize) -> Option> { + // SAFETY: the offset comes from the item's own layout, whose field at + // this name holds this marker's value type by census registration. + self.offset.map(|offset| unsafe { self.item.lanes().get(lane).rec().ptr().add(offset).cast::>().read() }) } } diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index c14372a971..3d47871b3e 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -2,8 +2,8 @@ use crate::render_ext::{PaintTarget, RenderExt}; use crate::to_peniko::{BlendModeExt, ToPenikoColor}; use core_types::CacheHash; use core_types::attribute::{ - Background as BackgroundAttr, BlendMode as BlendModeAttr, Clip, ClippingMask, Dimensions, EditorLayerPath, FontSize, LetterSpacing, LetterTilt, LineHeight, Location, MaxHeight, MaxWidth, Opacity, - OpacityFill, Transform, + Background as BackgroundAttr, BlendMode as BlendModeAttr, Clip, ClippingMask, Dimensions, EditorLayerPath, EditorTextFrame, FontSize, LetterSpacing, LetterTilt, LineHeight, Location, MaxHeight, + MaxWidth, Opacity, OpacityFill, Transform, }; use core_types::blending::BlendMode; use core_types::bounds::BoundingBox; @@ -16,7 +16,7 @@ use core_types::math::quad::Quad; use core_types::render_complexity::RenderComplexity; use core_types::transform::Footprint; use core_types::uuid::{NodeId, generate_uuid}; -use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_TEXT_FRAME, ATTR_TRANSFORM}; +use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM}; use dyn_any::DynAny; use glam::{DAffine2, DMat2, DVec2}; use graphene_hash::CacheHashWrapper; @@ -43,7 +43,8 @@ use std::ops::Deref; use std::sync::{Arc, LazyLock}; use text_nodes::markers::{Font, TextAlign}; use vector_types::gradient::GradientSpreadMethod; -use vector_types::{ATTR_EDITOR_CLICK_TARGET, ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD}; +use vector_types::markers::EditorClickTarget; +use vector_types::{ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD}; use vello::*; #[derive(Clone, Copy, Debug, PartialEq)] @@ -1588,7 +1589,7 @@ impl Render for List { } // Use click-target override if the item provides one (e.g. 'Text' node's per-glyph bboxes) - let click_target_vector = self.attribute::(ATTR_EDITOR_CLICK_TARGET, index).unwrap_or(source); + let click_target_vector = self.attr::(index).unwrap_or(source); let item_relative_transform = item_zero_inverse * transform; @@ -1617,7 +1618,7 @@ impl Render for List { } // Surface `editor:text_frame` for the Text tool's drag cage - if let Some(&frame) = self.attribute::(ATTR_EDITOR_TEXT_FRAME, index) { + if let Some(frame) = self.try_attr::(index) { metadata.text_frames.entry(element_id).or_insert(frame); } } @@ -1647,7 +1648,7 @@ impl Render for List { let transform: DAffine2 = self.attr::(index); // Use click-target override geometry if the item provides one (e.g. 'Text' node's per-glyph bounding boxes) - let vector = self.attribute::(ATTR_EDITOR_CLICK_TARGET, index).unwrap_or(source); + let vector = self.attr::(index).unwrap_or(source); extend_targets_from_vector(click_targets, self, index, vector, transform); } diff --git a/node-graph/nodes/text/src/path_builder.rs b/node-graph/nodes/text/src/path_builder.rs index eb037bcf19..4694ad374d 100644 --- a/node-graph/nodes/text/src/path_builder.rs +++ b/node-graph/nodes/text/src/path_builder.rs @@ -198,7 +198,7 @@ impl PathBuilder { for (entry, widened) in entries.iter().zip(layer_bboxes.iter()) { let glyph_local = [widened[0] - entry.1, widened[1] - entry.1]; let rect = Subpath::new_rectangle(glyph_local[0], glyph_local[1]); - self.vector_list.set_attribute(ATTR_EDITOR_CLICK_TARGET, entry.0, Vector::from_subpaths([rect], false)); + self.vector_list.set_attribute(ATTR_EDITOR_CLICK_TARGET, entry.0, Some(Vector::from_subpaths([rect], false))); } } @@ -208,7 +208,7 @@ impl PathBuilder { widen_horizontal_gaps(&mut bboxes, &self.merged_click_target_baselines); let widened_subpaths: Vec<_> = bboxes.iter().map(|[min, max]| Subpath::new_rectangle(*min, *max)).collect(); - self.vector_list.set_attribute(ATTR_EDITOR_CLICK_TARGET, 0, Vector::from_subpaths(widened_subpaths, false)); + self.vector_list.set_attribute(ATTR_EDITOR_CLICK_TARGET, 0, Some(Vector::from_subpaths(widened_subpaths, false))); } // Fill in text frame for items that don't have one yet (single-item mode, where item 0 = identity)