mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 07:18:04 +08:00
Migrate attribute call sites from string keys to typed keys
This commit is contained in:
@@ -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;
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -512,6 +512,14 @@ impl ListDyn {
|
||||
.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> {
|
||||
self.attributes
|
||||
.iter()
|
||||
.find_map(|(k, attribute)| if k == key { attribute.get_any(index)?.downcast_ref::<U>() } else { None })
|
||||
}
|
||||
|
||||
/// 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)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use core_types::Color;
|
||||
use core_types::attr::{self, Attr};
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use core_types::graphene_hash::CacheHash;
|
||||
use core_types::list::{ATTR_FILL, ATTR_STROKE, Item, ItemAttributeValues, List, NodeIdPath};
|
||||
use core_types::list::{Item, ItemAttributeValues, List};
|
||||
use core_types::ops::FromAnchorPosition;
|
||||
use core_types::render_complexity::RenderComplexity;
|
||||
use core_types::{ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color};
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use raster_types::{CPU, GPU, Raster};
|
||||
@@ -119,32 +120,32 @@ fn flatten_graphic_list<T>(content: List<Graphic>, extract_variant: fn(Graphic)
|
||||
// Whether the parent carries each attribute: a structural fact (column presence), never a value comparison.
|
||||
// Flattening composes a parent attribute onto its children only when the parent has it,
|
||||
// so an absent parent attribute never invents a column the children didn't already have.
|
||||
let parent_has_transform = current_graphic_item.attribute::<DAffine2>(ATTR_TRANSFORM).is_some();
|
||||
let parent_has_opacity = current_graphic_item.attribute::<f64>(ATTR_OPACITY).is_some();
|
||||
let parent_has_fill = current_graphic_item.attribute::<f64>(ATTR_OPACITY_FILL).is_some();
|
||||
let parent_has_layer_path = current_graphic_item.attribute::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH).is_some();
|
||||
let parent_has_transform = current_graphic_item.attr::<attr::Transform>().is_some();
|
||||
let parent_has_opacity = current_graphic_item.attr::<attr::Opacity>().is_some();
|
||||
let parent_has_fill = current_graphic_item.attr::<attr::OpacityFill>().is_some();
|
||||
let parent_has_layer_path = current_graphic_item.attr::<attr::editor::LayerPath>().is_some();
|
||||
|
||||
let layer_path: NodeIdPath = current_graphic_item.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
|
||||
let current_transform: DAffine2 = current_graphic_item.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let current_opacity: f64 = current_graphic_item.attribute_cloned_or(ATTR_OPACITY, 1.);
|
||||
let current_fill: f64 = current_graphic_item.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
|
||||
let layer_path = current_graphic_item.attr_cloned_or_default::<attr::editor::LayerPath>();
|
||||
let current_transform = current_graphic_item.attr_cloned_or_default::<attr::Transform>();
|
||||
let current_opacity = current_graphic_item.attr_cloned_or_default::<attr::Opacity>();
|
||||
let current_fill = current_graphic_item.attr_cloned_or_default::<attr::OpacityFill>();
|
||||
|
||||
match current_graphic_item.into_element() {
|
||||
// Compose the parent's transform/opacity/fill onto each child, but only for attributes the parent carries.
|
||||
// A child lacking one is padded with the composition identity (`1.` for opacity/fill, identity for transform), so composing through it is a no-op.
|
||||
Graphic::Graphic(mut sub_list) => {
|
||||
if parent_has_transform {
|
||||
for v in sub_list.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
for v in sub_list.iter_attr_values_mut_or_default::<attr::Transform>() {
|
||||
*v = current_transform * *v;
|
||||
}
|
||||
}
|
||||
if parent_has_opacity {
|
||||
for v in sub_list.iter_attribute_values_mut_or_default::<f64>(ATTR_OPACITY) {
|
||||
for v in sub_list.iter_attr_values_mut_or_default::<attr::Opacity>() {
|
||||
*v *= current_opacity;
|
||||
}
|
||||
}
|
||||
if parent_has_fill {
|
||||
for v in sub_list.iter_attribute_values_mut_or_default::<f64>(ATTR_OPACITY_FILL) {
|
||||
for v in sub_list.iter_attr_values_mut_or_default::<attr::OpacityFill>() {
|
||||
*v *= current_fill;
|
||||
}
|
||||
}
|
||||
@@ -155,22 +156,22 @@ fn flatten_graphic_list<T>(content: List<Graphic>, extract_variant: fn(Graphic)
|
||||
other => {
|
||||
if let Some(typed_list) = extract_variant(other) {
|
||||
for mut item in typed_list.into_iter() {
|
||||
// Each `|| item.attribute(...)` keeps an attribute the item itself carries
|
||||
// Each `|| item.attr::<...>()` keeps an attribute the item itself carries
|
||||
// (recomposed with the parent's identity value) even when the parent lacks it
|
||||
if parent_has_transform || item.attribute::<DAffine2>(ATTR_TRANSFORM).is_some() {
|
||||
let item_transform: DAffine2 = item.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
item.set_attribute(ATTR_TRANSFORM, current_transform * item_transform);
|
||||
if parent_has_transform || item.attr::<attr::Transform>().is_some() {
|
||||
let item_transform = item.attr_cloned_or_default::<attr::Transform>();
|
||||
item.set_attr::<attr::Transform>(current_transform * item_transform);
|
||||
}
|
||||
if parent_has_opacity || item.attribute::<f64>(ATTR_OPACITY).is_some() {
|
||||
let item_opacity: f64 = item.attribute_cloned_or(ATTR_OPACITY, 1.);
|
||||
item.set_attribute(ATTR_OPACITY, current_opacity * item_opacity);
|
||||
if parent_has_opacity || item.attr::<attr::Opacity>().is_some() {
|
||||
let item_opacity = item.attr_cloned_or_default::<attr::Opacity>();
|
||||
item.set_attr::<attr::Opacity>(current_opacity * item_opacity);
|
||||
}
|
||||
if parent_has_fill || item.attribute::<f64>(ATTR_OPACITY_FILL).is_some() {
|
||||
let item_fill: f64 = item.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
|
||||
item.set_attribute(ATTR_OPACITY_FILL, current_fill * item_fill);
|
||||
if parent_has_fill || item.attr::<attr::OpacityFill>().is_some() {
|
||||
let item_fill = item.attr_cloned_or_default::<attr::OpacityFill>();
|
||||
item.set_attr::<attr::OpacityFill>(current_fill * item_fill);
|
||||
}
|
||||
if parent_has_layer_path {
|
||||
item.set_attribute(ATTR_EDITOR_LAYER_PATH, layer_path.clone());
|
||||
item.set_attr::<attr::editor::LayerPath>(layer_path.clone());
|
||||
}
|
||||
|
||||
output.push(item);
|
||||
@@ -192,9 +193,9 @@ pub fn is_paint_present(graphic_list: &List<Graphic>) -> bool {
|
||||
graphic_list.element(0).is_some_and(|graphic| !graphic.is_empty())
|
||||
}
|
||||
|
||||
/// Look up the paint graphics stored under attribute for a vector item, in the canonical `List<Graphic>` form.
|
||||
pub fn graphic_list_at<'a>(list: &'a List<Vector>, index: usize, attribute: &str) -> Option<Cow<'a, List<Graphic>>> {
|
||||
list.attribute::<List<Graphic>>(attribute, index)
|
||||
/// Look up the paint graphics stored under the typed attribute for a vector item, in the canonical `List<Graphic>` form.
|
||||
pub fn graphic_list_at<A: Attr<Value = List<Graphic>>>(list: &List<Vector>, index: usize) -> Option<Cow<'_, List<Graphic>>> {
|
||||
list.attr::<A>(index)
|
||||
.map(Cow::Borrowed)
|
||||
// Treat a blank paint attribute as absent so an empty attribute doesn't count as painted
|
||||
.filter(|graphic_list| is_paint_present(graphic_list))
|
||||
@@ -202,25 +203,25 @@ pub fn graphic_list_at<'a>(list: &'a List<Vector>, index: usize, attribute: &str
|
||||
|
||||
/// Whether the item carries a non-blank canonical `List<Graphic>` paint attribute,
|
||||
/// checked by borrowing without cloning the renderable list.
|
||||
pub fn has_paint_at(list: &List<Vector>, index: usize, attribute: &str) -> bool {
|
||||
list.attribute::<List<Graphic>>(attribute, index).is_some_and(is_paint_present)
|
||||
pub fn has_paint_at<A: Attr<Value = List<Graphic>>>(list: &List<Vector>, index: usize) -> bool {
|
||||
list.attr::<A>(index).is_some_and(is_paint_present)
|
||||
}
|
||||
|
||||
/// Stores a paint attribute in its canonical `List<Graphic>` form, the only representation paint readers accept.
|
||||
pub fn set_paint_attribute(attributes: &mut ItemAttributeValues, key: &str, paint: impl IntoGraphicList) {
|
||||
attributes.insert(key, paint.into_graphic_list());
|
||||
pub fn set_paint_attribute<A: Attr<Value = List<Graphic>>>(attributes: &mut ItemAttributeValues, paint: impl IntoGraphicList) {
|
||||
attributes.set_attr::<A>(paint.into_graphic_list());
|
||||
}
|
||||
|
||||
/// Stores a paint attribute at a list index in its canonical `List<Graphic>` form, the only representation paint readers accept.
|
||||
pub fn set_paint_attribute_at<T>(list: &mut List<T>, index: usize, key: &str, paint: impl IntoGraphicList) {
|
||||
list.set_attribute(key, index, paint.into_graphic_list());
|
||||
pub fn set_paint_attribute_at<A: Attr<Value = List<Graphic>>, T>(list: &mut List<T>, index: usize, paint: impl IntoGraphicList) {
|
||||
list.set_attr::<A>(index, paint.into_graphic_list());
|
||||
}
|
||||
|
||||
/// Bake the provided transform into the per-item transforms of the paint graphics stored under the
|
||||
/// canonical `List<Graphic>` fill and stroke attributes.
|
||||
pub fn bake_paint_transforms(attributes: &mut ItemAttributeValues, transform: DAffine2) {
|
||||
fn bake_list_transform<T>(list: &mut List<T>, transform: DAffine2) {
|
||||
for item_transform in list.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
for item_transform in list.iter_attr_values_mut_or_default::<attr::Transform>() {
|
||||
*item_transform = transform * *item_transform;
|
||||
}
|
||||
}
|
||||
@@ -240,7 +241,7 @@ pub fn bake_paint_transforms(attributes: &mut ItemAttributeValues, transform: DA
|
||||
}
|
||||
}
|
||||
|
||||
for paint_key in [ATTR_FILL, ATTR_STROKE] {
|
||||
for paint_key in [crate::attr::Fill::name(), crate::attr::Stroke::name()] {
|
||||
if let Some(graphics) = attributes.get_mut::<List<Graphic>>(paint_key) {
|
||||
bake_graphic_paint_transform(graphics, transform);
|
||||
}
|
||||
@@ -306,10 +307,10 @@ impl IntoGraphicList for List<Vector> {
|
||||
fn into_graphic_list(self) -> List<Graphic> {
|
||||
// Propagate the `editor:layer_path` column (if present) from item 0 onto the wrapper Graphic item so a
|
||||
// subsequent `flatten_graphic_list` doesn't drop the inner Vector's layer stamp
|
||||
let layer_path = self.attribute::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, 0).cloned();
|
||||
let layer_path = self.attr::<attr::editor::LayerPath>(0).cloned();
|
||||
let mut graphic_list = List::new_from_element(Graphic::Vector(self));
|
||||
if let Some(layer_path) = layer_path {
|
||||
graphic_list.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer_path);
|
||||
graphic_list.set_attr::<attr::editor::LayerPath>(0, layer_path);
|
||||
}
|
||||
graphic_list
|
||||
}
|
||||
@@ -341,10 +342,10 @@ impl IntoGraphicList for List<Gradient> {
|
||||
|
||||
impl IntoGraphicList for List<String> {
|
||||
fn into_graphic_list(self) -> List<Graphic> {
|
||||
let layer_path = self.attribute::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, 0).cloned();
|
||||
let layer_path = self.attr::<attr::editor::LayerPath>(0).cloned();
|
||||
let mut graphic_list = List::new_from_element(Graphic::Text(self));
|
||||
if let Some(layer_path) = layer_path {
|
||||
graphic_list.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer_path);
|
||||
graphic_list.set_attr::<attr::editor::LayerPath>(0, layer_path);
|
||||
}
|
||||
graphic_list
|
||||
}
|
||||
@@ -416,7 +417,7 @@ impl Graphic {
|
||||
|
||||
pub fn had_clip_enabled(&self) -> bool {
|
||||
fn all_clipped<T>(list: &List<T>) -> bool {
|
||||
list.iter_attribute_values_or_default::<bool>(ATTR_CLIPPING_MASK).all(|clip| clip)
|
||||
list.iter_attr_values_or_default::<attr::ClippingMask>().all(|clip| clip)
|
||||
}
|
||||
|
||||
match self {
|
||||
@@ -435,12 +436,12 @@ impl Graphic {
|
||||
match self {
|
||||
Graphic::Vector(vector) => (0..vector.len()).all(|index| {
|
||||
let Some(element) = vector.element(index) else { return false };
|
||||
let opacity: f64 = vector.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity = vector.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
|
||||
let fill_opaque_or_absent = graphic_list_at(vector, index, ATTR_FILL).is_none_or(|graphic_list| graphic_list.element(0).is_none_or(|graphic| graphic.is_opaque()));
|
||||
let fill_opaque_or_absent = graphic_list_at::<crate::attr::Fill>(vector, index).is_none_or(|graphic_list| graphic_list.element(0).is_none_or(|graphic| graphic.is_opaque()));
|
||||
|
||||
let stroke_invisible_or_transparent = element.stroke.as_ref().is_none_or(|stroke| !stroke.has_renderable_stroke())
|
||||
|| graphic_list_at(vector, index, ATTR_STROKE).is_none_or(|graphic_list| graphic_list.element(0).is_none_or(|graphic| graphic.is_fully_transparent()));
|
||||
|| graphic_list_at::<crate::attr::Stroke>(vector, index).is_none_or(|graphic_list| graphic_list.element(0).is_none_or(|graphic| graphic.is_fully_transparent()));
|
||||
|
||||
opacity > 1. - f64::EPSILON && fill_opaque_or_absent && stroke_invisible_or_transparent
|
||||
}),
|
||||
@@ -453,15 +454,17 @@ impl Graphic {
|
||||
Graphic::None => false,
|
||||
Graphic::Graphic(list) => !list.is_empty() && list.iter_element_values().all(Graphic::is_opaque),
|
||||
Graphic::Vector(list) => {
|
||||
let is_paint_opaque_at = |key: &str, index: usize| graphic_list_at(list, index, key).is_some_and(|graphic_list| graphic_list.element(0).is_some_and(|graphic| graphic.is_opaque()));
|
||||
fn is_paint_opaque_at<A: Attr<Value = List<Graphic>>>(list: &List<Vector>, index: usize) -> bool {
|
||||
graphic_list_at::<A>(list, index).is_some_and(|graphic_list| graphic_list.element(0).is_some_and(|graphic| graphic.is_opaque()))
|
||||
}
|
||||
|
||||
!list.is_empty()
|
||||
&& (0..list.len()).all(|i| {
|
||||
let Some(vector) = list.element(i) else { return false };
|
||||
let opacity: f64 = list.attribute_cloned_or(ATTR_OPACITY, i, 1.);
|
||||
let opacity_fill: f64 = list.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
|
||||
let fill_opaque = opacity_fill >= 1. - f64::EPSILON && is_paint_opaque_at(ATTR_FILL, i);
|
||||
let stroke_opaque_or_invisible = vector.stroke.as_ref().is_none_or(|stroke| !stroke.has_renderable_stroke()) || is_paint_opaque_at(ATTR_STROKE, i);
|
||||
let opacity = list.attr_cloned_or_default::<attr::Opacity>(i);
|
||||
let opacity_fill = list.attr_cloned_or_default::<attr::OpacityFill>(i);
|
||||
let fill_opaque = opacity_fill >= 1. - f64::EPSILON && is_paint_opaque_at::<crate::attr::Fill>(list, i);
|
||||
let stroke_opaque_or_invisible = vector.stroke.as_ref().is_none_or(|stroke| !stroke.has_renderable_stroke()) || is_paint_opaque_at::<crate::attr::Stroke>(list, i);
|
||||
opacity >= 1. - f64::EPSILON && fill_opaque && stroke_opaque_or_invisible
|
||||
})
|
||||
}
|
||||
@@ -477,16 +480,17 @@ impl Graphic {
|
||||
Graphic::Graphic(list) => list.iter_element_values().all(Graphic::is_fully_transparent),
|
||||
Graphic::Vector(list) => (0..list.len()).all(|i| {
|
||||
let Some(vector) = list.element(i) else { return false };
|
||||
let is_paint_fully_transparent_at =
|
||||
|key: &str, index: usize| graphic_list_at(list, index, key).is_none_or(|graphic_list| graphic_list.element(0).is_none_or(|graphic| graphic.is_fully_transparent()));
|
||||
fn is_paint_fully_transparent_at<A: Attr<Value = List<Graphic>>>(list: &List<Vector>, index: usize) -> bool {
|
||||
graphic_list_at::<A>(list, index).is_none_or(|graphic_list| graphic_list.element(0).is_none_or(|graphic| graphic.is_fully_transparent()))
|
||||
}
|
||||
|
||||
let opacity: f64 = list.attribute_cloned_or(ATTR_OPACITY, i, 1.);
|
||||
let opacity = list.attr_cloned_or_default::<attr::Opacity>(i);
|
||||
if opacity <= f64::EPSILON {
|
||||
return true;
|
||||
}
|
||||
let opacity_fill: f64 = list.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
|
||||
let fill_invisible = opacity_fill <= f64::EPSILON || is_paint_fully_transparent_at(ATTR_FILL, i);
|
||||
let stroke_invisible = vector.stroke.as_ref().is_none_or(|stroke| !stroke.has_renderable_stroke()) || is_paint_fully_transparent_at(ATTR_STROKE, i);
|
||||
let opacity_fill = list.attr_cloned_or_default::<attr::OpacityFill>(i);
|
||||
let fill_invisible = opacity_fill <= f64::EPSILON || is_paint_fully_transparent_at::<crate::attr::Fill>(list, i);
|
||||
let stroke_invisible = vector.stroke.as_ref().is_none_or(|stroke| !stroke.has_renderable_stroke()) || is_paint_fully_transparent_at::<crate::attr::Stroke>(list, i);
|
||||
fill_invisible && stroke_invisible
|
||||
}),
|
||||
Graphic::Color(list) => list.iter_element_values().all(|color| color.a() == 0.),
|
||||
@@ -644,7 +648,7 @@ mod tests {
|
||||
fn flatten_does_not_invent_attributes() {
|
||||
let graphics = List::new_from_element(vector_graphic());
|
||||
let flattened: List<Vector> = graphics.into_flattened_list();
|
||||
for key in [ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, ATTR_EDITOR_LAYER_PATH] {
|
||||
for key in [attr::Opacity::name(), attr::OpacityFill::name(), attr::Transform::name(), attr::editor::LayerPath::name()] {
|
||||
assert!(!flattened.attribute_keys().any(|k| k == key), "flatten invented the `{key}` attribute");
|
||||
}
|
||||
}
|
||||
@@ -653,20 +657,19 @@ mod tests {
|
||||
#[test]
|
||||
fn flatten_propagates_present_attributes() {
|
||||
let mut graphics = List::new_from_element(vector_graphic());
|
||||
graphics.set_attribute(ATTR_OPACITY, 0, 0.5_f64);
|
||||
graphics.set_attr::<attr::Opacity>(0, 0.5);
|
||||
let flattened: List<Vector> = graphics.into_flattened_list();
|
||||
assert_eq!(flattened.attribute_cloned_or_default::<f64>(ATTR_OPACITY, 0), 0.5);
|
||||
assert_eq!(flattened.attr_cloned_or_default::<attr::Opacity>(0), 0.5);
|
||||
|
||||
let mut group = List::new_from_element(Graphic::Graphic(List::new_from_element(vector_graphic())));
|
||||
group.set_attribute(ATTR_OPACITY, 0, 0.5_f64);
|
||||
group.set_attr::<attr::Opacity>(0, 0.5);
|
||||
let flattened: List<Vector> = group.into_flattened_list();
|
||||
assert_eq!(flattened.attribute_cloned_or_default::<f64>(ATTR_OPACITY, 0), 0.5);
|
||||
assert_eq!(flattened.attr_cloned_or_default::<attr::Opacity>(0), 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod graphic_is_opaque_tests {
|
||||
use core_types::ATTR_SPREAD_METHOD;
|
||||
use vector_types::{GradientSpreadMethod, GradientStop};
|
||||
|
||||
use super::*;
|
||||
@@ -678,7 +681,7 @@ mod graphic_is_opaque_tests {
|
||||
|
||||
fn gradient_graphic(gradient: Gradient) -> Graphic {
|
||||
let mut gradient_list = List::new_from_element(gradient);
|
||||
gradient_list.set_attribute(ATTR_SPREAD_METHOD, 0, GradientSpreadMethod::Pad);
|
||||
gradient_list.set_attr::<vector_types::attr::SpreadMethod>(0, GradientSpreadMethod::Pad);
|
||||
Graphic::Gradient(gradient_list)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{Render, RenderSvgSegmentList, SvgRender};
|
||||
use core_types::color::SRGBA8;
|
||||
use core_types::list::List;
|
||||
use core_types::uuid::generate_uuid;
|
||||
use core_types::{ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD, ATTR_TRANSFORM, Color};
|
||||
use core_types::{Color, attr};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphic_types::Graphic;
|
||||
use graphic_types::vector_types::gradient::GradientType;
|
||||
@@ -93,9 +93,9 @@ impl RenderExt for List<Gradient> {
|
||||
let mut stop = String::new();
|
||||
|
||||
let Some(stops) = self.element(0) else { return 0 };
|
||||
let gradient_type: GradientType = self.attribute_cloned_or_default(ATTR_GRADIENT_TYPE, 0);
|
||||
let local_gradient_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
let spread_method: GradientSpreadMethod = self.attribute_cloned_or_default(ATTR_SPREAD_METHOD, 0);
|
||||
let gradient_type = self.attr_cloned_or_default::<vector_types::attr::GradientType>(0);
|
||||
let local_gradient_transform = self.attr_cloned_or_default::<attr::Transform>(0);
|
||||
let spread_method = self.attr_cloned_or_default::<vector_types::attr::SpreadMethod>(0);
|
||||
|
||||
for (position, color, original_midpoint) in stops.interpolated_samples() {
|
||||
stop.push_str("<stop");
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
use crate::render_ext::{PaintTarget, RenderExt};
|
||||
use crate::to_peniko::{BlendModeExt, ToPenikoColor};
|
||||
use core_types::CacheHash;
|
||||
use core_types::attr;
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::bounds::BoundingBox;
|
||||
use core_types::bounds::RenderBoundingBox;
|
||||
use core_types::color::Color;
|
||||
use core_types::color::SRGBA8;
|
||||
use core_types::consts::DEFAULT_FONT_SIZE;
|
||||
use core_types::list::{ATTR_FILL, ATTR_STROKE, Item, List, NodeIdPath};
|
||||
use core_types::list::{Item, List};
|
||||
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_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_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_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD,
|
||||
ATTR_TEXT_ALIGN, ATTR_TRANSFORM,
|
||||
};
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graphene_hash::CacheHashWrapper;
|
||||
use graphene_resource::Resource;
|
||||
use graphic_types::attr as graphic_attr;
|
||||
use graphic_types::graphic::{graphic_list_at, has_paint_at, is_paint_present, set_paint_attribute};
|
||||
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster, Texture};
|
||||
use graphic_types::vector_types::gradient::{Gradient, GradientType};
|
||||
@@ -39,6 +36,8 @@ use std::fmt::Write;
|
||||
use std::hash::Hash;
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use text_nodes::attr as text_attr;
|
||||
use vector_types::attr as vector_attr;
|
||||
use vector_types::gradient::GradientSpreadMethod;
|
||||
use vello::*;
|
||||
|
||||
@@ -360,7 +359,7 @@ fn emit_svg_fill_path(
|
||||
attributes.push("d", d);
|
||||
let matrix = format_transform_matrix(element_transform);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push(ATTR_TRANSFORM, matrix);
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
let defs = &mut attributes.0.svg_defs;
|
||||
let fill_attribute = fill_graphic_list
|
||||
@@ -397,9 +396,9 @@ pub(crate) fn gradient_placement(transform: DAffine2, gradient_type: GradientTyp
|
||||
fn create_peniko_gradient_brush(gradient_list: &List<Gradient>, multiplied_transform: &DAffine2) -> Option<(peniko::Brush, DAffine2)> {
|
||||
let stops = gradient_list.element(0)?;
|
||||
|
||||
let gradient_type: GradientType = gradient_list.attribute_cloned_or_default(ATTR_GRADIENT_TYPE, 0);
|
||||
let gradient_transform: DAffine2 = gradient_list.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
let spread_method: GradientSpreadMethod = gradient_list.attribute_cloned_or_default(ATTR_SPREAD_METHOD, 0);
|
||||
let gradient_type = gradient_list.attr_cloned_or_default::<vector_attr::GradientType>(0);
|
||||
let gradient_transform = gradient_list.attr_cloned_or_default::<attr::Transform>(0);
|
||||
let spread_method = gradient_list.attr_cloned_or_default::<vector_attr::SpreadMethod>(0);
|
||||
|
||||
let mut peniko_stops = peniko::ColorStops::new();
|
||||
for (position, color, _) in stops.interpolated_samples() {
|
||||
@@ -457,10 +456,10 @@ pub struct RenderMetadata {
|
||||
pub text_frames: HashMap<NodeId, DAffine2>,
|
||||
pub clip_targets: HashSet<NodeId>,
|
||||
pub vector_data: HashMap<NodeId, Arc<Vector>>,
|
||||
/// Per-layer `ATTR_FILL` row attribute, exposed so message handlers can read it.
|
||||
/// Per-layer `graphic_types::attr::Fill` row attribute, exposed so message handlers can read it.
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub fill_attributes: HashMap<NodeId, Arc<List<Graphic>>>,
|
||||
/// Per-layer `ATTR_STROKE` row attribute, exposed so message handlers can read it.
|
||||
/// Per-layer `graphic_types::attr::Stroke` row attribute, exposed so message handlers can read it.
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub stroke_attributes: HashMap<NodeId, Arc<List<Graphic>>>,
|
||||
pub backgrounds: Vec<Background>,
|
||||
@@ -581,9 +580,9 @@ impl Render for Graphic {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
// TODO: Find a way to handle more than the first item
|
||||
if !list.is_empty() {
|
||||
let layer_path: List<NodeId> = list.attribute_cloned_or_default::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, 0).0;
|
||||
let layer_path: List<NodeId> = list.attr_cloned_or_default::<attr::editor::LayerPath>(0).0;
|
||||
let layer = layer_path.iter_element_values().next_back().copied();
|
||||
let transform: DAffine2 = list.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
let transform = list.attr_cloned_or_default::<attr::Transform>(0);
|
||||
|
||||
metadata.first_element_source_id.insert(element_id, layer);
|
||||
metadata.local_transforms.insert(element_id, transform);
|
||||
@@ -594,7 +593,7 @@ impl Render for Graphic {
|
||||
|
||||
// TODO: Find a way to handle more than the first item
|
||||
if !list.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
|
||||
metadata.local_transforms.insert(element_id, list.attr_cloned_or_default::<attr::Transform>(0));
|
||||
}
|
||||
}
|
||||
Graphic::RasterGPU(list) => {
|
||||
@@ -602,7 +601,7 @@ impl Render for Graphic {
|
||||
|
||||
// TODO: Find a way to handle more than the first item
|
||||
if !list.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
|
||||
metadata.local_transforms.insert(element_id, list.attr_cloned_or_default::<attr::Transform>(0));
|
||||
}
|
||||
}
|
||||
Graphic::Color(list) => {
|
||||
@@ -610,7 +609,7 @@ impl Render for Graphic {
|
||||
|
||||
// TODO: Find a way to handle more than the first item
|
||||
if !list.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
|
||||
metadata.local_transforms.insert(element_id, list.attr_cloned_or_default::<attr::Transform>(0));
|
||||
}
|
||||
}
|
||||
Graphic::Gradient(list) => {
|
||||
@@ -618,7 +617,7 @@ impl Render for Graphic {
|
||||
|
||||
// TODO: Find a way to handle more than the first item
|
||||
if !list.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
|
||||
metadata.local_transforms.insert(element_id, list.attr_cloned_or_default::<attr::Transform>(0));
|
||||
}
|
||||
}
|
||||
Graphic::Text(list) => {
|
||||
@@ -626,7 +625,7 @@ impl Render for Graphic {
|
||||
|
||||
// TODO: Find a way to handle more than the first item
|
||||
if !list.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
|
||||
metadata.local_transforms.insert(element_id, list.attr_cloned_or_default::<attr::Transform>(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -699,10 +698,10 @@ impl Render for Graphic {
|
||||
|
||||
/// Reads the artboard metadata for the item at `index` from a `List<Artboard>`.
|
||||
fn read_artboard_attributes(list: &List<Artboard>, index: usize) -> (DVec2, DVec2, Color, bool) {
|
||||
let location: DVec2 = list.attribute_cloned_or_default(ATTR_LOCATION, index);
|
||||
let dimensions: DVec2 = list.attribute_cloned_or_default(ATTR_DIMENSIONS, index);
|
||||
let background: Color = list.attribute_cloned_or_default(ATTR_BACKGROUND, index);
|
||||
let clip: bool = list.attribute_cloned_or_default(ATTR_CLIP, index);
|
||||
let location = list.attr_cloned_or_default::<attr::Location>(index);
|
||||
let dimensions = list.attr_cloned_or_default::<attr::Dimensions>(index);
|
||||
let background = list.attr_cloned_or_default::<attr::Background>(index);
|
||||
let clip = list.attr_cloned_or_default::<attr::Clip>(index);
|
||||
(location, dimensions, background, clip)
|
||||
}
|
||||
|
||||
@@ -737,7 +736,7 @@ impl Render for List<Artboard> {
|
||||
|attributes| {
|
||||
let matrix = format_transform_matrix(DAffine2::from_translation(location));
|
||||
if !matrix.is_empty() {
|
||||
attributes.push(ATTR_TRANSFORM, matrix);
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
if clip {
|
||||
@@ -800,7 +799,7 @@ impl Render for List<Artboard> {
|
||||
let Some(content) = self.element(index).map(Artboard::as_graphic_list) else { continue };
|
||||
let (location, dimensions, _background, clip) = read_artboard_attributes(self, index);
|
||||
|
||||
let layer_path: List<NodeId> = self.attribute_cloned_or_default::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, index).0;
|
||||
let layer_path: List<NodeId> = self.attr_cloned_or_default::<attr::editor::LayerPath>(index).0;
|
||||
let element_id = layer_path.iter_element_values().next_back().copied();
|
||||
|
||||
if let Some(element_id) = element_id {
|
||||
@@ -823,7 +822,7 @@ impl Render for List<Artboard> {
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for index in 0..self.len() {
|
||||
let dimensions: DVec2 = self.attribute_cloned_or_default(ATTR_DIMENSIONS, index);
|
||||
let dimensions = self.attr_cloned_or_default::<attr::Dimensions>(index);
|
||||
let subpath_rectangle = Subpath::new_rectangle(DVec2::ZERO, dimensions);
|
||||
click_targets.push(ClickTarget::new_with_subpath(subpath_rectangle, 0.));
|
||||
}
|
||||
@@ -839,10 +838,10 @@ impl Render for List<Graphic> {
|
||||
let mut mask_state = None;
|
||||
|
||||
for index in 0..self.len() {
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let blend_mode: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let blend_mode = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let element = self.element(index).unwrap();
|
||||
|
||||
render.parent_tag(
|
||||
@@ -850,7 +849,7 @@ impl Render for List<Graphic> {
|
||||
|attributes| {
|
||||
let matrix = format_transform_matrix(transform);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push(ATTR_TRANSFORM, matrix);
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||
@@ -895,11 +894,11 @@ impl Render for List<Graphic> {
|
||||
let mut mask_element_and_transform = None;
|
||||
|
||||
for index in 0..self.len() {
|
||||
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let transform = transform * item_transform;
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let element = self.element(index).unwrap();
|
||||
|
||||
let mut layer = false;
|
||||
@@ -971,8 +970,8 @@ impl Render for List<Graphic> {
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||
for index in 0..self.len() {
|
||||
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let layer_path: List<NodeId> = self.attribute_cloned_or_default::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, index).0;
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let layer_path: List<NodeId> = self.attr_cloned_or_default::<attr::editor::LayerPath>(index).0;
|
||||
let layer = layer_path.iter_element_values().next_back().copied();
|
||||
let element = self.element(index).unwrap();
|
||||
|
||||
@@ -992,7 +991,7 @@ impl Render for List<Graphic> {
|
||||
let mut all_upstream_outlines = Vec::new();
|
||||
|
||||
for index in 0..self.len() {
|
||||
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let element = self.element(index).unwrap();
|
||||
|
||||
let mut new_click_targets = Vec::new();
|
||||
@@ -1019,7 +1018,7 @@ impl Render for List<Graphic> {
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for index in 0..self.len() {
|
||||
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let element = self.element(index).unwrap();
|
||||
let mut new_click_targets = Vec::new();
|
||||
|
||||
@@ -1035,7 +1034,7 @@ impl Render for List<Graphic> {
|
||||
|
||||
fn add_upstream_outline_targets(&self, outlines: &mut Vec<ClickTarget>) {
|
||||
for index in 0..self.len() {
|
||||
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let element = self.element(index).unwrap();
|
||||
let mut new_outlines = Vec::new();
|
||||
|
||||
@@ -1054,7 +1053,7 @@ impl Render for List<Graphic> {
|
||||
}
|
||||
|
||||
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
|
||||
let (elements, layers) = self.element_and_attribute_slices_mut::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH);
|
||||
let (elements, layers) = self.element_and_attr_slices_mut::<attr::editor::LayerPath>();
|
||||
for (element, layer) in elements.iter_mut().zip(layers.iter()) {
|
||||
element.new_ids_from_hash(layer.0.iter_element_values().next_back().copied());
|
||||
}
|
||||
@@ -1065,10 +1064,10 @@ impl Render for List<Vector> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for index in 0..self.len() {
|
||||
let Some(vector) = self.element(index) else { continue };
|
||||
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
|
||||
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
|
||||
let has_real_stroke = vector.stroke.as_ref().filter(|stroke| stroke.weight() > 0.);
|
||||
@@ -1097,10 +1096,10 @@ impl Render for List<Vector> {
|
||||
MaskType::Mask
|
||||
};
|
||||
|
||||
let fill_graphic_list = graphic_list_at(self, index, ATTR_FILL);
|
||||
let fill_graphic_list = graphic_list_at::<graphic_attr::Fill>(self, index);
|
||||
let fill_graphic = fill_graphic_list.as_ref().and_then(|l| l.element(0));
|
||||
|
||||
let stroke_graphic_list = graphic_list_at(self, index, ATTR_STROKE);
|
||||
let stroke_graphic_list = graphic_list_at::<graphic_attr::Stroke>(self, index);
|
||||
let stroke_graphic = stroke_graphic_list.as_ref().and_then(|l| l.element(0));
|
||||
|
||||
let path_is_closed = vector.stroke_bezier_paths().all(|path| path.closed());
|
||||
@@ -1135,8 +1134,8 @@ impl Render for List<Vector> {
|
||||
|
||||
// The mask must draw at full alpha so the SVG `<mask>`/`<clipPath>` fully zeroes the path interior.
|
||||
// The wrapping SVG group (above) handles the user-set opacity.
|
||||
let mut mask_item = Item::new_from_element(cloned_vector).with_attribute(ATTR_TRANSFORM, item_transform);
|
||||
set_paint_attribute(mask_item.attributes_mut(), ATTR_FILL, List::new_from_element(Color::BLACK));
|
||||
let mut mask_item = Item::new_from_element(cloned_vector).with_attr::<attr::Transform>(item_transform);
|
||||
set_paint_attribute::<graphic_attr::Fill>(mask_item.attributes_mut(), List::new_from_element(Color::BLACK));
|
||||
let vector_item = List::new_from_item(mask_item);
|
||||
|
||||
(id, mask_type, vector_item)
|
||||
@@ -1164,7 +1163,7 @@ impl Render for List<Vector> {
|
||||
attributes.push("d", path.clone());
|
||||
let matrix = format_transform_matrix(element_transform);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push(ATTR_TRANSFORM, matrix);
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
let defs = &mut attributes.0.svg_defs;
|
||||
@@ -1281,10 +1280,10 @@ impl Render for List<Vector> {
|
||||
use graphic_types::vector_types::vector;
|
||||
|
||||
let Some(element) = self.element(index) else { continue };
|
||||
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let multiplied_transform = parent_transform * item_transform;
|
||||
let has_real_stroke = element.stroke.as_ref().filter(|stroke| stroke.weight() > 0.);
|
||||
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform_is_invertible(*transform));
|
||||
@@ -1310,8 +1309,8 @@ impl Render for List<Vector> {
|
||||
}
|
||||
}
|
||||
|
||||
let fill_graphic_list = graphic_list_at(self, index, ATTR_FILL);
|
||||
let stroke_graphic_list = graphic_list_at(self, index, ATTR_STROKE);
|
||||
let fill_graphic_list = graphic_list_at::<graphic_attr::Fill>(self, index);
|
||||
let stroke_graphic_list = graphic_list_at::<graphic_attr::Stroke>(self, index);
|
||||
|
||||
// If we're using opacity or a blend mode, we need to push a layer
|
||||
let blend_mode = match render_params.render_mode {
|
||||
@@ -1484,8 +1483,8 @@ impl Render for List<Vector> {
|
||||
|
||||
// The mask must draw at full alpha so `SrcOut` fully zeroes the path interior.
|
||||
// The outer opacity/blend layer (above) handles the user-set opacity.
|
||||
let mut mask_item = Item::new_from_element(cloned_element).with_attribute(ATTR_TRANSFORM, item_transform);
|
||||
set_paint_attribute(mask_item.attributes_mut(), ATTR_FILL, List::new_from_element(Color::BLACK));
|
||||
let mut mask_item = Item::new_from_element(cloned_element).with_attr::<attr::Transform>(item_transform);
|
||||
set_paint_attribute::<graphic_attr::Fill>(mask_item.attributes_mut(), List::new_from_element(Color::BLACK));
|
||||
let vector_list = List::new_from_item(mask_item);
|
||||
|
||||
let bounds = element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);
|
||||
@@ -1559,7 +1558,7 @@ impl Render for List<Vector> {
|
||||
// Aggregate all items' targets per element_id so multi-item lists (e.g. the "Text to Vector Glyphs" node) produce hit areas for every glyph.
|
||||
// Targets are baked relative to item 0's transform since `Graphic::collect_metadata` records that as `local_transforms[element_id]`.
|
||||
let item_zero_transform: DAffine2 = if !self.is_empty() {
|
||||
self.attribute_cloned_or_default(ATTR_TRANSFORM, 0)
|
||||
self.attr_cloned_or_default::<attr::Transform>(0)
|
||||
} else {
|
||||
DAffine2::IDENTITY
|
||||
};
|
||||
@@ -1574,8 +1573,8 @@ impl Render for List<Vector> {
|
||||
|
||||
for index in 0..self.len() {
|
||||
let Some(source) = self.element(index) else { continue };
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let layer_path: List<NodeId> = self.attribute_cloned_or_default::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, index).0;
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let layer_path: List<NodeId> = self.attr_cloned_or_default::<attr::editor::LayerPath>(index).0;
|
||||
let layer = layer_path.iter_element_values().next_back().copied();
|
||||
|
||||
if let Some(element_id) = caller_element_id.or(layer) {
|
||||
@@ -1588,7 +1587,7 @@ impl Render for List<Vector> {
|
||||
}
|
||||
|
||||
// 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::<vector_attr::editor::ClickTarget>(index).unwrap_or(source);
|
||||
|
||||
let item_relative_transform = item_zero_inverse * transform;
|
||||
|
||||
@@ -1608,16 +1607,16 @@ impl Render for List<Vector> {
|
||||
if let std::collections::hash_map::Entry::Vacant(e) = metadata.vector_data.entry(element_id) {
|
||||
e.insert(Arc::new(source.clone()));
|
||||
|
||||
if let Some(fill_graphic) = graphic_list_at(self, index, ATTR_FILL) {
|
||||
if let Some(fill_graphic) = graphic_list_at::<graphic_attr::Fill>(self, index) {
|
||||
metadata.fill_attributes.insert(element_id, Arc::new(fill_graphic.into_owned()));
|
||||
}
|
||||
if let Some(stroke_graphic) = graphic_list_at(self, index, ATTR_STROKE) {
|
||||
if let Some(stroke_graphic) = graphic_list_at::<graphic_attr::Stroke>(self, index) {
|
||||
metadata.stroke_attributes.insert(element_id, Arc::new(stroke_graphic.into_owned()));
|
||||
}
|
||||
}
|
||||
|
||||
// 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.attr::<attr::editor::TextFrame>(index) {
|
||||
metadata.text_frames.entry(element_id).or_insert(frame);
|
||||
}
|
||||
}
|
||||
@@ -1625,7 +1624,7 @@ impl Render for List<Vector> {
|
||||
// If this item carries a snapshot of upstream graphic content (e.g. it was produced by Boolean Operation,
|
||||
// Combine Paths, Morph, or any other destructive merge), recurse into that snapshot so the editor can
|
||||
// surface the original child layers' click targets.
|
||||
let upstream_nested_layers = self.attribute_cloned_or_default::<List<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, index);
|
||||
let upstream_nested_layers = self.attr_cloned_or_default::<graphic_attr::editor::MergedLayers>(index);
|
||||
if !upstream_nested_layers.is_empty() {
|
||||
let mut upstream_footprint = footprint;
|
||||
upstream_footprint.transform *= transform;
|
||||
@@ -1645,10 +1644,10 @@ impl Render for List<Vector> {
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for index in 0..self.len() {
|
||||
let Some(source) = self.element(index) else { continue };
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
|
||||
// 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::<vector_attr::editor::ClickTarget>(index).unwrap_or(source);
|
||||
|
||||
extend_targets_from_vector(click_targets, self, index, vector, transform);
|
||||
}
|
||||
@@ -1658,7 +1657,7 @@ impl Render for List<Vector> {
|
||||
// Source geometry only, ignoring `editor:click_target`, so outlines reflect actual letterforms
|
||||
for index in 0..self.len() {
|
||||
let Some(source) = self.element(index) else { continue };
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
|
||||
extend_targets_from_vector(outlines, self, index, source, transform);
|
||||
}
|
||||
@@ -1674,7 +1673,7 @@ impl Render for List<Vector> {
|
||||
/// Build one `CompoundPath` (non-zero fill rule, so holes like the inside of an "O" work
|
||||
/// correctly) plus one `FreePoint` per disconnected anchor, apply the transform, and append.
|
||||
fn extend_targets_from_vector(targets: &mut Vec<ClickTarget>, vector_list: &List<Vector>, index: usize, geometry: &Vector, transform: DAffine2) {
|
||||
let filled = has_paint_at(vector_list, index, ATTR_FILL);
|
||||
let filled = has_paint_at::<graphic_attr::Fill>(vector_list, index);
|
||||
|
||||
let mut subpaths: Vec<Subpath<_>> = geometry.stroke_bezier_paths().collect();
|
||||
let all_subpaths_closed = subpaths.iter().all(|subpath| subpath.closed());
|
||||
@@ -1730,10 +1729,10 @@ impl Render for List<Raster<CPU>> {
|
||||
for index in 0..self.len() {
|
||||
let Some(image) = self.element(index) else { continue };
|
||||
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
|
||||
if image.data.is_empty() {
|
||||
continue;
|
||||
@@ -1752,7 +1751,7 @@ impl Render for List<Raster<CPU>> {
|
||||
let matrix = transform * DAffine2::from_scale(1. / size);
|
||||
let matrix = format_transform_matrix(matrix);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push(ATTR_TRANSFORM, matrix);
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
attributes.push("width", size.x.to_string());
|
||||
@@ -1795,7 +1794,7 @@ impl Render for List<Raster<CPU>> {
|
||||
attributes.push("href", base64_string);
|
||||
let matrix = format_transform_matrix(transform);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push(ATTR_TRANSFORM, matrix);
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||
@@ -1817,9 +1816,9 @@ impl Render for List<Raster<CPU>> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let blend_mode = blend_mode_attr.to_peniko();
|
||||
|
||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||
@@ -1834,7 +1833,7 @@ impl Render for List<Raster<CPU>> {
|
||||
layer = true;
|
||||
}
|
||||
|
||||
let transform_attribute: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let transform_attribute = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
|
||||
if let RenderMode::Outline = render_params.render_mode {
|
||||
let outline_transform: DAffine2 = transform * transform_attribute;
|
||||
@@ -1874,7 +1873,7 @@ impl Render for List<Raster<CPU>> {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
// TODO: Find a way to handle more than one item of the `List<Raster<...>>`
|
||||
if !self.is_empty() {
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(0);
|
||||
metadata.local_transforms.insert(element_id, transform);
|
||||
|
||||
// If this raster carries a snapshot of upstream graphic content (e.g. it was produced by Rasterize,
|
||||
@@ -1883,7 +1882,7 @@ impl Render for List<Raster<CPU>> {
|
||||
// The snapshot was captured before Rasterize shifted its input transforms to align with the rasterization
|
||||
// area, so the children are already in the coordinate space matching `footprint` here — we must NOT
|
||||
// multiply in `transform` (which is the rasterization area, not a layer-stack transform).
|
||||
let upstream_nested_layers = self.attribute_cloned_or_default::<List<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, 0);
|
||||
let upstream_nested_layers = self.attr_cloned_or_default::<graphic_attr::editor::MergedLayers>(0);
|
||||
if !upstream_nested_layers.is_empty() {
|
||||
upstream_nested_layers.collect_metadata(metadata, footprint, None);
|
||||
}
|
||||
@@ -1906,10 +1905,10 @@ impl Render for List<Raster<GPU>> {
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||
for index in 0..self.len() {
|
||||
let Some(raster) = self.element(index) else { continue };
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let clip_attr: bool = self.attribute_cloned_or_default(ATTR_CLIPPING_MASK, index);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let clip_attr = self.attr_cloned_or_default::<attr::ClippingMask>(index);
|
||||
let blend_mode = match render_params.render_mode {
|
||||
RenderMode::Outline => peniko::Mix::Normal,
|
||||
_ => blend_mode_attr.to_peniko(),
|
||||
@@ -1928,7 +1927,7 @@ impl Render for List<Raster<GPU>> {
|
||||
layer = true;
|
||||
}
|
||||
|
||||
let transform_attribute: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let transform_attribute = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
|
||||
if let RenderMode::Outline = render_params.render_mode {
|
||||
let outline_transform = transform * transform_attribute;
|
||||
@@ -1969,7 +1968,7 @@ impl Render for List<Raster<GPU>> {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
// TODO: Find a way to handle more than one item of the `List<Raster<...>>`
|
||||
if !self.is_empty() {
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(0);
|
||||
metadata.local_transforms.insert(element_id, transform);
|
||||
|
||||
// If this raster carries a snapshot of upstream graphic content (e.g. it was produced by Rasterize,
|
||||
@@ -1978,7 +1977,7 @@ impl Render for List<Raster<GPU>> {
|
||||
// The snapshot was captured before Rasterize shifted its input transforms to align with the rasterization
|
||||
// area, so the children are already in the coordinate space matching `footprint` here — we must NOT
|
||||
// multiply in `transform` (which is the rasterization area, not a layer-stack transform).
|
||||
let upstream_nested_layers = self.attribute_cloned_or_default::<List<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, 0);
|
||||
let upstream_nested_layers = self.attr_cloned_or_default::<graphic_attr::editor::MergedLayers>(0);
|
||||
if !upstream_nested_layers.is_empty() {
|
||||
upstream_nested_layers.collect_metadata(metadata, footprint, None);
|
||||
}
|
||||
@@ -2000,9 +1999,9 @@ impl Render for List<Raster<GPU>> {
|
||||
impl Render for List<Color> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for (index, color) in self.iter_element_values().enumerate() {
|
||||
let blend_mode: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let blend_mode = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
render.leaf_tag("polyline", |attributes| {
|
||||
// Stand-in for an infinite background. Chrome's SVG renderer keeps internal coordinates in f32 and loses
|
||||
// precision past ~2^24 (~16.7 million), causing tile-boundary artifacts that pop in and out during panning.
|
||||
@@ -2031,9 +2030,9 @@ impl Render for List<Color> {
|
||||
use vello::peniko;
|
||||
|
||||
for (index, color) in self.iter_element_values().enumerate() {
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let blend_mode = blend_mode_attr.to_peniko();
|
||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||
|
||||
@@ -2071,12 +2070,12 @@ impl Render for List<Gradient> {
|
||||
|
||||
for index in 0..self.len() {
|
||||
let Some(gradient) = self.element(index) else { continue };
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let blend_mode: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let spread_method: GradientSpreadMethod = self.attribute_cloned_or_default(ATTR_SPREAD_METHOD, index);
|
||||
let gradient_type: GradientType = self.attribute_cloned_or_default(ATTR_GRADIENT_TYPE, index);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let blend_mode = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let spread_method = self.attr_cloned_or_default::<vector_attr::SpreadMethod>(index);
|
||||
let gradient_type = self.attr_cloned_or_default::<vector_attr::GradientType>(index);
|
||||
let tag = if thumbnail_rect.is_some() { "rect" } else { "polyline" };
|
||||
render.leaf_tag(tag, |attributes| {
|
||||
if let Some((min, size)) = thumbnail_rect {
|
||||
@@ -2160,13 +2159,13 @@ impl Render for List<Gradient> {
|
||||
for (((index, gradient), spread_method), gradient_type) in self
|
||||
.iter_element_values()
|
||||
.enumerate()
|
||||
.zip(self.iter_attribute_values_or_default::<GradientSpreadMethod>(ATTR_SPREAD_METHOD))
|
||||
.zip(self.iter_attribute_values_or_default::<GradientType>(ATTR_GRADIENT_TYPE))
|
||||
.zip(self.iter_attr_values_or_default::<vector_attr::SpreadMethod>())
|
||||
.zip(self.iter_attr_values_or_default::<vector_attr::GradientType>())
|
||||
{
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let gradient_transform = parent_transform * transform;
|
||||
|
||||
let blend_mode = blend_mode_attr.to_peniko();
|
||||
@@ -2314,16 +2313,16 @@ fn draw_glyph_run_to_bezpaths(glyph_run: &parley::GlyphRun<'_, ()>, x_offset: f3
|
||||
fn text_item_size_and_transform(list: &List<String>, index: usize) -> Option<(DVec2, DAffine2)> {
|
||||
let text = list.element(index)?;
|
||||
let font: Resource = {
|
||||
let f: Resource = list.attribute_cloned_or_default(ATTR_FONT, index);
|
||||
let f = list.attr_cloned_or_default::<text_attr::Font>(index);
|
||||
if f.is_empty() { text_nodes::FALLBACK_FONT_RESOURCE.clone() } else { f }
|
||||
};
|
||||
let font_size: f64 = list.attribute_cloned_or(ATTR_FONT_SIZE, index, DEFAULT_FONT_SIZE);
|
||||
let line_height: f64 = list.attribute_cloned_or(ATTR_LINE_HEIGHT, index, 1.2);
|
||||
let letter_spacing: f64 = list.attribute_cloned_or(ATTR_LETTER_SPACING, index, 0.);
|
||||
let max_width: Option<f64> = list.attribute_cloned_or(ATTR_MAX_WIDTH, index, None);
|
||||
let max_height: Option<f64> = list.attribute_cloned_or(ATTR_MAX_HEIGHT, index, None);
|
||||
let align: text_nodes::TextAlign = list.attribute_cloned_or_default(ATTR_TEXT_ALIGN, index);
|
||||
let transform: DAffine2 = list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let font_size = list.attr_cloned_or::<attr::FontSize>(index, DEFAULT_FONT_SIZE);
|
||||
let line_height = list.attr_cloned_or::<attr::LineHeight>(index, 1.2);
|
||||
let letter_spacing = list.attr_cloned_or_default::<attr::LetterSpacing>(index);
|
||||
let max_width = list.attr_cloned_or_default::<attr::MaxWidth>(index);
|
||||
let max_height = list.attr_cloned_or_default::<attr::MaxHeight>(index);
|
||||
let align = list.attr_cloned_or_default::<text_attr::TextAlign>(index);
|
||||
let transform = list.attr_cloned_or_default::<attr::Transform>(index);
|
||||
|
||||
let typesetting = text_nodes::TypesettingConfig {
|
||||
font_size,
|
||||
@@ -2375,7 +2374,7 @@ pub fn graphic_list_bounding_box(list: &List<Graphic>, transform: DAffine2) -> R
|
||||
let mut any_infinite = false;
|
||||
|
||||
for index in 0..list.len() {
|
||||
let item_transform = transform * list.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, index);
|
||||
let item_transform = transform * list.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let Some(graphic) = list.element(index) else { continue };
|
||||
let bounds = match graphic {
|
||||
Graphic::Text(text_list) => text_list_bounding_box(text_list, item_transform),
|
||||
@@ -2409,21 +2408,21 @@ impl Render for List<String> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let font: Resource = {
|
||||
let f: Resource = self.attribute_cloned_or_default(ATTR_FONT, index);
|
||||
let f = self.attr_cloned_or_default::<text_attr::Font>(index);
|
||||
if f.is_empty() { text_nodes::FALLBACK_FONT_RESOURCE.clone() } else { f }
|
||||
};
|
||||
let font_size: f64 = self.attribute_cloned_or(ATTR_FONT_SIZE, index, DEFAULT_FONT_SIZE);
|
||||
let line_height: f64 = self.attribute_cloned_or(ATTR_LINE_HEIGHT, index, 1.2);
|
||||
let letter_spacing: f64 = self.attribute_cloned_or(ATTR_LETTER_SPACING, index, 0.);
|
||||
let max_width: Option<f64> = self.attribute_cloned_or(ATTR_MAX_WIDTH, index, None);
|
||||
let max_height: Option<f64> = self.attribute_cloned_or(ATTR_MAX_HEIGHT, index, None);
|
||||
let letter_tilt: f64 = self.attribute_cloned_or(ATTR_LETTER_TILT, index, 0.);
|
||||
let align: text_nodes::TextAlign = self.attribute_cloned_or_default(ATTR_TEXT_ALIGN, index);
|
||||
let font_size = self.attr_cloned_or::<attr::FontSize>(index, DEFAULT_FONT_SIZE);
|
||||
let line_height = self.attr_cloned_or::<attr::LineHeight>(index, 1.2);
|
||||
let letter_spacing = self.attr_cloned_or_default::<attr::LetterSpacing>(index);
|
||||
let max_width = self.attr_cloned_or_default::<attr::MaxWidth>(index);
|
||||
let max_height = self.attr_cloned_or_default::<attr::MaxHeight>(index);
|
||||
let letter_tilt = self.attr_cloned_or_default::<attr::LetterTilt>(index);
|
||||
let align = self.attr_cloned_or_default::<text_attr::TextAlign>(index);
|
||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||
|
||||
let typesetting = text_nodes::TypesettingConfig {
|
||||
@@ -2494,21 +2493,21 @@ impl Render for List<String> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let font: Resource = {
|
||||
let f: Resource = self.attribute_cloned_or_default(ATTR_FONT, index);
|
||||
let f = self.attr_cloned_or_default::<text_attr::Font>(index);
|
||||
if f.is_empty() { text_nodes::FALLBACK_FONT_RESOURCE.clone() } else { f }
|
||||
};
|
||||
let font_size: f64 = self.attribute_cloned_or(ATTR_FONT_SIZE, index, DEFAULT_FONT_SIZE);
|
||||
let line_height: f64 = self.attribute_cloned_or(ATTR_LINE_HEIGHT, index, 1.2);
|
||||
let letter_spacing: f64 = self.attribute_cloned_or(ATTR_LETTER_SPACING, index, 0.);
|
||||
let max_width: Option<f64> = self.attribute_cloned_or(ATTR_MAX_WIDTH, index, None);
|
||||
let max_height: Option<f64> = self.attribute_cloned_or(ATTR_MAX_HEIGHT, index, None);
|
||||
let letter_tilt: f64 = self.attribute_cloned_or(ATTR_LETTER_TILT, index, 0.);
|
||||
let align: text_nodes::TextAlign = self.attribute_cloned_or_default(ATTR_TEXT_ALIGN, index);
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let font_size = self.attr_cloned_or::<attr::FontSize>(index, DEFAULT_FONT_SIZE);
|
||||
let line_height = self.attr_cloned_or::<attr::LineHeight>(index, 1.2);
|
||||
let letter_spacing = self.attr_cloned_or_default::<attr::LetterSpacing>(index);
|
||||
let max_width = self.attr_cloned_or_default::<attr::MaxWidth>(index);
|
||||
let max_height = self.attr_cloned_or_default::<attr::MaxHeight>(index);
|
||||
let letter_tilt = self.attr_cloned_or_default::<attr::LetterTilt>(index);
|
||||
let align = self.attr_cloned_or_default::<text_attr::TextAlign>(index);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||
|
||||
let typesetting = text_nodes::TypesettingConfig {
|
||||
@@ -2559,7 +2558,7 @@ impl Render for List<String> {
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) {
|
||||
// Click targets are baked relative to item 0's transform, which `Graphic::collect_metadata` records as `local_transforms[element_id]`.
|
||||
let item_zero_transform: DAffine2 = if !self.is_empty() {
|
||||
self.attribute_cloned_or_default(ATTR_TRANSFORM, 0)
|
||||
self.attr_cloned_or_default::<attr::Transform>(0)
|
||||
} else {
|
||||
DAffine2::IDENTITY
|
||||
};
|
||||
@@ -2572,7 +2571,7 @@ impl Render for List<String> {
|
||||
let mut accumulated_click_targets: HashMap<NodeId, Vec<Arc<ClickTarget>>> = HashMap::new();
|
||||
|
||||
for index in 0..self.len() {
|
||||
let layer_path: List<NodeId> = self.attribute_cloned_or_default::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, index).0;
|
||||
let layer_path: List<NodeId> = self.attr_cloned_or_default::<attr::editor::LayerPath>(index).0;
|
||||
let layer = layer_path.iter_element_values().next_back().copied();
|
||||
let Some(element_id) = caller_element_id.or(layer) else { continue };
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use core_types::attr;
|
||||
use core_types::list::Item;
|
||||
use core_types::registry::types::Percentage;
|
||||
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_OPACITY, ATTR_OPACITY_FILL, BlendMode, Color, Ctx};
|
||||
use core_types::{BlendMode, Color, Ctx};
|
||||
use graphic_types::Graphic;
|
||||
use graphic_types::Vector;
|
||||
use graphic_types::raster_types::{CPU, GPU, Raster};
|
||||
@@ -19,7 +20,7 @@ fn blend_mode<T>(
|
||||
let mut content = content;
|
||||
let blend_mode = *blend_mode.element();
|
||||
|
||||
content.set_attribute(ATTR_BLEND_MODE, blend_mode);
|
||||
content.set_attr::<attr::BlendMode>(blend_mode);
|
||||
content
|
||||
}
|
||||
|
||||
@@ -54,13 +55,13 @@ fn opacity<T>(
|
||||
let (has_opacity, opacity, has_fill, fill) = (*has_opacity.element(), *opacity.element(), *has_fill.element(), *fill.element());
|
||||
|
||||
if has_opacity {
|
||||
let multiplied = content.attribute_cloned_or(ATTR_OPACITY, 1.) * (opacity / 100.);
|
||||
content.set_attribute(ATTR_OPACITY, multiplied);
|
||||
let multiplied = content.attr_cloned_or_default::<attr::Opacity>() * (opacity / 100.);
|
||||
content.set_attr::<attr::Opacity>(multiplied);
|
||||
}
|
||||
|
||||
if has_fill {
|
||||
let multiplied = content.attribute_cloned_or(ATTR_OPACITY_FILL, 1.) * (fill / 100.);
|
||||
content.set_attribute(ATTR_OPACITY_FILL, multiplied);
|
||||
let multiplied = content.attr_cloned_or_default::<attr::OpacityFill>() * (fill / 100.);
|
||||
content.set_attr::<attr::OpacityFill>(multiplied);
|
||||
}
|
||||
|
||||
content
|
||||
@@ -79,6 +80,6 @@ fn clipping_mask<T>(
|
||||
let mut content = content;
|
||||
let clip = *clip.element();
|
||||
|
||||
content.set_attribute(ATTR_CLIPPING_MASK, clip);
|
||||
content.set_attr::<attr::ClippingMask>(clip);
|
||||
content
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::brush_cache::BrushCache;
|
||||
use crate::brush_stroke::{BrushStyle, BrushTrace};
|
||||
use core_types::ATTR_TRANSFORM;
|
||||
use core_types::attr;
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use core_types::color::{Alpha, Color, Pixel, Sample};
|
||||
@@ -91,7 +91,7 @@ where
|
||||
return target;
|
||||
}
|
||||
|
||||
let (elements, transforms) = target.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
|
||||
let (elements, transforms) = target.element_and_attr_slices_mut::<attr::Transform>();
|
||||
for (element, transform_attribute) in elements.iter_mut().zip(transforms.iter()) {
|
||||
let target_width = element.width;
|
||||
let target_height = element.height;
|
||||
@@ -281,7 +281,7 @@ async fn brush(
|
||||
let has_erase_or_restore_strokes = trace.iter_element_values().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore));
|
||||
if has_erase_or_restore_strokes {
|
||||
let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE);
|
||||
let mut erase_restore_mask = Item::new_from_element(Raster::new_cpu(opaque_image)).with_attribute(ATTR_TRANSFORM, background_bounds);
|
||||
let mut erase_restore_mask = Item::new_from_element(Raster::new_cpu(opaque_image)).with_attr::<attr::Transform>(background_bounds);
|
||||
|
||||
for stroke in trace.into_iter().map(|row| row.into_element()) {
|
||||
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
||||
@@ -315,10 +315,10 @@ async fn brush(
|
||||
|
||||
// The paint operation changes only the raster and its bounds, so set just the resulting transform; blending, opacity,
|
||||
// clipping, and layer-path attributes carry through from the input `background` rather than being invented here.
|
||||
let transform: DAffine2 = actual_image.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform = actual_image.attr_cloned_or_default::<attr::Transform>();
|
||||
|
||||
*result_item.element_mut() = actual_image.into_element();
|
||||
result_item.set_attribute(ATTR_TRANSFORM, transform);
|
||||
result_item.set_attr::<attr::Transform>(transform);
|
||||
|
||||
result_item
|
||||
}
|
||||
@@ -328,8 +328,8 @@ pub fn blend_image_closure(foreground: Item<Raster<CPU>>, mut background: Item<R
|
||||
let background_size = DVec2::new(background.element().width as f64, background.element().height as f64);
|
||||
|
||||
// Transforms a point from the background image to the foreground image
|
||||
let foreground_transform: DAffine2 = foreground.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let background_transform: DAffine2 = background.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let foreground_transform = foreground.attr_cloned_or_default::<attr::Transform>();
|
||||
let background_transform = background.attr_cloned_or_default::<attr::Transform>();
|
||||
let background_to_foreground = DAffine2::from_scale(foreground_size) * foreground_transform.inverse() * background_transform * DAffine2::from_scale(1. / background_size);
|
||||
|
||||
// Footprint of the foreground image (0, 0)..(1, 1) in the background image space
|
||||
@@ -360,7 +360,7 @@ pub fn blend_stamp_closure(foreground: BrushStampGenerator<Color>, mut backgroun
|
||||
let background_size = DVec2::new(background.element().width as f64, background.element().height as f64);
|
||||
|
||||
// Transforms a point from the background image to the foreground image
|
||||
let background_transform: DAffine2 = background.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let background_transform = background.attr_cloned_or_default::<attr::Transform>();
|
||||
let background_to_foreground = background_transform * DAffine2::from_scale(1. / background_size);
|
||||
|
||||
// Footprint of the foreground image (0, 0)..(1, 1) in the background image space
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::brush_stroke::BrushStroke;
|
||||
use crate::brush_stroke::BrushStyle;
|
||||
use core_types::ATTR_TRANSFORM;
|
||||
use core_types::attr;
|
||||
use core_types::graphene_hash::CacheHashWrapper;
|
||||
use core_types::list::Item;
|
||||
use raster_types::CPU;
|
||||
@@ -51,7 +51,7 @@ impl BrushCacheImpl {
|
||||
|
||||
// Check if the first non-blended stroke is an extension of the last one.
|
||||
// Transform is set to ZERO (not the default IDENTITY) as a sentinel to mark this item as uninitialized.
|
||||
let mut first_stroke_texture = Item::new_from_element(Raster::<CPU>::default()).with_attribute(ATTR_TRANSFORM, glam::DAffine2::ZERO);
|
||||
let mut first_stroke_texture = Item::new_from_element(Raster::<CPU>::default()).with_attr::<attr::Transform>(glam::DAffine2::ZERO);
|
||||
let mut first_stroke_point_skip = 0;
|
||||
let strokes = input[num_blended_strokes..].to_vec();
|
||||
if !strokes.is_empty() && self.prev_input.len() > num_blended_strokes {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use core_types::attr;
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::transform::TransformMut;
|
||||
use core_types::{ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_LOCATION, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||
use core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphic_types::graphic::{Graphic, IntoGraphicList};
|
||||
use graphic_types::{Artboard, Vector};
|
||||
@@ -53,8 +54,8 @@ pub async fn create_artboard<T: IntoGraphicList>(
|
||||
|
||||
// Name is not stored here, it's resolved live from the parent layer's display name
|
||||
Item::new_from_element(Artboard::new(content))
|
||||
.with_attribute(ATTR_LOCATION, normalized_location)
|
||||
.with_attribute(ATTR_DIMENSIONS, normalized_dimensions)
|
||||
.with_attribute(ATTR_BACKGROUND, background)
|
||||
.with_attribute(ATTR_CLIP, clip)
|
||||
.with_attr::<attr::Location>(normalized_location)
|
||||
.with_attr::<attr::Dimensions>(normalized_dimensions)
|
||||
.with_attr::<attr::Background>(background)
|
||||
.with_attr::<attr::Clip>(clip)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use core_types::attr;
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use core_types::list::{AttributeValueDyn, Item, List, ListDyn, NodeIdPath};
|
||||
use core_types::registry::types::{Angle, SeedValue, SignedInteger};
|
||||
use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, AnyHash, BlendMode, CacheHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||
use core_types::{AnyHash, BlendMode, CacheHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphic_types::graphic::{Graphic, IntoGraphicList};
|
||||
use graphic_types::{Artboard, Vector};
|
||||
@@ -489,7 +490,7 @@ async fn mirror<T: BoundingBox + 'n + Send + Clone>(
|
||||
let normal = DVec2::from_angle(angle.to_radians());
|
||||
|
||||
// The mirror reference may be based on the bounding box if an explicit reference point is chosen
|
||||
let item_transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let item_transform = content.attr_cloned_or_default::<attr::Transform>();
|
||||
let RenderBoundingBox::Rectangle(bounding_box) = content.element().bounding_box(item_transform, false) else {
|
||||
return List::new_from_item(content);
|
||||
};
|
||||
@@ -521,7 +522,7 @@ async fn mirror<T: BoundingBox + 'n + Send + Clone>(
|
||||
|
||||
// Add the mirrored copy with the reflection composed onto its transform
|
||||
let mut mirrored = content;
|
||||
mirrored.set_attribute(ATTR_TRANSFORM, reflected_transform * item_transform);
|
||||
mirrored.set_attr::<attr::Transform>(reflected_transform * item_transform);
|
||||
result_list.push(mirrored);
|
||||
|
||||
result_list
|
||||
@@ -600,7 +601,7 @@ fn read_attribute_vector(
|
||||
let name = name.into_element();
|
||||
let mut result = List::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<Vector>(&name, index) else { continue };
|
||||
let Some(value) = content.attribute_dyn::<Vector>(&name, index) else { continue };
|
||||
result.push(Item::new_from_element(value.clone()));
|
||||
}
|
||||
result
|
||||
@@ -618,10 +619,10 @@ fn read_attribute_number(
|
||||
let mut result = List::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let value = content
|
||||
.attribute::<f64>(&name, index)
|
||||
.attribute_dyn::<f64>(&name, index)
|
||||
.copied()
|
||||
.or_else(|| content.attribute::<u64>(&name, index).map(|v| *v as f64))
|
||||
.or_else(|| content.attribute::<u32>(&name, index).map(|v| *v as f64));
|
||||
.or_else(|| content.attribute_dyn::<u64>(&name, index).map(|v| *v as f64))
|
||||
.or_else(|| content.attribute_dyn::<u32>(&name, index).map(|v| *v as f64));
|
||||
let Some(value) = value else { continue };
|
||||
result.push(Item::new_from_element(value));
|
||||
}
|
||||
@@ -639,7 +640,7 @@ fn read_attribute_bool(
|
||||
let name = name.into_element();
|
||||
let mut result = List::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<bool>(&name, index) else { continue };
|
||||
let Some(value) = content.attribute_dyn::<bool>(&name, index) else { continue };
|
||||
result.push(Item::new_from_element(*value));
|
||||
}
|
||||
result
|
||||
@@ -656,7 +657,7 @@ fn read_attribute_string(
|
||||
let name = name.into_element();
|
||||
let mut result = List::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<String>(&name, index) else { continue };
|
||||
let Some(value) = content.attribute_dyn::<String>(&name, index) else { continue };
|
||||
result.push(Item::new_from_element(value.clone()));
|
||||
}
|
||||
result
|
||||
@@ -673,7 +674,7 @@ fn read_attribute_transform(
|
||||
let name = name.into_element();
|
||||
let mut result = List::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<DAffine2>(&name, index) else { continue };
|
||||
let Some(value) = content.attribute_dyn::<DAffine2>(&name, index) else { continue };
|
||||
result.push(Item::new_from_element(*value));
|
||||
}
|
||||
result
|
||||
@@ -690,7 +691,7 @@ fn read_attribute_color(
|
||||
let name = name.into_element();
|
||||
let mut result = List::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<Color>(&name, index) else { continue };
|
||||
let Some(value) = content.attribute_dyn::<Color>(&name, index) else { continue };
|
||||
result.push(Item::new_from_element(*value));
|
||||
}
|
||||
result
|
||||
@@ -707,7 +708,7 @@ fn read_attribute_blend_mode(
|
||||
let name = name.into_element();
|
||||
let mut result = List::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<BlendMode>(&name, index) else { continue };
|
||||
let Some(value) = content.attribute_dyn::<BlendMode>(&name, index) else { continue };
|
||||
result.push(Item::new_from_element(*value));
|
||||
}
|
||||
result
|
||||
@@ -724,7 +725,7 @@ fn read_attribute_gradient_type(
|
||||
let name = name.into_element();
|
||||
let mut result = List::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<GradientType>(&name, index) else { continue };
|
||||
let Some(value) = content.attribute_dyn::<GradientType>(&name, index) else { continue };
|
||||
result.push(Item::new_from_element(*value));
|
||||
}
|
||||
result
|
||||
@@ -741,7 +742,7 @@ fn read_attribute_spread_method(
|
||||
let name = name.into_element();
|
||||
let mut result = List::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<GradientSpreadMethod>(&name, index) else { continue };
|
||||
let Some(value) = content.attribute_dyn::<GradientSpreadMethod>(&name, index) else { continue };
|
||||
result.push(Item::new_from_element(*value));
|
||||
}
|
||||
result
|
||||
@@ -758,7 +759,7 @@ fn read_attribute_gradient_stops(
|
||||
let name = name.into_element();
|
||||
let mut result = List::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<Gradient>(&name, index) else { continue };
|
||||
let Some(value) = content.attribute_dyn::<Gradient>(&name, index) else { continue };
|
||||
result.push(Item::new_from_element(value.clone()));
|
||||
}
|
||||
result
|
||||
@@ -775,7 +776,7 @@ fn read_attribute_artboard(
|
||||
let name = name.into_element();
|
||||
let mut result = List::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<Artboard>(&name, index) else { continue };
|
||||
let Some(value) = content.attribute_dyn::<Artboard>(&name, index) else { continue };
|
||||
result.push(Item::new_from_element(value.clone()));
|
||||
}
|
||||
result
|
||||
@@ -792,7 +793,7 @@ fn read_attribute_raster(
|
||||
let name = name.into_element();
|
||||
let mut result = List::with_capacity(content.len());
|
||||
for index in 0..content.len() {
|
||||
let Some(value) = content.attribute::<Raster<CPU>>(&name, index) else { continue };
|
||||
let Some(value) = content.attribute_dyn::<Raster<CPU>>(&name, index) else { continue };
|
||||
result.push(Item::new_from_element(value.clone()));
|
||||
}
|
||||
result
|
||||
@@ -869,7 +870,7 @@ pub async fn legacy_layer_extend<T: 'n + Send + Clone>(
|
||||
|
||||
let mut base = base;
|
||||
for mut row in new.into_iter() {
|
||||
row.set_attribute(ATTR_EDITOR_LAYER_PATH, layer_path.clone());
|
||||
row.set_attr::<attr::editor::LayerPath>(layer_path.clone());
|
||||
base.push(row);
|
||||
}
|
||||
|
||||
@@ -926,7 +927,7 @@ pub async fn flatten_graphic(_: impl Ctx, content: List<Graphic>, fully_flatten:
|
||||
for index in 0..current_graphic_list.len() {
|
||||
let Some(current_element) = current_graphic_list.element(index) else { continue };
|
||||
let current_element = current_element.clone();
|
||||
let current_transform: DAffine2 = current_graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let current_transform = current_graphic_list.attr_cloned_or_default::<attr::Transform>(index);
|
||||
|
||||
let recurse = fully_flatten || recursion_depth == 0;
|
||||
|
||||
@@ -934,7 +935,7 @@ pub async fn flatten_graphic(_: impl Ctx, content: List<Graphic>, fully_flatten:
|
||||
// If we're allowed to recurse, flatten any graphics we encounter
|
||||
Graphic::Graphic(mut current_element) if recurse => {
|
||||
// Apply the parent graphic's transform to all child elements
|
||||
for graphic_transform in current_element.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
for graphic_transform in current_element.iter_attr_values_mut_or_default::<attr::Transform>() {
|
||||
*graphic_transform = current_transform * *graphic_transform;
|
||||
}
|
||||
|
||||
@@ -974,15 +975,15 @@ pub async fn flatten_vector<T: IntoGraphicList>(_: impl Ctx, #[implementations(L
|
||||
// already holds the original transforms; pre-compensate by item 0's inverse so the renderer's
|
||||
// `upstream_footprint *= item_0_transform` recursion cancels out and leaves the originals intact.
|
||||
let mut graphic_list = graphic_list;
|
||||
let item_0_transform: DAffine2 = output.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
let item_0_transform = output.attr_cloned_or_default::<attr::Transform>(0);
|
||||
if item_0_transform.matrix2.determinant().abs() > f64::EPSILON {
|
||||
let inverse = item_0_transform.inverse();
|
||||
for transform in graphic_list.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
for transform in graphic_list.iter_attr_values_mut_or_default::<attr::Transform>() {
|
||||
*transform = inverse * *transform;
|
||||
}
|
||||
}
|
||||
|
||||
output.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, graphic_list);
|
||||
output.set_attr::<graphic_types::attr::editor::MergedLayers>(0, graphic_list);
|
||||
}
|
||||
|
||||
output
|
||||
|
||||
@@ -10,9 +10,9 @@ use core_types::list::List;
|
||||
use core_types::math::bbox::Bbox;
|
||||
use core_types::ops::Convert;
|
||||
use core_types::transform::Footprint;
|
||||
#[cfg(target_family = "wasm")]
|
||||
use core_types::{ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, WasmNotSend};
|
||||
use core_types::{Color, Ctx};
|
||||
#[cfg(target_family = "wasm")]
|
||||
use core_types::{WasmNotSend, attr};
|
||||
pub use graph_craft::application_io::resource::{Resource, ResourceHash};
|
||||
pub use graph_craft::application_io::*;
|
||||
pub use graph_craft::document::value::RenderOutputType;
|
||||
@@ -243,7 +243,7 @@ where
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for transform in data.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
for transform in data.iter_attr_values_mut_or_default::<attr::Transform>() {
|
||||
*transform = DAffine2::from_translation(-aabb.start) * *transform;
|
||||
}
|
||||
data.render_svg(&mut render, &render_params);
|
||||
@@ -270,8 +270,8 @@ where
|
||||
let image = Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32);
|
||||
List::new_from_item(
|
||||
Item::new_from_element(Raster::new_cpu(image))
|
||||
.with_attribute(ATTR_TRANSFORM, footprint.transform)
|
||||
.with_attribute(ATTR_EDITOR_MERGED_LAYERS, upstream_graphic_list),
|
||||
.with_attr::<attr::Transform>(footprint.transform)
|
||||
.with_attr::<graphic_types::attr::editor::MergedLayers>(upstream_graphic_list),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use core_types::Ctx;
|
||||
use core_types::consts::{DEFAULT_FONT_SIZE, DEFAULT_LINE_HEIGHT};
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::{ATTR_FONT, ATTR_FONT_SIZE, ATTR_LETTER_SPACING, ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_TEXT_ALIGN, Ctx};
|
||||
use graph_craft::application_io::resource::Resource;
|
||||
use graphic_types::Vector;
|
||||
pub use text_nodes::*;
|
||||
@@ -69,28 +69,28 @@ fn text(
|
||||
let mut item = Item::new_from_element(text);
|
||||
|
||||
if font != Resource::default() {
|
||||
item.set_attribute(ATTR_FONT, font);
|
||||
item.set_attr::<attr::Font>(font);
|
||||
}
|
||||
if (size - DEFAULT_FONT_SIZE).abs() > f64::EPSILON {
|
||||
item.set_attribute(ATTR_FONT_SIZE, size);
|
||||
item.set_attr::<core_types::attr::FontSize>(size);
|
||||
}
|
||||
if (line_height - DEFAULT_LINE_HEIGHT).abs() > f64::EPSILON {
|
||||
item.set_attribute(ATTR_LINE_HEIGHT, line_height);
|
||||
item.set_attr::<core_types::attr::LineHeight>(line_height);
|
||||
}
|
||||
if letter_spacing != 0. {
|
||||
item.set_attribute(ATTR_LETTER_SPACING, letter_spacing);
|
||||
item.set_attr::<core_types::attr::LetterSpacing>(letter_spacing);
|
||||
}
|
||||
if letter_tilt != 0. {
|
||||
item.set_attribute(ATTR_LETTER_TILT, letter_tilt);
|
||||
item.set_attr::<core_types::attr::LetterTilt>(letter_tilt);
|
||||
}
|
||||
if has_max_width {
|
||||
item.set_attribute(ATTR_MAX_WIDTH, Some(max_width));
|
||||
item.set_attr::<core_types::attr::MaxWidth>(Some(max_width));
|
||||
}
|
||||
if has_max_height {
|
||||
item.set_attribute(ATTR_MAX_HEIGHT, Some(max_height));
|
||||
item.set_attr::<core_types::attr::MaxHeight>(Some(max_height));
|
||||
}
|
||||
if align != TextAlign::default() {
|
||||
item.set_attribute(ATTR_TEXT_ALIGN, align);
|
||||
item.set_attr::<attr::TextAlign>(align);
|
||||
}
|
||||
|
||||
item
|
||||
|
||||
@@ -1381,7 +1381,7 @@ fn gradient_value(_: impl Ctx, _primary: (), gradient: Item<Gradient>) -> Item<G
|
||||
#[node_macro::node(category("Color"))]
|
||||
fn gradient_type(_: impl Ctx, gradient: Item<Gradient>, gradient_type: Item<vector_types::GradientType>) -> Item<Gradient> {
|
||||
let mut gradient = gradient;
|
||||
gradient.set_attribute(core_types::ATTR_GRADIENT_TYPE, *gradient_type.element());
|
||||
gradient.set_attr::<vector_types::attr::GradientType>(*gradient_type.element());
|
||||
gradient
|
||||
}
|
||||
|
||||
@@ -1389,7 +1389,7 @@ fn gradient_type(_: impl Ctx, gradient: Item<Gradient>, gradient_type: Item<vect
|
||||
#[node_macro::node(category("Color"))]
|
||||
fn spread_method(_: impl Ctx, gradient: Item<Gradient>, spread_method: Item<vector_types::GradientSpreadMethod>) -> Item<Gradient> {
|
||||
let mut gradient = gradient;
|
||||
gradient.set_attribute(core_types::ATTR_SPREAD_METHOD, *spread_method.element());
|
||||
gradient.set_attr::<vector_types::attr::SpreadMethod>(*spread_method.element());
|
||||
gradient
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use core_types::list::{ATTR_FILL, Item, ItemAttributeValues, List};
|
||||
use core_types::{
|
||||
ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_GRADIENT_TYPE, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_TRANSFORM, Color, Ctx,
|
||||
};
|
||||
use core_types::attr::{self, Attr};
|
||||
use core_types::list::{Item, ItemAttributeValues, List};
|
||||
use core_types::{Color, Ctx};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphic_types::graphic::{bake_paint_transforms, set_paint_attribute};
|
||||
use graphic_types::vector_types::gradient::{GradientSpreadMethod, GradientType};
|
||||
use graphic_types::vector_types::subpath::{ManipulatorGroup, Subpath};
|
||||
use graphic_types::vector_types::vector::PointId;
|
||||
use graphic_types::vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;
|
||||
@@ -43,8 +41,8 @@ async fn boolean_operation<I: graphic_types::IntoGraphicList>(
|
||||
|
||||
// Replace the transformation matrix with a mutation of the vector points themselves
|
||||
if result_vector_list.element_mut(0).is_some() {
|
||||
let transform: DAffine2 = result_vector_list.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
result_vector_list.set_attribute(ATTR_TRANSFORM, 0, DAffine2::IDENTITY);
|
||||
let transform = result_vector_list.attr_cloned_or_default::<attr::Transform>(0);
|
||||
result_vector_list.set_attr::<attr::Transform>(0, DAffine2::IDENTITY);
|
||||
|
||||
let result_vector = result_vector_list.element_mut(0).unwrap();
|
||||
Vector::transform(result_vector, transform);
|
||||
@@ -52,10 +50,10 @@ async fn boolean_operation<I: graphic_types::IntoGraphicList>(
|
||||
|
||||
// Snapshot the input layers as the `editor:merged_layers` attribute so the renderer can recurse into them
|
||||
// for editor click-target preservation.
|
||||
result_vector_list.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, content.clone());
|
||||
result_vector_list.set_attr::<graphic_types::attr::editor::MergedLayers>(0, content.clone());
|
||||
|
||||
// Clean up the boolean operation result by merging duplicated points
|
||||
let merge_transform: DAffine2 = result_vector_list.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
let merge_transform = result_vector_list.attr_cloned_or_default::<attr::Transform>(0);
|
||||
result_vector_list.element_mut(0).unwrap().merge_by_distance_spatial(merge_transform, 0.0001);
|
||||
}
|
||||
|
||||
@@ -139,9 +137,9 @@ fn boolean_operation_on_vector_list(vector: &List<Vector>, boolean_operation: Bo
|
||||
};
|
||||
let mut row = if let Some(index) = copy_from_index {
|
||||
let mut attributes = vector.clone_item_attributes(index);
|
||||
let copy_from_transform: DAffine2 = vector.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let copy_from_transform = vector.attr_cloned_or_default::<attr::Transform>(index);
|
||||
// The boolean op bakes input transforms into the output geometry, so the result item carries no transform of its own
|
||||
attributes.insert(ATTR_TRANSFORM, DAffine2::IDENTITY);
|
||||
attributes.set_attr::<attr::Transform>(DAffine2::IDENTITY);
|
||||
|
||||
bake_paint_transforms(&mut attributes, copy_from_transform);
|
||||
|
||||
@@ -157,7 +155,7 @@ fn boolean_operation_on_vector_list(vector: &List<Vector>, boolean_operation: Bo
|
||||
|
||||
for index in 0..vector.len() {
|
||||
let element = vector.element(index).unwrap();
|
||||
paths.push(to_bez_path(element, vector.attribute_cloned_or_default(ATTR_TRANSFORM, index)));
|
||||
paths.push(to_bez_path(element, vector.attr_cloned_or_default::<attr::Transform>(index)));
|
||||
}
|
||||
|
||||
let top = match Topology::<WindingNumber>::from_paths(paths.iter().enumerate().map(|(idx, path)| (path, (idx, paths.len()))), EPSILON) {
|
||||
@@ -185,18 +183,18 @@ fn flatten_vector(graphic_list: &List<Graphic>) -> List<Vector> {
|
||||
Graphic::None => Vec::new(),
|
||||
Graphic::Vector(vector) => {
|
||||
// Apply the parent graphic's transform to each element of the `List<Vector>`
|
||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let parent_transform = graphic_list.attr_cloned_or_default::<attr::Transform>(index);
|
||||
vector
|
||||
.into_iter()
|
||||
.map(|mut sub_vector| {
|
||||
let current_transform: DAffine2 = sub_vector.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
*sub_vector.attribute_mut_or_insert_default(ATTR_TRANSFORM) = parent_transform * current_transform;
|
||||
let current_transform = sub_vector.attr_cloned_or_default::<attr::Transform>();
|
||||
*sub_vector.attr_mut_or_insert_default::<attr::Transform>() = parent_transform * current_transform;
|
||||
sub_vector
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::RasterCPU(image) => {
|
||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let parent_transform = graphic_list.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let make_item = |transform: DAffine2, source_attributes: &ItemAttributeValues| {
|
||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||
subpath.apply_transform(transform);
|
||||
@@ -204,10 +202,16 @@ fn flatten_vector(graphic_list: &List<Graphic>) -> List<Vector> {
|
||||
let element = Vector::from_subpath(subpath);
|
||||
|
||||
let mut item = Item::new_from_element(element);
|
||||
for key in [ATTR_BLEND_MODE, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH] {
|
||||
for key in [
|
||||
attr::BlendMode::name(),
|
||||
attr::Opacity::name(),
|
||||
attr::OpacityFill::name(),
|
||||
attr::ClippingMask::name(),
|
||||
attr::editor::LayerPath::name(),
|
||||
] {
|
||||
item.attributes_mut().insert_cloned_from(source_attributes, key);
|
||||
}
|
||||
set_paint_attribute(item.attributes_mut(), ATTR_FILL, List::new_from_element(Color::BLACK));
|
||||
set_paint_attribute::<graphic_types::attr::Fill>(item.attributes_mut(), List::new_from_element(Color::BLACK));
|
||||
item
|
||||
};
|
||||
|
||||
@@ -216,14 +220,14 @@ fn flatten_vector(graphic_list: &List<Graphic>) -> List<Vector> {
|
||||
// back to the originating raster layer
|
||||
(0..image.len())
|
||||
.map(|i| {
|
||||
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
|
||||
let row_transform = image.attr_cloned_or_default::<attr::Transform>(i);
|
||||
let source_attributes = image.clone_item_attributes(i);
|
||||
make_item(parent_transform * row_transform, &source_attributes)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::RasterGPU(image) => {
|
||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let parent_transform = graphic_list.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let make_item = |transform: DAffine2, source_attributes: &ItemAttributeValues| {
|
||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||
subpath.apply_transform(transform);
|
||||
@@ -231,10 +235,16 @@ fn flatten_vector(graphic_list: &List<Graphic>) -> List<Vector> {
|
||||
let element = Vector::from_subpath(subpath);
|
||||
|
||||
let mut item = Item::new_from_element(element);
|
||||
for key in [ATTR_BLEND_MODE, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH] {
|
||||
for key in [
|
||||
attr::BlendMode::name(),
|
||||
attr::Opacity::name(),
|
||||
attr::OpacityFill::name(),
|
||||
attr::ClippingMask::name(),
|
||||
attr::editor::LayerPath::name(),
|
||||
] {
|
||||
item.attributes_mut().insert_cloned_from(source_attributes, key);
|
||||
}
|
||||
set_paint_attribute(item.attributes_mut(), ATTR_FILL, List::new_from_element(Color::BLACK));
|
||||
set_paint_attribute::<graphic_types::attr::Fill>(item.attributes_mut(), List::new_from_element(Color::BLACK));
|
||||
item
|
||||
};
|
||||
|
||||
@@ -243,16 +253,16 @@ fn flatten_vector(graphic_list: &List<Graphic>) -> List<Vector> {
|
||||
// back to the originating raster layer
|
||||
(0..image.len())
|
||||
.map(|i| {
|
||||
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
|
||||
let row_transform = image.attr_cloned_or_default::<attr::Transform>(i);
|
||||
let source_attributes = image.clone_item_attributes(i);
|
||||
make_item(parent_transform * row_transform, &source_attributes)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::Graphic(mut graphic) => {
|
||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let parent_transform = graphic_list.attr_cloned_or_default::<attr::Transform>(index);
|
||||
// Apply the parent graphic's transform to each element of the inner `List`
|
||||
for transform in graphic.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
for transform in graphic.iter_attr_values_mut_or_default::<attr::Transform>() {
|
||||
*transform = parent_transform * *transform;
|
||||
}
|
||||
|
||||
@@ -266,7 +276,7 @@ fn flatten_vector(graphic_list: &List<Graphic>) -> List<Vector> {
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let (color, mut attributes) = row.into_parts();
|
||||
set_paint_attribute(&mut attributes, ATTR_FILL, List::new_from_element(color));
|
||||
set_paint_attribute::<graphic_types::attr::Fill>(&mut attributes, List::new_from_element(color));
|
||||
|
||||
let mut element = Vector::default();
|
||||
element.set_stroke_transform(DAffine2::IDENTITY);
|
||||
@@ -280,16 +290,16 @@ fn flatten_vector(graphic_list: &List<Graphic>) -> List<Vector> {
|
||||
let (stops, mut attributes) = row.into_parts();
|
||||
|
||||
let mut gradient_paint = List::new_from_element(stops);
|
||||
if let Some(transform) = attributes.remove::<DAffine2>(ATTR_TRANSFORM) {
|
||||
gradient_paint.set_attribute(ATTR_TRANSFORM, 0, transform);
|
||||
if let Some(transform) = attributes.remove_attr::<attr::Transform>() {
|
||||
gradient_paint.set_attr::<attr::Transform>(0, transform);
|
||||
}
|
||||
if let Some(gradient_type) = attributes.remove::<GradientType>(ATTR_GRADIENT_TYPE) {
|
||||
gradient_paint.set_attribute(ATTR_GRADIENT_TYPE, 0, gradient_type);
|
||||
if let Some(gradient_type) = attributes.remove_attr::<vector_types::attr::GradientType>() {
|
||||
gradient_paint.set_attr::<vector_types::attr::GradientType>(0, gradient_type);
|
||||
}
|
||||
if let Some(spread_method) = attributes.remove::<GradientSpreadMethod>(ATTR_SPREAD_METHOD) {
|
||||
gradient_paint.set_attribute(ATTR_SPREAD_METHOD, 0, spread_method);
|
||||
if let Some(spread_method) = attributes.remove_attr::<vector_types::attr::SpreadMethod>() {
|
||||
gradient_paint.set_attr::<vector_types::attr::SpreadMethod>(0, spread_method);
|
||||
}
|
||||
set_paint_attribute(&mut attributes, ATTR_FILL, gradient_paint);
|
||||
set_paint_attribute::<graphic_types::attr::Fill>(&mut attributes, gradient_paint);
|
||||
|
||||
let mut element = Vector::default();
|
||||
element.set_stroke_transform(DAffine2::IDENTITY);
|
||||
@@ -299,12 +309,12 @@ fn flatten_vector(graphic_list: &List<Graphic>) -> List<Vector> {
|
||||
.collect::<Vec<_>>(),
|
||||
Graphic::Text(text) => {
|
||||
// Shape the glyphs into vectors (each item's own transform is applied), then compose the parent's transform like the other arms
|
||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let parent_transform = graphic_list.attr_cloned_or_default::<attr::Transform>(index);
|
||||
text_nodes::shape_text_list(&text, false)
|
||||
.into_iter()
|
||||
.map(|mut sub_vector| {
|
||||
let current_transform: DAffine2 = sub_vector.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
*sub_vector.attribute_mut_or_insert_default(ATTR_TRANSFORM) = parent_transform * current_transform;
|
||||
let current_transform = sub_vector.attr_cloned_or_default::<attr::Transform>();
|
||||
*sub_vector.attr_mut_or_insert_default::<attr::Transform>() = parent_transform * current_transform;
|
||||
sub_vector
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::adjustments::{CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, NoiseType};
|
||||
use core_types::ATTR_TRANSFORM;
|
||||
use core_types::attr;
|
||||
use core_types::color::Color;
|
||||
use core_types::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut};
|
||||
use core_types::context::{Ctx, ExtractFootprint};
|
||||
@@ -32,7 +32,7 @@ impl From<std::io::Error> for Error {
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Item<Raster<CPU>>) -> Item<Raster<CPU>> {
|
||||
let image_frame_transform: DAffine2 = image_frame.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let image_frame_transform = image_frame.attr_cloned_or_default::<attr::Transform>();
|
||||
|
||||
let footprint = ctx.footprint();
|
||||
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
||||
@@ -86,7 +86,7 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Item
|
||||
// we need to adjust the offset if we truncate the offset calculation
|
||||
|
||||
let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);
|
||||
attributes.insert(ATTR_TRANSFORM, new_transform);
|
||||
attributes.set_attr::<attr::Transform>(new_transform);
|
||||
|
||||
Item::from_parts(Raster::new_cpu(image), attributes)
|
||||
}
|
||||
@@ -163,7 +163,7 @@ pub fn mask(
|
||||
|
||||
let mut row = image;
|
||||
let image_size = DVec2::new(row.element().width as f64, row.element().height as f64);
|
||||
let stencil_transform: DAffine2 = stencil.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let stencil_transform = stencil.attr_cloned_or_default::<attr::Transform>();
|
||||
let mask_size = stencil_transform.scale_magnitudes();
|
||||
|
||||
if mask_size == DVec2::ZERO {
|
||||
@@ -171,7 +171,7 @@ pub fn mask(
|
||||
}
|
||||
|
||||
// Transforms a point from the background image to the foreground image
|
||||
let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform_attribute = row.attr_cloned_or_default::<attr::Transform>();
|
||||
let bg_to_fg = transform_attribute * DAffine2::from_scale(1. / image_size);
|
||||
let stencil_transform_inverse = stencil_transform.inverse();
|
||||
|
||||
@@ -196,7 +196,7 @@ pub fn mask(
|
||||
pub fn extend_image_to_bounds(_: impl Ctx, image: Item<Raster<CPU>>, bounds: Item<DAffine2>) -> Item<Raster<CPU>> {
|
||||
let bounds = *bounds.element();
|
||||
|
||||
let image_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let image_transform = image.attr_cloned_or_default::<attr::Transform>();
|
||||
let image_aabb = Bbox::unit().affine_transform(image_transform).to_axis_aligned_bbox();
|
||||
let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox();
|
||||
if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) {
|
||||
@@ -232,7 +232,7 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: Item<Raster<CPU>>, bounds: Ite
|
||||
// let layer_to_new_texture_space = (DAffine2::from_scale(1. / new_scale) * DAffine2::from_translation(new_start) * layer_to_image_space).inverse();
|
||||
let new_texture_to_layer_space = image_transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);
|
||||
|
||||
attributes.insert(ATTR_TRANSFORM, new_texture_to_layer_space);
|
||||
attributes.set_attr::<attr::Transform>(new_texture_to_layer_space);
|
||||
Item::from_parts(Raster::new_cpu(new_image), attributes)
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ pub fn empty_image(_: impl Ctx, transform: Item<DAffine2>, color: Item<Color>) -
|
||||
|
||||
let image = Image::new(width, height, color.into_element());
|
||||
|
||||
Item::new_from_element(Raster::new_cpu(image)).with_attribute(ATTR_TRANSFORM, transform)
|
||||
Item::new_from_element(Raster::new_cpu(image)).with_attr::<attr::Transform>(transform)
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
@@ -376,7 +376,7 @@ pub fn noise_pattern(
|
||||
}
|
||||
}
|
||||
|
||||
return Item::new_from_element(Raster::new_cpu(image)).with_attribute(ATTR_TRANSFORM, transform);
|
||||
return Item::new_from_element(Raster::new_cpu(image)).with_attr::<attr::Transform>(transform);
|
||||
}
|
||||
};
|
||||
noise.set_noise_type(Some(noise_type));
|
||||
@@ -434,7 +434,7 @@ pub fn noise_pattern(
|
||||
}
|
||||
}
|
||||
|
||||
Item::new_from_element(Raster::new_cpu(image)).with_attribute(ATTR_TRANSFORM, transform)
|
||||
Item::new_from_element(Raster::new_cpu(image)).with_attr::<attr::Transform>(transform)
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Pattern"))]
|
||||
@@ -478,7 +478,7 @@ pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> Item<Raster<CPU>> {
|
||||
data,
|
||||
..Default::default()
|
||||
}))
|
||||
.with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(offset) * DAffine2::from_scale(size))
|
||||
.with_attr::<attr::Transform>(DAffine2::from_translation(offset) * DAffine2::from_scale(size))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::gcore::Context;
|
||||
use core::f64::consts::TAU;
|
||||
use core_types::attr;
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::registry::types::{Angle, PixelSize};
|
||||
use core_types::{ATTR_TRANSFORM, CloneVarArgs, Color, Ctx, ExtractAll, InjectVarArgs, OwnedContextImpl};
|
||||
use core_types::{CloneVarArgs, Color, Ctx, ExtractAll, InjectVarArgs, OwnedContextImpl};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphic_types::{Artboard, Graphic, Vector};
|
||||
use raster_types::{CPU, GPU, Raster};
|
||||
@@ -102,10 +103,10 @@ pub async fn repeat_array<T: Send + Clone + 'static>(
|
||||
for row_index in 0..generated_content.len() {
|
||||
let Some(mut row) = generated_content.clone_item(row_index) else { continue };
|
||||
|
||||
let local_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let local_transform = row.attr_cloned_or_default::<attr::Transform>();
|
||||
let local_translation = DAffine2::from_translation(local_transform.translation);
|
||||
let local_matrix = DAffine2::from_mat2(local_transform.matrix2);
|
||||
*row.attribute_mut_or_insert_default(ATTR_TRANSFORM) = local_translation * transform * local_matrix;
|
||||
*row.attr_mut_or_insert_default::<attr::Transform>() = local_translation * transform * local_matrix;
|
||||
|
||||
result_list.push(row);
|
||||
}
|
||||
@@ -158,10 +159,10 @@ async fn repeat_radial<T: Send + Clone + 'static>(
|
||||
for row_index in 0..generated_content.len() {
|
||||
let Some(mut row) = generated_content.clone_item(row_index) else { continue };
|
||||
|
||||
let local_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let local_transform = row.attr_cloned_or_default::<attr::Transform>();
|
||||
let local_translation = DAffine2::from_translation(local_transform.translation);
|
||||
let local_matrix = DAffine2::from_mat2(local_transform.matrix2);
|
||||
*row.attribute_mut_or_insert_default(ATTR_TRANSFORM) = local_translation * transform * local_matrix;
|
||||
*row.attr_mut_or_insert_default::<attr::Transform>() = local_translation * transform * local_matrix;
|
||||
|
||||
result_list.push(row);
|
||||
}
|
||||
@@ -200,7 +201,7 @@ async fn repeat_on_points<T: Send + Clone + 'static>(
|
||||
|
||||
for points_index in 0..points.len() {
|
||||
let Some(points_element) = points.element(points_index) else { continue };
|
||||
let transform: DAffine2 = points.attribute_cloned_or_default(ATTR_TRANSFORM, points_index);
|
||||
let transform = points.attr_cloned_or_default::<attr::Transform>(points_index);
|
||||
|
||||
let mut iteration = async |index, point| {
|
||||
let transformed_point = transform.transform_point2(point);
|
||||
@@ -209,7 +210,7 @@ async fn repeat_on_points<T: Send + Clone + 'static>(
|
||||
let generated_content = content.eval(new_ctx.into_context()).await;
|
||||
|
||||
for mut generated_row in generated_content.into_iter() {
|
||||
generated_row.attribute_mut_or_insert_default::<DAffine2>(ATTR_TRANSFORM).translation = transformed_point;
|
||||
generated_row.attr_mut_or_insert_default::<attr::Transform>().translation = transformed_point;
|
||||
result_list.push(generated_row);
|
||||
}
|
||||
};
|
||||
@@ -301,7 +302,7 @@ mod test {
|
||||
let bounds = generated
|
||||
.element(index)
|
||||
.unwrap()
|
||||
.bounding_box_with_transform(generated.attribute_cloned_or_default(ATTR_TRANSFORM, index))
|
||||
.bounding_box_with_transform(generated.attr_cloned_or_default::<attr::Transform>(index))
|
||||
.unwrap();
|
||||
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
|
||||
assert_eq!((bounds[1] - bounds[0]).x, position.y);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::{ATTR_TYPE, Ctx};
|
||||
use core_types::{Ctx, attr};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::unescape_string;
|
||||
@@ -265,7 +265,7 @@ fn query_json_all(
|
||||
let mut results = Vec::new();
|
||||
resolve_all(&value, &segments, !*unquote_strings.element(), &mut results);
|
||||
|
||||
results.into_iter().map(|(text, ty)| Item::new_from_element(text).with_attribute(ATTR_TYPE, ty.to_string())).collect()
|
||||
results.into_iter().map(|(text, ty)| Item::new_from_element(text).with_attr::<attr::Type>(ty.to_string())).collect()
|
||||
}
|
||||
|
||||
/// A parsed segment of a JSON access path.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use core_types::attr;
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::{ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_TEXT_FRAME, ATTR_TRANSFORM};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use parley::GlyphRun;
|
||||
use skrifa::GlyphId;
|
||||
@@ -15,13 +15,13 @@ pub struct PathBuilder {
|
||||
origin: DVec2,
|
||||
glyph_subpaths: Vec<Subpath<PointId>>,
|
||||
pub vector_list: List<Vector>,
|
||||
/// Per-glyph AABBs collected in single-item mode, published as `ATTR_EDITOR_CLICK_TARGET` in `finalize()`.
|
||||
/// Per-glyph AABBs collected in single-item mode, published as `vector_types::attr::editor::ClickTarget` in `finalize()`.
|
||||
merged_click_target_bboxes: Vec<[DVec2; 2]>,
|
||||
/// Per-glyph baselines, parallel to `merged_click_target_bboxes`. Groups glyphs by line for the widening pass.
|
||||
merged_click_target_baselines: Vec<f64>,
|
||||
/// Per-glyph AABBs in glyph-local space (multi-item mode), widened in `finalize()` to fill gaps.
|
||||
per_glyph_bboxes: Vec<Option<[DVec2; 2]>>,
|
||||
/// Text frame size, stamped per item as `ATTR_EDITOR_TEXT_FRAME` relative to each item's origin.
|
||||
/// Text frame size, stamped per item as `attr::editor::TextFrame` relative to each item's origin.
|
||||
text_frame_size: DVec2,
|
||||
/// First glyph's baseline offset (pre-height-filter). Used for the empty placeholder item so
|
||||
/// `local_transforms` stays stable when all glyphs are clipped during a resize drag.
|
||||
@@ -85,8 +85,8 @@ impl PathBuilder {
|
||||
let frame_in_item_local = DAffine2::from_scale_angle_translation(self.text_frame_size, 0., -glyph_offset);
|
||||
|
||||
let item = Item::new_from_element(Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false))
|
||||
.with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(glyph_offset))
|
||||
.with_attribute(ATTR_EDITOR_TEXT_FRAME, frame_in_item_local);
|
||||
.with_attr::<attr::Transform>(DAffine2::from_translation(glyph_offset))
|
||||
.with_attr::<attr::editor::TextFrame>(frame_in_item_local);
|
||||
self.vector_list.push(item);
|
||||
|
||||
// Defer click target creation to `finalize()` where adjacent AABBs get widened
|
||||
@@ -170,8 +170,8 @@ impl PathBuilder {
|
||||
if self.vector_list.is_empty() {
|
||||
let frame_in_item_local = DAffine2::from_scale_angle_translation(self.text_frame_size, 0., -self.first_glyph_offset);
|
||||
let item = Item::new_from_element(Vector::default())
|
||||
.with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(self.first_glyph_offset))
|
||||
.with_attribute(ATTR_EDITOR_TEXT_FRAME, frame_in_item_local);
|
||||
.with_attr::<attr::Transform>(DAffine2::from_translation(self.first_glyph_offset))
|
||||
.with_attr::<attr::editor::TextFrame>(frame_in_item_local);
|
||||
self.vector_list.push(item);
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ impl PathBuilder {
|
||||
.enumerate()
|
||||
.filter_map(|(index, bbox)| {
|
||||
let bbox = (*bbox)?;
|
||||
let offset = self.vector_list.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, index).translation;
|
||||
let offset = self.vector_list.attr_cloned_or_default::<attr::Transform>(index).translation;
|
||||
Some((index, offset, [bbox[0] + offset, bbox[1] + offset]))
|
||||
})
|
||||
.collect();
|
||||
@@ -197,7 +197,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_attr::<vector_types::attr::editor::ClickTarget>(entry.0, Vector::from_subpaths([rect], false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,14 +207,14 @@ 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_attr::<vector_types::attr::editor::ClickTarget>(0, 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)
|
||||
let frame = DAffine2::from_scale(self.text_frame_size);
|
||||
for index in 0..self.vector_list.len() {
|
||||
if self.vector_list.attribute::<DAffine2>(ATTR_EDITOR_TEXT_FRAME, index).is_none() {
|
||||
self.vector_list.set_attribute(ATTR_EDITOR_TEXT_FRAME, index, frame);
|
||||
if self.vector_list.attr::<attr::editor::TextFrame>(index).is_none() {
|
||||
self.vector_list.set_attr::<attr::editor::TextFrame>(index, frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::registry::types::SignedInteger;
|
||||
use core_types::{ATTR_END, ATTR_NAME, ATTR_START, Ctx};
|
||||
use core_types::{Ctx, attr};
|
||||
|
||||
/// Checks whether the string contains a match for the given regular expression pattern. Optionally restricts the match to only the start and/or end of the string.
|
||||
#[node_macro::node(category("Text: Regex"))]
|
||||
@@ -159,10 +159,7 @@ fn regex_find(
|
||||
let start = captured.map_or(0_u64, |m| m.start() as u64);
|
||||
let end = captured.map_or(0_u64, |m| m.end() as u64);
|
||||
let name = capture_names.get(i).cloned().flatten().unwrap_or_default();
|
||||
Item::new_from_element(text)
|
||||
.with_attribute(ATTR_START, start)
|
||||
.with_attribute(ATTR_END, end)
|
||||
.with_attribute(ATTR_NAME, name)
|
||||
Item::new_from_element(text).with_attr::<attr::Start>(start).with_attr::<attr::End>(end).with_attr::<attr::Name>(name)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -208,8 +205,8 @@ fn regex_find_all(
|
||||
.filter_map(|m| m.ok())
|
||||
.map(|m| {
|
||||
Item::new_from_element(m.as_str().to_string())
|
||||
.with_attribute(ATTR_START, m.start() as u64)
|
||||
.with_attribute(ATTR_END, m.end() as u64)
|
||||
.with_attr::<attr::Start>(m.start() as u64)
|
||||
.with_attr::<attr::End>(m.end() as u64)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
use super::TypesettingConfig;
|
||||
use super::text_context::TextContext;
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::list::{Item, List, NodeIdPath};
|
||||
use core_types::{
|
||||
ATTR_BLEND_MODE, ATTR_EDITOR_LAYER_PATH, ATTR_FONT, ATTR_FONT_SIZE, ATTR_LETTER_SPACING, ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_OPACITY, ATTR_OPACITY_FILL,
|
||||
ATTR_TEXT_ALIGN, ATTR_TRANSFORM,
|
||||
};
|
||||
use core_types::attr;
|
||||
use core_types::list::{Item, List};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_resource::Resource;
|
||||
use vector_types::Vector;
|
||||
@@ -33,45 +29,45 @@ pub fn shape_text_item(item: &Item<String>, separate_glyphs: bool) -> List<Vecto
|
||||
|
||||
// Use fallback font when none is explicitly attached.
|
||||
let font: Resource = {
|
||||
let font: Resource = item.attribute_cloned_or_default(ATTR_FONT);
|
||||
let font: Resource = item.attr_cloned_or_default::<crate::attr::Font>();
|
||||
if font.is_empty() { super::FALLBACK_FONT_RESOURCE.clone() } else { font }
|
||||
};
|
||||
|
||||
let defaults = TypesettingConfig::default();
|
||||
let typesetting = TypesettingConfig {
|
||||
font_size: item.attribute_cloned_or(ATTR_FONT_SIZE, defaults.font_size),
|
||||
line_height_ratio: item.attribute_cloned_or(ATTR_LINE_HEIGHT, defaults.line_height_ratio),
|
||||
letter_spacing: item.attribute_cloned_or(ATTR_LETTER_SPACING, defaults.letter_spacing),
|
||||
letter_tilt: item.attribute_cloned_or(ATTR_LETTER_TILT, defaults.letter_tilt),
|
||||
max_width: item.attribute_cloned_or::<Option<f64>>(ATTR_MAX_WIDTH, defaults.max_width),
|
||||
max_height: item.attribute_cloned_or::<Option<f64>>(ATTR_MAX_HEIGHT, defaults.max_height),
|
||||
align: item.attribute_cloned_or(ATTR_TEXT_ALIGN, defaults.align),
|
||||
font_size: item.attr_cloned_or::<attr::FontSize>(defaults.font_size),
|
||||
line_height_ratio: item.attr_cloned_or::<attr::LineHeight>(defaults.line_height_ratio),
|
||||
letter_spacing: item.attr_cloned_or::<attr::LetterSpacing>(defaults.letter_spacing),
|
||||
letter_tilt: item.attr_cloned_or::<attr::LetterTilt>(defaults.letter_tilt),
|
||||
max_width: item.attr_cloned_or::<attr::MaxWidth>(defaults.max_width),
|
||||
max_height: item.attr_cloned_or::<attr::MaxHeight>(defaults.max_height),
|
||||
align: item.attr_cloned_or::<crate::attr::TextAlign>(defaults.align),
|
||||
};
|
||||
|
||||
let vectors = to_path(text, &font, typesetting, separate_glyphs);
|
||||
let transform = item.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM);
|
||||
let layer_path = item.attribute::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH).cloned();
|
||||
let blend_mode = item.attribute::<BlendMode>(ATTR_BLEND_MODE).copied();
|
||||
let opacity = item.attribute::<f64>(ATTR_OPACITY).copied();
|
||||
let opacity_fill = item.attribute::<f64>(ATTR_OPACITY_FILL).copied();
|
||||
let transform = item.attr_cloned_or_default::<attr::Transform>();
|
||||
let layer_path = item.attr::<attr::editor::LayerPath>().cloned();
|
||||
let blend_mode = item.attr::<attr::BlendMode>().copied();
|
||||
let opacity = item.attr::<attr::Opacity>().copied();
|
||||
let opacity_fill = item.attr::<attr::OpacityFill>().copied();
|
||||
|
||||
let mut result = List::new();
|
||||
for mut produced in vectors.into_iter() {
|
||||
if transform != DAffine2::IDENTITY {
|
||||
let local = produced.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM);
|
||||
produced.set_attribute(ATTR_TRANSFORM, transform * local);
|
||||
let local = produced.attr_cloned_or_default::<attr::Transform>();
|
||||
produced.set_attr::<attr::Transform>(transform * local);
|
||||
}
|
||||
if let Some(layer_path) = &layer_path {
|
||||
produced.set_attribute(ATTR_EDITOR_LAYER_PATH, layer_path.clone());
|
||||
produced.set_attr::<attr::editor::LayerPath>(layer_path.clone());
|
||||
}
|
||||
if let Some(blend_mode) = blend_mode {
|
||||
produced.set_attribute(ATTR_BLEND_MODE, blend_mode);
|
||||
produced.set_attr::<attr::BlendMode>(blend_mode);
|
||||
}
|
||||
if let Some(opacity) = opacity {
|
||||
produced.set_attribute(ATTR_OPACITY, opacity);
|
||||
produced.set_attr::<attr::Opacity>(opacity);
|
||||
}
|
||||
if let Some(opacity_fill) = opacity_fill {
|
||||
produced.set_attribute(ATTR_OPACITY_FILL, opacity_fill);
|
||||
produced.set_attr::<attr::OpacityFill>(opacity_fill);
|
||||
}
|
||||
result.push(produced);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use core::f64;
|
||||
use core_types::attr;
|
||||
use core_types::color::Color;
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::transform::{ApplyTransform, ScaleType, Transform};
|
||||
use core_types::{ATTR_TRANSFORM, CloneVarArgs, Context, Ctx, ExtractAll, InjectFootprint, ModifyFootprint, OwnedContextImpl};
|
||||
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, InjectFootprint, ModifyFootprint, OwnedContextImpl};
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graphic_types::raster_types::{CPU, GPU, Raster};
|
||||
use graphic_types::{Artboard, Graphic, Vector};
|
||||
@@ -106,7 +107,7 @@ fn reset_transform<T>(
|
||||
let mut content = content;
|
||||
let (reset_translation, reset_rotation, reset_scale) = (*reset_translation.element(), *reset_rotation.element(), *reset_scale.element());
|
||||
|
||||
let item_transform = content.attribute_mut_or_insert_default::<DAffine2>(ATTR_TRANSFORM);
|
||||
let item_transform = content.attr_mut_or_insert_default::<attr::Transform>();
|
||||
|
||||
if reset_translation {
|
||||
item_transform.translation = DVec2::ZERO;
|
||||
@@ -147,14 +148,14 @@ fn replace_transform<T>(
|
||||
let mut content = content;
|
||||
let transform = *transform.element();
|
||||
|
||||
content.set_attribute(ATTR_TRANSFORM, transform.transform());
|
||||
content.set_attr::<attr::Transform>(transform.transform());
|
||||
content
|
||||
}
|
||||
|
||||
/// Obtains the transform of the input content.
|
||||
#[node_macro::node(category("Math: Transform"), path(core_types::vector))]
|
||||
fn extract_transform<T: 'n + Send>(_: impl Ctx, #[implementations(Graphic, Vector, Raster<CPU>, Raster<GPU>, Color, Gradient, String, Artboard)] content: Item<T>) -> Item<DAffine2> {
|
||||
Item::new_from_element(content.attribute_cloned_or_default(ATTR_TRANSFORM))
|
||||
Item::new_from_element(content.attr_cloned_or_default::<attr::Transform>())
|
||||
}
|
||||
|
||||
/// Produces the inverse of the input transform, which is the transform that undoes the effect of the original transform.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use core_types::list::{Item, List, NodeIdPath};
|
||||
use core_types::transform::BakeTransform;
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, Ctx};
|
||||
use core_types::{Ctx, attr};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphic_types::Vector;
|
||||
use vector_types::vector::VectorModification;
|
||||
@@ -13,7 +13,7 @@ async fn path_modify(_ctx: impl Ctx, vector: Item<Vector>, modification: Item<Bo
|
||||
modification.into_element().apply(vector.element_mut());
|
||||
|
||||
// Drop the stale click-target override so hit testing uses the geometry the user is now editing
|
||||
vector.remove_attribute::<Vector>(ATTR_EDITOR_CLICK_TARGET);
|
||||
vector.remove_attr::<vector_types::attr::editor::ClickTarget>();
|
||||
|
||||
// Set the path to the encapsulating subgraph (drop our own trailing entry from `node_path`),
|
||||
// matching the `path_of_subgraph` proto so editor tools can route data back to the parent layer.
|
||||
@@ -22,9 +22,9 @@ async fn path_modify(_ctx: impl Ctx, vector: Item<Vector>, modification: Item<Bo
|
||||
let len = node_path.len();
|
||||
node_path.into_iter().take(len.saturating_sub(1)).collect()
|
||||
};
|
||||
let existing = vector.attribute_cloned_or_default::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH).0;
|
||||
let existing = vector.attr_cloned_or_default::<attr::editor::LayerPath>().0;
|
||||
let layer_path = if existing.is_empty() { subgraph_path } else { existing };
|
||||
vector.set_attribute(ATTR_EDITOR_LAYER_PATH, NodeIdPath(layer_path));
|
||||
vector.set_attr::<attr::editor::LayerPath>(NodeIdPath(layer_path));
|
||||
|
||||
vector
|
||||
}
|
||||
@@ -33,7 +33,7 @@ async fn path_modify(_ctx: impl Ctx, vector: Item<Vector>, modification: Item<Bo
|
||||
#[node_macro::node(category("Vector"))]
|
||||
async fn bake_transform<T: BakeTransform + 'n + Send + 'static>(_ctx: impl Ctx, #[implementations(Vector, DAffine2, DVec2)] content: Item<T>) -> Item<T> {
|
||||
let mut content = content;
|
||||
if let Some(transform) = content.remove_attribute::<DAffine2>(ATTR_TRANSFORM) {
|
||||
if let Some(transform) = content.remove_attr::<attr::Transform>() {
|
||||
content.element_mut().bake_transform(&transform);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
use core::cmp::Ordering;
|
||||
use core::f64::consts::{PI, TAU};
|
||||
use core::hash::{Hash, Hasher};
|
||||
use core_types::attr::{self, Attr};
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use core_types::list::{ATTR_FILL, ATTR_STROKE, Item, ItemAttributeValues, List, ListDyn, NodeIdPath};
|
||||
use core_types::list::{Item, ItemAttributeValues, List, ListDyn};
|
||||
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
|
||||
use core_types::transform::{Footprint, Transform};
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{
|
||||
ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_GRADIENT_TYPE, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_TRANSFORM, CloneVarArgs,
|
||||
Color, Context, Ctx, ExtractAll, OwnedContextImpl,
|
||||
};
|
||||
use core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graphic_types::Vector;
|
||||
use graphic_types::graphic::{bake_paint_transforms, graphic_list_at, has_paint_at, is_paint_present, set_paint_attribute_at};
|
||||
@@ -73,32 +71,32 @@ impl VectorListIterMut for List<Vector> {
|
||||
trait VectorItemMut {
|
||||
fn for_each_vector_mut(&mut self, f: impl FnMut(&mut Vector, DAffine2));
|
||||
|
||||
fn set_vector_paint(&mut self, key: &str, paint: List<Graphic>);
|
||||
fn set_vector_paint<A: Attr<Value = List<Graphic>>>(&mut self, paint: List<Graphic>);
|
||||
}
|
||||
|
||||
impl VectorItemMut for Item<Vector> {
|
||||
fn for_each_vector_mut(&mut self, mut f: impl FnMut(&mut Vector, DAffine2)) {
|
||||
let transform = self.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>();
|
||||
f(self.element_mut(), transform);
|
||||
}
|
||||
|
||||
fn set_vector_paint(&mut self, key: &str, paint: List<Graphic>) {
|
||||
self.set_attribute(key, paint);
|
||||
fn set_vector_paint<A: Attr<Value = List<Graphic>>>(&mut self, paint: List<Graphic>) {
|
||||
self.set_attr::<A>(paint);
|
||||
}
|
||||
}
|
||||
|
||||
impl VectorItemMut for Item<Graphic> {
|
||||
fn for_each_vector_mut(&mut self, mut f: impl FnMut(&mut Vector, DAffine2)) {
|
||||
let Some(vector_list) = self.element_mut().as_vector_mut() else { return };
|
||||
let (elements, transforms) = vector_list.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
|
||||
let (elements, transforms) = vector_list.element_and_attr_slices_mut::<attr::Transform>();
|
||||
for (vector, transform) in elements.iter_mut().zip(transforms.iter()) {
|
||||
f(vector, *transform);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_vector_paint(&mut self, key: &str, paint: List<Graphic>) {
|
||||
fn set_vector_paint<A: Attr<Value = List<Graphic>>>(&mut self, paint: List<Graphic>) {
|
||||
let Some(vector_list) = self.element_mut().as_vector_mut() else { return };
|
||||
for slot in vector_list.iter_attribute_values_mut_or_default::<List<Graphic>>(key) {
|
||||
for slot in vector_list.iter_attr_values_mut_or_default::<A>() {
|
||||
*slot = paint.clone();
|
||||
}
|
||||
}
|
||||
@@ -161,10 +159,10 @@ where
|
||||
let paint = List::new_from_element(color).into_graphic_list();
|
||||
|
||||
if fill {
|
||||
set_paint_attribute_at(vector_list, index, ATTR_FILL, paint.clone());
|
||||
set_paint_attribute_at::<graphic_types::attr::Fill, _>(vector_list, index, paint.clone());
|
||||
}
|
||||
if stroke && vector_list.element(index).is_some_and(|vector| vector.stroke.is_some()) {
|
||||
set_paint_attribute_at(vector_list, index, ATTR_STROKE, paint.clone());
|
||||
set_paint_attribute_at::<graphic_types::attr::Stroke, _>(vector_list, index, paint.clone());
|
||||
}
|
||||
|
||||
i += 1;
|
||||
@@ -208,19 +206,19 @@ where
|
||||
for graphic in fill.iter_element_values_mut() {
|
||||
let Graphic::Gradient(gradient) = graphic else { continue };
|
||||
|
||||
if gradient.iter_attribute_values::<GradientType>(ATTR_GRADIENT_TYPE).is_none() {
|
||||
for value in gradient.iter_attribute_values_mut_or_default::<GradientType>(ATTR_GRADIENT_TYPE) {
|
||||
if gradient.iter_attr_values::<vector_types::attr::GradientType>().is_none() {
|
||||
for value in gradient.iter_attr_values_mut_or_default::<vector_types::attr::GradientType>() {
|
||||
*value = _gradient_type;
|
||||
}
|
||||
}
|
||||
|
||||
if gradient.iter_attribute_values::<GradientSpreadMethod>(ATTR_SPREAD_METHOD).is_none() {
|
||||
for value in gradient.iter_attribute_values_mut_or_default::<GradientSpreadMethod>(ATTR_SPREAD_METHOD) {
|
||||
if gradient.iter_attr_values::<vector_types::attr::SpreadMethod>().is_none() {
|
||||
for value in gradient.iter_attr_values_mut_or_default::<vector_types::attr::SpreadMethod>() {
|
||||
*value = _spread_method;
|
||||
}
|
||||
}
|
||||
|
||||
if gradient.iter_attribute_values::<DAffine2>(ATTR_TRANSFORM).is_none() {
|
||||
if gradient.iter_attr_values::<attr::Transform>().is_none() {
|
||||
// Without an explicit placement, derive one covering the paint target's bounding box (the CSS `auto` behavior)
|
||||
let transform = if _has_transform {
|
||||
_transform
|
||||
@@ -246,13 +244,13 @@ where
|
||||
initial_gradient_transform_for_bounding_box([min, max])
|
||||
};
|
||||
|
||||
for value in gradient.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
for value in gradient.iter_attr_values_mut_or_default::<attr::Transform>() {
|
||||
*value = transform;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content.set_vector_paint(ATTR_FILL, fill);
|
||||
content.set_vector_paint::<graphic_types::attr::Fill>(fill);
|
||||
content
|
||||
}
|
||||
|
||||
@@ -326,7 +324,7 @@ where
|
||||
});
|
||||
|
||||
let paint = paint.into_graphic_list();
|
||||
content.set_vector_paint(ATTR_STROKE, paint);
|
||||
content.set_vector_paint::<graphic_types::attr::Stroke>(paint);
|
||||
content
|
||||
}
|
||||
|
||||
@@ -388,7 +386,7 @@ async fn copy_to_points<I: 'n + Send + Clone>(
|
||||
let do_scale = random_scale_difference.abs() > 1e-6;
|
||||
let do_rotation = random_rotation.abs() > 1e-6;
|
||||
|
||||
let points_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let points_transform: DAffine2 = row.attr_cloned_or_default::<attr::Transform>();
|
||||
for &point in row.element().point_domain.positions() {
|
||||
let translation = points_transform.transform_point2(point);
|
||||
|
||||
@@ -417,8 +415,8 @@ async fn copy_to_points<I: 'n + Send + Clone>(
|
||||
|
||||
for row_index in 0..content.len() {
|
||||
let Some(mut row) = content.clone_item(row_index) else { continue };
|
||||
let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
row.set_attribute(ATTR_TRANSFORM, transform * row_transform);
|
||||
let row_transform: DAffine2 = row.attr_cloned_or_default::<attr::Transform>();
|
||||
row.set_attr::<attr::Transform>(transform * row_transform);
|
||||
|
||||
result_list.push(row);
|
||||
}
|
||||
@@ -446,7 +444,7 @@ async fn round_corners(
|
||||
min_angle_threshold: Item<Angle>,
|
||||
) -> Item<Vector> {
|
||||
let (radius, roundness, edge_length_limit, min_angle_threshold) = (*radius.element(), *roundness.element(), *edge_length_limit.element(), *min_angle_threshold.element());
|
||||
let source_transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let source_transform: DAffine2 = source.attr_cloned_or_default::<attr::Transform>();
|
||||
let source_transform_inverse = source_transform.inverse();
|
||||
let (source, attributes) = source.into_parts();
|
||||
|
||||
@@ -550,7 +548,7 @@ pub fn merge_by_distance(
|
||||
|
||||
match algorithm {
|
||||
MergeByDistanceAlgorithm::Spatial => {
|
||||
let transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform: DAffine2 = content.attr_cloned_or_default::<attr::Transform>();
|
||||
content.element_mut().merge_by_distance_spatial(transform, distance);
|
||||
}
|
||||
MergeByDistanceAlgorithm::Topological => content.element_mut().merge_by_distance_topological(distance),
|
||||
@@ -767,12 +765,12 @@ async fn extrude(_: impl Ctx, source: Item<Vector>, direction: Item<DVec2>, join
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
async fn box_warp(_: impl Ctx, content: Item<Vector>, #[expose] rectangle: Item<Vector>) -> Item<Vector> {
|
||||
let target_transform: DAffine2 = rectangle.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let target_transform: DAffine2 = rectangle.attr_cloned_or_default::<attr::Transform>();
|
||||
let target = rectangle.into_element();
|
||||
|
||||
let mut row = content;
|
||||
{
|
||||
let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform: DAffine2 = row.attr_cloned_or_default::<attr::Transform>();
|
||||
let vector = std::mem::take(row.element_mut());
|
||||
|
||||
// Get the bounding box of the source vector geometry
|
||||
@@ -832,7 +830,7 @@ async fn box_warp(_: impl Ctx, content: Item<Vector>, #[expose] rectangle: Item<
|
||||
|
||||
// Reset the transform since we've applied it directly to the points
|
||||
*row.element_mut() = result;
|
||||
row.set_attribute(ATTR_TRANSFORM, DAffine2::IDENTITY);
|
||||
row.set_attr::<attr::Transform>(DAffine2::IDENTITY);
|
||||
}
|
||||
row
|
||||
}
|
||||
@@ -942,8 +940,8 @@ where
|
||||
RowsOrColumns::Rows => DVec2::new(strip.along_position, strip.cross_position),
|
||||
RowsOrColumns::Columns => DVec2::new(strip.cross_position, strip.along_position),
|
||||
};
|
||||
let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
row.set_attribute(ATTR_TRANSFORM, DAffine2::from_translation(target_position - top_left) * row_transform);
|
||||
let row_transform: DAffine2 = row.attr_cloned_or_default::<attr::Transform>();
|
||||
row.set_attr::<attr::Transform>(DAffine2::from_translation(target_position - top_left) * row_transform);
|
||||
|
||||
strip.along_position += along + separation;
|
||||
} else {
|
||||
@@ -954,8 +952,8 @@ where
|
||||
RowsOrColumns::Rows => DVec2::new(0., new_cross),
|
||||
RowsOrColumns::Columns => DVec2::new(new_cross, 0.),
|
||||
};
|
||||
let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
row.set_attribute(ATTR_TRANSFORM, DAffine2::from_translation(target_position - top_left) * row_transform);
|
||||
let row_transform: DAffine2 = row.attr_cloned_or_default::<attr::Transform>();
|
||||
row.set_attr::<attr::Transform>(DAffine2::from_translation(target_position - top_left) * row_transform);
|
||||
|
||||
strips.push(Strip {
|
||||
along_position: along + separation,
|
||||
@@ -986,7 +984,7 @@ async fn auto_tangents(
|
||||
) -> Item<Vector> {
|
||||
let (spread, preserve_existing) = (*spread.element(), *preserve_existing.element());
|
||||
|
||||
let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform: DAffine2 = source.attr_cloned_or_default::<attr::Transform>();
|
||||
let (source, attributes) = source.into_parts();
|
||||
|
||||
let mut result = Vector {
|
||||
@@ -1146,7 +1144,7 @@ async fn bounding_box(_: impl Ctx, content: Item<Vector>) -> Item<Vector> {
|
||||
async fn dimensions(_: impl Ctx, content: Item<Vector>) -> Item<DVec2> {
|
||||
let dimensions = content
|
||||
.element()
|
||||
.bounding_box_with_transform(content.attribute_cloned_or_default(ATTR_TRANSFORM))
|
||||
.bounding_box_with_transform(content.attr_cloned_or_default::<attr::Transform>())
|
||||
.map(|[top_left, bottom_right]| bottom_right - top_left)
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -1358,7 +1356,7 @@ async fn offset_path(_: impl Ctx, content: Item<Vector>, distance: Item<f64>, jo
|
||||
let mut content = content;
|
||||
let (distance, join, miter_limit) = (*distance.element(), *join.element(), *miter_limit.element());
|
||||
|
||||
let transform_attribute: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform_attribute: DAffine2 = content.attr_cloned_or_default::<attr::Transform>();
|
||||
let transform = Affine::new(transform_attribute.to_cols_array());
|
||||
let vector = std::mem::take(content.element_mut());
|
||||
|
||||
@@ -1406,7 +1404,7 @@ where
|
||||
let flattened: List<Vector> = graphic_list.clone().into_flattened_list();
|
||||
|
||||
// A fill exists when the canonical attribute carries paint
|
||||
let has_fills: Vec<bool> = (0..flattened.len()).map(|index| has_paint_at(&flattened, index, ATTR_FILL)).collect();
|
||||
let has_fills: Vec<bool> = (0..flattened.len()).map(|index| has_paint_at::<graphic_types::attr::Fill>(&flattened, index)).collect();
|
||||
|
||||
let mut output: List<Vector> = flattened
|
||||
.into_iter()
|
||||
@@ -1466,14 +1464,14 @@ where
|
||||
vector.stroke = None;
|
||||
let mut fill_attributes = attributes.clone();
|
||||
// No stroke remains on the fill row
|
||||
fill_attributes.remove::<List<Graphic>>(ATTR_STROKE);
|
||||
fill_attributes.remove_attr::<graphic_types::attr::Stroke>();
|
||||
Item::from_parts(vector, fill_attributes)
|
||||
});
|
||||
|
||||
let mut stroke_attributes = attributes;
|
||||
// Drop the original fill and use the stroke paint to fill the outlined stroke
|
||||
stroke_attributes.remove::<List<Graphic>>(ATTR_FILL);
|
||||
stroke_attributes.rename(ATTR_STROKE, ATTR_FILL);
|
||||
stroke_attributes.remove_attr::<graphic_types::attr::Fill>();
|
||||
stroke_attributes.rename(graphic_types::attr::Stroke::name(), graphic_types::attr::Fill::name());
|
||||
|
||||
let stroke_row = Item::from_parts(solidified_stroke, stroke_attributes);
|
||||
|
||||
@@ -1492,15 +1490,15 @@ where
|
||||
// already holds the original transforms; pre-compensate by row 0's inverse so the renderer's
|
||||
// `upstream_footprint *= row_0_transform` recursion cancels out and leaves the originals intact.
|
||||
let mut graphic_list = graphic_list;
|
||||
let row_0_transform: DAffine2 = output.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
let row_0_transform: DAffine2 = output.attr_cloned_or_default::<attr::Transform>(0);
|
||||
if row_0_transform.matrix2.determinant().abs() > f64::EPSILON {
|
||||
let inverse = row_0_transform.inverse();
|
||||
for transform in graphic_list.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
for transform in graphic_list.iter_attr_values_mut_or_default::<attr::Transform>() {
|
||||
*transform = inverse * *transform;
|
||||
}
|
||||
}
|
||||
|
||||
output.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, graphic_list);
|
||||
output.set_attr::<graphic_types::attr::editor::MergedLayers>(0, graphic_list);
|
||||
}
|
||||
|
||||
output
|
||||
@@ -1574,14 +1572,14 @@ pub async fn combine_paths<T: IntoGraphicList>(_: impl Ctx, #[implementations(Li
|
||||
// Concatenate every vector element's subpaths into the single output compound path
|
||||
for index in 0..flattened.len() {
|
||||
let Some(element) = flattened.element(index) else { continue };
|
||||
let layer_path: List<NodeId> = flattened.attribute_cloned_or_default::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, index).0;
|
||||
let layer_path: List<NodeId> = flattened.attr_cloned_or_default::<attr::editor::LayerPath>(index).0;
|
||||
let node_id = layer_path.iter_element_values().next_back().map(|node_id| node_id.0).unwrap_or_default();
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
(index, node_id).hash(&mut hasher);
|
||||
let collision_hash_seed = hasher.finish();
|
||||
|
||||
let source_transform = flattened.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let source_transform = flattened.attr_cloned_or_default::<attr::Transform>(index);
|
||||
output.concat(element, source_transform, collision_hash_seed);
|
||||
|
||||
// TODO: Make this instead use the first encountered stroke
|
||||
@@ -1595,10 +1593,10 @@ pub async fn combine_paths<T: IntoGraphicList>(_: impl Ctx, #[implementations(Li
|
||||
let source_attributes = flattened.clone_item_attributes(primary);
|
||||
let mut attributes = ItemAttributeValues::new();
|
||||
|
||||
attributes.insert_cloned_from(&source_attributes, ATTR_FILL);
|
||||
attributes.insert_cloned_from(&source_attributes, ATTR_STROKE);
|
||||
attributes.insert_cloned_from(&source_attributes, graphic_types::attr::Fill::name());
|
||||
attributes.insert_cloned_from(&source_attributes, graphic_types::attr::Stroke::name());
|
||||
// Adopt the last input item's layer (if any) so the editor can also bucket clicks under a contributing child layer
|
||||
attributes.insert_cloned_from(&source_attributes, ATTR_EDITOR_LAYER_PATH);
|
||||
attributes.insert_cloned_from(&source_attributes, attr::editor::LayerPath::name());
|
||||
bake_paint_transforms(&mut attributes, source_transform);
|
||||
|
||||
let output = std::mem::take(output_list.element_mut(0).unwrap());
|
||||
@@ -1608,7 +1606,7 @@ pub async fn combine_paths<T: IntoGraphicList>(_: impl Ctx, #[implementations(Li
|
||||
// Preserve a reference to the original upstream `List<Graphic>` so the renderer can recurse into it
|
||||
// when collecting metadata, exposing the original child layers' click targets to editor tools.
|
||||
// This is the same mechanism Boolean Operation uses to keep its inputs editable after the merge.
|
||||
output_list.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, graphic_list);
|
||||
output_list.set_attr::<graphic_types::attr::editor::MergedLayers>(0, graphic_list);
|
||||
|
||||
output_list.into_iter().next().unwrap_or_default()
|
||||
}
|
||||
@@ -1654,12 +1652,12 @@ async fn sample_polyline(
|
||||
stroke: std::mem::take(&mut content.element_mut().stroke),
|
||||
};
|
||||
// Transfer the stroke transform from the input vector content to the result.
|
||||
result.set_stroke_transform(content.attribute_cloned_or_default(ATTR_TRANSFORM));
|
||||
result.set_stroke_transform(content.attr_cloned_or_default::<attr::Transform>());
|
||||
|
||||
for local_bezpath in content.element().stroke_bezpath_iter() {
|
||||
// Apply the transform to compute sample locations in world space (for correct distance-based spacing)
|
||||
let mut world_bezpath = local_bezpath.clone();
|
||||
let transform_attribute: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform_attribute: DAffine2 = content.attr_cloned_or_default::<attr::Transform>();
|
||||
world_bezpath.apply_affine(Affine::new(transform_attribute.to_cols_array()));
|
||||
|
||||
// Per-segment perimeter lengths (transform-baked) for distance-based spacing
|
||||
@@ -1718,7 +1716,7 @@ async fn simplify(
|
||||
|
||||
let options = SimplifyOptions::default();
|
||||
|
||||
let transform_attribute: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform_attribute: DAffine2 = content.attr_cloned_or_default::<attr::Transform>();
|
||||
let transform = Affine::new(transform_attribute.to_cols_array());
|
||||
let inverse_transform = transform.inverse();
|
||||
|
||||
@@ -1812,7 +1810,7 @@ async fn decimate(
|
||||
points.iter().enumerate().filter(|(i, _)| keep[*i]).map(|(_, p)| *p).collect()
|
||||
}
|
||||
|
||||
let transform_attribute: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform_attribute: DAffine2 = content.attr_cloned_or_default::<attr::Transform>();
|
||||
let transform = Affine::new(transform_attribute.to_cols_array());
|
||||
let inverse_transform = transform.inverse();
|
||||
|
||||
@@ -1992,7 +1990,7 @@ async fn position_on_path(
|
||||
let (progression, reverse, parameterized_distance) = (progression.into_element(), reverse.into_element(), parameterized_distance.into_element());
|
||||
let euclidian = !parameterized_distance;
|
||||
|
||||
let transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform: DAffine2 = content.attr_cloned_or_default::<attr::Transform>();
|
||||
let mut bezpaths: Vec<_> = content.element().stroke_bezpath_iter().map(|bezpath| (bezpath, transform)).collect();
|
||||
let bezpath_count = bezpaths.len() as f64;
|
||||
let progression = progression.clamp(0., bezpath_count);
|
||||
@@ -2031,7 +2029,7 @@ async fn tangent_on_path(
|
||||
let (progression, reverse, parameterized_distance, radians) = (progression.into_element(), reverse.into_element(), parameterized_distance.into_element(), radians.into_element());
|
||||
let euclidian = !parameterized_distance;
|
||||
|
||||
let transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform: DAffine2 = content.attr_cloned_or_default::<attr::Transform>();
|
||||
let mut bezpaths: Vec<_> = content.element().stroke_bezpath_iter().map(|bezpath| (bezpath, transform)).collect();
|
||||
let bezpath_count = bezpaths.len() as f64;
|
||||
let progression = progression.clamp(0., bezpath_count);
|
||||
@@ -2222,7 +2220,7 @@ async fn jitter_points(
|
||||
let (max_distance, seed, along_normals) = (*max_distance.element(), *seed.element(), *along_normals.element());
|
||||
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
||||
let transform_attribute: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform_attribute: DAffine2 = content.attr_cloned_or_default::<attr::Transform>();
|
||||
let inverse_linear = inverse_linear_or_repair(transform_attribute.matrix2);
|
||||
|
||||
let deltas: Vec<_> = (0..content.element().point_domain.positions().len())
|
||||
@@ -2247,7 +2245,7 @@ async fn jitter_points(
|
||||
})
|
||||
.collect();
|
||||
|
||||
let transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform: DAffine2 = content.attr_cloned_or_default::<attr::Transform>();
|
||||
apply_point_deltas(content.element_mut(), &deltas, transform);
|
||||
|
||||
content
|
||||
@@ -2267,7 +2265,7 @@ async fn offset_points(
|
||||
) -> Item<Vector> {
|
||||
let mut content = content;
|
||||
let distance = *distance.element();
|
||||
let transform_attribute: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform_attribute: DAffine2 = content.attr_cloned_or_default::<attr::Transform>();
|
||||
let inverse_linear = inverse_linear_or_repair(transform_attribute.matrix2);
|
||||
|
||||
let deltas: Vec<_> = (0..content.element().point_domain.positions().len())
|
||||
@@ -2285,7 +2283,7 @@ async fn offset_points(
|
||||
})
|
||||
.collect();
|
||||
|
||||
let transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform: DAffine2 = content.attr_cloned_or_default::<attr::Transform>();
|
||||
apply_point_deltas(content.element_mut(), &deltas, transform);
|
||||
|
||||
content
|
||||
@@ -2409,8 +2407,8 @@ async fn morph<I: IntoGraphicList>(
|
||||
}
|
||||
|
||||
fn lerp_gradient_transform(gradient_list_a: &List<Gradient>, gradient_list_b: &List<Gradient>, time: f64) -> DAffine2 {
|
||||
let transform_a = gradient_list_a.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0);
|
||||
let transform_b = gradient_list_b.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0);
|
||||
let transform_a = gradient_list_a.attr_cloned_or_default::<attr::Transform>(0);
|
||||
let transform_b = gradient_list_b.attr_cloned_or_default::<attr::Transform>(0);
|
||||
|
||||
let start_a = transform_a.translation;
|
||||
let end_a = transform_a.translation + transform_a.matrix2.x_axis;
|
||||
@@ -2470,7 +2468,7 @@ async fn morph<I: IntoGraphicList>(
|
||||
let metadata_source = if time < 0.5 { gradient_list_a } else { gradient_list_b };
|
||||
|
||||
let mut gradient_list = metadata_source.clone();
|
||||
gradient_list.set_attribute(ATTR_TRANSFORM, 0, lerp_gradient_transform(gradient_list_a, gradient_list_b, time));
|
||||
gradient_list.set_attr::<attr::Transform>(0, lerp_gradient_transform(gradient_list_a, gradient_list_b, time));
|
||||
|
||||
gradient_with_stops(gradient_list, stops)
|
||||
}),
|
||||
@@ -2499,7 +2497,7 @@ async fn morph<I: IntoGraphicList>(
|
||||
let default_polyline = || {
|
||||
let mut default_path = BezPath::new();
|
||||
for index in 0..content.len() {
|
||||
let transform_attribute: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let transform_attribute: DAffine2 = content.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let origin = transform_attribute.translation;
|
||||
let point = kurbo::Point::new(origin.x, origin.y);
|
||||
if index == 0 {
|
||||
@@ -2513,7 +2511,7 @@ async fn morph<I: IntoGraphicList>(
|
||||
|
||||
let control_bezpaths: Vec<BezPath> = {
|
||||
// User-provided path: collect all subpaths with the path's transform applied
|
||||
let path_transform: DAffine2 = path.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let path_transform: DAffine2 = path.attr_cloned_or_default::<attr::Transform>();
|
||||
let paths: Vec<BezPath> = path
|
||||
.element()
|
||||
.stroke_bezpath_iter()
|
||||
@@ -2585,8 +2583,8 @@ async fn morph<I: IntoGraphicList>(
|
||||
if content.element(source_index).is_none() || content.element(target_index).is_none() {
|
||||
return 0.;
|
||||
}
|
||||
let source_transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, source_index);
|
||||
let target_transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, target_index);
|
||||
let source_transform: DAffine2 = content.attr_cloned_or_default::<attr::Transform>(source_index);
|
||||
let target_transform: DAffine2 = content.attr_cloned_or_default::<attr::Transform>(target_index);
|
||||
let (s_angle, s_scale, s_skew) = source_transform.decompose_rotation_scale_skew();
|
||||
let (t_angle, t_scale, t_skew) = target_transform.decompose_rotation_scale_skew();
|
||||
|
||||
@@ -2658,14 +2656,14 @@ async fn morph<I: IntoGraphicList>(
|
||||
};
|
||||
|
||||
// Lerp blending attributes: opacity/fill interpolate, blend_mode/clip step at the midpoint
|
||||
let source_blend_mode: BlendMode = content.attribute_cloned_or_default(ATTR_BLEND_MODE, source_index);
|
||||
let target_blend_mode: BlendMode = content.attribute_cloned_or_default(ATTR_BLEND_MODE, target_index);
|
||||
let source_opacity: f64 = content.attribute_cloned_or(ATTR_OPACITY, source_index, 1.);
|
||||
let target_opacity: f64 = content.attribute_cloned_or(ATTR_OPACITY, target_index, 1.);
|
||||
let source_fill: f64 = content.attribute_cloned_or(ATTR_OPACITY_FILL, source_index, 1.);
|
||||
let target_fill: f64 = content.attribute_cloned_or(ATTR_OPACITY_FILL, target_index, 1.);
|
||||
let source_clip: bool = content.attribute_cloned_or_default(ATTR_CLIPPING_MASK, source_index);
|
||||
let target_clip: bool = content.attribute_cloned_or_default(ATTR_CLIPPING_MASK, target_index);
|
||||
let source_blend_mode: BlendMode = content.attr_cloned_or_default::<attr::BlendMode>(source_index);
|
||||
let target_blend_mode: BlendMode = content.attr_cloned_or_default::<attr::BlendMode>(target_index);
|
||||
let source_opacity: f64 = content.attr_cloned_or_default::<attr::Opacity>(source_index);
|
||||
let target_opacity: f64 = content.attr_cloned_or_default::<attr::Opacity>(target_index);
|
||||
let source_fill: f64 = content.attr_cloned_or_default::<attr::OpacityFill>(source_index);
|
||||
let target_fill: f64 = content.attr_cloned_or_default::<attr::OpacityFill>(target_index);
|
||||
let source_clip: bool = content.attr_cloned_or_default::<attr::ClippingMask>(source_index);
|
||||
let target_clip: bool = content.attr_cloned_or_default::<attr::ClippingMask>(target_index);
|
||||
|
||||
let lerped_blend_mode = if time < 0.5 { source_blend_mode } else { target_blend_mode };
|
||||
let lerped_opacity = source_opacity + (target_opacity - source_opacity) * time;
|
||||
@@ -2687,8 +2685,8 @@ async fn morph<I: IntoGraphicList>(
|
||||
// This decomposition must match the one used in Stroke::lerp so the renderer's stroke_transform.inverse()
|
||||
// correctly cancels the element transform, keeping the stroke uniform when Stroke is after Transform.
|
||||
let lerped_transform = {
|
||||
let source_transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, source_index);
|
||||
let target_transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, target_index);
|
||||
let source_transform: DAffine2 = content.attr_cloned_or_default::<attr::Transform>(source_index);
|
||||
let target_transform: DAffine2 = content.attr_cloned_or_default::<attr::Transform>(target_index);
|
||||
let (s_angle, s_scale, s_skew) = source_transform.decompose_rotation_scale_skew();
|
||||
let (t_angle, t_scale, t_skew) = target_transform.decompose_rotation_scale_skew();
|
||||
|
||||
@@ -2717,7 +2715,7 @@ async fn morph<I: IntoGraphicList>(
|
||||
// in which case we skip pre-compensation to avoid propagating NaN through merged_layers transforms.
|
||||
if lerped_transform.matrix2.determinant().abs() > f64::EPSILON {
|
||||
let lerped_inverse = lerped_transform.inverse();
|
||||
for transform in graphic_list_content.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
for transform in graphic_list_content.iter_attr_values_mut_or_default::<attr::Transform>() {
|
||||
*transform = lerped_inverse * *transform;
|
||||
}
|
||||
}
|
||||
@@ -2729,8 +2727,8 @@ async fn morph<I: IntoGraphicList>(
|
||||
let endpoint_element = content.element(endpoint_index).unwrap();
|
||||
|
||||
let mut attributes = content.clone_item_attributes(endpoint_index);
|
||||
attributes.insert(ATTR_TRANSFORM, lerped_transform);
|
||||
attributes.insert(ATTR_EDITOR_MERGED_LAYERS, graphic_list_content);
|
||||
attributes.set_attr::<attr::Transform>(lerped_transform);
|
||||
attributes.set_attr::<graphic_types::attr::editor::MergedLayers>(graphic_list_content);
|
||||
|
||||
return Item::from_parts(endpoint_element.clone(), attributes);
|
||||
}
|
||||
@@ -2756,13 +2754,13 @@ async fn morph<I: IntoGraphicList>(
|
||||
let mut vector = Vector { stroke, ..Default::default() };
|
||||
|
||||
let fill_paint = {
|
||||
let source = graphic_list_at(&content, source_index, ATTR_FILL);
|
||||
let target = graphic_list_at(&content, target_index, ATTR_FILL);
|
||||
let source = graphic_list_at::<graphic_types::attr::Fill>(&content, source_index);
|
||||
let target = graphic_list_at::<graphic_types::attr::Fill>(&content, target_index);
|
||||
lerp_graphic(source.as_deref(), target.as_deref(), time)
|
||||
};
|
||||
let stroke_paint = {
|
||||
let source = graphic_list_at(&content, source_index, ATTR_STROKE);
|
||||
let target = graphic_list_at(&content, target_index, ATTR_STROKE);
|
||||
let source = graphic_list_at::<graphic_types::attr::Stroke>(&content, source_index);
|
||||
let target = graphic_list_at::<graphic_types::attr::Stroke>(&content, target_index);
|
||||
lerp_graphic(source.as_deref(), target.as_deref(), time)
|
||||
};
|
||||
|
||||
@@ -2913,31 +2911,31 @@ async fn morph<I: IntoGraphicList>(
|
||||
// the click-target identity (so the editor can route clicks back to one of the contributing layers)
|
||||
let primary_index = if time < 0.5 { source_index } else { target_index };
|
||||
let mut item = Item::new_from_element(vector);
|
||||
item.set_attribute(ATTR_TRANSFORM, lerped_transform);
|
||||
item.set_attr::<attr::Transform>(lerped_transform);
|
||||
|
||||
// Propagate each blending/layer column only when the input carries it, so attribute presence stays determined by the graph rather than by runtime values
|
||||
if content.attribute::<BlendMode>(ATTR_BLEND_MODE, source_index).is_some() {
|
||||
item.set_attribute(ATTR_BLEND_MODE, lerped_blend_mode);
|
||||
if content.attr::<attr::BlendMode>(source_index).is_some() {
|
||||
item.set_attr::<attr::BlendMode>(lerped_blend_mode);
|
||||
}
|
||||
if content.attribute::<f64>(ATTR_OPACITY, source_index).is_some() {
|
||||
item.set_attribute(ATTR_OPACITY, lerped_opacity);
|
||||
if content.attr::<attr::Opacity>(source_index).is_some() {
|
||||
item.set_attr::<attr::Opacity>(lerped_opacity);
|
||||
}
|
||||
if content.attribute::<f64>(ATTR_OPACITY_FILL, source_index).is_some() {
|
||||
item.set_attribute(ATTR_OPACITY_FILL, lerped_fill);
|
||||
if content.attr::<attr::OpacityFill>(source_index).is_some() {
|
||||
item.set_attr::<attr::OpacityFill>(lerped_fill);
|
||||
}
|
||||
if content.attribute::<bool>(ATTR_CLIPPING_MASK, source_index).is_some() {
|
||||
item.set_attribute(ATTR_CLIPPING_MASK, lerped_clip);
|
||||
if content.attr::<attr::ClippingMask>(source_index).is_some() {
|
||||
item.set_attr::<attr::ClippingMask>(lerped_clip);
|
||||
}
|
||||
if let Some(layer_path) = content.attribute::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, primary_index) {
|
||||
item.set_attribute(ATTR_EDITOR_LAYER_PATH, layer_path.clone());
|
||||
if let Some(layer_path) = content.attr::<attr::editor::LayerPath>(primary_index) {
|
||||
item.set_attr::<attr::editor::LayerPath>(layer_path.clone());
|
||||
}
|
||||
item.set_attribute(ATTR_EDITOR_MERGED_LAYERS, graphic_list_content);
|
||||
item.set_attr::<graphic_types::attr::editor::MergedLayers>(graphic_list_content);
|
||||
|
||||
if let Some(fill) = fill_paint {
|
||||
item.set_attribute(ATTR_FILL, fill);
|
||||
item.set_attr::<graphic_types::attr::Fill>(fill);
|
||||
}
|
||||
if let Some(stroke) = stroke_paint {
|
||||
item.set_attribute(ATTR_STROKE, stroke);
|
||||
item.set_attr::<graphic_types::attr::Stroke>(stroke);
|
||||
}
|
||||
|
||||
item
|
||||
@@ -3217,7 +3215,7 @@ fn bevel_algorithm(mut vector: Vector, transform: DAffine2, distance: f64) -> Ve
|
||||
fn bevel(_: impl Ctx, source: Item<Vector>, #[default(10.)] distance: Item<Length>) -> Item<Vector> {
|
||||
let distance = *distance.element();
|
||||
|
||||
let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform: DAffine2 = source.attr_cloned_or_default::<attr::Transform>();
|
||||
let (element, attributes) = source.into_parts();
|
||||
|
||||
Item::from_parts(bevel_algorithm(element, transform, distance), attributes)
|
||||
@@ -3233,7 +3231,7 @@ fn close_path(_: impl Ctx, source: Item<Vector>) -> Item<Vector> {
|
||||
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
|
||||
fn point_inside(_: impl Ctx, source: Item<Vector>, point: Item<DVec2>) -> Item<bool> {
|
||||
let point = point.into_element();
|
||||
let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform: DAffine2 = source.attr_cloned_or_default::<attr::Transform>();
|
||||
let inside = source.element().check_point_inside_shape(transform, point);
|
||||
|
||||
Item::new_from_element(inside)
|
||||
@@ -3292,7 +3290,7 @@ async fn index_points(
|
||||
|
||||
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
|
||||
async fn path_length(_: impl Ctx, source: Item<Vector>) -> Item<f64> {
|
||||
let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform: DAffine2 = source.attr_cloned_or_default::<attr::Transform>();
|
||||
let length = source
|
||||
.element()
|
||||
.stroke_bezpath_iter()
|
||||
@@ -3310,7 +3308,7 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node<Cont
|
||||
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
|
||||
let vector = content.eval(new_ctx).await;
|
||||
|
||||
let transform: DAffine2 = vector.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform: DAffine2 = vector.attr_cloned_or_default::<attr::Transform>();
|
||||
let area_scale = transform.matrix2.determinant().abs();
|
||||
let area = vector.element().stroke_bezpath_iter().map(|subpath| subpath.area() * area_scale).sum::<f64>();
|
||||
|
||||
@@ -3323,7 +3321,7 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node<
|
||||
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
|
||||
let vector = content.eval(new_ctx).await;
|
||||
|
||||
let transform: DAffine2 = vector.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform: DAffine2 = vector.attr_cloned_or_default::<attr::Transform>();
|
||||
let position = element_centroid(vector.element(), transform, centroid_type);
|
||||
|
||||
Item::new_from_element(position)
|
||||
@@ -3390,7 +3388,7 @@ mod test {
|
||||
fn create_vector_item(bezpath: BezPath, transform: DAffine2) -> Item<Vector> {
|
||||
let mut row = Vector::default();
|
||||
row.append_bezpath(bezpath);
|
||||
Item::new_from_element(row).with_attribute(ATTR_TRANSFORM, transform)
|
||||
Item::new_from_element(row).with_attr::<attr::Transform>(transform)
|
||||
}
|
||||
|
||||
fn item<T>(value: T) -> Item<T> {
|
||||
@@ -3559,7 +3557,7 @@ mod test {
|
||||
// Test a rectangular path with non-zero rotation
|
||||
let square = Vector::from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY));
|
||||
let mut square = List::new_from_element(square);
|
||||
square.with_attribute_mut_or_default(ATTR_TRANSFORM, 0, |t: &mut DAffine2| *t *= DAffine2::from_angle(std::f64::consts::FRAC_PI_4));
|
||||
square.with_attr_mut_or_default::<attr::Transform, _, _>(0, |t| *t *= DAffine2::from_angle(std::f64::consts::FRAC_PI_4));
|
||||
let bounding_box = BoundingBoxNodeMapped { content: FutureWrapperNode(square) }.eval(Footprint::default()).await;
|
||||
let bounding_box = bounding_box.element(0).unwrap();
|
||||
assert_eq!(bounding_box.region_manipulator_groups().count(), 1);
|
||||
@@ -3692,7 +3690,7 @@ mod test {
|
||||
async fn morph() {
|
||||
let mut rectangles = vector_node_from_bezpath(Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY));
|
||||
let mut second_rectangle = rectangles.clone_item(0).unwrap();
|
||||
*second_rectangle.attribute_mut_or_insert_default::<DAffine2>(ATTR_TRANSFORM) *= DAffine2::from_translation((-100., -100.).into());
|
||||
*second_rectangle.attr_mut_or_insert_default::<attr::Transform>() *= DAffine2::from_translation((-100., -100.).into());
|
||||
rectangles.push(second_rectangle);
|
||||
|
||||
let morphed = super::morph(
|
||||
@@ -3712,7 +3710,7 @@ mod test {
|
||||
vec![DVec2::new(0., 0.), DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]
|
||||
);
|
||||
// The interpolated transform carries the midpoint translation (approximate due to arc-length parameterization)
|
||||
assert!((morphed.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0).translation - DVec2::new(-50., -50.)).length() < 1e-3);
|
||||
assert!((morphed.attr_cloned_or_default::<attr::Transform>(0).translation - DVec2::new(-50., -50.)).length() < 1e-3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -3724,11 +3722,11 @@ mod test {
|
||||
};
|
||||
|
||||
let item_a = Item::new_from_element(rect())
|
||||
.with_attribute(ATTR_TRANSFORM, DAffine2::IDENTITY)
|
||||
.with_attribute(ATTR_FILL, List::new_from_element(Color::RED).into_graphic_list());
|
||||
.with_attr::<attr::Transform>(DAffine2::IDENTITY)
|
||||
.with_attr::<graphic_types::attr::Fill>(List::new_from_element(Color::RED).into_graphic_list());
|
||||
let item_b = Item::new_from_element(rect())
|
||||
.with_attribute(ATTR_TRANSFORM, DAffine2::from_translation((-100., -100.).into()))
|
||||
.with_attribute(ATTR_FILL, List::new_from_element(Color::BLUE).into_graphic_list());
|
||||
.with_attr::<attr::Transform>(DAffine2::from_translation((-100., -100.).into()))
|
||||
.with_attr::<graphic_types::attr::Fill>(List::new_from_element(Color::BLUE).into_graphic_list());
|
||||
|
||||
let mut content = List::new_from_item(item_a);
|
||||
content.push(item_b);
|
||||
@@ -3744,7 +3742,7 @@ mod test {
|
||||
.await;
|
||||
let morphed = List::new_from_item(morphed);
|
||||
|
||||
let fill = graphic_list_at(&morphed, 0, ATTR_FILL).expect("Morph should keep the fill paint at the midpoint");
|
||||
let fill = graphic_list_at::<graphic_types::attr::Fill>(&morphed, 0).expect("Morph should keep the fill paint at the midpoint");
|
||||
|
||||
// Interpolated color between red and blue should have >0 value on both R and B
|
||||
let Some(Graphic::Color(colors)) = fill.element(0) else {
|
||||
@@ -3825,7 +3823,7 @@ mod test {
|
||||
source.push(curve.as_path_el());
|
||||
|
||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));
|
||||
let vector_item = Item::new_from_element(Vector::from_bezpath(source)).with_attribute(ATTR_TRANSFORM, transform);
|
||||
let vector_item = Item::new_from_element(Vector::from_bezpath(source)).with_attr::<attr::Transform>(transform);
|
||||
|
||||
let beveled = super::bevel((), vector_item, Item::new_from_element(2_f64.sqrt() * 100.));
|
||||
let beveled = beveled.element();
|
||||
|
||||
Reference in New Issue
Block a user