From 3d6b50a99bf69f1b0aba73208277718df8d63131 Mon Sep 17 00:00:00 2001 From: Timon Date: Sat, 18 Jul 2026 13:09:16 +0000 Subject: [PATCH] Remove the string-keyed attribute constants and accessor family --- node-graph/libraries/core-types/src/lib.rs | 5 - node-graph/libraries/core-types/src/list.rs | 314 ++++---------------- 2 files changed, 63 insertions(+), 256 deletions(-) diff --git a/node-graph/libraries/core-types/src/lib.rs b/node-graph/libraries/core-types/src/lib.rs index 32b35976ff..cb8ed30890 100644 --- a/node-graph/libraries/core-types/src/lib.rs +++ b/node-graph/libraries/core-types/src/lib.rs @@ -25,11 +25,6 @@ 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; diff --git a/node-graph/libraries/core-types/src/list.rs b/node-graph/libraries/core-types/src/list.rs index 1247161238..7bee28d79d 100644 --- a/node-graph/libraries/core-types/src/list.rs +++ b/node-graph/libraries/core-types/src/list.rs @@ -8,79 +8,6 @@ use glam::DAffine2; use graphene_hash::CacheHash; use std::fmt::Debug; -// ================================================= -// Standard attribute keys used across the data flow -// ================================================= - -/// Item's `DAffine2` transformation, composed multiplicatively through nested groups. -pub const ATTR_TRANSFORM: &str = "transform"; -/// Item's `BlendMode`, controlling how it composites with content beneath it. -pub const ATTR_BLEND_MODE: &str = "blend_mode"; -/// Item's opacity multiplier (`f64`, implicit default `1.`). -/// Composed multiplicatively through nested groups. Affects content clipped to the item. -pub const ATTR_OPACITY: &str = "opacity"; -/// Item's fill opacity multiplier (`f64`, implicit default `1.`). -/// Like opacity but does not affect content clipped to the item. -pub const ATTR_OPACITY_FILL: &str = "opacity_fill"; -/// `bool` for whether an item inherits the alpha of the content beneath it (clipping mask). -pub const ATTR_CLIPPING_MASK: &str = "clipping_mask"; -/// `NodeIdPath` 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. -pub const ATTR_EDITOR_LAYER_PATH: &str = "editor:layer_path"; -/// `List` snapshot of the upstream content that fed into a destructive merge -/// (Boolean Operation, Rasterize, etc.), so the editor can still surface click targets for -/// the original child layers after their content has been collapsed. -pub const ATTR_EDITOR_MERGED_LAYERS: &str = "editor:merged_layers"; -/// Optional `Vector` that overrides the item's own geometry for click-target generation. -/// Used by the 'Text' node for per-glyph bounding-box rectangles so glyphs are selectable -/// by clicking anywhere within their bounds, not just the filled letterform. -pub const ATTR_EDITOR_CLICK_TARGET: &str = "editor:click_target"; -/// `DAffine2` 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. -pub const ATTR_EDITOR_TEXT_FRAME: &str = "editor:text_frame"; -/// `u64` byte offset where a regex match begins ('Regex Find All', 'Regex Capture' text nodes). -pub const ATTR_START: &str = "start"; -/// `u64` byte offset where a regex match ends ('Regex Find All', 'Regex Capture' text nodes). -pub const ATTR_END: &str = "end"; -/// `String` for a regex named-capture-group's name, or empty for unnamed groups ('Regex Capture' text node). -pub const ATTR_NAME: &str = "name"; -/// `String` for a JSON value's type (`"string"`, `"number"`, `"object"`, etc.) from 'JSON Query All'. -pub const ATTR_TYPE: &str = "type"; -/// Artboard's `DVec2` top-left corner in document coordinates. -pub const ATTR_LOCATION: &str = "location"; -/// Artboard's `DVec2` width and height. -pub const ATTR_DIMENSIONS: &str = "dimensions"; -/// Artboard's `Color` background fill. -pub const ATTR_BACKGROUND: &str = "background"; -/// `bool` for whether an artboard clips content to its bounds. -pub const ATTR_CLIP: &str = "clip"; -/// Gradient's `GradientSpreadMethod` (`Pad`, `Reflect`, or `Repeat`). -pub const ATTR_SPREAD_METHOD: &str = "spread_method"; -/// Gradient's `GradientType` (`Linear` or `Radial`). -pub const ATTR_GRADIENT_TYPE: &str = "gradient_type"; -/// Vector graphics object's filled area paint, of type List where T is any graphic type. -pub const ATTR_FILL: &str = "fill"; -/// Vector graphics object's stroke paint, of type List where T is any graphic type. -pub const ATTR_STROKE: &str = "stroke"; -/// Text item's font size in document-space units (`f64`, implicit default `24.`). -pub const ATTR_FONT_SIZE: &str = "font_size"; -/// Text item's font, as a `Resource` of the loaded font file. -pub const ATTR_FONT: &str = "font"; -/// Text item's line height as a ratio of the font size (`f64`, implicit default `1.2`). -pub const ATTR_LINE_HEIGHT: &str = "line_height"; -/// Text item's extra spacing between letters in document-space units (`f64`, implicit default `0.`). -pub const ATTR_LETTER_SPACING: &str = "letter_spacing"; -/// Text item's maximum line-wrap width in document-space units (`Option`, implicit default `None`). -pub const ATTR_MAX_WIDTH: &str = "max_width"; -/// Text item's maximum block height in document-space units, past which lines are not drawn (`Option`, implicit default `None`). -pub const ATTR_MAX_HEIGHT: &str = "max_height"; -/// Text item's faux-italic letter tilt angle in degrees (`f64`, implicit default `0.`). -pub const ATTR_LETTER_TILT: &str = "letter_tilt"; -/// Text item's `TextAlign` horizontal alignment of lines within the block. -pub const ATTR_TEXT_ALIGN: &str = "text_align"; - // ===================== // TYPE: NodeIdPath // ===================== @@ -505,13 +432,6 @@ impl ListDyn { self.len == 0 } - /// Returns a reference to the attribute value at the given key and item index, downcast to `U`, if present and matching. - pub fn attribute(&self, key: &str, index: usize) -> Option<&U> { - self.attributes - .iter() - .find_map(|(k, attribute)| if k == key { attribute.get_any(index)?.downcast_ref::() } else { None }) - } - /// Returns a reference to the attribute value at the given runtime key and item index, downcast to `U`, if present and matching. /// For keys known at compile time use [`Self::attr`]; this variant is for keys only known at runtime (e.g. the attribute nodes). pub fn attribute_dyn(&self, key: &str, index: usize) -> Option<&U> { @@ -522,7 +442,7 @@ impl ListDyn { /// Returns a reference to the value of the typed attribute at the given item index, if present. pub fn attr(&self, index: usize) -> Option<&A::Value> { - self.attribute(A::name(), index) + self.attribute_dyn(A::name(), index) } } @@ -644,11 +564,6 @@ impl ItemAttributeValues { .find_map(|(existing_key, value)| if existing_key == key { (**value).as_any_mut().downcast_mut::() } else { None }) } - /// Gets a mutable reference to the value, inserting a default if it doesn't exist or has the wrong type. - pub fn get_or_insert_default_mut(&mut self, key: &str) -> &mut T { - self.get_or_insert_with_mut(key, T::default) - } - /// Gets a mutable reference to the value, inserting the provided default if it doesn't exist or has the wrong type. pub fn get_or_insert_with_mut(&mut self, key: &str, default: impl FnOnce() -> T) -> &mut T { let needs_insert = match self.0.iter().position(|(existing_key, _)| existing_key == key) { @@ -1061,9 +976,9 @@ impl List { self.attributes.keys() } - // ============================ - // Attribute-oriented iteration - // ============================ + // ================= + // Element iteration + // ================= /// Returns an iterator over shared references to all element values. pub fn iter_element_values(&self) -> std::slice::Iter<'_, T> { @@ -1075,28 +990,6 @@ impl List { self.element.iter_mut() } - /// Returns an iterator over shared references to the values of a typed attribute, or `None` if the attribute doesn't exist or has the wrong type. - pub fn iter_attribute_values(&self, key: &str) -> Option> { - self.attributes.get_attribute_slice::(key).map(|s| s.iter()) - } - - /// Returns an iterator over mutable references to the values of a typed attribute attribute, or `None` if the attribute doesn't exist or has the wrong type. - pub fn iter_attribute_values_mut(&mut self, key: &str) -> Option> { - self.attributes.get_attribute_slice_mut::(key).map(|s| s.iter_mut()) - } - - /// Returns an iterator that yields cloned attribute values for the given key, falling back to `U::default()` for each item if the attribute is missing or has the wrong type. - pub fn iter_attribute_values_or_default(&self, key: &str) -> impl Iterator + '_ { - let slice = self.attributes.get_attribute_slice::(key); - let len = self.element.len(); - (0..len).map(move |i| slice.map_or_else(U::default, |s| s[i].clone())) - } - - /// Returns a mutable iterator over a typed attribute, creating the attribute with default values if it doesn't exist or has the wrong type. - pub fn iter_attribute_values_mut_or_default(&mut self, key: &str) -> std::slice::IterMut<'_, U> { - self.attributes.get_or_create_attribute_slice_mut::(key).iter_mut() - } - // ====================== // Indexed element access // ====================== @@ -1115,26 +1008,12 @@ impl List { // Indexed attribute access // ======================== - /// Returns a shared reference to the attribute value at the given item index and key, if it exists and can be downcast to the requested type. - pub fn attribute(&self, key: &str, index: usize) -> Option<&U> { + /// Returns a shared reference to the attribute value at the given item index and runtime key, if it exists and can be downcast to the requested type. + /// For keys known at compile time use [`Self::attr`]; this variant is for keys only known at runtime (e.g. the attribute nodes). + pub fn attribute_dyn(&self, key: &str, index: usize) -> Option<&U> { self.attributes.get_value(key, index) } - /// Returns a clone of the attribute value at the given item index and key, or `U::default()` if absent or of a different type. - pub fn attribute_cloned_or_default(&self, key: &str, index: usize) -> U { - self.attributes.get_value::(key, index).cloned().unwrap_or_default() - } - - /// Returns a clone of the attribute value at the given item index and key, or the provided default if absent or of a different type. - pub fn attribute_cloned_or(&self, key: &str, index: usize, default: U) -> U { - self.attributes.get_value::(key, index).cloned().unwrap_or(default) - } - - /// Sets the attribute value at the given item index and key, creating the attribute with defaults if it doesn't exist. - pub fn set_attribute(&mut self, key: impl Into, index: usize, value: U) { - self.attributes.set_value(key, index, value); - } - /// Sets a single type-erased attribute value at the given index, creating the attribute from the value's underlying type if it doesn't exist (padded with defaults to match the list's length). /// Falls back to default if the value's type doesn't match an existing attribute. pub fn set_attribute_value_dyn(&mut self, key: impl Into, index: usize, value: AttributeValueDyn) { @@ -1149,17 +1028,6 @@ impl List { } } - /// Removes the entire attribute for the given key, if present. - pub fn remove_attribute(&mut self, key: &str) { - self.attributes.remove_attribute(key); - } - - /// Runs the given closure on a mutable reference to the attribute value at the given item index, - /// creating the attribute with defaults if it doesn't exist, and returns the closure's result. - pub fn with_attribute_mut_or_default R>(&mut self, key: &str, index: usize, f: F) -> R { - f(self.attributes.get_or_insert_default_value::(key, index)) - } - /// Returns a debug-formatted display string for the attribute at the given item index and key. pub fn attribute_display_value(&self, key: &str, index: usize, overrides: fn(&dyn std::any::Any) -> Option) -> Option { self.attributes.display_value(key, index, overrides) @@ -1170,26 +1038,13 @@ impl List { self.attributes.get_any_value(key, index) } - // ==================== - // Split borrow helpers - // ==================== - - /// Returns disjoint mutable references to the element slice and a typed attribute slice, creating the attribute with defaults if it doesn't exist. - /// This enables simultaneous mutable access to elements and a single attribute without borrowing conflicts. - pub fn element_and_attribute_slices_mut(&mut self, key: &str) -> (&mut [T], &mut [U]) { - let Self { element, attributes } = self; - let attribute_position = attributes.find_or_create_attribute::(key); - let attribute = (*attributes.attributes[attribute_position].1).as_any_mut().downcast_mut::>().unwrap(); - (element.as_mut_slice(), &mut attribute.0) - } - // ================== // Typed key variants // ================== /// Returns a shared reference to the value of the typed attribute at the given item index, if present. pub fn attr(&self, index: usize) -> Option<&A::Value> { - self.attribute(A::name(), index) + self.attributes.get_value(A::name(), index) } /// Returns a clone of the value of the typed attribute at the given item index, or the key's default value if absent. @@ -1204,28 +1059,28 @@ impl List { /// Sets the value of the typed attribute at the given item index, creating the attribute with defaults if it doesn't exist. pub fn set_attr(&mut self, index: usize, value: A::Value) { - self.set_attribute(A::name(), index, value); + self.attributes.set_value(A::name(), index, value); } /// Removes the entire typed attribute, if present. pub fn remove_attr(&mut self) { - self.remove_attribute(A::name()); + self.attributes.remove_attribute(A::name()); } /// Runs the given closure on a mutable reference to the value of the typed attribute at the given item index, /// creating the attribute with defaults if it doesn't exist, and returns the closure's result. pub fn with_attr_mut_or_default R>(&mut self, index: usize, f: F) -> R { - self.with_attribute_mut_or_default(A::name(), index, f) + f(self.attributes.get_or_insert_default_value::(A::name(), index)) } /// Returns an iterator over shared references to the values of the typed attribute, or `None` if it doesn't exist. pub fn iter_attr_values(&self) -> Option> { - self.iter_attribute_values(A::name()) + self.attributes.get_attribute_slice::(A::name()).map(|s| s.iter()) } /// Returns an iterator over mutable references to the values of the typed attribute, or `None` if it doesn't exist. pub fn iter_attr_values_mut(&mut self) -> Option> { - self.iter_attribute_values_mut(A::name()) + self.attributes.get_attribute_slice_mut::(A::name()).map(|s| s.iter_mut()) } /// Returns an iterator that yields cloned values of the typed attribute, falling back to the key's default value for each item if the attribute is missing. @@ -1237,12 +1092,16 @@ impl List { /// Returns a mutable iterator over the typed attribute, creating the attribute with defaults if it doesn't exist. pub fn iter_attr_values_mut_or_default(&mut self) -> std::slice::IterMut<'_, A::Value> { - self.iter_attribute_values_mut_or_default(A::name()) + self.attributes.get_or_create_attribute_slice_mut::(A::name()).iter_mut() } /// Returns disjoint mutable references to the element slice and the typed attribute's slice, creating the attribute with defaults if it doesn't exist. + /// This enables simultaneous mutable access to elements and a single attribute without borrowing conflicts. pub fn element_and_attr_slices_mut(&mut self) -> (&mut [T], &mut [A::Value]) { - self.element_and_attribute_slices_mut(A::name()) + let Self { element, attributes } = self; + let attribute_position = attributes.find_or_create_attribute::(A::name()); + let attribute = (*attributes.attributes[attribute_position].1).as_any_mut().downcast_mut::>().unwrap(); + (element.as_mut_slice(), &mut attribute.0) } // ================== @@ -1271,7 +1130,7 @@ impl BoundingBox for List { fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox { let mut combined_bounds = None; - for (element, item_transform) in self.iter_element_values().zip(self.iter_attribute_values_or_default::(ATTR_TRANSFORM)) { + for (element, item_transform) in self.iter_element_values().zip(self.iter_attr_values_or_default::()) { match element.bounding_box(transform * item_transform, include_stroke) { RenderBoundingBox::None => continue, RenderBoundingBox::Infinite => return RenderBoundingBox::Infinite, @@ -1293,7 +1152,7 @@ impl BoundingBox for List { let mut combined_bounds = None; let mut any_infinite = false; - for (element, item_transform) in self.iter_element_values().zip(self.iter_attribute_values_or_default::(ATTR_TRANSFORM)) { + for (element, item_transform) in self.iter_element_values().zip(self.iter_attr_values_or_default::()) { match element.thumbnail_bounding_box(transform * item_transform, include_stroke) { RenderBoundingBox::None => continue, RenderBoundingBox::Infinite => any_infinite = true, @@ -1365,14 +1224,14 @@ impl PartialEq for List { impl ApplyTransform for List { /// Right-multiplies the modification into each item's transform attribute. fn apply_transform(&mut self, modification: &DAffine2) { - for transform in self.iter_attribute_values_mut_or_default::(ATTR_TRANSFORM) { + for transform in self.iter_attr_values_mut_or_default::() { *transform *= *modification; } } /// Left-multiplies the modification into each item's transform attribute. fn left_apply_transform(&mut self, modification: &DAffine2) { - for transform in self.iter_attribute_values_mut_or_default::(ATTR_TRANSFORM) { + for transform in self.iter_attr_values_mut_or_default::() { *transform = *modification * *transform; } } @@ -1469,55 +1328,9 @@ impl Item { &mut self.attributes } - /// Returns a reference to the attribute value for the given key, if it exists and is of the requested type. - pub fn attribute(&self, key: &str) -> Option<&U> { - self.attributes.get(key) - } - - /// Returns the attribute value for the given key, or the provided default if absent or of a different type. - pub fn attribute_or<'a, U: 'static>(&'a self, key: &str, default: &'a U) -> &'a U { - self.attribute(key).unwrap_or(default) - } - - /// Returns a clone of the attribute value for the given key, or the provided default if absent or of a different type. - pub fn attribute_cloned_or(&self, key: &str, default: U) -> U { - self.attribute(key).cloned().unwrap_or(default) - } - - /// Returns a clone of the attribute value for the given key, or `U`'s default value if absent or of a different type. - pub fn attribute_cloned_or_default(&self, key: &str) -> U { - self.attribute(key).cloned().unwrap_or_default() - } - - /// Returns a mutable reference to the attribute value for the given key, if it exists and is of the requested type. - pub fn attribute_mut(&mut self, key: &str) -> Option<&mut U> { - self.attributes.get_mut(key) - } - - /// Returns a mutable reference to the attribute value for the given key, inserting a default value if absent or of a different type. - pub fn attribute_mut_or_insert_default(&mut self, key: &str) -> &mut U { - self.attributes.get_or_insert_default_mut(key) - } - - /// Sets the attribute value for the given key, replacing any existing entry with the same key. - pub fn set_attribute(&mut self, key: impl Into, value: U) { - self.attributes.insert(key, value); - } - - /// Sets the attribute value for the given key and returns the item, enabling builder-style chaining. - pub fn with_attribute(mut self, key: impl Into, value: U) -> Self { - self.set_attribute(key, value); - self - } - - /// Removes and returns the attribute value for the given key, if it exists and is of the requested type. - pub fn remove_attribute(&mut self, key: &str) -> Option { - self.attributes.remove(key) - } - - // ================== - // Typed key variants - // ================== + // ====================== + // Typed attribute access + // ====================== /// Returns a reference to the value of the typed attribute, if present. pub fn attr(&self) -> Option<&A::Value> { @@ -1587,13 +1400,13 @@ impl From for List { impl ApplyTransform for Item { /// Right-multiplies the modification into the item's transform attribute. fn apply_transform(&mut self, modification: &DAffine2) { - let transform = self.attribute_mut_or_insert_default::(ATTR_TRANSFORM); + let transform = self.attr_mut_or_insert_default::(); *transform *= *modification; } /// Left-multiplies the modification into the item's transform attribute. fn left_apply_transform(&mut self, modification: &DAffine2) { - let transform = self.attribute_mut_or_insert_default::(ATTR_TRANSFORM); + let transform = self.attr_mut_or_insert_default::(); *transform = *modification * *transform; } } @@ -1638,6 +1451,7 @@ impl DoubleEndedIterator for ItemIter { #[cfg(test)] mod tests { use super::*; + use crate::attr; // An item that doesn't set opacity must read as fully opaque even once a sibling introduces the // opacity attribute, otherwise the dense store pads it with f64's `0.` default and it vanishes. @@ -1646,65 +1460,63 @@ mod tests { // Collecting items (the path Boolean Operation takes when merging operands) let mut collected = List::<()>::new(); collected.push(Item::new_from_element(())); - collected.push(Item::new_from_element(()).with_attribute(ATTR_OPACITY, 1_f64)); - assert_eq!(collected.attribute_cloned_or_default::(ATTR_OPACITY, 0), 1.); + collected.push(Item::new_from_element(()).with_attr::(1.)); + assert_eq!(collected.attr::(0), Some(&1.)); // Extending one list with another let mut base = List::<()>::new(); base.push(Item::new_from_element(())); let mut tail = List::<()>::new(); - tail.push(Item::new_from_element(()).with_attribute(ATTR_OPACITY_FILL, 1_f64)); + tail.push(Item::new_from_element(()).with_attr::(1.)); base.extend(tail); - assert_eq!(base.attribute_cloned_or_default::(ATTR_OPACITY_FILL, 0), 1.); + assert_eq!(base.attr::(0), Some(&1.)); // Setting one item's opacity leaves the others opaque, not transparent let mut indexed = List::<()>::new(); indexed.push(Item::new_from_element(())); indexed.push(Item::new_from_element(())); - indexed.set_attribute(ATTR_OPACITY, 1, 0.5_f64); - assert_eq!(indexed.attribute_cloned_or_default::(ATTR_OPACITY, 0), 1.); - assert_eq!(indexed.attribute_cloned_or_default::(ATTR_OPACITY, 1), 0.5); + indexed.set_attr::(1, 0.5); + assert_eq!(indexed.attr::(0), Some(&1.)); + assert_eq!(indexed.attr::(1), Some(&0.5)); - // A non-opacity numeric attribute still falls back to its type default + // A non-opacity numeric attribute still pads with its type default let mut other = List::<()>::new(); other.push(Item::new_from_element(())); - other.push(Item::new_from_element(()).with_attribute(ATTR_START, 5_u64)); - assert_eq!(other.attribute_cloned_or_default::(ATTR_START, 0), 0); + other.push(Item::new_from_element(()).with_attr::(5)); + assert_eq!(other.attr::(0), Some(&0)); } // The typed keys must resolve to the same names as the string constants, and the typed // and string-keyed accessors must hit the same storage. #[test] fn typed_attribute_keys() { - use crate::attr; + assert_eq!(attr::Transform::name(), "transform"); + assert_eq!(attr::BlendMode::name(), "blend_mode"); + assert_eq!(attr::Opacity::name(), "opacity"); + assert_eq!(attr::OpacityFill::name(), "opacity_fill"); + assert_eq!(attr::ClippingMask::name(), "clipping_mask"); + assert_eq!(attr::editor::LayerPath::name(), "editor:layer_path"); + assert_eq!(attr::editor::TextFrame::name(), "editor:text_frame"); + assert_eq!(attr::Start::name(), "start"); + assert_eq!(attr::End::name(), "end"); + assert_eq!(attr::Name::name(), "name"); + assert_eq!(attr::Type::name(), "type"); + assert_eq!(attr::Location::name(), "location"); + assert_eq!(attr::Dimensions::name(), "dimensions"); + assert_eq!(attr::Background::name(), "background"); + assert_eq!(attr::Clip::name(), "clip"); + assert_eq!(attr::FontSize::name(), "font_size"); + assert_eq!(attr::LineHeight::name(), "line_height"); + assert_eq!(attr::LetterSpacing::name(), "letter_spacing"); + assert_eq!(attr::MaxWidth::name(), "max_width"); + assert_eq!(attr::MaxHeight::name(), "max_height"); + assert_eq!(attr::LetterTilt::name(), "letter_tilt"); - assert_eq!(attr::Transform::name(), ATTR_TRANSFORM); - assert_eq!(attr::BlendMode::name(), ATTR_BLEND_MODE); - assert_eq!(attr::Opacity::name(), ATTR_OPACITY); - assert_eq!(attr::OpacityFill::name(), ATTR_OPACITY_FILL); - assert_eq!(attr::ClippingMask::name(), ATTR_CLIPPING_MASK); - assert_eq!(attr::editor::LayerPath::name(), ATTR_EDITOR_LAYER_PATH); - assert_eq!(attr::editor::TextFrame::name(), ATTR_EDITOR_TEXT_FRAME); - assert_eq!(attr::Start::name(), ATTR_START); - assert_eq!(attr::End::name(), ATTR_END); - assert_eq!(attr::Name::name(), ATTR_NAME); - assert_eq!(attr::Type::name(), ATTR_TYPE); - assert_eq!(attr::Location::name(), ATTR_LOCATION); - assert_eq!(attr::Dimensions::name(), ATTR_DIMENSIONS); - assert_eq!(attr::Background::name(), ATTR_BACKGROUND); - assert_eq!(attr::Clip::name(), ATTR_CLIP); - assert_eq!(attr::FontSize::name(), ATTR_FONT_SIZE); - assert_eq!(attr::LineHeight::name(), ATTR_LINE_HEIGHT); - assert_eq!(attr::LetterSpacing::name(), ATTR_LETTER_SPACING); - assert_eq!(attr::MaxWidth::name(), ATTR_MAX_WIDTH); - assert_eq!(attr::MaxHeight::name(), ATTR_MAX_HEIGHT); - assert_eq!(attr::LetterTilt::name(), ATTR_LETTER_TILT); - - // Typed writes are visible through string reads and vice versa + // Typed writes are visible through dynamic string reads and vice versa let mut item = Item::new_from_element(()); item.set_attr::(0.5); - assert_eq!(item.attribute::(ATTR_OPACITY), Some(&0.5)); - item.set_attribute(ATTR_START, 5_u64); + assert_eq!(item.attributes().get::("opacity"), Some(&0.5)); + item.attributes_mut().insert("start", 5_u64); assert_eq!(item.attr::(), Some(&5)); // A missing attribute reads as the key's declared default