mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 10:58:04 +08:00
Cleanup
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
use core_types::arena::{Arena, ArenaCell};
|
||||
use core_types::context::{Ctx, CtxSnapshot, DeriveCtx, ExtractAll};
|
||||
use core_types::frame_table::{FrameTable, Lookup};
|
||||
use core_types::gnode::GNode;
|
||||
use core_types::gpoll::{Extent, Finality, GPoll, Interrupt};
|
||||
use core_types::graphene_hash::CacheHash;
|
||||
use core_types::memo::*;
|
||||
use core_types::node::Node;
|
||||
use core_types::registry::cache_key;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
@@ -35,7 +35,7 @@ fn memoize<I: CacheHash, T: Clone>(input: I, #[data] cache: Arc<Mutex<Option<(u6
|
||||
fn memoize_extent<C, T, NodeContent>(node: &MemoizeNode<T, NodeContent>, ctx: &C) -> GPoll<Extent>
|
||||
where
|
||||
T: Clone,
|
||||
NodeContent: GNode<C, Output = T>,
|
||||
NodeContent: Node<C, Output = T>,
|
||||
{
|
||||
node.content.extent(ctx)
|
||||
}
|
||||
@@ -71,7 +71,7 @@ fn frame_memo<'e, T: Clone + 'static>(ctx: impl Ctx + CacheHash + ExtractArena<'
|
||||
fn frame_memo_extent<C, T, NodeContent>(node: &FrameMemoNode<T, NodeContent>, ctx: &C) -> GPoll<Extent>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
NodeContent: GNode<C, Output = T>,
|
||||
NodeContent: Node<C, Output = T>,
|
||||
{
|
||||
node.content.extent(ctx)
|
||||
}
|
||||
@@ -129,12 +129,12 @@ mod tests {
|
||||
use core_types::Type;
|
||||
use core_types::concrete;
|
||||
use core_types::context::{ContextImpl, EvalScope};
|
||||
use core_types::registry::{EdgeHandle, ErasedGNode, ErasedLendGNode};
|
||||
use core_types::registry::{EdgeHandle, ErasedLendNode, ErasedNode};
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
struct CountingNode(AtomicU32);
|
||||
|
||||
impl<Input> GNode<Input> for CountingNode {
|
||||
impl<Input> Node<Input> for CountingNode {
|
||||
type Output = u32;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<u32> {
|
||||
@@ -144,7 +144,7 @@ mod tests {
|
||||
|
||||
struct PartialCountingNode(AtomicU32);
|
||||
|
||||
impl<Input> GNode<Input> for PartialCountingNode {
|
||||
impl<Input> Node<Input> for PartialCountingNode {
|
||||
type Output = u32;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<u32> {
|
||||
@@ -154,7 +154,7 @@ mod tests {
|
||||
|
||||
struct ValueNode<T>(T);
|
||||
|
||||
impl<T: Clone, Input> GNode<Input> for ValueNode<T> {
|
||||
impl<T: Clone, Input> Node<Input> for ValueNode<T> {
|
||||
type Output = T;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<T> {
|
||||
@@ -173,7 +173,7 @@ mod tests {
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let handle = EdgeHandle::new(Arc::new(MonitorNode::new(ValueNode(11u32))) as Arc<ErasedGNode<u32>>);
|
||||
let handle = EdgeHandle::new(Arc::new(MonitorNode::new(ValueNode(11u32))) as Arc<ErasedNode<u32>>);
|
||||
assert!(handle.serialize().is_none(), "no record before the first eval");
|
||||
|
||||
let edge = handle.duplicate().downcast::<u32>().unwrap();
|
||||
@@ -233,8 +233,8 @@ mod tests {
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let edge = EdgeHandle::new(Arc::new(CountingNode(AtomicU32::new(0))) as Arc<ErasedGNode<u32>>);
|
||||
let memoized = EdgeHandle::new(Arc::new(MemoizeNode::new(edge.downcast::<u32>().unwrap())) as Arc<ErasedGNode<u32>>);
|
||||
let edge = EdgeHandle::new(Arc::new(CountingNode(AtomicU32::new(0))) as Arc<ErasedNode<u32>>);
|
||||
let memoized = EdgeHandle::new(Arc::new(MemoizeNode::new(edge.downcast::<u32>().unwrap())) as Arc<ErasedNode<u32>>);
|
||||
let stacked = MemoizeNode::new(memoized.downcast::<u32>().unwrap());
|
||||
|
||||
assert_eq!(stacked.eval(&ctx), GPoll::Final(1));
|
||||
@@ -248,8 +248,8 @@ mod tests {
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let edge = EdgeHandle::new(Arc::new(ValueNode("lent out".to_string())) as Arc<ErasedGNode<String>>);
|
||||
let lending = EdgeHandle::new_ref(Arc::new(FrameMemoNode::new(edge.downcast::<String>().unwrap())) as Arc<ErasedLendGNode<String>>);
|
||||
let edge = EdgeHandle::new(Arc::new(ValueNode("lent out".to_string())) as Arc<ErasedNode<String>>);
|
||||
let lending = EdgeHandle::new_ref(Arc::new(FrameMemoNode::new(edge.downcast::<String>().unwrap())) as Arc<ErasedLendNode<String>>);
|
||||
assert_eq!(*lending.ty(), core_types::registry::lend_edge_type::<String>());
|
||||
|
||||
let node = lending.downcast_lend::<String>().unwrap();
|
||||
|
||||
@@ -3,9 +3,6 @@ use core_types::runtime::SourceFuture;
|
||||
use core_types::{Ctx, ExtractFootprint, ops::Convert, ops::ConvertAsync, transform::Footprint};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
// Re-export TypeNode from core-types for convenience
|
||||
pub use core_types::ops::TypeNode;
|
||||
|
||||
/// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes.
|
||||
#[node_macro::node(category("General"), skip_impl)]
|
||||
fn passthrough<'i, T: 'i + Send>(_: impl Ctx, content: T) -> T {
|
||||
|
||||
@@ -190,14 +190,14 @@ mod tests {
|
||||
use super::*;
|
||||
use core_types::arena::Arena;
|
||||
use core_types::context::{ContextImpl, EvalScope, VarArgsResult};
|
||||
use core_types::gnode::GNode;
|
||||
use core_types::gpoll::GPoll;
|
||||
use core_types::node::Node;
|
||||
use core_types::{ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime};
|
||||
use graphene_application_io::TimingInformation;
|
||||
|
||||
struct ProbeNode;
|
||||
|
||||
impl<'a> GNode<ContextImpl<'a>> for ProbeNode {
|
||||
impl<'a> Node<ContextImpl<'a>> for ProbeNode {
|
||||
type Output = RenderOutput;
|
||||
|
||||
fn eval(&self, ctx: &ContextImpl<'a>) -> GPoll<RenderOutput> {
|
||||
@@ -241,7 +241,7 @@ mod tests {
|
||||
let ctx = root.with_varargs(&varargs);
|
||||
|
||||
let graph = CreateContextNode::new(ProbeNode);
|
||||
let GPoll::Final(result) = <CreateContextNode<ProbeNode> as GNode<ContextImpl>>::eval(&graph, &ctx) else {
|
||||
let GPoll::Final(result) = <CreateContextNode<ProbeNode> as Node<ContextImpl>>::eval(&graph, &ctx) else {
|
||||
panic!("create_context must complete synchronously");
|
||||
};
|
||||
assert_eq!(
|
||||
|
||||
@@ -1066,15 +1066,15 @@ mod graphene_test {
|
||||
use super::*;
|
||||
use core_types::arena::Arena;
|
||||
use core_types::context::{ContextImpl, EvalScope, ExtractIndex};
|
||||
use core_types::gnode::{BatchStatus, GNode};
|
||||
use core_types::gpoll::{Finality, GPoll};
|
||||
use core_types::registry::{EdgeHandle, ErasedGNode, construct};
|
||||
use core_types::node::{BatchStatus, Node};
|
||||
use core_types::registry::{EdgeHandle, ErasedNode, construct};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::sync::Arc;
|
||||
|
||||
struct SourceNode<T>(T);
|
||||
|
||||
impl<T: Clone, Input> GNode<Input> for SourceNode<T> {
|
||||
impl<T: Clone, Input> Node<Input> for SourceNode<T> {
|
||||
type Output = T;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<T> {
|
||||
@@ -1084,7 +1084,7 @@ mod graphene_test {
|
||||
|
||||
struct IndexNode;
|
||||
|
||||
impl<Input: ExtractIndex> GNode<Input> for IndexNode {
|
||||
impl<Input: ExtractIndex> Node<Input> for IndexNode {
|
||||
type Output = f64;
|
||||
|
||||
fn eval(&self, input: &Input) -> GPoll<f64> {
|
||||
@@ -1097,13 +1097,13 @@ mod graphene_test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_add_evaluates_through_the_gnode_path() {
|
||||
fn generated_add_evaluates_through_the_node_path() {
|
||||
let arena = Arena::new(64);
|
||||
let scope = scope_fixture(&arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let graph = AddNode::new(SourceNode(1.0f64), SourceNode(2.0f64));
|
||||
assert_eq!(GNode::eval(&graph, &ctx), GPoll::Final(3.0));
|
||||
assert_eq!(Node::eval(&graph, &ctx), GPoll::Final(3.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1112,7 +1112,7 @@ mod graphene_test {
|
||||
let scope = scope_fixture(&arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let erased: Box<ErasedGNode<f64>> = Box::new(AddNode::new(IndexNode, SourceNode(10.0f64)));
|
||||
let erased: Box<ErasedNode<f64>> = Box::new(AddNode::new(IndexNode, SourceNode(10.0f64)));
|
||||
let mut scratch = [const { MaybeUninit::uninit() }; 4];
|
||||
let status = erased.eval_batch(&ctx, 2..6, Some(&mut scratch));
|
||||
let BatchStatus::Filled(lanes, finality) = status else {
|
||||
@@ -1129,11 +1129,11 @@ mod graphene_test {
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let entries = logical_or_entries();
|
||||
let value = EdgeHandle::new(Arc::new(SourceNode(true)) as Arc<ErasedGNode<bool>>);
|
||||
let other_value = EdgeHandle::new(Arc::new(SourceNode(false)) as Arc<ErasedGNode<bool>>);
|
||||
let value = EdgeHandle::new(Arc::new(SourceNode(true)) as Arc<ErasedNode<bool>>);
|
||||
let other_value = EdgeHandle::new(Arc::new(SourceNode(false)) as Arc<ErasedNode<bool>>);
|
||||
let wired = construct(&entries[0], vec![value, other_value]).unwrap().downcast::<bool>().unwrap();
|
||||
|
||||
assert_eq!(GNode::eval(&wired, &ctx), GPoll::Final(true));
|
||||
assert_eq!(Node::eval(&wired, &ctx), GPoll::Final(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1159,11 +1159,11 @@ mod graphene_test {
|
||||
assert_eq!(entries[3].io.inputs, vec![core_types::concrete!(DVec2), core_types::concrete!(DVec2)]);
|
||||
assert_eq!(entries[3].io.output, core_types::concrete!(DVec2));
|
||||
|
||||
let augend = EdgeHandle::new(Arc::new(SourceNode(1.5f64)) as Arc<ErasedGNode<f64>>);
|
||||
let addend = EdgeHandle::new(Arc::new(SourceNode(2.5f64)) as Arc<ErasedGNode<f64>>);
|
||||
let augend = EdgeHandle::new(Arc::new(SourceNode(1.5f64)) as Arc<ErasedNode<f64>>);
|
||||
let addend = EdgeHandle::new(Arc::new(SourceNode(2.5f64)) as Arc<ErasedNode<f64>>);
|
||||
let wired = construct(&entries[0], vec![augend, addend]).unwrap().downcast::<f64>().unwrap();
|
||||
|
||||
assert_eq!(GNode::eval(&wired, &ctx), GPoll::Final(4.0));
|
||||
assert_eq!(Node::eval(&wired, &ctx), GPoll::Final(4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1173,7 +1173,7 @@ mod graphene_test {
|
||||
|
||||
struct CountingSource(Arc<AtomicU32>, f64);
|
||||
|
||||
impl<Input> GNode<Input> for CountingSource {
|
||||
impl<Input> Node<Input> for CountingSource {
|
||||
type Output = f64;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<f64> {
|
||||
@@ -1190,7 +1190,7 @@ mod graphene_test {
|
||||
let untaken = Arc::new(AtomicU32::new(0));
|
||||
let graph = SwitchNode::new(SourceNode(true), CountingSource(taken.clone(), 1.0), CountingSource(untaken.clone(), 2.0));
|
||||
|
||||
assert_eq!(GNode::eval(&graph, &ctx), GPoll::Final(1.0));
|
||||
assert_eq!(Node::eval(&graph, &ctx), GPoll::Final(1.0));
|
||||
assert_eq!(taken.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(untaken.load(Ordering::Relaxed), 0);
|
||||
}
|
||||
@@ -1199,7 +1199,7 @@ mod graphene_test {
|
||||
fn converted_switch_passes_branch_status_through() {
|
||||
struct PendingSource;
|
||||
|
||||
impl<Input> GNode<Input> for PendingSource {
|
||||
impl<Input> Node<Input> for PendingSource {
|
||||
type Output = f64;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<f64> {
|
||||
@@ -1209,7 +1209,7 @@ mod graphene_test {
|
||||
|
||||
struct PartialSource;
|
||||
|
||||
impl<Input> GNode<Input> for PartialSource {
|
||||
impl<Input> Node<Input> for PartialSource {
|
||||
type Output = f64;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<f64> {
|
||||
@@ -1222,17 +1222,17 @@ mod graphene_test {
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let pending = SwitchNode::new(SourceNode(true), PendingSource, PartialSource);
|
||||
assert_eq!(GNode::eval(&pending, &ctx), GPoll::Pending);
|
||||
assert_eq!(Node::eval(&pending, &ctx), GPoll::Pending);
|
||||
|
||||
let partial = SwitchNode::new(SourceNode(false), PendingSource, PartialSource);
|
||||
assert_eq!(GNode::eval(&partial, &ctx), GPoll::Partial(7.0));
|
||||
assert_eq!(Node::eval(&partial, &ctx), GPoll::Partial(7.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converted_switch_merges_condition_status_into_the_branch_result() {
|
||||
struct PartialCondition;
|
||||
|
||||
impl<Input> GNode<Input> for PartialCondition {
|
||||
impl<Input> Node<Input> for PartialCondition {
|
||||
type Output = bool;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<bool> {
|
||||
@@ -1245,14 +1245,14 @@ mod graphene_test {
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let graph = SwitchNode::new(PartialCondition, SourceNode(1.0f64), SourceNode(2.0f64));
|
||||
assert_eq!(GNode::eval(&graph, &ctx), GPoll::Partial(1.0));
|
||||
assert_eq!(Node::eval(&graph, &ctx), GPoll::Partial(1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_eval_computes_on_stand_in_and_traces_fallback() {
|
||||
struct FallbackNode;
|
||||
|
||||
impl<Input> GNode<Input> for FallbackNode {
|
||||
impl<Input> Node<Input> for FallbackNode {
|
||||
type Output = f64;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<f64> {
|
||||
@@ -1265,7 +1265,7 @@ mod graphene_test {
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let graph = AddNode::new(FallbackNode, SourceNode(5.0f64));
|
||||
let GPoll::Fallback(boxed) = GNode::eval(&graph, &ctx) else {
|
||||
let GPoll::Fallback(boxed) = Node::eval(&graph, &ctx) else {
|
||||
panic!("fallback must propagate with the computed stand-in");
|
||||
};
|
||||
assert_eq!(boxed.0, 5.0);
|
||||
|
||||
@@ -179,143 +179,3 @@ fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
||||
|
||||
Ok(result_list)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use core_types::Ctx;
|
||||
use core_types::Node;
|
||||
use core_types::transform::Footprint;
|
||||
use glam::DVec2;
|
||||
use graphene_core::ReadPositionNode;
|
||||
use graphene_core::extract_xy::{ExtractXyNode, XY};
|
||||
use graphic_types::Vector;
|
||||
use kurbo::Shape;
|
||||
use kurbo::{BezPath, DEFAULT_ACCURACY, Rect};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use vector_nodes::generator_nodes::RectangleNode;
|
||||
use vector_types::subpath::Subpath;
|
||||
|
||||
fn vector_node_from_bezpath(bezpath: BezPath) -> List<Vector> {
|
||||
List::new_from_element(Vector::from_bezpath(bezpath))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FutureWrapperNode<T: Clone>(T);
|
||||
|
||||
impl<'i, I: Ctx, T: 'i + Clone + Send> Node<'i, I> for FutureWrapperNode<T> {
|
||||
type Output = Pin<Box<dyn Future<Output = T> + 'i + Send>>;
|
||||
fn eval(&'i self, _input: I) -> Self::Output {
|
||||
let value = self.0.clone();
|
||||
Box::pin(async move { value })
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn repeat_on_points_test() {
|
||||
let context = OwnedContextImpl::default().into_context();
|
||||
let rect = RectangleNode::new(
|
||||
FutureWrapperNode(()),
|
||||
ExtractXyNode::new(ReadPositionNode::new(FutureWrapperNode(()), FutureWrapperNode(0)), FutureWrapperNode(XY::Y)),
|
||||
FutureWrapperNode(2_f64),
|
||||
FutureWrapperNode(false),
|
||||
FutureWrapperNode(0_f64),
|
||||
FutureWrapperNode(false),
|
||||
);
|
||||
|
||||
let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];
|
||||
let points = List::new_from_element(Vector::from_subpath(Subpath::from_anchors(positions, false)));
|
||||
let generated = super::repeat_on_points(context, points, &rect, false).await;
|
||||
assert_eq!(generated.len(), positions.len());
|
||||
for (position, index) in positions.into_iter().zip(0..generated.len()) {
|
||||
let bounds = generated
|
||||
.element(index)
|
||||
.unwrap()
|
||||
.bounding_box_with_transform(generated.attribute_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);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn repeat() {
|
||||
let direction = DVec2::X * 1.5;
|
||||
let count = 3;
|
||||
let context = OwnedContextImpl::default().into_context();
|
||||
let repeated = super::repeat_array(
|
||||
context,
|
||||
&FutureWrapperNode(vector_node_from_bezpath(Rect::new(0., 0., 1., 1.).to_path(DEFAULT_ACCURACY))),
|
||||
direction,
|
||||
0.,
|
||||
count,
|
||||
)
|
||||
.await;
|
||||
let vector_list = vector_nodes::flatten_path(Footprint::default(), repeated).await;
|
||||
let vector = vector_list.element(0).unwrap();
|
||||
assert_eq!(vector.region_manipulator_groups().count(), 3);
|
||||
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
|
||||
assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn repeat_single_copy() {
|
||||
let context = OwnedContextImpl::default().into_context();
|
||||
let repeated = super::repeat_array(
|
||||
context,
|
||||
&FutureWrapperNode(vector_node_from_bezpath(Rect::new(0., 0., 1., 1.).to_path(DEFAULT_ACCURACY))),
|
||||
DVec2::new(12., 10.),
|
||||
45.,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
let vector_list = vector_nodes::flatten_path(Footprint::default(), repeated).await;
|
||||
let vector = vector_list.element(0).unwrap();
|
||||
assert_eq!(vector.region_manipulator_groups().count(), 1);
|
||||
|
||||
let (_, manipulator_groups) = vector.region_manipulator_groups().next().unwrap();
|
||||
let anchor = manipulator_groups[0].anchor;
|
||||
assert!(anchor.length() < 1e-5, "Expected the single copy to be untransformed, found anchor {anchor}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn repeat_transform_position() {
|
||||
let direction = DVec2::new(12., 10.);
|
||||
let count = 8;
|
||||
let context = OwnedContextImpl::default().into_context();
|
||||
let repeated = super::repeat_array(
|
||||
context,
|
||||
&FutureWrapperNode(vector_node_from_bezpath(Rect::new(0., 0., 1., 1.).to_path(DEFAULT_ACCURACY))),
|
||||
direction,
|
||||
0.,
|
||||
count,
|
||||
)
|
||||
.await;
|
||||
let vector_list = vector_nodes::flatten_path(Footprint::default(), repeated).await;
|
||||
let vector = vector_list.element(0).unwrap();
|
||||
assert_eq!(vector.region_manipulator_groups().count(), 8);
|
||||
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
|
||||
assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn repeat_radial() {
|
||||
let context = OwnedContextImpl::default().into_context();
|
||||
let repeated = super::repeat_radial(context, &FutureWrapperNode(vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY))), 45., 4., 8).await;
|
||||
let vector_list = vector_nodes::flatten_path(Footprint::default(), repeated).await;
|
||||
let vector = vector_list.element(0).unwrap();
|
||||
assert_eq!(vector.region_manipulator_groups().count(), 8);
|
||||
|
||||
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
|
||||
let expected_angle = (index as f64 + 1.) * 45.;
|
||||
|
||||
let center = (manipulator_groups[0].anchor + manipulator_groups[2].anchor) / 2.;
|
||||
let actual_angle = DVec2::Y.angle_to(center).to_degrees();
|
||||
|
||||
assert!((actual_angle - expected_angle).abs() % 360. < 1e-5, "Expected {expected_angle} found {actual_angle}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user