Merge pull request #76 from ModProg/button-fun

derive(LinearInterpolate) on enum
This commit is contained in:
Jonathan Johnson 2023-11-14 11:28:52 -08:00 committed by GitHub
commit 64584c4b14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 233 additions and 48 deletions

33
Cargo.lock generated
View file

@ -473,6 +473,26 @@ dependencies = [
"syn",
]
[[package]]
name = "derive_more"
version = "1.0.0-beta.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7abbfc297053be59290e3152f8cbcd52c8642e0728b69ee187d991d4c1af08d"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "1.0.0-beta.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dispatch"
version = "0.2.0"
@ -721,6 +741,7 @@ version = "0.1.0"
dependencies = [
"ahash",
"alot",
"derive_more",
"gooey-macros",
"intentional",
"interner",
@ -738,7 +759,7 @@ version = "0.1.0"
dependencies = [
"attribute-derive",
"insta",
"manyhow 0.9.0",
"manyhow 0.10.0",
"prettyplease",
"proc-macro2",
"quote",
@ -1118,11 +1139,11 @@ dependencies = [
[[package]]
name = "manyhow"
version = "0.9.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aebef87880bafc898c6bed1435e8fdc58634275ff97693a4bb96ad561c73c43"
checksum = "4efde575f79afb9c637eb4663aa451f0bf227413aa734fbbec077cab5900be85"
dependencies = [
"manyhow-macros 0.9.0",
"manyhow-macros 0.10.0",
"proc-macro2",
"quote",
"syn",
@ -1141,9 +1162,9 @@ dependencies = [
[[package]]
name = "manyhow-macros"
version = "0.9.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f74cc8a0d8b05a7e919011c78a2744e7dea66567c05fb046666f3bae383d8d04"
checksum = "fcee04599474650eb26ae5a5c7837e30e55242267ff1bf0adc760b6fcdc3fa2a"
dependencies = [
"proc-macro-utils",
"proc-macro2",

View file

@ -25,6 +25,7 @@ tracing-subscriber = { version = "0.3", optional = true, features = [
palette = "0.7.3"
ahash = "0.8.6"
gooey-macros = { version = "0.1.0", path = "gooey-macros" }
derive_more = { version = "1.0.0-beta.6", features = ["from"] }
# [patch."https://github.com/khonsulabs/kludgine"]

View file

@ -10,7 +10,7 @@ proc-macro = true
[dependencies]
attribute-derive = "0.8.1"
manyhow = "0.9.0"
manyhow = "0.10.0"
proc-macro2 = "1.0.69"
quote = "1.0.33"
quote-use = "0.7.2"

View file

@ -1,66 +1,114 @@
use manyhow::bail;
use manyhow::{bail, ensure};
use quote::ToTokens;
use syn::{Field, ItemStruct};
use syn::{Data, DeriveInput, Field, Variant};
use crate::*;
pub fn linear_interpolate(
ItemStruct {
ident,
DeriveInput {
ident: item_ident,
generics,
fields,
data,
..
}: ItemStruct,
}: DeriveInput,
) -> Result<TokenStream> {
if let Some(generic) = generics.type_params().next() {
bail!(generic, "generics not supported");
}
let fields = match fields {
syn::Fields::Unit => bail!(ident, "unit structs are not supported"),
fields => fields
.into_iter()
.enumerate()
.map(|(idx, Field { ident, .. })| {
let ident = ident
.map(ToTokens::into_token_stream)
.unwrap_or_else(|| proc_macro2::Literal::usize_unsuffixed(idx).into_token_stream());
quote!(#ident: ::gooey::animation::LinearInterpolate::lerp(&self.#ident, &__target.#ident, __percent),)
}),
let doc;
let body = match data {
Data::Struct(data) => {
let fields = match data.fields {
syn::Fields::Unit => bail!(item_ident, "unit structs are not supported"),
fields => fields
.into_iter()
.enumerate()
.map(|(idx, Field { ident, .. })| {
let ident = ident
.map(ToTokens::into_token_stream)
.unwrap_or_else(|| proc_macro2::Literal::usize_unsuffixed(idx).into_token_stream());
quote!(#ident: ::gooey::animation::LinearInterpolate::lerp(&self.#ident, &__target.#ident, __percent),)
}),
};
doc = "# Panics\n Panics if any field's lerp panics (this should only happen on percentages outside 0..1 range).";
quote!(#item_ident{#(#fields)*})
}
Data::Enum(data) => {
let variants = data
.variants
.into_iter()
.map(
|Variant {
ident,
fields,
discriminant,
..
}| {
if let Some(discriminant) = discriminant {
bail!(discriminant, "discriminants are not supported");
}
ensure!(fields.is_empty(), fields, "enum fields are not supported");
Ok(quote!(#item_ident::#ident #fields))
},
)
.collect::<Result<Vec<_>>>()?;
let last = variants
.last()
.map(ToTokens::to_token_stream)
.unwrap_or_else(|| quote!(unreachable!()));
let idx: Vec<_> = (0..variants.len()).collect();
doc = "# Panics\n Panics if the the enum variants are overflown (this can only happen on percentages outside 0..1 range).";
quote! {
# use ::gooey::animation::LinearInterpolate;
fn variant_to_index(__v: &#item_ident) -> usize {
match __v {
#(#variants => #idx,)*
}
}
let __self = variant_to_index(&self);
let __target = variant_to_index(&__target);
match LinearInterpolate::lerp(&__self, &__target, __percent) {
#(#idx => #variants,)*
_ => #last,
}
}
}
Data::Union(union) => bail!((union.union_token, union.fields), "unions not supported"),
};
Ok(quote! {
impl ::gooey::animation::LinearInterpolate for #ident {
impl ::gooey::animation::LinearInterpolate for #item_ident {
#[doc = #doc]
fn lerp(&self, __target: &Self, __percent: f32) -> Self {
#ident{#(#fields)*}
#body
}
}
})
}
#[cfg(test)]
macro_rules! expansion_snapshot {
(#[derive($fn:expr)]$($tokens:tt)*) => {{
use insta::assert_snapshot;
use prettyplease::unparse;
use syn::{parse2, parse_quote};
let input = parse_quote!($($tokens)*);
let output = $fn(input).unwrap();
assert_snapshot!(unparse(&parse2(output).unwrap()))
}};
}
#[test]
fn test() {
expansion_snapshot! {
mod test {
use super::*;
expansion_snapshot! {struct_
#[derive(linear_interpolate)]
struct HelloWorld {
fielda: Hello,
fieldb: World,
}
};
expansion_snapshot! {
}
expansion_snapshot! {tuple_struct
#[derive(linear_interpolate)]
struct HelloWorld(Hello, World);
};
}
expansion_snapshot! {enum_
#[derive(linear_interpolate)]
enum Enum{A, B}
}
expansion_snapshot! {empty_enum
#[derive(linear_interpolate)]
enum Enum{}
}
}

View file

@ -1,6 +1,28 @@
use manyhow::{manyhow, Result};
use proc_macro2::TokenStream;
use quote_use::quote_use as quote;
#[cfg(test)]
macro_rules! expansion_snapshot {
($name:ident $($tokens:tt)*) => {
#[test]
fn $name() {
expansion_snapshot!{$($tokens)*}
}
};
(#[derive($fn:expr)]$($tokens:tt)*) => {{
use insta::assert_snapshot;
use prettyplease::unparse;
use syn::{parse2, parse_quote};
let input = parse_quote!($($tokens)*);
let output = $fn(input).unwrap();
match &parse2(output.clone()) {
Ok(ok) => assert_snapshot!(unparse(ok)),
Err(_) => panic!("{output}"),
}
}};
}
mod animation;
#[manyhow(proc_macro_derive(LinearInterpolate))]

View file

@ -0,0 +1,23 @@
---
source: gooey-macros/src/animation.rs
expression: unparse(ok)
---
impl ::gooey::animation::LinearInterpolate for Enum {
/**# Panics
Panics if the the enum variants are overflown (this can only happen on percentages outside 0..1 range).*/
fn lerp(&self, __target: &Self, __percent: f32) -> Self {
fn variant_to_index(__v: &Enum) -> usize {
match __v {}
}
let __self = variant_to_index(&self);
let __target = variant_to_index(&self);
match ::gooey::animation::LinearInterpolate::lerp(
&__self,
&__target,
__percent,
) {
_ => unreachable!(),
}
}
}

View file

@ -0,0 +1,28 @@
---
source: gooey-macros/src/animation.rs
expression: unparse(ok)
---
impl ::gooey::animation::LinearInterpolate for Enum {
/**# Panics
Panics if the the enum variants are overflown (this can only happen on percentages outside 0..1 range).*/
fn lerp(&self, __target: &Self, __percent: f32) -> Self {
fn variant_to_index(__v: &Enum) -> usize {
match __v {
Enum::A => 0usize,
Enum::B => 1usize,
}
}
let __self = variant_to_index(&self);
let __target = variant_to_index(&self);
match ::gooey::animation::LinearInterpolate::lerp(
&__self,
&__target,
__percent,
) {
0usize => Enum::A,
1usize => Enum::B,
_ => Enum::B,
}
}
}

View file

@ -1,8 +1,10 @@
---
source: src/animation.rs
expression: unparse(&parse2(output).unwrap())
source: gooey-macros/src/animation.rs
expression: unparse(ok)
---
impl ::gooey::animation::LinearInterpolate for HelloWorld {
/**# Panics
Panics if any field's lerp panics (this should only happen on percentages outside 0..1 range).*/
fn lerp(&self, __target: &Self, __percent: f32) -> Self {
HelloWorld {
fielda: ::gooey::animation::LinearInterpolate::lerp(

View file

@ -1,8 +1,10 @@
---
source: gooey-macros/src/animation.rs
expression: unparse(&parse2(output).unwrap())
expression: unparse(ok)
---
impl ::gooey::animation::LinearInterpolate for HelloWorld {
/**# Panics
Panics if any field's lerp panics (this should only happen on percentages outside 0..1 range).*/
fn lerp(&self, __target: &Self, __percent: f32) -> Self {
HelloWorld {
0: ::gooey::animation::LinearInterpolate::lerp(

View file

@ -49,6 +49,7 @@ use std::thread;
use std::time::{Duration, Instant};
use alot::{LotId, Lots};
use derive_more::From;
use intentional::Cast;
use kempt::Set;
use kludgine::figures::Ranged;
@ -608,12 +609,49 @@ impl Animate for Duration {
}
/// Performs a linear interpolation between two values.
///
/// This trait can be derived for structs and fieldless enums.
///
/// Note: for fields that don't implement [`LinerarInterpolate`](trait@LinearInterpolate)
/// the wrappers [`BinaryLerp`] and [`ImmediateLerp`] can be used.
///
/// ```
/// use gooey::animation::{BinaryLerp, ImmediateLerp, LinearInterpolate};
/// use gooey::kludgine::Color;
///
/// #[derive(LinearInterpolate, PartialEq, Debug)]
/// struct Struct(Color, BinaryLerp<&'static str>, ImmediateLerp<&'static str>);
///
/// let from = Struct(Color::BLACK, "hello".into(), "hello".into());
/// let to = Struct(Color::WHITE, "world".into(), "world".into());
///
/// assert_eq!(
/// from.lerp(&to, 0.41),
/// Struct(Color::DIMGRAY, "hello".into(), "world".into())
/// );
/// assert_eq!(
/// from.lerp(&to, 0.663),
/// Struct(Color::DARKGRAY, "world".into(), "world".into())
/// );
///
/// #[derive(LinearInterpolate, PartialEq, Debug)]
/// enum Enum {
/// A,
/// B,
/// C,
/// }
/// assert_eq!(Enum::A.lerp(&Enum::B, 0.4), Enum::A);
/// assert_eq!(Enum::A.lerp(&Enum::C, 0.1), Enum::A);
/// assert_eq!(Enum::A.lerp(&Enum::C, 0.4), Enum::B);
/// assert_eq!(Enum::A.lerp(&Enum::C, 0.9), Enum::C);
/// ```
pub trait LinearInterpolate: PartialEq {
/// Interpolate linearly between `self` and `target` using `percent`.
#[must_use]
fn lerp(&self, target: &Self, percent: f32) -> Self;
}
/// Derives [`LinerarInterpolate`](trait@LinearInterpolate) for structs and fieldless enums.
pub use gooey_macros::LinearInterpolate;
macro_rules! impl_lerp_for_int {
@ -733,7 +771,7 @@ impl LinearInterpolate for Color {
///
/// This wrapper can be used to add [`LinearInterpolate`] to types that normally
/// don't support interpolation.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, From)]
pub struct BinaryLerp<T>(T);
impl<T> LinearInterpolate for BinaryLerp<T>
@ -754,7 +792,7 @@ where
///
/// This wrapper can be used to add [`LinearInterpolate`] to types that normally
/// don't support interpolation.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, From)]
pub struct ImmediateLerp<T>(T);
impl<T> LinearInterpolate for ImmediateLerp<T>