Add Table<Gradient> as a graphical type (#3051)

This commit is contained in:
Keavon Chambers
2025-08-13 01:42:51 -07:00
committed by GitHub
parent 1b351aca76
commit bdc029c692
33 changed files with 689 additions and 573 deletions

View File

@@ -1,5 +1,6 @@
use crate::blending::AlphaBlending;
use crate::bounds::{BoundingBox, RenderBoundingBox};
use crate::gradient::GradientStops;
use crate::math::quad::Quad;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::{Table, TableRow};
@@ -98,6 +99,7 @@ async fn create_artboard<T: Into<Table<Graphic>> + 'n>(
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
Context -> DAffine2,
)]
content: impl Node<Context<'static>, Output = T>,

View File

@@ -1,3 +1,4 @@
use crate::gradient::GradientStops;
use crate::raster_types::{CPU, Raster};
use crate::registry::types::Percentage;
use crate::table::Table;
@@ -41,6 +42,13 @@ impl MultiplyAlpha for Table<Color> {
}
}
}
impl MultiplyAlpha for Table<GradientStops> {
fn multiply_alpha(&mut self, factor: f64) {
for row in self.iter_mut() {
row.alpha_blending.opacity *= factor as f32;
}
}
}
pub(super) trait MultiplyFill {
fn multiply_fill(&mut self, factor: f64);
@@ -78,6 +86,13 @@ impl MultiplyFill for Table<Color> {
}
}
}
impl MultiplyFill for Table<GradientStops> {
fn multiply_fill(&mut self, factor: f64) {
for row in self.iter_mut() {
row.alpha_blending.fill *= factor as f32;
}
}
}
trait SetBlendMode {
fn set_blend_mode(&mut self, blend_mode: BlendMode);
@@ -111,6 +126,13 @@ impl SetBlendMode for Table<Color> {
}
}
}
impl SetBlendMode for Table<GradientStops> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for row in self.iter_mut() {
row.alpha_blending.blend_mode = blend_mode;
}
}
}
trait SetClip {
fn set_clip(&mut self, clip: bool);
@@ -144,6 +166,13 @@ impl SetClip for Table<Color> {
}
}
}
impl SetClip for Table<GradientStops> {
fn set_clip(&mut self, clip: bool) {
for row in self.iter_mut() {
row.alpha_blending.clip = clip;
}
}
}
#[node_macro::node(category("Style"))]
fn blend_mode<T: SetBlendMode>(
@@ -153,6 +182,7 @@ fn blend_mode<T: SetBlendMode>(
Table<Vector>,
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
)]
mut value: T,
blend_mode: BlendMode,
@@ -170,6 +200,7 @@ fn opacity<T: MultiplyAlpha>(
Table<Vector>,
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
)]
mut value: T,
#[default(100.)] opacity: Percentage,
@@ -187,6 +218,7 @@ fn blending<T: SetBlendMode + MultiplyAlpha + MultiplyFill + SetClip>(
Table<Vector>,
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
)]
mut value: T,
blend_mode: BlendMode,

View File

@@ -1,4 +1,4 @@
use crate::Color;
use crate::{Color, gradient::GradientStops};
use glam::{DAffine2, DVec2};
#[derive(Clone, Copy, Default, Debug, PartialEq)]
@@ -33,3 +33,8 @@ impl BoundingBox for Color {
RenderBoundingBox::Infinite
}
}
impl BoundingBox for GradientStops {
fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> RenderBoundingBox {
RenderBoundingBox::Infinite
}
}

View File

@@ -126,7 +126,6 @@ pub struct Gradient {
pub gradient_type: GradientType,
pub start: DVec2,
pub end: DVec2,
pub transform: DAffine2,
}
impl Default for Gradient {
@@ -136,7 +135,6 @@ impl Default for Gradient {
gradient_type: GradientType::Linear,
start: DVec2::new(0., 0.5),
end: DVec2::new(1., 0.5),
transform: DAffine2::IDENTITY,
}
}
}
@@ -147,7 +145,6 @@ impl std::hash::Hash for Gradient {
[].iter()
.chain(self.start.to_array().iter())
.chain(self.end.to_array().iter())
.chain(self.transform.to_cols_array().iter())
.chain(self.stops.0.iter().map(|(position, _)| position))
.for_each(|x| x.to_bits().hash(state));
self.stops.0.iter().for_each(|(_, color)| color.hash(state));
@@ -171,20 +168,15 @@ impl std::fmt::Display for Gradient {
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 {
Gradient {
start,
end,
stops: GradientStops::new(vec![(0., start_color.to_gamma_srgb()), (1., end_color.to_gamma_srgb())]),
transform,
gradient_type,
}
pub fn new(start: DVec2, start_color: Color, end: DVec2, end_color: Color, gradient_type: GradientType) -> Self {
let stops = GradientStops::new(vec![(0., start_color.to_gamma_srgb()), (1., end_color.to_gamma_srgb())]);
Self { start, end, stops, gradient_type }
}
pub fn lerp(&self, other: &Self, time: f64) -> Self {
let start = self.start + (other.start - self.start) * time;
let end = self.end + (other.end - self.end) * time;
let transform = self.transform;
let stops = self
.stops
.0
@@ -199,13 +191,7 @@ impl Gradient {
let stops = GradientStops::new(stops);
let gradient_type = if time < 0.5 { self.gradient_type } else { other.gradient_type };
Self {
start,
end,
transform,
stops,
gradient_type,
}
Self { start, end, stops, gradient_type }
}
/// Insert a stop into the gradient, the index if successful

View File

@@ -1,5 +1,6 @@
use crate::blending::AlphaBlending;
use crate::bounds::{BoundingBox, RenderBoundingBox};
use crate::gradient::GradientStops;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::{Table, TableRow};
use crate::uuid::NodeId;
@@ -17,6 +18,7 @@ pub enum Graphic {
RasterCPU(Table<Raster<CPU>>),
RasterGPU(Table<Raster<GPU>>),
Color(Table<Color>),
Gradient(Table<GradientStops>),
}
impl Default for Graphic {
@@ -145,6 +147,28 @@ impl From<Table<Color>> for Option<Color> {
}
}
// GradientStops
impl From<GradientStops> for Graphic {
fn from(gradient: GradientStops) -> Self {
Graphic::Gradient(Table::new_from_element(gradient))
}
}
impl From<Table<GradientStops>> for Graphic {
fn from(gradient: Table<GradientStops>) -> Self {
Graphic::Gradient(gradient)
}
}
impl From<GradientStops> for Table<Graphic> {
fn from(gradient: GradientStops) -> Self {
Table::new_from_element(Graphic::Gradient(Table::new_from_element(gradient)))
}
}
impl From<Table<GradientStops>> for Table<Graphic> {
fn from(gradient: Table<GradientStops>) -> Self {
Table::new_from_element(Graphic::Gradient(gradient))
}
}
// DAffine2
impl From<DAffine2> for Graphic {
fn from(_: DAffine2) -> Self {
@@ -207,6 +231,7 @@ impl Graphic {
Graphic::RasterCPU(raster) => raster.iter().all(|row| row.alpha_blending.clip),
Graphic::RasterGPU(raster) => raster.iter().all(|row| row.alpha_blending.clip),
Graphic::Color(color) => color.iter().all(|row| row.alpha_blending.clip),
Graphic::Gradient(gradient) => gradient.iter().all(|row| row.alpha_blending.clip),
}
}
@@ -230,6 +255,7 @@ impl BoundingBox for Graphic {
Graphic::RasterGPU(raster) => raster.bounding_box(transform, include_stroke),
Graphic::Graphic(graphic) => graphic.bounding_box(transform, include_stroke),
Graphic::Color(color) => color.bounding_box(transform, include_stroke),
Graphic::Gradient(gradient) => gradient.bounding_box(transform, include_stroke),
}
}
}
@@ -237,7 +263,7 @@ impl BoundingBox for Graphic {
#[node_macro::node(category(""))]
async fn source_node_id<I: 'n + Send + Clone>(
_: impl Ctx,
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>)] content: Table<I>,
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)] content: Table<I>,
node_path: Vec<NodeId>,
) -> Table<I> {
// Get the penultimate element of the node path, or None if the path is too short
@@ -257,11 +283,11 @@ async fn source_node_id<I: 'n + Send + Clone>(
async fn extend<I: 'n + Send + Clone>(
_: impl Ctx,
/// The table whose rows will appear at the start of the extended table.
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>)]
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
base: Table<I>,
/// The table whose rows will appear at the end of the extended table.
#[expose]
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>)]
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
new: Table<I>,
) -> Table<I> {
let mut base = base;
@@ -274,9 +300,9 @@ async fn extend<I: 'n + Send + Clone>(
#[node_macro::node(category(""))]
async fn legacy_layer_extend<I: 'n + Send + Clone>(
_: impl Ctx,
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>)] base: Table<I>,
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)] base: Table<I>,
#[expose]
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>)]
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
new: Table<I>,
nested_node_path: Vec<NodeId>,
) -> Table<I> {
@@ -302,6 +328,7 @@ async fn wrap_graphic<T: Into<Graphic> + 'n>(
Table<Raster<CPU>>,
Table<Raster<GPU>>,
Table<Color>,
Table<GradientStops>,
DAffine2,
)]
content: T,
@@ -320,6 +347,7 @@ async fn to_graphic<T: Into<Table<Graphic>> + 'n>(
Table<Raster<CPU>>,
Table<Raster<GPU>>,
Table<Color>,
Table<GradientStops>,
)]
content: T,
) -> Table<Graphic> {
@@ -427,6 +455,7 @@ fn index<T: AtIndex + Clone + Default>(
Table<Raster<CPU>>,
Table<Raster<GPU>>,
Table<Color>,
Table<GradientStops>,
)]
collection: T,
/// The index of the item to retrieve, starting from 0 for the first item.

View File

@@ -17,7 +17,7 @@ fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(bool, f64, u32,
#[node_macro::node(category("Text"))]
fn serialize<T: serde::Serialize>(
_: impl Ctx,
#[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>)] value: T,
#[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>, Table<GradientStops>)] value: T,
) -> String {
serde_json::to_string(&value).unwrap_or_else(|_| "Serialization Error".to_string())
}

View File

@@ -1,8 +1,8 @@
use crate::gradient::GradientStops;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::vector::Vector;
use crate::{Artboard, Color, Graphic};
use glam::DVec2;
pub trait RenderComplexity {
fn render_complexity(&self) -> usize {
@@ -30,6 +30,7 @@ impl RenderComplexity for Graphic {
Self::RasterCPU(table) => table.render_complexity(),
Self::RasterGPU(table) => table.render_complexity(),
Self::Color(table) => table.render_complexity(),
Self::Gradient(table) => table.render_complexity(),
}
}
}
@@ -59,8 +60,8 @@ impl RenderComplexity for Color {
}
}
impl RenderComplexity for String {}
impl RenderComplexity for bool {}
impl RenderComplexity for f32 {}
impl RenderComplexity for f64 {}
impl RenderComplexity for DVec2 {}
impl RenderComplexity for GradientStops {
fn render_complexity(&self) -> usize {
1
}
}

View File

@@ -1,3 +1,4 @@
use crate::gradient::GradientStops;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::transform::{ApplyTransform, Footprint, Transform};
@@ -17,6 +18,8 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
)]
value: impl Node<Context<'static>, Output = T>,
translate: DVec2,
@@ -44,7 +47,7 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
#[node_macro::node(category(""))]
fn replace_transform<Data, TransformInput: Transform>(
_: impl Ctx,
#[implementations(Table<Vector>, Table<Raster<CPU>>, Table<Graphic>, Table<Color>)] mut data: Table<Data>,
#[implementations(Table<Vector>, Table<Raster<CPU>>, Table<Graphic>, Table<Color>, Table<GradientStops>)] mut data: Table<Data>,
#[implementations(DAffine2)] transform: TransformInput,
) -> Table<Data> {
for data_transform in data.iter_mut() {
@@ -62,6 +65,7 @@ async fn extract_transform<T>(
Table<Raster<CPU>>,
Table<Raster<GPU>>,
Table<Color>,
Table<GradientStops>,
)]
vector: Table<T>,
) -> DAffine2 {
@@ -97,6 +101,7 @@ async fn boundless_footprint<T: 'n + 'static>(
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
Context -> String,
Context -> f64,
)]
@@ -116,6 +121,7 @@ async fn freeze_real_time<T: 'n + 'static>(
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
Context -> String,
Context -> f64,
)]

View File

@@ -1,3 +1,4 @@
use crate::gradient::GradientStops;
use crate::raster_types::{CPU, Raster};
use crate::table::{Table, TableRowRef};
use crate::vector::Vector;
@@ -14,6 +15,7 @@ async fn instance_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
)]
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
reverse: bool,
@@ -56,6 +58,7 @@ async fn instance_repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
)]
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
#[default(1)] count: u64,

View File

@@ -127,6 +127,15 @@ impl From<Table<Color>> for Fill {
}
}
impl From<Table<GradientStops>> for Fill {
fn from(gradient: Table<GradientStops>) -> Fill {
Fill::Gradient(Gradient {
stops: gradient.iter().nth(0).map(|row| row.element.clone()).unwrap_or_default(),
..Default::default()
})
}
}
impl From<Gradient> for Fill {
fn from(gradient: Gradient) -> Fill {
Fill::Gradient(gradient)

View File

@@ -113,6 +113,8 @@ async fn fill<F: Into<Fill> + 'n + Send, V: VectorTableIterMut + 'n + Send>(
Table<Vector>,
Table<Vector>,
Table<Vector>,
Table<Vector>,
Table<Graphic>,
Table<Graphic>,
Table<Graphic>,
Table<Graphic>,
@@ -122,9 +124,11 @@ async fn fill<F: Into<Fill> + 'n + Send, V: VectorTableIterMut + 'n + Send>(
#[implementations(
Fill,
Table<Color>,
Table<GradientStops>,
Gradient,
Fill,
Table<Color>,
Table<GradientStops>,
Gradient,
)]
#[default(Color::BLACK)]
@@ -134,11 +138,7 @@ async fn fill<F: Into<Fill> + 'n + Send, V: VectorTableIterMut + 'n + Send>(
) -> V {
let fill: Fill = fill.into();
for vector in content.vector_iter_mut() {
let mut fill = fill.clone();
if let Fill::Gradient(gradient) = &mut fill {
gradient.transform *= *vector.transform;
}
vector.element.style.set_fill(fill);
vector.element.style.set_fill(fill.clone());
}
content
@@ -206,7 +206,7 @@ where
async fn repeat<I: 'n + Send + Clone>(
_: impl Ctx,
// TODO: Implement other graphical types.
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>)] instance: Table<I>,
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>, Table<GradientStops>)] instance: Table<I>,
#[default(100., 100.)]
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
direction: PixelSize,
@@ -242,7 +242,7 @@ async fn repeat<I: 'n + Send + Clone>(
async fn circular_repeat<I: 'n + Send + Clone>(
_: impl Ctx,
// TODO: Implement other graphical types.
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>)] instance: Table<I>,
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>, Table<GradientStops>)] instance: Table<I>,
angle_offset: Angle,
#[unit(" px")]
#[default(5)]
@@ -278,7 +278,7 @@ async fn copy_to_points<I: 'n + Send + Clone>(
points: Table<Vector>,
/// Artwork to be copied and placed at each point.
#[expose]
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>)]
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>, Table<GradientStops>)]
instance: Table<I>,
/// Minimum range of randomized sizes given to each instance.
#[default(1)]
@@ -353,7 +353,7 @@ async fn copy_to_points<I: 'n + Send + Clone>(
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn mirror<I: 'n + Send + Clone>(
_: impl Ctx,
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>)] content: Table<I>,
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>, Table<GradientStops>)] content: Table<I>,
#[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint,
#[unit(" px")] offset: f64,
#[range((-90., 90.))] angle: Angle,
@@ -1888,7 +1888,7 @@ fn point_inside(_: impl Ctx, source: Table<Vector>, point: DVec2) -> bool {
}
#[node_macro::node(category("General"), path(graphene_core::vector))]
async fn count_elements<I>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>)] source: Table<I>) -> u64 {
async fn count_elements<I>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)] source: Table<I>) -> u64 {
source.len() as u64
}