New node: Pointer Position (#3535)

* New node: Pointer Position

* Fix test
This commit is contained in:
Keavon Chambers 2025-12-27 16:02:23 -08:00 committed by GitHub
parent 4ab75c9c1c
commit fa45efa9e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 119 additions and 18 deletions

View File

@ -383,6 +383,16 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
for document_id in self.document_ids.iter() { for document_id in self.document_ids.iter() {
let node_to_inspect = self.node_to_inspect(); let node_to_inspect = self.node_to_inspect();
let Some(document) = self.documents.get_mut(document_id) else {
log::error!("Tried to render non-existent document");
continue;
};
let document_to_viewport = document
.navigation_handler
.calculate_offset_transform(viewport.center_in_viewport_space().into(), &document.document_ptz);
let pointer_position = document_to_viewport.inverse().transform_point2(ipp.mouse.position);
let scale = viewport.scale(); let scale = viewport.scale();
// Use exact physical dimensions from browser (via ResizeObserver's devicePixelContentBoxSize) // Use exact physical dimensions from browser (via ResizeObserver's devicePixelContentBoxSize)
let physical_resolution = viewport.size().to_physical().into_dvec2().round().as_uvec2(); let physical_resolution = viewport.size().to_physical().into_dvec2().round().as_uvec2();
@ -395,6 +405,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
timing_information, timing_information,
node_to_inspect, node_to_inspect,
true, true,
pointer_position,
) { ) {
responses.add_front(message); responses.add_front(message);
} }
@ -1054,13 +1065,18 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
return; return;
}; };
let document_to_viewport = document
.navigation_handler
.calculate_offset_transform(viewport.center_in_viewport_space().into(), &document.document_ptz);
let pointer_position = document_to_viewport.inverse().transform_point2(ipp.mouse.position);
let scale = viewport.scale(); let scale = viewport.scale();
// Use exact physical dimensions from browser (via ResizeObserver's devicePixelContentBoxSize) // Use exact physical dimensions from browser (via ResizeObserver's devicePixelContentBoxSize)
let physical_resolution = viewport.size().to_physical().into_dvec2().round().as_uvec2(); let physical_resolution = viewport.size().to_physical().into_dvec2().round().as_uvec2();
let result = self let result = self
.executor .executor
.submit_node_graph_evaluation(document, document_id, physical_resolution, scale, timing_information, node_to_inspect, ignore_hash); .submit_node_graph_evaluation(document, document_id, physical_resolution, scale, timing_information, node_to_inspect, ignore_hash, pointer_position);
match result { match result {
Err(description) => { Err(description) => {

View File

@ -1,6 +1,6 @@
use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use glam::{DAffine2, UVec2}; use glam::{DAffine2, DVec2, UVec2};
use graph_craft::document::value::{RenderOutput, TaggedValue}; use graph_craft::document::value::{RenderOutput, TaggedValue};
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput}; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
use graph_craft::proto::GraphErrors; use graph_craft::proto::GraphErrors;
@ -81,6 +81,7 @@ impl NodeGraphExecutor {
}; };
(node_runtime, node_executor) (node_runtime, node_executor)
} }
/// Execute the network by flattening it and creating a borrow stack. /// Execute the network by flattening it and creating a borrow stack.
fn queue_execution(&mut self, render_config: RenderConfig) -> u64 { fn queue_execution(&mut self, render_config: RenderConfig) -> u64 {
let execution_id = self.current_execution_id; let execution_id = self.current_execution_id;
@ -140,6 +141,7 @@ impl NodeGraphExecutor {
viewport_resolution: UVec2, viewport_resolution: UVec2,
viewport_scale: f64, viewport_scale: f64,
time: TimingInformation, time: TimingInformation,
pointer: DVec2,
) -> Result<Message, String> { ) -> Result<Message, String> {
let viewport = Footprint { let viewport = Footprint {
transform: document.metadata().document_to_viewport, transform: document.metadata().document_to_viewport,
@ -150,6 +152,7 @@ impl NodeGraphExecutor {
viewport, viewport,
scale: viewport_scale, scale: viewport_scale,
time, time,
pointer,
export_format: graphene_std::application_io::ExportFormat::Raster, export_format: graphene_std::application_io::ExportFormat::Raster,
render_mode: document.render_mode, render_mode: document.render_mode,
hide_artboards: false, hide_artboards: false,
@ -175,9 +178,10 @@ impl NodeGraphExecutor {
time: TimingInformation, time: TimingInformation,
node_to_inspect: Option<NodeId>, node_to_inspect: Option<NodeId>,
ignore_hash: bool, ignore_hash: bool,
pointer: DVec2,
) -> Result<Message, String> { ) -> Result<Message, String> {
self.update_node_graph(document, node_to_inspect, ignore_hash)?; self.update_node_graph(document, node_to_inspect, ignore_hash)?;
self.submit_current_node_graph_evaluation(document, document_id, viewport_resolution, viewport_scale, time) self.submit_current_node_graph_evaluation(document, document_id, viewport_resolution, viewport_scale, time, pointer)
} }
/// Evaluates a node graph for export /// Evaluates a node graph for export
@ -208,6 +212,7 @@ impl NodeGraphExecutor {
}, },
scale: export_config.scale_factor, scale: export_config.scale_factor,
time: Default::default(), time: Default::default(),
pointer: DVec2::ZERO,
export_format, export_format,
render_mode: document.render_mode, render_mode: document.render_mode,
hide_artboards: export_config.transparent_background, hide_artboards: export_config.transparent_background,

View File

@ -48,7 +48,7 @@ impl EditorTestUtils {
Err(e) => return Err(format!("update_node_graph_instrumented failed\n\n{e}")), Err(e) => return Err(format!("update_node_graph_instrumented failed\n\n{e}")),
}; };
if let Err(e) = exector.submit_current_node_graph_evaluation(document, DocumentId(0), UVec2::ONE, 1., Default::default()) { if let Err(e) = exector.submit_current_node_graph_evaluation(document, DocumentId(0), UVec2::ONE, 1., Default::default(), DVec2::ZERO) {
return Err(format!("submit_current_node_graph_evaluation failed\n\n{e}")); return Err(format!("submit_current_node_graph_evaluation failed\n\n{e}"));
} }
runtime.run().await; runtime.run().await;

View File

@ -57,7 +57,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_node::create_context::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_node::create_context::IDENTIFIER),
context_features: graphene_std::ContextDependencies { context_features: graphene_std::ContextDependencies {
extract: ContextFeatures::empty(), extract: ContextFeatures::empty(),
inject: ContextFeatures::REAL_TIME | ContextFeatures::ANIMATION_TIME | ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS, inject: ContextFeatures::REAL_TIME | ContextFeatures::ANIMATION_TIME | ContextFeatures::POINTER | ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
}, },
..Default::default() ..Default::default()
}, },

