Animated thumbnails

This commit is contained in:
Adam
2025-07-17 00:31:09 -07:00
parent c2815ea0e3
commit 08c2d0543c
12 changed files with 239 additions and 60 deletions

View File

@@ -1,5 +1,5 @@
use crate::Color;
use glam::{DAffine2, DVec2};
use glam::{DAffine2, DVec2, IVec2, UVec2};
pub trait BoundingBox {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]>;
@@ -15,10 +15,80 @@ macro_rules! none_impl {
};
}
none_impl!(String);
none_impl!(bool);
none_impl!(f32);
none_impl!(f64);
none_impl!(DVec2);
none_impl!(Option<Color>);
none_impl!(Vec<Color>);
impl BoundingBox for u32 {
fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
text_bbox(i32_width(*self as i32))
}
}
impl BoundingBox for f64 {
fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
text_bbox(f64_width(*self))
}
}
impl BoundingBox for DVec2 {
fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
let width_x = f64_width(self.x);
let width_y = f64_width(self.y);
let total_width = width_x + width_y + 50.;
text_bbox(total_width)
}
}
impl BoundingBox for IVec2 {
fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
let width_x = i32_width(self.x);
let width_y = i32_width(self.y);
let total_width = width_x + width_y + 50.;
text_bbox(total_width)
}
}
impl BoundingBox for UVec2 {
fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
let width_x = i32_width(self.x as i32);
let width_y = i32_width(self.y as i32);
let total_width = width_x + width_y + 50.;
text_bbox(total_width)
}
}
impl BoundingBox for bool {
fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
text_bbox(60.)
}
}
impl BoundingBox for String {
fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
let width = self.len() * 16;
text_bbox(width as f64)
}
}
impl BoundingBox for Option<Color> {
fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
Some([(0., -5.).into(), (150., 110.).into()])
}
}
fn f64_width(f64: f64) -> f64 {
let left_of_decimal_width = i32_width(f64 as i32);
left_of_decimal_width + 5. + 2. * 16.
}
fn i32_width(i32: i32) -> f64 {
let number_of_digits = (i32.abs()).checked_ilog10().unwrap_or(0) + 1;
let mut width = number_of_digits * 16;
if i32 < 0 {
width += 20;
}
width.into()
}
fn text_bbox(width: f64) -> Option<[DVec2; 2]> {
Some([(-width / 2., 0.).into(), (width / 2., 30.).into()])
}

View File

@@ -630,8 +630,8 @@ fn get_animation_time(ctx: impl Ctx + ExtractAnimationTime) -> Option<f64> {
}
#[node_macro::node(category("Context Getter"))]
fn get_index(ctx: impl Ctx + ExtractIndex) -> Option<usize> {
ctx.try_index()
fn get_index(ctx: impl Ctx + ExtractIndex) -> Option<u32> {
ctx.try_index().map(|index| index as u32)
}
// #[node_macro::node(category("Loop"))]

View File

@@ -1,4 +1,4 @@
use crate::Color;
use crate::{Color, bounds::BoundingBox, vector::VectorDataTable};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
@@ -32,6 +32,12 @@ impl Default for GradientStops {
}
}
impl BoundingBox for GradientStops {
fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
Into::<VectorDataTable>::into(Into::<Gradient>::into(self.clone())).bounding_box(DAffine2::default(), false)
}
}
impl IntoIterator for GradientStops {
type Item = (f64, Color);
type IntoIter = std::vec::IntoIter<(f64, Color)>;
@@ -169,6 +175,24 @@ impl std::fmt::Display for Gradient {
}
}
impl From<GradientStops> for Gradient {
fn from(gradient_stops: GradientStops) -> Gradient {
Gradient {
stops: gradient_stops.clone(),
gradient_type: GradientType::Linear,
start: (0., 0.).into(),
end: (1., 0.).into(),
transform: DAffine2::IDENTITY,
}
}
}
impl BoundingBox for Gradient {
fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
Into::<VectorDataTable>::into(self.clone()).bounding_box(DAffine2::default(), false)
}
}
impl Gradient {
/// Constructs a new gradient with the colors at 0 and 1 specified.
pub fn new(start: DVec2, start_color: Color, end: DVec2, end_color: Color, transform: DAffine2, gradient_type: GradientType) -> Self {

View File

@@ -1,3 +1,4 @@
use crate::gradient::{Gradient, GradientStops};
use crate::instances::Instances;
use crate::raster_types::{CPU, GPU, Raster};
use crate::vector::VectorData;
@@ -54,8 +55,10 @@ impl RenderComplexity for Raster<GPU> {
impl RenderComplexity for String {}
impl RenderComplexity for bool {}
impl RenderComplexity for f32 {}
impl RenderComplexity for u32 {}
impl RenderComplexity for f64 {}
impl RenderComplexity for DVec2 {}
impl RenderComplexity for Option<Color> {}
impl RenderComplexity for Vec<Color> {}
impl RenderComplexity for GradientStops {}
impl RenderComplexity for Gradient {}

View File

@@ -5,13 +5,15 @@ mod modification;
use super::misc::{dvec2_to_point, point_to_dvec2};
use super::style::{PathStyle, Stroke};
use crate::bounds::BoundingBox;
use crate::gradient::{Gradient, GradientType};
use crate::instances::Instances;
use crate::math::quad::Quad;
use crate::transform::Transform;
use crate::vector::click_target::{ClickTargetType, FreePoint};
use crate::vector::style::Fill;
use crate::{AlphaBlending, Color, GraphicGroupTable};
pub use attributes::*;
use bezier_rs::{BezierHandles, ManipulatorGroup};
use bezier_rs::{BezierHandles, ManipulatorGroup, Subpath};
use core::borrow::Borrow;
use core::hash::Hash;
use dyn_any::DynAny;
@@ -511,6 +513,36 @@ impl BoundingBox for VectorDataTable {
}
}
/// Convert a Gradient/GradientStops into VectorDataTable for rendering thumbnails
impl From<Gradient> for VectorDataTable {
fn from(mut gradient: Gradient) -> VectorDataTable {
match gradient.gradient_type {
GradientType::Linear => {
let mut rectangle = VectorData::from_subpath(Subpath::new_rect((0., 0.).into(), (150., 100.).into()));
// Handle vertical gradients
let intersection = if gradient.start.x == gradient.end.x {
DVec2::new(0., 100.)
} else {
let slope = (gradient.start.y - gradient.end.y) / (gradient.start.x - gradient.end.x);
if slope > 100. / 150. { DVec2::new(100. / slope, 100.) } else { DVec2::new(150., slope * 150.) }
};
gradient.start = (0., 0.).into();
gradient.end = intersection;
rectangle.style.fill = Fill::Gradient(gradient);
Instances::new(rectangle)
}
GradientType::Radial => {
let mut circle = VectorData::from_subpath(Subpath::new_ellipse((-100., -100.).into(), (100., 100.).into()));
gradient.start = (0., 0.).into();
gradient.end = (100., 0.).into();
gradient.transform = DAffine2::IDENTITY;
circle.style.fill = Fill::Gradient(gradient);
Instances::new(circle)
}
}
}
}
/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curvature).
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
pub enum ManipulatorPointId {