mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 06:38:03 +08:00
Remove the string-keyed attribute constants and accessor family
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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<Graphic>` 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<T> where T is any graphic type.
|
||||
pub const ATTR_FILL: &str = "fill";
|
||||
/// Vector graphics object's stroke paint, of type List<T> 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<f64>`, 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<f64>`, 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<U: 'static>(&self, key: &str, index: usize) -> Option<&U> {
|
||||
self.attributes
|
||||
.iter()
|
||||
.find_map(|(k, attribute)| if k == key { attribute.get_any(index)?.downcast_ref::<U>() } 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<U: 'static>(&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<A: 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::<T>() } 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<T: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static>(&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<T: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static>(&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<T> List<T> {
|
||||
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<T> List<T> {
|
||||
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<U: 'static>(&self, key: &str) -> Option<std::slice::Iter<'_, U>> {
|
||||
self.attributes.get_attribute_slice::<U>(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<U: 'static>(&mut self, key: &str) -> Option<std::slice::IterMut<'_, U>> {
|
||||
self.attributes.get_attribute_slice_mut::<U>(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<U: Clone + Default + 'static>(&self, key: &str) -> impl Iterator<Item = U> + '_ {
|
||||
let slice = self.attributes.get_attribute_slice::<U>(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<U: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static>(&mut self, key: &str) -> std::slice::IterMut<'_, U> {
|
||||
self.attributes.get_or_create_attribute_slice_mut::<U>(key).iter_mut()
|
||||
}
|
||||
|
||||
// ======================
|
||||
// Indexed element access
|
||||
// ======================
|
||||
@@ -1115,26 +1008,12 @@ impl<T> List<T> {
|
||||
// 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<U: 'static>(&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<U: 'static>(&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<U: Clone + Default + 'static>(&self, key: &str, index: usize) -> U {
|
||||
self.attributes.get_value::<U>(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<U: Clone + 'static>(&self, key: &str, index: usize, default: U) -> U {
|
||||
self.attributes.get_value::<U>(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<U: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static>(&mut self, key: impl Into<String>, 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<String>, index: usize, value: AttributeValueDyn) {
|
||||
@@ -1149,17 +1028,6 @@ impl<T> List<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<U: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static, R, F: FnOnce(&mut U) -> R>(&mut self, key: &str, index: usize, f: F) -> R {
|
||||
f(self.attributes.get_or_insert_default_value::<U>(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<String>) -> Option<String> {
|
||||
self.attributes.display_value(key, index, overrides)
|
||||
@@ -1170,26 +1038,13 @@ impl<T> List<T> {
|
||||
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<U: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static>(&mut self, key: &str) -> (&mut [T], &mut [U]) {
|
||||
let Self { element, attributes } = self;
|
||||
let attribute_position = attributes.find_or_create_attribute::<U>(key);
|
||||
let attribute = (*attributes.attributes[attribute_position].1).as_any_mut().downcast_mut::<Attribute<U>>().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<A: 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<T> List<T> {
|
||||
|
||||
/// 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<A: 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<A: 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<A: Attr, R, F: FnOnce(&mut A::Value) -> 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::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<A: Attr>(&self) -> Option<std::slice::Iter<'_, A::Value>> {
|
||||
self.iter_attribute_values(A::name())
|
||||
self.attributes.get_attribute_slice::<A::Value>(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<A: Attr>(&mut self) -> Option<std::slice::IterMut<'_, A::Value>> {
|
||||
self.iter_attribute_values_mut(A::name())
|
||||
self.attributes.get_attribute_slice_mut::<A::Value>(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<T> List<T> {
|
||||
|
||||
/// 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<A: Attr>(&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::Value>(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<A: Attr>(&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::Value>(A::name());
|
||||
let attribute = (*attributes.attributes[attribute_position].1).as_any_mut().downcast_mut::<Attribute<A::Value>>().unwrap();
|
||||
(element.as_mut_slice(), &mut attribute.0)
|
||||
}
|
||||
|
||||
// ==================
|
||||
@@ -1271,7 +1130,7 @@ impl<T: BoundingBox> BoundingBox for List<T> {
|
||||
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::<DAffine2>(ATTR_TRANSFORM)) {
|
||||
for (element, item_transform) in self.iter_element_values().zip(self.iter_attr_values_or_default::<crate::attr::Transform>()) {
|
||||
match element.bounding_box(transform * item_transform, include_stroke) {
|
||||
RenderBoundingBox::None => continue,
|
||||
RenderBoundingBox::Infinite => return RenderBoundingBox::Infinite,
|
||||
@@ -1293,7 +1152,7 @@ impl<T: BoundingBox> BoundingBox for List<T> {
|
||||
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::<DAffine2>(ATTR_TRANSFORM)) {
|
||||
for (element, item_transform) in self.iter_element_values().zip(self.iter_attr_values_or_default::<crate::attr::Transform>()) {
|
||||
match element.thumbnail_bounding_box(transform * item_transform, include_stroke) {
|
||||
RenderBoundingBox::None => continue,
|
||||
RenderBoundingBox::Infinite => any_infinite = true,
|
||||
@@ -1365,14 +1224,14 @@ impl<T: PartialEq> PartialEq for List<T> {
|
||||
impl<T> ApplyTransform for List<T> {
|
||||
/// 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::<DAffine2>(ATTR_TRANSFORM) {
|
||||
for transform in self.iter_attr_values_mut_or_default::<crate::attr::Transform>() {
|
||||
*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::<DAffine2>(ATTR_TRANSFORM) {
|
||||
for transform in self.iter_attr_values_mut_or_default::<crate::attr::Transform>() {
|
||||
*transform = *modification * *transform;
|
||||
}
|
||||
}
|
||||
@@ -1469,55 +1328,9 @@ impl<T> Item<T> {
|
||||
&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<U: 'static>(&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<U: Clone + 'static>(&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<U: Clone + Default + 'static>(&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<U: 'static>(&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<U: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static>(&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<U: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static>(&mut self, key: impl Into<String>, 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<U: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static>(mut self, key: impl Into<String>, 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<U: 'static>(&mut self, key: &str) -> Option<U> {
|
||||
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<A: Attr>(&self) -> Option<&A::Value> {
|
||||
@@ -1587,13 +1400,13 @@ impl<T> From<T> for List<T> {
|
||||
impl<T> ApplyTransform for Item<T> {
|
||||
/// 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::<DAffine2>(ATTR_TRANSFORM);
|
||||
let transform = self.attr_mut_or_insert_default::<crate::attr::Transform>();
|
||||
*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::<DAffine2>(ATTR_TRANSFORM);
|
||||
let transform = self.attr_mut_or_insert_default::<crate::attr::Transform>();
|
||||
*transform = *modification * *transform;
|
||||
}
|
||||
}
|
||||
@@ -1638,6 +1451,7 @@ impl<T> DoubleEndedIterator for ItemIter<T> {
|
||||
#[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::<f64>(ATTR_OPACITY, 0), 1.);
|
||||
collected.push(Item::new_from_element(()).with_attr::<attr::Opacity>(1.));
|
||||
assert_eq!(collected.attr::<attr::Opacity>(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::<attr::OpacityFill>(1.));
|
||||
base.extend(tail);
|
||||
assert_eq!(base.attribute_cloned_or_default::<f64>(ATTR_OPACITY_FILL, 0), 1.);
|
||||
assert_eq!(base.attr::<attr::OpacityFill>(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::<f64>(ATTR_OPACITY, 0), 1.);
|
||||
assert_eq!(indexed.attribute_cloned_or_default::<f64>(ATTR_OPACITY, 1), 0.5);
|
||||
indexed.set_attr::<attr::Opacity>(1, 0.5);
|
||||
assert_eq!(indexed.attr::<attr::Opacity>(0), Some(&1.));
|
||||
assert_eq!(indexed.attr::<attr::Opacity>(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::<u64>(ATTR_START, 0), 0);
|
||||
other.push(Item::new_from_element(()).with_attr::<attr::Start>(5));
|
||||
assert_eq!(other.attr::<attr::Start>(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::<attr::Opacity>(0.5);
|
||||
assert_eq!(item.attribute::<f64>(ATTR_OPACITY), Some(&0.5));
|
||||
item.set_attribute(ATTR_START, 5_u64);
|
||||
assert_eq!(item.attributes().get::<f64>("opacity"), Some(&0.5));
|
||||
item.attributes_mut().insert("start", 5_u64);
|
||||
assert_eq!(item.attr::<attr::Start>(), Some(&5));
|
||||
|
||||
// A missing attribute reads as the key's declared default
|
||||
|
||||
Reference in New Issue
Block a user