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:
Keavon Chambers
2024-06-09 22:55:13 -07:00
committed by GitHub
parent 449729f1e1
commit a9a4b5cd19
48 changed files with 1380 additions and 664 deletions

View File

@@ -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 }));