View File

@ -239,6 +239,7 @@ pub struct RenderConfig {
pub scale: f64, pub scale: f64,
pub export_format: ExportFormat, pub export_format: ExportFormat,
pub time: TimingInformation, pub time: TimingInformation,
pub pointer: DVec2,
#[serde(alias = "view_mode")] #[serde(alias = "view_mode")]
pub render_mode: RenderMode, pub render_mode: RenderMode,
pub hide_artboards: bool, pub hide_artboards: bool,

View File

@ -1,4 +1,5 @@
use crate::transform::Footprint; use crate::transform::Footprint;
use glam::DVec2;
pub use no_std_types::context::{ArcCtx, Ctx}; pub use no_std_types::context::{ArcCtx, Ctx};
use std::any::Any; use std::any::Any;
use std::borrow::Borrow; use std::borrow::Borrow;
@ -26,6 +27,10 @@ pub trait ExtractAnimationTime {
fn try_animation_time(&self) -> Option<f64>; fn try_animation_time(&self) -> Option<f64>;
} }
pub trait ExtractPointer {
fn try_pointer(&self) -> Option<DVec2>;
}
pub trait ExtractIndex { pub trait ExtractIndex {
fn try_index(&self) -> Option<impl Iterator<Item = usize>>; fn try_index(&self) -> Option<impl Iterator<Item = usize>>;
} }
@ -47,6 +52,7 @@ pub trait CloneVarArgs: ExtractVarArgs {
pub trait InjectFootprint {} pub trait InjectFootprint {}
pub trait InjectRealTime {} pub trait InjectRealTime {}
pub trait InjectAnimationTime {} pub trait InjectAnimationTime {}
pub trait InjectPointer {}
pub trait InjectIndex {} pub trait InjectIndex {}
pub trait InjectVarArgs {} pub trait InjectVarArgs {}
@ -54,23 +60,26 @@ pub trait InjectVarArgs {}
pub trait ModifyFootprint: ExtractFootprint + InjectFootprint {} pub trait ModifyFootprint: ExtractFootprint + InjectFootprint {}
pub trait ModifyRealTime: ExtractRealTime + InjectRealTime {} pub trait ModifyRealTime: ExtractRealTime + InjectRealTime {}
pub trait ModifyAnimationTime: ExtractAnimationTime + InjectAnimationTime {} pub trait ModifyAnimationTime: ExtractAnimationTime + InjectAnimationTime {}
pub trait ModifyPointer: ExtractPointer + InjectPointer {}
pub trait ModifyIndex: ExtractIndex + InjectIndex {} pub trait ModifyIndex: ExtractIndex + InjectIndex {}
pub trait ModifyVarArgs: ExtractVarArgs + InjectVarArgs {} pub trait ModifyVarArgs: ExtractVarArgs + InjectVarArgs {}
pub trait ExtractAll: ExtractFootprint + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractVarArgs {} pub trait ExtractAll: ExtractFootprint + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractPointer + ExtractVarArgs {}
impl<T: ?Sized + ExtractFootprint + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractVarArgs> ExtractAll for T {} impl<T: ?Sized + ExtractFootprint + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractPointer + ExtractVarArgs> ExtractAll for T {}
impl<T: Ctx> InjectFootprint for T {} impl<T: Ctx> InjectFootprint for T {}
impl<T: Ctx> InjectRealTime for T {} impl<T: Ctx> InjectRealTime for T {}
impl<T: Ctx> InjectIndex for T {} impl<T: Ctx> InjectIndex for T {}
impl<T: Ctx> InjectAnimationTime for T {} impl<T: Ctx> InjectAnimationTime for T {}
impl<T: Ctx> InjectPointer for T {}
impl<T: Ctx> InjectVarArgs for T {} impl<T: Ctx> InjectVarArgs for T {}
impl<T: Ctx + InjectFootprint + ExtractFootprint> ModifyFootprint for T {} impl<T: Ctx + InjectFootprint + ExtractFootprint> ModifyFootprint for T {}
impl<T: Ctx + InjectRealTime + ExtractRealTime> ModifyRealTime for T {} impl<T: Ctx + InjectRealTime + ExtractRealTime> ModifyRealTime for T {}
impl<T: Ctx + InjectIndex + ExtractIndex> ModifyIndex for T {} impl<T: Ctx + InjectIndex + ExtractIndex> ModifyIndex for T {}
impl<T: Ctx + InjectAnimationTime + ExtractAnimationTime> ModifyAnimationTime for T {} impl<T: Ctx + InjectAnimationTime + ExtractAnimationTime> ModifyAnimationTime for T {}
impl<T: Ctx + InjectPointer + ExtractPointer> ModifyPointer for T {}
impl<T: Ctx + InjectVarArgs + ExtractVarArgs> ModifyVarArgs for T {} impl<T: Ctx + InjectVarArgs + ExtractVarArgs> ModifyVarArgs for T {}
// Public enum for flexible node macro codegen // Public enum for flexible node macro codegen
@ -79,11 +88,13 @@ pub enum ContextFeature {
ExtractFootprint, ExtractFootprint,
ExtractRealTime, ExtractRealTime,
ExtractAnimationTime, ExtractAnimationTime,
ExtractPointer,
ExtractIndex, ExtractIndex,
ExtractVarArgs, ExtractVarArgs,
InjectFootprint, InjectFootprint,
InjectRealTime, InjectRealTime,
InjectAnimationTime, InjectAnimationTime,
InjectPointer,
InjectIndex, InjectIndex,
InjectVarArgs, InjectVarArgs,
} }
@ -96,8 +107,9 @@ bitflags! {
const FOOTPRINT = 1 << 0; const FOOTPRINT = 1 << 0;
const REAL_TIME = 1 << 1; const REAL_TIME = 1 << 1;
const ANIMATION_TIME = 1 << 2; const ANIMATION_TIME = 1 << 2;
const INDEX = 1 << 3; const POINTER = 1 << 3;
const VARARGS = 1 << 4; const INDEX = 1 << 4;
const VARARGS = 1 << 5;
} }
} }
@ -116,6 +128,7 @@ impl From<&[ContextFeature]> for ContextDependencies {
ContextFeature::ExtractFootprint => ContextFeatures::FOOTPRINT, ContextFeature::ExtractFootprint => ContextFeatures::FOOTPRINT,
ContextFeature::ExtractRealTime => ContextFeatures::REAL_TIME, ContextFeature::ExtractRealTime => ContextFeatures::REAL_TIME,
ContextFeature::ExtractAnimationTime => ContextFeatures::ANIMATION_TIME, ContextFeature::ExtractAnimationTime => ContextFeatures::ANIMATION_TIME,
ContextFeature::ExtractPointer => ContextFeatures::POINTER,
ContextFeature::ExtractIndex => ContextFeatures::INDEX, ContextFeature::ExtractIndex => ContextFeatures::INDEX,
ContextFeature::ExtractVarArgs => ContextFeatures::VARARGS, ContextFeature::ExtractVarArgs => ContextFeatures::VARARGS,
_ => ContextFeatures::empty(), _ => ContextFeatures::empty(),
@ -124,6 +137,7 @@ impl From<&[ContextFeature]> for ContextDependencies {
ContextFeature::InjectFootprint => ContextFeatures::FOOTPRINT, ContextFeature::InjectFootprint => ContextFeatures::FOOTPRINT,
ContextFeature::InjectRealTime => ContextFeatures::REAL_TIME, ContextFeature::InjectRealTime => ContextFeatures::REAL_TIME,
ContextFeature::InjectAnimationTime => ContextFeatures::ANIMATION_TIME, ContextFeature::InjectAnimationTime => ContextFeatures::ANIMATION_TIME,
ContextFeature::InjectPointer => ContextFeatures::POINTER,
ContextFeature::InjectIndex => ContextFeatures::INDEX, ContextFeature::InjectIndex => ContextFeatures::INDEX,
ContextFeature::InjectVarArgs => ContextFeatures::VARARGS, ContextFeature::InjectVarArgs => ContextFeatures::VARARGS,
_ => ContextFeatures::empty(), _ => ContextFeatures::empty(),
@ -174,6 +188,11 @@ impl<T: ExtractAnimationTime + Sync> ExtractAnimationTime for Option<T> {
self.as_ref().and_then(|x| x.try_animation_time()) self.as_ref().and_then(|x| x.try_animation_time())
} }
} }
impl<T: ExtractPointer + Sync> ExtractPointer for Option<T> {
fn try_pointer(&self) -> Option<DVec2> {
self.as_ref().and_then(|x| x.try_pointer())
}
}
impl<T: ExtractIndex> ExtractIndex for Option<T> { impl<T: ExtractIndex> ExtractIndex for Option<T> {
fn try_index(&self) -> Option<impl Iterator<Item = usize>> { fn try_index(&self) -> Option<impl Iterator<Item = usize>> {
self.as_ref().and_then(|x| x.try_index()) self.as_ref().and_then(|x| x.try_index())
@ -211,6 +230,11 @@ impl<T: ExtractAnimationTime + Sync> ExtractAnimationTime for Arc<T> {
(**self).try_animation_time() (**self).try_animation_time()
} }
} }
impl<T: ExtractPointer + Sync> ExtractPointer for Arc<T> {
fn try_pointer(&self) -> Option<DVec2> {
(**self).try_pointer()
}
}
impl<T: ExtractIndex> ExtractIndex for Arc<T> { impl<T: ExtractIndex> ExtractIndex for Arc<T> {
fn try_index(&self) -> Option<impl Iterator<Item = usize>> { fn try_index(&self) -> Option<impl Iterator<Item = usize>> {
(**self).try_index() (**self).try_index()
@ -303,6 +327,11 @@ impl ExtractAnimationTime for OwnedContextImpl {
self.animation_time self.animation_time
} }
} }
impl ExtractPointer for OwnedContextImpl {
fn try_pointer(&self) -> Option<DVec2> {
self.pointer
}
}
impl ExtractIndex for OwnedContextImpl { impl ExtractIndex for OwnedContextImpl {
fn try_index(&self) -> Option<impl Iterator<Item = usize>> { fn try_index(&self) -> Option<impl Iterator<Item = usize>> {
self.index.clone().map(|x| x.into_iter().rev()) self.index.clone().map(|x| x.into_iter().rev())
@ -363,6 +392,7 @@ pub struct OwnedContextImpl {
index: Option<Vec<usize>>, index: Option<Vec<usize>>,
real_time: Option<f64>, real_time: Option<f64>,
animation_time: Option<f64>, animation_time: Option<f64>,
pointer: Option<DVec2>,
} }
impl std::fmt::Debug for OwnedContextImpl { impl std::fmt::Debug for OwnedContextImpl {
@ -374,6 +404,7 @@ impl std::fmt::Debug for OwnedContextImpl {
.field("index", &self.index) .field("index", &self.index)
.field("real_time", &self.real_time) .field("real_time", &self.real_time)
.field("animation_time", &self.animation_time) .field("animation_time", &self.animation_time)
.field("pointer", &self.pointer)
.finish() .finish()
} }
} }
@ -392,6 +423,7 @@ impl Hash for OwnedContextImpl {
self.index.hash(state); self.index.hash(state);
self.real_time.map(|x| x.to_bits()).hash(state); self.real_time.map(|x| x.to_bits()).hash(state);
self.animation_time.map(|x| x.to_bits()).hash(state); self.animation_time.map(|x| x.to_bits()).hash(state);
self.pointer.map(|v| (v.x.to_bits(), v.y.to_bits())).hash(state);
} }
} }
@ -400,12 +432,14 @@ impl OwnedContextImpl {
pub fn from<T: ExtractAll + CloneVarArgs>(value: T) -> Self { pub fn from<T: ExtractAll + CloneVarArgs>(value: T) -> Self {
OwnedContextImpl::from_flags(value, ContextFeatures::all()) OwnedContextImpl::from_flags(value, ContextFeatures::all())
} }
#[track_caller] #[track_caller]
pub fn from_flags<T: ExtractAll + CloneVarArgs>(value: T, bitflags: ContextFeatures) -> Self { pub fn from_flags<T: ExtractAll + CloneVarArgs>(value: T, bitflags: ContextFeatures) -> Self {
let footprint = bitflags.contains(ContextFeatures::FOOTPRINT).then(|| value.try_footprint().copied()).flatten(); let footprint = bitflags.contains(ContextFeatures::FOOTPRINT).then(|| value.try_footprint().copied()).flatten();
let index = bitflags.contains(ContextFeatures::INDEX).then(|| value.try_index()).flatten(); let index = bitflags.contains(ContextFeatures::INDEX).then(|| value.try_index()).flatten();
let real_time = bitflags.contains(ContextFeatures::REAL_TIME).then(|| value.try_real_time()).flatten(); let real_time = bitflags.contains(ContextFeatures::REAL_TIME).then(|| value.try_real_time()).flatten();
let animation_time = bitflags.contains(ContextFeatures::ANIMATION_TIME).then(|| value.try_animation_time()).flatten(); let animation_time = bitflags.contains(ContextFeatures::ANIMATION_TIME).then(|| value.try_animation_time()).flatten();
let pointer = bitflags.contains(ContextFeatures::POINTER).then(|| value.try_pointer()).flatten();
let parent = bitflags let parent = bitflags
.contains(ContextFeatures::VARARGS) .contains(ContextFeatures::VARARGS)
.then(|| match value.varargs_len() { .then(|| match value.varargs_len() {
@ -421,8 +455,10 @@ impl OwnedContextImpl {
index: index.map(|x| x.collect()), index: index.map(|x| x.collect()),
real_time, real_time,
animation_time, animation_time,
pointer,
} }
} }
pub const fn empty() -> Self { pub const fn empty() -> Self {
OwnedContextImpl { OwnedContextImpl {
footprint: None, footprint: None,
@ -431,6 +467,7 @@ impl OwnedContextImpl {
index: None, index: None,
real_time: None, real_time: None,
animation_time: None, animation_time: None,
pointer: None,
} }
} }
} }
@ -475,6 +512,10 @@ impl OwnedContextImpl {
self.animation_time = Some(animation_time); self.animation_time = Some(animation_time);
self self
} }
pub fn with_pointer(mut self, pointer: DVec2) -> Self {
self.pointer = Some(pointer);
self
}
pub fn with_vararg(mut self, value: Box<dyn AnyHash + Send + Sync>) -> Self { pub fn with_vararg(mut self, value: Box<dyn AnyHash + Send + Sync>) -> Self {
assert!(self.varargs.is_none_or(|value| value.is_empty())); assert!(self.varargs.is_none_or(|value| value.is_empty()));
self.varargs = Some(Arc::new([value])); self.varargs = Some(Arc::new([value]));

View File

@ -1112,11 +1112,11 @@ impl<Upstream> Vector<Upstream> {
for neighbors in &mut adjacency { for neighbors in &mut adjacency {
neighbors.sort_by(|a, b| { neighbors.sort_by(|a, b| {
let angle = [a, b].map(|side| { let angle = [a, b].map(|side| {
let curve = PathSeg::from(self.path_segment_from_index( let curve = self.path_segment_from_index(
self.segment_domain.start_point[side.segment_index], self.segment_domain.start_point[side.segment_index],
self.segment_domain.end_point[side.segment_index], self.segment_domain.end_point[side.segment_index],
self.segment_domain.handles[side.segment_index], self.segment_domain.handles[side.segment_index],
)); );
let curve = if side.reversed { curve.reverse() } else { curve }; let curve = if side.reversed { curve.reverse() } else { curve };
let tangent = curve.tangent_at_start(); let tangent = curve.tangent_at_start();
tangent.y.atan2(tangent.x) tangent.y.atan2(tangent.x)
@ -1140,20 +1140,19 @@ impl<Upstream> Vector<Upstream> {
} }
} }
return FaceIterator::new(faces, self); FaceIterator::new(faces, self)
} }
fn construct_face(&self, adjacency: &Vec<Vec<FaceSide>>, first: FaceSide, faces: &mut Faces, seen: &mut FaceSideSet) -> Option<()> { fn construct_face(&self, adjacency: &[Vec<FaceSide>], first: FaceSide, faces: &mut Faces, seen: &mut FaceSideSet) -> Option<()> {
faces.start_new_face(); faces.start_new_face();
let max_iterations = self.segment_domain.id.len() * 2; let max_iterations = self.segment_domain.id.len() * 2;
let mut side = first; let mut side = first;
for _iteration in 1..max_iterations { for _iteration in 1..max_iterations {
if seen.contains(side) { if seen.contains(side) {
log::debug!("Encountered seen side {:?}, aborting face construction", side);
return None; return None;
} }
seen.insert(side); seen.insert(side);
faces.add_side(side.clone()); faces.add_side(side);
let next_vertex = if side.reversed { let next_vertex = if side.reversed {
self.segment_domain.start_point[side.segment_index] self.segment_domain.start_point[side.segment_index]
} else { } else {

View File

@ -414,11 +414,13 @@ fn parse_context_feature_idents(ty: &Type) -> Vec<Ident> {
"ExtractFootprint" "ExtractFootprint"
| "ExtractRealTime" | "ExtractRealTime"
| "ExtractAnimationTime" | "ExtractAnimationTime"
| "ExtractPointer"
| "ExtractIndex" | "ExtractIndex"
| "ExtractVarArgs" | "ExtractVarArgs"
| "InjectFootprint" | "InjectFootprint"
| "InjectRealTime" | "InjectRealTime"
| "InjectAnimationTime" | "InjectAnimationTime"
| "InjectPointer"
| "InjectIndex" | "InjectIndex"
| "InjectVarArgs" => { | "InjectVarArgs" => {
features.push(segment.ident.clone()); features.push(segment.ident.clone());

View File

@ -1,4 +1,5 @@
use core_types::{Ctx, ExtractAnimationTime, ExtractRealTime}; use core_types::{Ctx, ExtractAnimationTime, ExtractPointer, ExtractRealTime};
use glam::DVec2;
const DAY: f64 = 1000. * 3600. * 24.; const DAY: f64 = 1000. * 3600. * 24.;
@ -47,6 +48,12 @@ fn animation_time(ctx: impl Ctx + ExtractAnimationTime) -> f64 {
ctx.try_animation_time().unwrap_or_default() ctx.try_animation_time().unwrap_or_default()
} }
/// Produces the current position of the user's pointer within the document canvas.
#[node_macro::node(category("Animation"))]
fn pointer_position(ctx: impl Ctx + ExtractPointer) -> DVec2 {
ctx.try_pointer().unwrap_or_default()
}
// TODO: These nodes require more sophisticated algorithms for giving the correct result // TODO: These nodes require more sophisticated algorithms for giving the correct result
// #[node_macro::node(category("Animation"))] // #[node_macro::node(category("Animation"))]
// fn month(ctx: impl Ctx + ExtractRealTime) -> f64 { // fn month(ctx: impl Ctx + ExtractRealTime) -> f64 {

View File

@ -113,6 +113,7 @@ async fn create_context<'a: 'n>(
.with_footprint(footprint) .with_footprint(footprint)
.with_real_time(render_config.time.time) .with_real_time(render_config.time.time)
.with_animation_time(render_config.time.animation_time.as_secs_f64()) .with_animation_time(render_config.time.animation_time.as_secs_f64())
.with_pointer(render_config.pointer)
.with_vararg(Box::new(render_params)) .with_vararg(Box::new(render_params))
.into_context(); .into_context();

View File

@ -471,12 +471,41 @@ fn ceiling<T: num_traits::float::Float>(
value.ceil() value.ceil()
} }
trait AbsoluteValue {
fn abs(self) -> Self;
}
impl AbsoluteValue for DVec2 {
fn abs(self) -> Self {
DVec2::new(self.x.abs(), self.y.abs())
}
}
impl AbsoluteValue for f32 {
fn abs(self) -> Self {
self.abs()
}
}
impl AbsoluteValue for f64 {
fn abs(self) -> Self {
self.abs()
}
}
impl AbsoluteValue for i32 {
fn abs(self) -> Self {
self.abs()
}
}
impl AbsoluteValue for i64 {
fn abs(self) -> Self {
self.abs()
}
}
/// The absolute value function (`abs`) removes the negative sign from an input value, if present. /// The absolute value function (`abs`) removes the negative sign from an input value, if present.
#[node_macro::node(category("Math: Numeric"))] #[node_macro::node(category("Math: Numeric"))]
fn absolute_value<T: num_traits::sign::Signed>( fn absolute_value<T: AbsoluteValue>(
_: impl Ctx, _: impl Ctx,
/// The number to be made positive. /// The number to be made positive.
#[implementations(f64, f32, i32, i64)] #[implementations(f64, f32, i32, i64, DVec2)]
value: T, value: T,
) -> T { ) -> T {
value.abs() value.abs()