mirror of
https://github.com/danbulant/cushy
synced 2026-05-27 22:03:00 +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",
|
"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]]
|
[[package]]
|
||||||
name = "dispatch"
|
name = "dispatch"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -721,6 +741,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"alot",
|
"alot",
|
||||||
|
"derive_more",
|
||||||
"gooey-macros",
|
"gooey-macros",
|
||||||
"intentional",
|
"intentional",
|
||||||
"interner",
|
"interner",
|
||||||
|
|
@ -738,7 +759,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"attribute-derive",
|
"attribute-derive",
|
||||||
"insta",
|
"insta",
|
||||||
"manyhow 0.9.0",
|
"manyhow 0.10.0",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -1118,11 +1139,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "manyhow"
|
name = "manyhow"
|
||||||
version = "0.9.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9aebef87880bafc898c6bed1435e8fdc58634275ff97693a4bb96ad561c73c43"
|
checksum = "4efde575f79afb9c637eb4663aa451f0bf227413aa734fbbec077cab5900be85"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"manyhow-macros 0.9.0",
|
"manyhow-macros 0.10.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
|
|
@ -1141,9 +1162,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "manyhow-macros"
|
name = "manyhow-macros"
|
||||||
version = "0.9.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f74cc8a0d8b05a7e919011c78a2744e7dea66567c05fb046666f3bae383d8d04"
|
checksum = "fcee04599474650eb26ae5a5c7837e30e55242267ff1bf0adc760b6fcdc3fa2a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-utils",
|
"proc-macro-utils",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ tracing-subscriber = { version = "0.3", optional = true, features = [
|
||||||
palette = "0.7.3"
|
palette = "0.7.3"
|
||||||
ahash = "0.8.6"
|
ahash = "0.8.6"
|
||||||
gooey-macros = { version = "0.1.0", path = "gooey-macros" }
|
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"]
|
# [patch."https://github.com/khonsulabs/kludgine"]
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
attribute-derive = "0.8.1"
|
attribute-derive = "0.8.1"
|
||||||
manyhow = "0.9.0"
|
manyhow = "0.10.0"
|
||||||
proc-macro2 = "1.0.69"
|
proc-macro2 = "1.0.69"
|
||||||
quote = "1.0.33"
|
quote = "1.0.33"
|
||||||
quote-use = "0.7.2"
|
quote-use = "0.7.2"
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,114 @@
|
||||||
use manyhow::bail;
|
use manyhow::{bail, ensure};
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use syn::{Field, ItemStruct};
|
use syn::{Data, DeriveInput, Field, Variant};
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub fn linear_interpolate(
|
pub fn linear_interpolate(
|
||||||
ItemStruct {
|
DeriveInput {
|
||||||
ident,
|
ident: item_ident,
|
||||||
generics,
|
generics,
|
||||||
fields,
|
data,
|
||||||
..
|
..
|
||||||
}: ItemStruct,
|
}: DeriveInput,
|
||||||
) -> Result<TokenStream> {
|
) -> Result<TokenStream> {
|
||||||
if let Some(generic) = generics.type_params().next() {
|
if let Some(generic) = generics.type_params().next() {
|
||||||
bail!(generic, "generics not supported");
|
bail!(generic, "generics not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
let fields = match fields {
|
let doc;
|
||||||
syn::Fields::Unit => bail!(ident, "unit structs are not supported"),
|
|
||||||
fields => fields
|
let body = match data {
|
||||||
.into_iter()
|
Data::Struct(data) => {
|
||||||
.enumerate()
|
let fields = match data.fields {
|
||||||
.map(|(idx, Field { ident, .. })| {
|
syn::Fields::Unit => bail!(item_ident, "unit structs are not supported"),
|
||||||
let ident = ident
|
fields => fields
|
||||||
.map(ToTokens::into_token_stream)
|
.into_iter()
|
||||||
.unwrap_or_else(|| proc_macro2::Literal::usize_unsuffixed(idx).into_token_stream());
|
.enumerate()
|
||||||
quote!(#ident: ::gooey::animation::LinearInterpolate::lerp(&self.#ident, &__target.#ident, __percent),)
|
.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! {
|
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 {
|
fn lerp(&self, __target: &Self, __percent: f32) -> Self {
|
||||||
#ident{#(#fields)*}
|
#body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
macro_rules! expansion_snapshot {
|
mod test {
|
||||||
(#[derive($fn:expr)]$($tokens:tt)*) => {{
|
use super::*;
|
||||||
use insta::assert_snapshot;
|
expansion_snapshot! {struct_
|
||||||
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! {
|
|
||||||
#[derive(linear_interpolate)]
|
#[derive(linear_interpolate)]
|
||||||
struct HelloWorld {
|
struct HelloWorld {
|
||||||
fielda: Hello,
|
fielda: Hello,
|
||||||
fieldb: World,
|
fieldb: World,
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
expansion_snapshot! {
|
expansion_snapshot! {tuple_struct
|
||||||
#[derive(linear_interpolate)]
|
#[derive(linear_interpolate)]
|
||||||
struct HelloWorld(Hello, World);
|
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 manyhow::{manyhow, Result};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote_use::quote_use as quote;
|
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;
|
mod animation;
|
||||||
|
|
||||||
#[manyhow(proc_macro_derive(LinearInterpolate))]
|
#[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
|
source: gooey-macros/src/animation.rs
|
||||||
expression: unparse(&parse2(output).unwrap())
|
expression: unparse(ok)
|
||||||
---
|
---
|
||||||
impl ::gooey::animation::LinearInterpolate for HelloWorld {
|
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 {
|
fn lerp(&self, __target: &Self, __percent: f32) -> Self {
|
||||||
HelloWorld {
|
HelloWorld {
|
||||||
fielda: ::gooey::animation::LinearInterpolate::lerp(
|
fielda: ::gooey::animation::LinearInterpolate::lerp(
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
---
|
---
|
||||||
source: gooey-macros/src/animation.rs
|
source: gooey-macros/src/animation.rs
|
||||||
expression: unparse(&parse2(output).unwrap())
|
expression: unparse(ok)
|
||||||
---
|
---
|
||||||
impl ::gooey::animation::LinearInterpolate for HelloWorld {
|
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 {
|
fn lerp(&self, __target: &Self, __percent: f32) -> Self {
|
||||||
HelloWorld {
|
HelloWorld {
|
||||||
0: ::gooey::animation::LinearInterpolate::lerp(
|
0: ::gooey::animation::LinearInterpolate::lerp(
|
||||||
|
|
@ -49,6 +49,7 @@ use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use alot::{LotId, Lots};
|
use alot::{LotId, Lots};
|
||||||
|
use derive_more::From;
|
||||||
use intentional::Cast;
|
use intentional::Cast;
|
||||||
use kempt::Set;
|
use kempt::Set;
|
||||||
use kludgine::figures::Ranged;
|
use kludgine::figures::Ranged;
|
||||||
|
|
@ -608,12 +609,49 @@ impl Animate for Duration {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a linear interpolation between two values.
|
/// 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 {
|
pub trait LinearInterpolate: PartialEq {
|
||||||
/// Interpolate linearly between `self` and `target` using `percent`.
|
/// Interpolate linearly between `self` and `target` using `percent`.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn lerp(&self, target: &Self, percent: f32) -> Self;
|
fn lerp(&self, target: &Self, percent: f32) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Derives [`LinerarInterpolate`](trait@LinearInterpolate) for structs and fieldless enums.
|
||||||
pub use gooey_macros::LinearInterpolate;
|
pub use gooey_macros::LinearInterpolate;
|
||||||
|
|
||||||
macro_rules! impl_lerp_for_int {
|
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
|
/// This wrapper can be used to add [`LinearInterpolate`] to types that normally
|
||||||
/// don't support interpolation.
|
/// 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);
|
pub struct BinaryLerp<T>(T);
|
||||||
|
|
||||||
impl<T> LinearInterpolate for BinaryLerp<T>
|
impl<T> LinearInterpolate for BinaryLerp<T>
|
||||||
|
|
@ -754,7 +792,7 @@ where
|
||||||
///
|
///
|
||||||
/// This wrapper can be used to add [`LinearInterpolate`] to types that normally
|
/// This wrapper can be used to add [`LinearInterpolate`] to types that normally
|
||||||
/// don't support interpolation.
|
/// 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);
|
pub struct ImmediateLerp<T>(T);
|
||||||
|
|
||||||
impl<T> LinearInterpolate for ImmediateLerp<T>
|
impl<T> LinearInterpolate for ImmediateLerp<T>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue