mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 02:48:12 +08:00
Move gradient picking into the color picker (#1778)
* Gradient picker * Fix up color picker layout CSS problems * Begin hooking up SpectrumInput for gradient in the ColorPicker * Working gradient picking on the frontend only * Plumb FillColorChoice into the backend * Hook everything else up, just with a weird bug remaining * Fix some svelty reactivity issues * Add and remove stops * Cleanup * Rename type * Fill node document format upgrading * Fix lint * Polish the color picker UX and fix a bug --------- Co-authored-by: 0hypercube <0hypercube@gmail.com>
This commit is contained in:
@@ -265,6 +265,9 @@ impl Color {
|
||||
pub const RED: Color = Color::from_rgbf32_unchecked(1., 0., 0.);
|
||||
pub const GREEN: Color = Color::from_rgbf32_unchecked(0., 1., 0.);
|
||||
pub const BLUE: Color = Color::from_rgbf32_unchecked(0., 0., 1.);
|
||||
pub const YELLOW: Color = Color::from_rgbf32_unchecked(1., 1., 0.);
|
||||
pub const CYAN: Color = Color::from_rgbf32_unchecked(0., 1., 1.);
|
||||
pub const MAGENTA: Color = Color::from_rgbf32_unchecked(1., 0., 1.);
|
||||
pub const TRANSPARENT: Color = Self {
|
||||
red: 0.,
|
||||
green: 0.,
|
||||
|
||||
@@ -27,29 +27,62 @@ pub enum GradientType {
|
||||
Radial,
|
||||
}
|
||||
|
||||
// TODO: Someday we could switch this to a Box[T] to avoid over-allocation
|
||||
/// A list of colors associated with positions (in the range 0 to 1) along a gradient.
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny, specta::Type)]
|
||||
pub struct GradientStops(pub Vec<(f64, Color)>);
|
||||
|
||||
impl std::hash::Hash for GradientStops {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.len().hash(state);
|
||||
self.0.iter().for_each(|(position, color)| {
|
||||
position.to_bits().hash(state);
|
||||
color.hash(state);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GradientStops {
|
||||
fn default() -> Self {
|
||||
Self(vec![(0., Color::BLACK), (1., Color::WHITE)])
|
||||
}
|
||||
}
|
||||
|
||||
/// A gradient fill.
|
||||
///
|
||||
/// Contains the start and end points, along with the colors at varying points along the length.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, DynAny, specta::Type)]
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny, specta::Type)]
|
||||
pub struct Gradient {
|
||||
pub stops: GradientStops,
|
||||
pub gradient_type: GradientType,
|
||||
pub start: DVec2,
|
||||
pub end: DVec2,
|
||||
pub transform: DAffine2,
|
||||
pub positions: Vec<(f64, Color)>,
|
||||
pub gradient_type: GradientType,
|
||||
}
|
||||
|
||||
impl Default for Gradient {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
stops: GradientStops::default(),
|
||||
gradient_type: GradientType::Linear,
|
||||
start: DVec2::new(0., 0.5),
|
||||
end: DVec2::new(1., 0.5),
|
||||
transform: DAffine2::IDENTITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::hash::Hash for Gradient {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.positions.len().hash(state);
|
||||
self.stops.0.len().hash(state);
|
||||
[].iter()
|
||||
.chain(self.start.to_array().iter())
|
||||
.chain(self.end.to_array().iter())
|
||||
.chain(self.transform.to_cols_array().iter())
|
||||
.chain(self.positions.iter().map(|(position, _)| position))
|
||||
.chain(self.stops.0.iter().map(|(position, _)| position))
|
||||
.for_each(|x| x.to_bits().hash(state));
|
||||
self.positions.iter().for_each(|(_, color)| color.hash(state));
|
||||
self.stops.0.iter().for_each(|(_, color)| color.hash(state));
|
||||
self.gradient_type.hash(state);
|
||||
}
|
||||
}
|
||||
@@ -60,7 +93,7 @@ impl Gradient {
|
||||
Gradient {
|
||||
start,
|
||||
end,
|
||||
positions: vec![(0., start_color), (1., end_color)],
|
||||
stops: GradientStops(vec![(0., start_color), (1., end_color)]),
|
||||
transform,
|
||||
gradient_type,
|
||||
}
|
||||
@@ -70,23 +103,25 @@ impl Gradient {
|
||||
let start = self.start + (other.start - self.start) * time;
|
||||
let end = self.end + (other.end - self.end) * time;
|
||||
let transform = self.transform;
|
||||
let positions = self
|
||||
.positions
|
||||
let stops = self
|
||||
.stops
|
||||
.0
|
||||
.iter()
|
||||
.zip(other.positions.iter())
|
||||
.zip(other.stops.0.iter())
|
||||
.map(|((a_pos, a_color), (b_pos, b_color))| {
|
||||
let position = a_pos + (b_pos - a_pos) * time;
|
||||
let color = a_color.lerp(b_color, time as f32);
|
||||
(position, color)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let stops = GradientStops(stops);
|
||||
let gradient_type = if time < 0.5 { self.gradient_type } else { other.gradient_type };
|
||||
|
||||
Self {
|
||||
start,
|
||||
end,
|
||||
transform,
|
||||
positions,
|
||||
stops,
|
||||
gradient_type,
|
||||
}
|
||||
}
|
||||
@@ -97,9 +132,9 @@ impl Gradient {
|
||||
let transformed_bound_transform = DAffine2::from_scale_angle_translation(transformed_bounds[1] - transformed_bounds[0], 0., transformed_bounds[0]);
|
||||
let updated_transform = multiplied_transform * bound_transform;
|
||||
|
||||
let mut positions = String::new();
|
||||
for (position, color) in self.positions.iter() {
|
||||
let _ = write!(positions, r##"<stop offset="{}" stop-color="#{}" />"##, position, color.with_alpha(color.a()).rgba_hex());
|
||||
let mut stop = String::new();
|
||||
for (position, color) in self.stops.0.iter() {
|
||||
let _ = write!(stop, r##"<stop offset="{}" stop-color="#{}" />"##, position, color.with_alpha(color.a()).rgba_hex());
|
||||
}
|
||||
|
||||
let mod_gradient = transformed_bound_transform.inverse();
|
||||
@@ -121,7 +156,7 @@ impl Gradient {
|
||||
let _ = write!(
|
||||
svg_defs,
|
||||
r#"<linearGradient id="{}" x1="{}" x2="{}" y1="{}" y2="{}" gradientTransform="matrix({})">{}</linearGradient>"#,
|
||||
gradient_id, start.x, end.x, start.y, end.y, transform, positions
|
||||
gradient_id, start.x, end.x, start.y, end.y, transform, stop
|
||||
);
|
||||
}
|
||||
GradientType::Radial => {
|
||||
@@ -129,7 +164,7 @@ impl Gradient {
|
||||
let _ = write!(
|
||||
svg_defs,
|
||||
r#"<radialGradient id="{}" cx="{}" cy="{}" r="{}" gradientTransform="matrix({})">{}</radialGradient>"#,
|
||||
gradient_id, start.x, start.y, radius, transform, positions
|
||||
gradient_id, start.x, start.y, radius, transform, stop
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -151,11 +186,11 @@ impl Gradient {
|
||||
}
|
||||
|
||||
// Compute the color of the inserted stop
|
||||
let get_color = |index: usize, time: f64| match (self.positions[index].1, self.positions.get(index + 1).map(|(_, c)| *c)) {
|
||||
let get_color = |index: usize, time: f64| match (self.stops.0[index].1, self.stops.0.get(index + 1).map(|(_, c)| *c)) {
|
||||
// Lerp between the nearest colors if applicable
|
||||
(a, Some(b)) => a.lerp(
|
||||
&b,
|
||||
((time - self.positions[index].0) / self.positions.get(index + 1).map(|end| end.0 - self.positions[index].0).unwrap_or_default()) as f32,
|
||||
((time - self.stops.0[index].0) / self.stops.0.get(index + 1).map(|end| end.0 - self.stops.0[index].0).unwrap_or_default()) as f32,
|
||||
),
|
||||
// Use the start or the end color if applicable
|
||||
(v, _) => v,
|
||||
@@ -163,14 +198,14 @@ impl Gradient {
|
||||
|
||||
// Compute the correct index to keep the positions in order
|
||||
let mut index = 0;
|
||||
while self.positions.len() > index && self.positions[index].0 <= new_position {
|
||||
while self.stops.0.len() > index && self.stops.0[index].0 <= new_position {
|
||||
index += 1;
|
||||
}
|
||||
|
||||
let new_color = get_color(index - 1, new_position);
|
||||
|
||||
// Insert the new stop
|
||||
self.positions.insert(index, (new_position, new_color));
|
||||
self.stops.0.insert(index, (new_position, new_color));
|
||||
|
||||
Some(index)
|
||||
}
|
||||
@@ -178,7 +213,9 @@ impl Gradient {
|
||||
|
||||
/// Describes the fill of a layer.
|
||||
///
|
||||
/// Can be None, a solid [Color], a linear [Gradient], a radial [Gradient] or potentially some sort of image or pattern in the future
|
||||
/// Can be None, a solid [Color], or a linear/radial [Gradient].
|
||||
///
|
||||
/// In the future we'll probably also add a pattern fill.
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash, specta::Type)]
|
||||
pub enum Fill {
|
||||
@@ -207,8 +244,8 @@ impl Fill {
|
||||
match self {
|
||||
Self::None => Color::BLACK,
|
||||
Self::Solid(color) => *color,
|
||||
// TODO: Should correctly sample the gradient
|
||||
Self::Gradient(Gradient { positions, .. }) => positions[0].1,
|
||||
// TODO: Should correctly sample the gradient the equation here: https://svgwg.org/svg2-draft/pservers.html#Gradients
|
||||
Self::Gradient(Gradient { stops, .. }) => stops.0[0].1,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,13 +258,13 @@ impl Fill {
|
||||
(Self::Solid(a), Self::Solid(b)) => Self::Solid(a.lerp(b, time as f32)),
|
||||
(Self::Solid(a), Self::Gradient(b)) => {
|
||||
let mut solid_to_gradient = b.clone();
|
||||
solid_to_gradient.positions.iter_mut().for_each(|(_, color)| *color = *a);
|
||||
solid_to_gradient.stops.0.iter_mut().for_each(|(_, color)| *color = *a);
|
||||
let a = &solid_to_gradient;
|
||||
Self::Gradient(a.lerp(b, time))
|
||||
}
|
||||
(Self::Gradient(a), Self::Solid(b)) => {
|
||||
let mut gradient_to_solid = a.clone();
|
||||
gradient_to_solid.positions.iter_mut().for_each(|(_, color)| *color = *b);
|
||||
gradient_to_solid.stops.0.iter_mut().for_each(|(_, color)| *color = *b);
|
||||
let b = &gradient_to_solid;
|
||||
Self::Gradient(a.lerp(b, time))
|
||||
}
|
||||
@@ -248,22 +285,91 @@ impl Fill {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the fill is not none
|
||||
pub fn is_some(&self) -> bool {
|
||||
*self != Self::None
|
||||
}
|
||||
|
||||
/// Extract a gradient from the fill
|
||||
pub fn as_gradient(&self) -> Option<&Gradient> {
|
||||
if let Self::Gradient(gradient) = self {
|
||||
Some(gradient)
|
||||
} else {
|
||||
None
|
||||
match self {
|
||||
Self::Gradient(gradient) => Some(gradient),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum describing the type of [Fill]
|
||||
impl From<Color> for Fill {
|
||||
fn from(color: Color) -> Fill {
|
||||
Fill::Solid(color)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<Color>> for Fill {
|
||||
fn from(color: Option<Color>) -> Fill {
|
||||
Fill::solid_or_none(color)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Gradient> for Fill {
|
||||
fn from(gradient: Gradient) -> Fill {
|
||||
Fill::Gradient(gradient)
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the fill of a layer, but unlike [`Fill`], this doesn't store a [`Gradient`] directly but just its [`GradientStops`].
|
||||
///
|
||||
/// Can be None, a solid [Color], or a linear/radial [Gradient].
|
||||
///
|
||||
/// In the future we'll probably also add a pattern fill.
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash, specta::Type)]
|
||||
pub enum FillChoice {
|
||||
#[default]
|
||||
None,
|
||||
Solid(Color),
|
||||
Gradient(GradientStops),
|
||||
}
|
||||
|
||||
impl FillChoice {
|
||||
pub fn from_optional_color(color: Option<Color>) -> Self {
|
||||
match color {
|
||||
Some(color) => Self::Solid(color),
|
||||
None => Self::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_solid(&self) -> Option<Color> {
|
||||
let Self::Solid(color) = self else { return None };
|
||||
Some(*color)
|
||||
}
|
||||
|
||||
pub fn as_gradient(&self) -> Option<&GradientStops> {
|
||||
let Self::Gradient(gradient) = self else { return None };
|
||||
Some(gradient)
|
||||
}
|
||||
|
||||
/// Convert this [`FillChoice`] to a [`Fill`] using the provided [`Gradient`] as a base for the positional information of the gradient.
|
||||
/// If a gradient isn't provided, default gradient positional information is used in cases where the [`FillChoice`] is a [`Gradient`].
|
||||
pub fn to_fill(&self, existing_gradient: Option<&Gradient>) -> Fill {
|
||||
match self {
|
||||
Self::None => Fill::None,
|
||||
Self::Solid(color) => Fill::Solid(*color),
|
||||
Self::Gradient(stops) => {
|
||||
let mut fill = existing_gradient.cloned().unwrap_or_default();
|
||||
fill.stops = stops.clone();
|
||||
Fill::Gradient(fill)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Fill> for FillChoice {
|
||||
fn from(fill: Fill) -> Self {
|
||||
match fill {
|
||||
Fill::None => FillChoice::None,
|
||||
Fill::Solid(color) => FillChoice::Solid(color),
|
||||
Fill::Gradient(gradient) => FillChoice::Gradient(gradient.stops),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum describing the type of [Fill].
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash, specta::Type)]
|
||||
pub enum FillType {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::misc::CentroidType;
|
||||
use super::style::{Fill, FillType, Gradient, GradientType, Stroke};
|
||||
use super::style::{Fill, Stroke};
|
||||
use super::{PointId, SegmentId, StrokeId, VectorData};
|
||||
use crate::renderer::GraphicElementRendered;
|
||||
use crate::transform::{Footprint, Transform, TransformMut};
|
||||
@@ -11,37 +11,14 @@ use glam::{DAffine2, DVec2};
|
||||
use rand::{Rng, SeedableRng};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SetFillNode<FillType, SolidColor, GradientType, Start, End, Transform, Positions> {
|
||||
fill_type: FillType,
|
||||
solid_color: SolidColor,
|
||||
gradient_type: GradientType,
|
||||
start: Start,
|
||||
end: End,
|
||||
transform: Transform,
|
||||
positions: Positions,
|
||||
pub struct SetFillNode<Fill> {
|
||||
fill: Fill,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(SetFillNode)]
|
||||
fn set_vector_data_fill(
|
||||
mut vector_data: VectorData,
|
||||
fill_type: FillType,
|
||||
solid_color: Option<Color>,
|
||||
gradient_type: GradientType,
|
||||
start: DVec2,
|
||||
end: DVec2,
|
||||
transform: DAffine2,
|
||||
positions: Vec<(f64, Color)>,
|
||||
) -> VectorData {
|
||||
vector_data.style.set_fill(match fill_type {
|
||||
FillType::Solid => solid_color.map_or(Fill::None, Fill::Solid),
|
||||
FillType::Gradient => Fill::Gradient(Gradient {
|
||||
start,
|
||||
end,
|
||||
transform,
|
||||
positions,
|
||||
gradient_type,
|
||||
}),
|
||||
});
|
||||
fn set_vector_data_fill<T: Into<Fill>>(mut vector_data: VectorData, fill: T) -> VectorData {
|
||||
vector_data.style.set_fill(fill.into());
|
||||
|
||||
vector_data
|
||||
}
|
||||
|
||||
|
||||
@@ -59,8 +59,11 @@ pub enum TaggedValue {
|
||||
LineCap(graphene_core::vector::style::LineCap),
|
||||
LineJoin(graphene_core::vector::style::LineJoin),
|
||||
FillType(graphene_core::vector::style::FillType),
|
||||
FillChoice(graphene_core::vector::style::FillChoice),
|
||||
Gradient(graphene_core::vector::style::Gradient),
|
||||
GradientType(graphene_core::vector::style::GradientType),
|
||||
GradientPositions(Vec<(f64, graphene_core::Color)>),
|
||||
#[serde(alias = "GradientPositions")] // TODO: Eventually remove this alias (probably starting late 2024)
|
||||
GradientStops(graphene_core::vector::style::GradientStops),
|
||||
Quantization(graphene_core::quantization::QuantizationChannels),
|
||||
OptionalColor(Option<graphene_core::raster::color::Color>),
|
||||
ManipulatorGroupIds(Vec<graphene_core::uuid::ManipulatorGroupId>),
|
||||
@@ -130,10 +133,12 @@ impl Hash for TaggedValue {
|
||||
Self::LineCap(x) => x.hash(state),
|
||||
Self::LineJoin(x) => x.hash(state),
|
||||
Self::FillType(x) => x.hash(state),
|
||||
Self::FillChoice(x) => x.hash(state),
|
||||
Self::Gradient(x) => x.hash(state),
|
||||
Self::GradientType(x) => x.hash(state),
|
||||
Self::GradientPositions(x) => {
|
||||
x.len().hash(state);
|
||||
for (position, color) in x {
|
||||
Self::GradientStops(x) => {
|
||||
x.0.len().hash(state);
|
||||
for (position, color) in &x.0 {
|
||||
position.to_bits().hash(state);
|
||||
color.hash(state);
|
||||
}
|
||||
@@ -208,8 +213,10 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::LineCap(x) => Box::new(x),
|
||||
TaggedValue::LineJoin(x) => Box::new(x),
|
||||
TaggedValue::FillType(x) => Box::new(x),
|
||||
TaggedValue::FillChoice(x) => Box::new(x),
|
||||
TaggedValue::Gradient(x) => Box::new(x),
|
||||
TaggedValue::GradientType(x) => Box::new(x),
|
||||
TaggedValue::GradientPositions(x) => Box::new(x),
|
||||
TaggedValue::GradientStops(x) => Box::new(x),
|
||||
TaggedValue::Quantization(x) => Box::new(x),
|
||||
TaggedValue::OptionalColor(x) => Box::new(x),
|
||||
TaggedValue::ManipulatorGroupIds(x) => Box::new(x),
|
||||
@@ -287,8 +294,10 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::LineCap(_) => concrete!(graphene_core::vector::style::LineCap),
|
||||
TaggedValue::LineJoin(_) => concrete!(graphene_core::vector::style::LineJoin),
|
||||
TaggedValue::FillType(_) => concrete!(graphene_core::vector::style::FillType),
|
||||
TaggedValue::FillChoice(_) => concrete!(graphene_core::vector::style::FillChoice),
|
||||
TaggedValue::Gradient(_) => concrete!(graphene_core::vector::style::Gradient),
|
||||
TaggedValue::GradientType(_) => concrete!(graphene_core::vector::style::GradientType),
|
||||
TaggedValue::GradientPositions(_) => concrete!(Vec<(f64, graphene_core::Color)>),
|
||||
TaggedValue::GradientStops(_) => concrete!(graphene_core::vector::style::GradientStops),
|
||||
TaggedValue::Quantization(_) => concrete!(graphene_core::quantization::QuantizationChannels),
|
||||
TaggedValue::OptionalColor(_) => concrete!(Option<graphene_core::Color>),
|
||||
TaggedValue::ManipulatorGroupIds(_) => concrete!(Vec<graphene_core::uuid::ManipulatorGroupId>),
|
||||
@@ -354,8 +363,10 @@ impl<'a> TaggedValue {
|
||||
x if x == TypeId::of::<graphene_core::vector::style::LineCap>() => Ok(TaggedValue::LineCap(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<graphene_core::vector::style::LineJoin>() => Ok(TaggedValue::LineJoin(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<graphene_core::vector::style::FillType>() => Ok(TaggedValue::FillType(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<graphene_core::vector::style::FillChoice>() => Ok(TaggedValue::FillChoice(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<graphene_core::vector::style::Gradient>() => Ok(TaggedValue::Gradient(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<graphene_core::vector::style::GradientType>() => Ok(TaggedValue::GradientType(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<Vec<(f64, graphene_core::Color)>>() => Ok(TaggedValue::GradientPositions(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<graphene_core::vector::style::GradientStops>() => Ok(TaggedValue::GradientStops(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<graphene_core::quantization::QuantizationChannels>() => Ok(TaggedValue::Quantization(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<Option<graphene_core::Color>>() => Ok(TaggedValue::OptionalColor(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<Vec<graphene_core::uuid::ManipulatorGroupId>>() => Ok(TaggedValue::ManipulatorGroupIds(*downcast(input).unwrap())),
|
||||
@@ -434,7 +445,7 @@ impl<'a> TaggedValue {
|
||||
x if x == TypeId::of::<graphene_core::vector::style::LineJoin>() => TaggedValue::LineJoin(graphene_core::vector::style::LineJoin::Miter),
|
||||
x if x == TypeId::of::<graphene_core::vector::style::FillType>() => TaggedValue::FillType(graphene_core::vector::style::FillType::Solid),
|
||||
x if x == TypeId::of::<graphene_core::vector::style::GradientType>() => TaggedValue::GradientType(Default::default()),
|
||||
x if x == TypeId::of::<Vec<(f64, graphene_core::Color)>>() => TaggedValue::GradientPositions(Default::default()),
|
||||
x if x == TypeId::of::<graphene_core::vector::style::GradientStops>() => TaggedValue::GradientStops(Default::default()),
|
||||
x if x == TypeId::of::<graphene_core::quantization::QuantizationChannels>() => TaggedValue::Quantization(Default::default()),
|
||||
x if x == TypeId::of::<Option<graphene_core::Color>>() => TaggedValue::OptionalColor(Default::default()),
|
||||
x if x == TypeId::of::<Vec<graphene_core::uuid::ManipulatorGroupId>>() => TaggedValue::ManipulatorGroupIds(Default::default()),
|
||||
|
||||
@@ -706,7 +706,10 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
register_node!(graphene_core::transform::SetTransformNode<_>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
|
||||
register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [DAffine2]),
|
||||
register_node!(graphene_core::transform::SetTransformNode<_>, input: ImageFrame<Color>, params: [DAffine2]),
|
||||
register_node!(graphene_core::vector::SetFillNode<_, _, _, _, _, _, _>, input: VectorData, params: [graphene_core::vector::style::FillType, Option<graphene_core::Color>, graphene_core::vector::style::GradientType, DVec2, DVec2, DAffine2, Vec<(f64, graphene_core::Color)>]),
|
||||
register_node!(graphene_core::vector::SetFillNode<_>, input: VectorData, params: [graphene_std::vector::style::Fill]),
|
||||
register_node!(graphene_core::vector::SetFillNode<_>, input: VectorData, params: [Color]),
|
||||
register_node!(graphene_core::vector::SetFillNode<_>, input: VectorData, params: [Option<Color>]),
|
||||
register_node!(graphene_core::vector::SetFillNode<_>, input: VectorData, params: [graphene_std::vector::style::Gradient]),
|
||||
register_node!(graphene_core::vector::SetStrokeNode<_, _, _, _, _, _, _>, input: VectorData, params: [Option<graphene_core::Color>, f64, Vec<f64>, f64, graphene_core::vector::style::LineCap, graphene_core::vector::style::LineJoin, f64]),
|
||||
register_node!(graphene_core::vector::RepeatNode<_, _, _>, input: VectorData, params: [DVec2, f64, u32]),
|
||||
register_node!(graphene_core::vector::BoundingBoxNode, input: VectorData, params: []),
|
||||
|
||||
@@ -6,18 +6,106 @@ use syn::{
|
||||
PredicateType, ReturnType, Token, TraitBound, Type, TypeImplTrait, TypeParam, TypeParamBound, TypeTuple, WhereClause, WherePredicate,
|
||||
};
|
||||
|
||||
/// A macro used to construct a proto node implementation from the given struct and the decorated function.
|
||||
///
|
||||
/// This works by generating two `impl` blocks for the given struct:
|
||||
///
|
||||
/// - `impl TheGivenStruct`:
|
||||
/// Attaches a `new` constructor method to the struct.
|
||||
/// - `impl Node for TheGivenStruct`:
|
||||
/// Implements the [`Node`] trait for the struct, with the `eval` method inside which is a modified version of the decorated function. See below for how the function is modified.
|
||||
///
|
||||
/// # Usage of this and similar macros
|
||||
///
|
||||
/// You'll use this macro most commonly when writing proto nodes. It's a convenient combination of the [`node_new`] and [`node_impl`] proc macros, which handles both of the bullet points above, respectively. There can only be one constructor method, but additional functions decorated by the [`node_impl`] macro can be added to implement different functionality across multiple type signatures.
|
||||
///
|
||||
/// # Useful hint
|
||||
///
|
||||
/// It can be helpful to run the "rust-analyzer: Expand macro recursively at carat" command from the VS Code command palette (or your editor's equivalent) to see the generated code of the macro to understand how the translation magic works.
|
||||
///
|
||||
/// # How generics and type signatures are handled
|
||||
///
|
||||
/// The given struct has various fields, each of them generic. These correspond with the node's parameters (the secondary inputs, but not the primary input). We can implement multiple functions with different type signatures, each each of these are converted by the [`node_impl`] macro into separate `impl` blocks for different `Node` traits.
|
||||
///
|
||||
/// ## Type signature translation
|
||||
///
|
||||
/// The conversion into an `impl Node` corresponding with the decorated function's type signature involves:
|
||||
///
|
||||
/// - Mapping the type of the function's first argument (the node's primary input) to the impl'd `Node`'s generic type, e.g.:
|
||||
///
|
||||
/// ```
|
||||
/// Node<'input, Color>
|
||||
/// ```
|
||||
///
|
||||
/// for a `Color` primary input type.
|
||||
/// - Mapping the type of the function's remaining arguments (the node's secondary inputs) to the given struct fields' generic types, e.g.:
|
||||
///
|
||||
/// ```
|
||||
/// TheGivenStruct<S0, S1>
|
||||
/// where S0: Node<'input, (), Output = f64>,
|
||||
/// where S1: Node<'input, (), Output = f64>,
|
||||
/// ```
|
||||
///
|
||||
/// for two `f64` parameter (secondary input) types. Since Graphene works by having each function evaluate its upstream node as a lambda that returns output data, these secondary inputs are not directly `f64` values but rather `Node`s that output `f64` values when evaluated (in this case, with an empty input of `()`).
|
||||
/// - Mapping the function's return type to the impl'd `Node` trait's associated type, e.g.:
|
||||
///
|
||||
/// ```
|
||||
/// Output = Color
|
||||
/// ```
|
||||
///
|
||||
/// for a `Color` return (secondary output) type.
|
||||
///
|
||||
/// ## `eval()` method generation
|
||||
///
|
||||
/// The conversion of the decorated function's body into the `eval` method within the `impl Node` block involves the following steps:
|
||||
///
|
||||
/// - The function's body gets copied over to the interior of the `eval` method.
|
||||
/// - The function's argument list only has its first argument (the node's primary input) copied over to the `eval` function signature. The remaining arguments (the node's secondary inputs) are not copied over as `eval` function arguments.
|
||||
/// - A series of `let` declarations are added before the copied-over function body, one for each secondary input. They look like `let secondaryA: SomeOutputType = self.secondaryA.eval(someInput);`. Each one is calling the `eval()` method on its corresponding struct field, obtaining the evaluated value of that secondary input node that gets used in the function body in the lines below these `let` declarations.
|
||||
///
|
||||
/// This process is necessary because the arguments in the original decorated function don't really exist with the actual values. Instead, they live as fields in the struct and they are `Node`s that output the actual values only once evaluated. So with the magic performed by this macro, the function body can written pretending to be working with the actual secondary input values, but the real types are `impl Node<SomeInputType, Output = SomeOutputType>` and they live in `self` as struct fields.
|
||||
///
|
||||
/// The function body runs with the actual primary input value from the `eval` method's argument and the secondary input values from the `eval` method's `let` declarations. The result looks like this:
|
||||
///
|
||||
/// ```
|
||||
/// fn eval(&'input self, color: Color) -> Self::Output {
|
||||
/// let secondaryA = self.secondaryA.eval(());
|
||||
/// let secondaryB = self.secondaryB.eval(());
|
||||
/// {
|
||||
/// Color::from_rgbaf32_unchecked(
|
||||
/// color.r() / secondaryA,
|
||||
/// color.g() / secondaryA,
|
||||
/// color.b() / secondaryA,
|
||||
/// color.a() * secondaryB,
|
||||
/// )
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// There is one exception where a `let` declaration is not added if an opt-out is desired. Any argument given to the decorated function may be of type `impl Node<SomeInputType, Output = SomeOutputType>` which will tell the macro not to add a `let` declaration for that argument. This allows for manually calling `eval` on the struct field in the function body, like `self.secondaryA.eval(())`.
|
||||
///
|
||||
/// When a `let` declaration is generated automatically, this is called **automatic composition**. When opting out, this is called **manual composition**.
|
||||
#[proc_macro_attribute]
|
||||
pub fn node_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut imp = node_impl_proxy(attr.clone(), item.clone());
|
||||
let new = node_new_impl(attr, item);
|
||||
imp.extend(new);
|
||||
imp
|
||||
// Performs the `node_impl` macro's functionality of attaching an `impl Node for TheGivenStruct` block to the node struct
|
||||
let node_impl = node_impl_proxy(attr.clone(), item.clone());
|
||||
|
||||
// Performs the `node_new` macro's functionality of attaching a `new` constructor method to the node struct
|
||||
let mut new_constructor = node_new_impl(attr, item);
|
||||
|
||||
// Combines the two pieces of Rust source code
|
||||
new_constructor.extend(node_impl);
|
||||
|
||||
new_constructor
|
||||
}
|
||||
|
||||
/// Attaches an `impl TheGivenStruct` block to the node struct, containing a `new` constructor method. This is almost always called by the combined [`node_fn`] macro instead of using this one, however it can be used separately if needed. See that macro's documentation for more information.
|
||||
#[proc_macro_attribute]
|
||||
pub fn node_new(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
node_new_impl(attr, item)
|
||||
}
|
||||
|
||||
/// Attaches an `impl Node for TheGivenStruct` block to the node struct, containing an implementation of the node's `eval` method for a certain type signature. This can be called with multiple separate functions each having different type signatures. The [`node_fn`] macro calls this macro as well as defining a `new` constructor method on the node struct, which is a necessary part of defining a proto node; therefore you will most likely call that macro on the first decorated function and this macro on any additional decorated functions to provide additional type signatures for the proto node. See that macro's documentation for more information.
|
||||
#[proc_macro_attribute]
|
||||
pub fn node_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
node_impl_proxy(attr, item)
|
||||
@@ -59,7 +147,7 @@ fn node_new_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
impl <#(#args),*> #node_name<#(#args),*>
|
||||
{
|
||||
pub const fn new(#(#parameter_idents: #struct_generics_iter),*) -> Self{
|
||||
Self{
|
||||
Self {
|
||||
#(#parameter_idents,)*
|
||||
#(#arg_idents: core::marker::PhantomData,)*
|
||||
}
|
||||
@@ -92,6 +180,7 @@ fn node_impl_proxy(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
node_impl_impl(attr, item, Asyncness::Sync)
|
||||
}
|
||||
}
|
||||
|
||||
enum Asyncness {
|
||||
Sync,
|
||||
AllAsync,
|
||||
@@ -203,7 +292,7 @@ fn node_impl_impl(attr: TokenStream, item: TokenStream, asyncness: Asyncness) ->
|
||||
};
|
||||
let mut body_with_inputs = quote::quote!(
|
||||
#parameters
|
||||
{#body}
|
||||
#body
|
||||
);
|
||||
if async_out {
|
||||
body_with_inputs = quote::quote!(Box::pin(async move { #body_with_inputs }));
|
||||
|
||||
Reference in New Issue
Block a user