mirror of
https://github.com/danbulant/cushy
synced 2026-05-24 12:28:23 +00:00
Merge pull request #76 from ModProg/button-fun
derive(LinearInterpolate) on enum
This commit is contained in:
commit
64584c4b14
10 changed files with 233 additions and 48 deletions
33
Cargo.lock
generated
33
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))]
|
||||
|
|
|
|||
|
|
@ -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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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(
|
||||
|
|
@ -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(
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue