Merge origin/master into the async record refactor

Scaffolding merge for the reconcile; the final series to master is
authored fresh. Rank plumbing resolves to our axis-IR model, the node
macro and the LaneSource render walk stay ours, master's vector
restructure and gradient vocabulary are adopted, and the paint and
appearance adoption is deliberately deferred behind our fill and stroke
markers.
This commit is contained in:
Dennis Kobert
2026-09-08 15:03:57 +00:00
385 changed files with 34669 additions and 20078 deletions

View File

@@ -3,7 +3,7 @@ use core_types::list::List;
use core_types::transform::Footprint;
use core_types::{CacheHash, Color, Context, Ctx, DeriveCtx, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime};
use glam::{DAffine2, DVec2};
use graphic_types::vector_types::GradientStops;
use graphic_types::vector_types::Gradient;
use graphic_types::{Artboard, Graphic, Vector};
use raster_types::{CPU, GPU, Raster};
@@ -36,17 +36,20 @@ fn real_time(
/// The time and date component to be produced as a number.
component: RealTimeMode,
) -> f64 {
let component = component.into_element();
let real_time = ctx.try_real_time().unwrap_or_default();
// TODO: Implement proper conversion using and existing time implementation
match component {
let result = match component {
RealTimeMode::Utc => real_time,
RealTimeMode::Year => (real_time / DAY / 365.25).floor() + 1970., // TODO: Factor in a chosen timezone
RealTimeMode::Hour => (real_time / 1000. / 3600.).floor() % 24., // TODO: Factor in a chosen timezone
RealTimeMode::Minute => (real_time / 1000. / 60.).floor() % 60., // TODO: Factor in a chosen timezone
RealTimeMode::Second => (real_time / 1000.).floor() % 60.,
RealTimeMode::Millisecond => real_time % 1000.,
}
};
Item::new_from_element(result)
}
/// Produces the time, in seconds on the timeline, since the beginning of animation playback.
@@ -58,7 +61,7 @@ fn animation_time(
#[unit("/sec")]
rate: f64,
) -> f64 {
ctx.try_animation_time().unwrap_or_default() * rate
Item::new_from_element(ctx.try_animation_time().unwrap_or_default() * *rate.element())
}
#[node_macro::node(category("Debug"))]
@@ -74,16 +77,23 @@ fn quantize_real_time<T>(
Context -> DAffine2,
Context -> Footprint,
Context -> DVec2,
Context -> Vector,
Context -> Graphic,
Context -> Raster<CPU>,
Context -> Raster<GPU>,
Context -> Color,
Context -> Gradient,
Context -> Artboard,
Context -> List<String>,
Context -> List<f64>,
Context -> List<DVec2>,
Context -> List<Vector>,
Context -> List<Graphic>,
Context -> List<Raster<CPU>>,
Context -> List<Raster<GPU>>,
Context -> List<Color>,
Context -> List<Gradient>,
Context -> List<Artboard>,
Context -> List<GradientStops>,
Context -> List<String>,
Context -> List<f64>,
Context -> (),
)]
value: impl Node<Context<'_>, Output = T>,
#[default(1)]
@@ -92,6 +102,7 @@ fn quantize_real_time<T>(
) -> GPoll<T> {
let time = ctx.try_real_time().unwrap_or_default();
let time = time / 1000.;
let quantum = quantum.into_element();
let mut quantized_time = (time * quantum.recip()).round() / quantum.recip();
if !quantized_time.is_finite() {
quantized_time = time;
@@ -114,16 +125,23 @@ fn quantize_animation_time<T>(
Context -> DAffine2,
Context -> Footprint,
Context -> DVec2,
Context -> Vector,
Context -> Graphic,
Context -> Raster<CPU>,
Context -> Raster<GPU>,
Context -> Color,
Context -> Gradient,
Context -> Artboard,
Context -> List<String>,
Context -> List<f64>,
Context -> List<DVec2>,
Context -> List<Vector>,
Context -> List<Graphic>,
Context -> List<Raster<CPU>>,
Context -> List<Raster<GPU>>,
Context -> List<Color>,
Context -> List<Gradient>,
Context -> List<Artboard>,
Context -> List<GradientStops>,
Context -> List<String>,
Context -> List<f64>,
Context -> (),
)]
value: impl Node<Context<'_>, Output = T>,
#[default(1)]
@@ -131,6 +149,7 @@ fn quantize_animation_time<T>(
quantum: f64,
) -> GPoll<T> {
let time = ctx.try_animation_time().unwrap_or_default();
let quantum = quantum.into_element();
let mut quantized_time = (time * quantum.recip()).round() / quantum.recip();
if !quantized_time.is_finite() {
quantized_time = time;
@@ -142,7 +161,7 @@ fn quantize_animation_time<T>(
/// Produces the current position of the user's pointer within the document canvas.
#[node_macro::node(category("Animation"))]
fn pointer_position(ctx: impl Ctx + ExtractPointerPosition) -> DVec2 {
ctx.try_pointer_position().unwrap_or_default()
Item::new_from_element(ctx.try_pointer_position().unwrap_or_default())
}
// TODO: These nodes require more sophisticated algorithms for giving the correct result

View File

@@ -1,9 +1,9 @@
use core_types::gpoll::{Extent, GPoll, GraphError, Interrupt};
use core_types::list::List;
use core_types::list::{Item, List};
use core_types::{Color, ExtractVarArgs};
use core_types::{Ctx, ExtractIndex, ExtractIndices, ExtractPosition};
use glam::DVec2;
use graphic_types::vector_types::GradientStops;
use graphic_types::vector_types::Gradient;
use graphic_types::{Graphic, Vector};
use raster_types::{CPU, Raster};
@@ -40,7 +40,7 @@ fn read_color(ctx: impl Ctx + ExtractVarArgs) -> List<Color> {
}
#[node_macro::node(category("Context"), path(graphene_core::vector))]
fn read_gradient(ctx: impl Ctx + ExtractVarArgs) -> List<GradientStops> {
fn read_gradient(ctx: impl Ctx + ExtractVarArgs) -> List<Gradient> {
let Ok(var_arg) = ctx.vararg(0) else { return Default::default() };
let var_arg = var_arg as &dyn std::any::Any;
@@ -111,12 +111,49 @@ fn read_color_row_extent<C: Ctx + ExtractVarArgs>(_: &ReadColorRowNode, ctx: &C,
/// Rank-model vararg source: the mapped row's items as lanes, elements only.
#[node_macro::node(category("Test"), extent_raw(read_gradient_row_extent))]
pub fn read_gradient_row(ctx: impl Ctx + ExtractVarArgs + ExtractIndex) -> Result<IList<GradientStops>, Interrupt> {
pub fn read_gradient_row(ctx: impl Ctx + ExtractVarArgs + ExtractIndex) -> Result<IList<Gradient>, Interrupt> {
vararg_element(ctx)
}
fn read_gradient_row_extent<C: Ctx + ExtractVarArgs>(_: &ReadGradientRowNode, ctx: &C, level: u8) -> GPoll<Extent> {
vararg_lanes::<GradientStops>(ctx, level)
vararg_lanes::<Gradient>(ctx, level)
}
/// Widens a numeric vararg row into `f64`, keeping each item's attributes.
fn widen_vararg<T: Clone + 'static>(var_arg: &dyn std::any::Any, widen: impl Fn(T) -> f64) -> Option<List<f64>> {
let list = var_arg.downcast_ref::<List<T>>()?.clone();
Some(
list.into_iter()
.map(|item| {
let (element, attributes) = item.into_parts();
Item::from_parts(widen(element), attributes)
})
.collect(),
)
}
/// Reads the current number from within a **Map** node's loop.
#[node_macro::node(category("Context"))]
fn read_number(ctx: impl Ctx + ExtractVarArgs) -> List<f64> {
let Ok(var_arg) = ctx.vararg(0) else { return Default::default() };
let var_arg = var_arg as &dyn std::any::Any;
if let Some(list) = var_arg.downcast_ref::<List<f64>>() {
return list.clone();
}
// Numeric rows carry several possible element types, so probe each and widen to f64
if let Some(list) = widen_vararg(var_arg, |value: f32| value as f64) {
return list;
}
if let Some(list) = widen_vararg(var_arg, |value: u32| value as f64) {
return list;
}
if let Some(list) = widen_vararg(var_arg, |value: u64| value as f64) {
return list;
}
Default::default()
}
#[node_macro::node(category("Context"), path(core_types::vector))]

View File

@@ -1,6 +1,5 @@
use core_types::Ctx;
use glam::{DAffine2, DVec2};
use raster_types::{CPU, Raster};
/// Meant for debugging purposes, not general use. Logs the input value to the console and passes it through unchanged.
#[node_macro::node(category("Debug"), name("Log to Console"))]

View File

@@ -1,16 +1,22 @@
use core_types::list::Item;
use core_types::{CacheHash, Ctx};
use dyn_any::DynAny;
use glam::{DVec2, IVec2, UVec2};
/// Obtains the X or Y component of a vec2.
///
/// The inverse of this node is "Vec2 Value", which can have either or both its X and Y parameters exposed as graph inputs.
#[node_macro::node(name("Extract XY"), category("Math: Vector"))]
fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2)] vector: T, axis: XY) -> f64 {
match axis {
/// The inverse of this node is **Combine Vec2**, which composes a vec2 from its X and Y components.
#[node_macro::node(name("Extract XY"), category("Math: Vec2"))]
fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2)] vector: Item<T>, axis: Item<XY>) -> Item<f64> {
let vector = vector.into_element();
let axis = axis.into_element();
let result = match axis {
XY::X => vector.into().x,
XY::Y => vector.into().y,
}
};
Item::new_from_element(result)
}
/// The X or Y component of a vec2.

View File

@@ -9,6 +9,7 @@ fn passthrough<T: Send>(_: impl Ctx, content: T) -> T {
content
}
/// Shifts a whole value onto a connector's type through the std `Into` trait, serving the whole-`List` erasure onto `ListDyn` under the input adapter identifier.
#[node_macro::node(category(""), skip_impl)]
fn into<T: Send + Into<O>, O: Send>(_: impl Ctx, value: T, #[data] _out_ty: PhantomData<O>) -> O {
value.into()