Distinguish an absent column from a defaulted one on the lane source

This commit is contained in:
Dennis Kobert
2026-08-26 15:48:01 +00:00
parent b685ca77f2
commit 2b06d27729
5 changed files with 28 additions and 21 deletions
+12 -3
View File
@@ -6,9 +6,13 @@ use crate::attribute::Attribute;
/// One marker's column on a source, resolved once so lane reads skip the key /// One marker's column on a source, resolved once so lane reads skip the key
/// lookup. /// lookup.
pub trait LaneColumn<'a, A: Attribute> { pub trait LaneColumn<'a, A: Attribute> {
/// The lane's value, or the marker's census default where the column is /// The lane's value, `None` where the source carries no such column.
/// absent. fn try_get(&self, lane: usize) -> Option<A::Value<'a>>;
fn get(&self, lane: usize) -> A::Value<'a>;
/// 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. /// A source of lanes carrying an element and census attributes.
@@ -27,6 +31,11 @@ pub trait LaneSource {
fn attr<A: Attribute>(&self, lane: usize) -> A::Value<'_> { fn attr<A: Attribute>(&self, lane: usize) -> A::Value<'_> {
self.column::<A>().get(lane) self.column::<A>().get(lane)
} }
/// Distinguishes an absent column from one holding the census default.
fn try_attr<A: Attribute>(&self, lane: usize) -> Option<A::Value<'_>> {
self.column::<A>().try_get(lane)
}
} }
#[cfg(test)] #[cfg(test)]
+2 -2
View File
@@ -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> { impl<'a, A: crate::attribute::Attribute> crate::lane::LaneColumn<'a, A> for ListColumn<'a, A> {
fn get(&self, lane: usize) -> A::Value<'a> { fn try_get(&self, lane: usize) -> Option<A::Value<'a>> {
self.stored.and_then(|column| column.get_any(lane)).and_then(A::from_stored).unwrap_or_else(A::default) self.stored.and_then(|column| column.get_any(lane)).and_then(A::from_stored)
} }
} }
@@ -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> { impl<'a, A: crate::attribute::Attribute> crate::lane::LaneColumn<'a, A> for RunColumn<'a, A> {
fn get(&self, lane: usize) -> A::Value<'a> { fn try_get(&self, lane: usize) -> Option<A::Value<'a>> {
match self.offset { // SAFETY: the offset comes from the item's own layout, whose field at
// SAFETY: the offset comes from the item's own layout, whose field at // this name holds this marker's value type by census registration.
// 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::<A::Value<'a>>().read() })
Some(offset) => unsafe { self.item.lanes().get(lane).rec().ptr().add(offset).cast::<A::Value<'a>>().read() },
None => A::default(),
}
} }
} }
@@ -2,8 +2,8 @@ use crate::render_ext::{PaintTarget, RenderExt};
use crate::to_peniko::{BlendModeExt, ToPenikoColor}; use crate::to_peniko::{BlendModeExt, ToPenikoColor};
use core_types::CacheHash; use core_types::CacheHash;
use core_types::attribute::{ use core_types::attribute::{
Background as BackgroundAttr, BlendMode as BlendModeAttr, Clip, ClippingMask, Dimensions, EditorLayerPath, FontSize, LetterSpacing, LetterTilt, LineHeight, Location, MaxHeight, MaxWidth, Opacity, Background as BackgroundAttr, BlendMode as BlendModeAttr, Clip, ClippingMask, Dimensions, EditorLayerPath, EditorTextFrame, FontSize, LetterSpacing, LetterTilt, LineHeight, Location, MaxHeight,
OpacityFill, Transform, MaxWidth, Opacity, OpacityFill, Transform,
}; };
use core_types::blending::BlendMode; use core_types::blending::BlendMode;
use core_types::bounds::BoundingBox; use core_types::bounds::BoundingBox;
@@ -16,7 +16,7 @@ use core_types::math::quad::Quad;
use core_types::render_complexity::RenderComplexity; use core_types::render_complexity::RenderComplexity;
use core_types::transform::Footprint; use core_types::transform::Footprint;
use core_types::uuid::{NodeId, generate_uuid}; 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 dyn_any::DynAny;
use glam::{DAffine2, DMat2, DVec2}; use glam::{DAffine2, DMat2, DVec2};
use graphene_hash::CacheHashWrapper; use graphene_hash::CacheHashWrapper;
@@ -43,7 +43,8 @@ use std::ops::Deref;
use std::sync::{Arc, LazyLock}; use std::sync::{Arc, LazyLock};
use text_nodes::markers::{Font, TextAlign}; use text_nodes::markers::{Font, TextAlign};
use vector_types::gradient::GradientSpreadMethod; 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::*; use vello::*;
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
@@ -1588,7 +1589,7 @@ impl Render for List<Vector> {
} }
// Use click-target override if the item provides one (e.g. 'Text' node's per-glyph bboxes) // Use click-target override if the item provides one (e.g. 'Text' node's per-glyph bboxes)
let click_target_vector = self.attribute::<Vector>(ATTR_EDITOR_CLICK_TARGET, index).unwrap_or(source); let click_target_vector = self.attr::<EditorClickTarget>(index).unwrap_or(source);
let item_relative_transform = item_zero_inverse * transform; let item_relative_transform = item_zero_inverse * transform;
@@ -1617,7 +1618,7 @@ impl Render for List<Vector> {
} }
// Surface `editor:text_frame` for the Text tool's drag cage // Surface `editor:text_frame` for the Text tool's drag cage
if let Some(&frame) = self.attribute::<DAffine2>(ATTR_EDITOR_TEXT_FRAME, index) { if let Some(frame) = self.try_attr::<EditorTextFrame>(index) {
metadata.text_frames.entry(element_id).or_insert(frame); metadata.text_frames.entry(element_id).or_insert(frame);
} }
} }
@@ -1647,7 +1648,7 @@ impl Render for List<Vector> {
let transform: DAffine2 = self.attr::<Transform>(index); let transform: DAffine2 = self.attr::<Transform>(index);
// Use click-target override geometry if the item provides one (e.g. 'Text' node's per-glyph bounding boxes) // Use click-target override geometry if the item provides one (e.g. 'Text' node's per-glyph bounding boxes)
let vector = self.attribute::<Vector>(ATTR_EDITOR_CLICK_TARGET, index).unwrap_or(source); let vector = self.attr::<EditorClickTarget>(index).unwrap_or(source);
extend_targets_from_vector(click_targets, self, index, vector, transform); extend_targets_from_vector(click_targets, self, index, vector, transform);
} }
+2 -2
View File
@@ -198,7 +198,7 @@ impl PathBuilder {
for (entry, widened) in entries.iter().zip(layer_bboxes.iter()) { for (entry, widened) in entries.iter().zip(layer_bboxes.iter()) {
let glyph_local = [widened[0] - entry.1, widened[1] - entry.1]; let glyph_local = [widened[0] - entry.1, widened[1] - entry.1];
let rect = Subpath::new_rectangle(glyph_local[0], glyph_local[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); 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(); 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) // Fill in text frame for items that don't have one yet (single-item mode, where item 0 = identity)