Merge pull request #74 from ModProg/button-fun

button outline without drawing
This commit is contained in:
Jonathan Johnson 2023-11-13 16:29:41 -08:00 committed by GitHub
commit cc7d4bac45
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 431 additions and 52 deletions

208
Cargo.lock generated
View file

@ -154,6 +154,34 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "attribute-derive"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c94f43ede6f25dab1dea046bff84d85dea61bd49aba7a9011ad66c0d449077b"
dependencies = [
"attribute-derive-macro",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "attribute-derive-macro"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b409e2b2d2dc206d2c0ad3575a93f001ae21a1593e2d0c69b69c308e63f3b422"
dependencies = [
"collection_literals",
"interpolator",
"manyhow 0.8.1",
"proc-macro-utils",
"proc-macro2",
"quote",
"quote-use",
"syn",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -316,6 +344,12 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "collection_literals"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271"
[[package]]
name = "color_quant"
version = "1.1.0"
@ -338,6 +372,18 @@ dependencies = [
"memchr",
]
[[package]]
name = "console"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"windows-sys 0.45.0",
]
[[package]]
name = "core-foundation"
version = "0.9.3"
@ -416,6 +462,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "derive-where"
version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146398d62142a0f35248a608f17edf0dde57338354966d6e41d0eb2d16980ccb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dispatch"
version = "0.2.0"
@ -437,6 +494,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "equivalent"
version = "1.0.1"
@ -658,6 +721,7 @@ version = "0.1.0"
dependencies = [
"ahash",
"alot",
"gooey-macros",
"intentional",
"interner",
"kempt",
@ -668,6 +732,20 @@ dependencies = [
"tracing-subscriber",
]
[[package]]
name = "gooey-macros"
version = "0.1.0"
dependencies = [
"attribute-derive",
"insta",
"manyhow 0.9.0",
"prettyplease",
"proc-macro2",
"quote",
"quote-use",
"syn",
]
[[package]]
name = "gpu-alloc"
version = "0.6.0"
@ -792,6 +870,19 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "insta"
version = "1.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc"
dependencies = [
"console",
"lazy_static",
"linked-hash-map",
"similar",
"yaml-rust",
]
[[package]]
name = "intentional"
version = "0.1.0"
@ -804,6 +895,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c60687056b35a996f2213287048a7092d801b61df5fee3bd5bd9bf6f17a2d0"
[[package]]
name = "interpolator"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8"
[[package]]
name = "io-lifetimes"
version = "1.0.11"
@ -938,6 +1035,12 @@ dependencies = [
"redox_syscall 0.4.1",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.11"
@ -1001,6 +1104,52 @@ dependencies = [
"libc",
]
[[package]]
name = "manyhow"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "516b76546495d933baa165075b95c0a15e8f7ef75e53f56b19b7144d80fd52bd"
dependencies = [
"manyhow-macros 0.8.1",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "manyhow"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aebef87880bafc898c6bed1435e8fdc58634275ff97693a4bb96ad561c73c43"
dependencies = [
"manyhow-macros 0.9.0",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "manyhow-macros"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ba072c0eadade3160232e70893311f1f8903974488096e2eb8e48caba2f0cf1"
dependencies = [
"proc-macro-utils",
"proc-macro2",
"quote",
]
[[package]]
name = "manyhow-macros"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f74cc8a0d8b05a7e919011c78a2744e7dea66567c05fb046666f3bae383d8d04"
dependencies = [
"proc-macro-utils",
"proc-macro2",
"quote",
]
[[package]]
name = "matchers"
version = "0.1.0"
@ -1442,6 +1591,16 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
[[package]]
name = "prettyplease"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro-crate"
version = "2.0.0"
@ -1451,6 +1610,17 @@ dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro-utils"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f59e109e2f795a5070e69578c4dc101068139f74616778025ae1011d4cd41a8"
dependencies = [
"proc-macro2",
"quote",
"smallvec",
]
[[package]]
name = "proc-macro2"
version = "1.0.69"
@ -1484,6 +1654,29 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "quote-use"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7b5abe3fe82fdeeb93f44d66a7b444dedf2e4827defb0a8e69c437b2de2ef94"
dependencies = [
"quote",
"quote-use-macros",
"syn",
]
[[package]]
name = "quote-use-macros"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97ea44c7e20f16017a76a245bb42188517e13d16dcb1aa18044bc406cdc3f4af"
dependencies = [
"derive-where",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "rand"
version = "0.8.5"
@ -1711,6 +1904,12 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "similar"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597"
[[package]]
name = "siphasher"
version = "0.3.11"
@ -2698,6 +2897,15 @@ version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "yazi"
version = "0.1.6"

View file

@ -1,3 +1,5 @@
[workspace]
[package]
name = "gooey"
version = "0.1.0"
@ -22,6 +24,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" }
# [patch."https://github.com/khonsulabs/kludgine"]

View file

@ -1,17 +1,28 @@
use gooey::value::Dynamic;
use gooey::widget::MakeWidget;
use gooey::widgets::button::ButtonOutline;
use gooey::widgets::Button;
use gooey::Run;
use kludgine::Color;
// begin rustme snippet: readme
fn main() -> gooey::Result {
// Create a dynamic usize.
let count = Dynamic::new(0_usize);
let count = Dynamic::new(0_isize);
// Create a new button with a label that is produced by mapping the contents
// of `count`.
Button::new(count.map_each(ToString::to_string))
// Set the `on_click` callback to a closure that increments the counter.
.on_click(count.with_clone(|count| move |_| count.set(count.get() + 1)))
.and(
// Creates a second, outlined button
Button::new(count.map_each(ToString::to_string))
// Set the `on_click` callback to a closure that decrements the counter.
.on_click(count.with_clone(|count| move |_| count.set(count.get() - 1)))
.with(&ButtonOutline, Color::DARKRED),
)
.into_columns()
// Run the button as an an application.
.run()
}

21
gooey-macros/Cargo.toml Normal file
View file

@ -0,0 +1,21 @@
[package]
name = "gooey-macros"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
attribute-derive = "0.8.1"
manyhow = "0.9.0"
proc-macro2 = "1.0.69"
quote = "1.0.33"
quote-use = "0.7.2"
syn = "2.0.39"
[dev-dependencies]
insta = "1.34.0"
prettyplease = "0.2.15"

View file

@ -0,0 +1,66 @@
use manyhow::bail;
use quote::ToTokens;
use syn::{Field, ItemStruct};
use crate::*;
pub fn linear_interpolate(
ItemStruct {
ident,
generics,
fields,
..
}: ItemStruct,
) -> 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),)
}),
};
Ok(quote! {
impl ::gooey::animation::LinearInterpolate for #ident {
fn lerp(&self, __target: &Self, __percent: f32) -> Self {
#ident{#(#fields)*}
}
}
})
}
#[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! {
#[derive(linear_interpolate)]
struct HelloWorld {
fielda: Hello,
fieldb: World,
}
};
expansion_snapshot! {
#[derive(linear_interpolate)]
struct HelloWorld(Hello, World);
};
}

7
gooey-macros/src/lib.rs Normal file
View file

@ -0,0 +1,7 @@
use manyhow::{manyhow, Result};
use quote_use::quote_use as quote;
use proc_macro2::TokenStream;
mod animation;
#[manyhow(proc_macro_derive(LinearInterpolate))]
pub use animation::linear_interpolate;

View file

@ -0,0 +1,21 @@
---
source: gooey-macros/src/animation.rs
expression: unparse(&parse2(output).unwrap())
---
impl ::gooey::animation::LinearInterpolate for HelloWorld {
fn lerp(&self, __target: &Self, __percent: f32) -> Self {
HelloWorld {
0: ::gooey::animation::LinearInterpolate::lerp(
&self.0,
&__target.0,
__percent,
),
1: ::gooey::animation::LinearInterpolate::lerp(
&self.1,
&__target.1,
__percent,
),
}
}
}

View file

@ -0,0 +1,21 @@
---
source: src/animation.rs
expression: unparse(&parse2(output).unwrap())
---
impl ::gooey::animation::LinearInterpolate for HelloWorld {
fn lerp(&self, __target: &Self, __percent: f32) -> Self {
HelloWorld {
fielda: ::gooey::animation::LinearInterpolate::lerp(
&self.fielda,
&__target.fielda,
__percent,
),
fieldb: ::gooey::animation::LinearInterpolate::lerp(
&self.fieldb,
&__target.fieldb,
__percent,
),
}
}
}

View file

@ -616,6 +616,8 @@ pub trait LinearInterpolate: PartialEq {
fn lerp(&self, target: &Self, percent: f32) -> Self;
}
pub use gooey_macros::LinearInterpolate;
macro_rules! impl_lerp_for_int {
($type:ident, $unsigned:ident, $float:ident) => {
impl LinearInterpolate for $type {

View file

@ -2,6 +2,9 @@
#![warn(clippy::pedantic, missing_docs)]
#![allow(clippy::module_name_repetitions, clippy::missing_errors_doc)]
// for proc-macros
extern crate self as gooey;
#[macro_use]
mod utils;

View file

@ -3,11 +3,12 @@ use std::panic::UnwindSafe;
use std::time::Duration;
use kludgine::app::winit::event::{DeviceId, ElementState, KeyEvent, MouseButton};
use kludgine::figures::units::{Px, UPx};
use kludgine::figures::units::{Lp, Px, UPx};
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, ScreenScale, Size};
use kludgine::shapes::StrokeOptions;
use kludgine::Color;
use crate::animation::{AnimationHandle, AnimationTarget, Spawn};
use crate::animation::{AnimationHandle, AnimationTarget, LinearInterpolate, Spawn};
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext};
use crate::styles::components::{
AutoFocusableControls, Easing, IntrinsicPadding, OpaqueWidgetColor, SurfaceColor, TextColor,
@ -28,11 +29,17 @@ pub struct Button {
pub enabled: Value<bool>,
currently_enabled: bool,
buttons_pressed: usize,
background_color: Option<Dynamic<Color>>,
text_color: Option<Dynamic<Color>>,
active_style: Option<Dynamic<ButtonStyle>>,
color_animation: AnimationHandle,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, LinearInterpolate)]
struct ButtonStyle {
background: Color,
foreground: Color,
outline: Color,
}
impl Button {
/// Returns a new button with the provided label.
pub fn new(content: impl MakeWidget) -> Self {
@ -42,8 +49,7 @@ impl Button {
enabled: Value::Constant(true),
currently_enabled: true,
buttons_pressed: 0,
background_color: None,
text_color: None,
active_style: None,
color_animation: AnimationHandle::default(),
}
}
@ -77,62 +83,63 @@ impl Button {
}
fn update_colors(&mut self, context: &WidgetContext<'_, '_>, immediate: bool) {
let (background_color, text_color) = match () {
() if !self.enabled.get() => (
context.get(&ButtonDisabledBackground),
context.get(&ButtonDisabledForeground),
),
let new_style = match () {
() if !self.enabled.get() => ButtonStyle {
background: context.get(&ButtonDisabledBackground),
foreground: context.get(&ButtonDisabledForeground),
outline: context.get(&ButtonDisabledOutline),
},
// TODO this probably should use actual style.
() if context.is_default() => (
context.theme().primary.color,
context.theme().primary.on_color,
),
() if context.active() => (
context.get(&ButtonActiveBackground),
context.get(&ButtonActiveForeground),
),
() if context.hovered() => (
context.get(&ButtonHoverBackground),
context.get(&ButtonHoverForeground),
),
() => (
context.get(&ButtonBackground),
context.get(&ButtonForeground),
),
() if context.is_default() => ButtonStyle {
background: context.theme().primary.color,
foreground: context.theme().primary.on_color,
outline: Color::CLEAR_BLACK,
},
() if context.active() => ButtonStyle {
background: context.get(&ButtonActiveBackground),
foreground: context.get(&ButtonActiveForeground),
outline: context.get(&ButtonActiveOutline),
},
() if context.hovered() => ButtonStyle {
background: context.get(&ButtonHoverBackground),
foreground: context.get(&ButtonHoverForeground),
outline: context.get(&ButtonHoverOutline),
},
() => ButtonStyle {
background: context.get(&ButtonBackground),
foreground: context.get(&ButtonForeground),
outline: context.get(&ButtonOutline),
},
};
match (immediate, &self.background_color, &self.text_color) {
(false, Some(bg), Some(text)) => {
self.color_animation = (
bg.transition_to(background_color),
text.transition_to(text_color),
)
match (immediate, &self.active_style) {
(false, Some(style)) => {
self.color_animation = (style.transition_to(new_style))
.over(Duration::from_millis(150))
.with_easing(context.get(&Easing))
.spawn();
}
(true, Some(bg), Some(text)) => {
bg.update(background_color);
text.update(text_color);
(true, Some(style)) => {
style.update(new_style);
self.color_animation.clear();
}
_ => {
self.background_color = Some(Dynamic::new(background_color));
let text_color = Dynamic::new(text_color);
self.text_color = Some(text_color.clone());
context.attach_styles(Styles::new().with(&TextColor, text_color));
let new_style = Dynamic::new(new_style);
let foreground = new_style.map_each(|s| s.foreground);
self.active_style = Some(new_style);
context.attach_styles(Styles::new().with(&TextColor, foreground));
}
}
}
fn current_background(&mut self, context: &WidgetContext<'_, '_>) -> Color {
if self.background_color.is_none() {
fn current_style(&mut self, context: &WidgetContext<'_, '_>) -> ButtonStyle {
if self.active_style.is_none() {
self.update_colors(context, false);
}
let background_color = self.background_color.as_ref().expect("always initialized");
context.redraw_when_changed(background_color);
background_color.get()
let style = self.active_style.as_ref().expect("always initialized");
context.redraw_when_changed(style);
style.get()
}
}
@ -149,8 +156,10 @@ impl Widget for Button {
self.enabled.redraw_when_changed(context);
let background_color = self.current_background(context);
context.gfx.fill(background_color);
let style = self.current_style(context);
context.gfx.fill(style.background);
context.stroke_outline::<Lp>(style.outline, StrokeOptions::default());
if context.focused() {
context.draw_focus_ring();
@ -322,5 +331,15 @@ define_components! {
/// The foreground color of the button when the mouse cursor is hovering over
/// it.
ButtonDisabledForeground(Color, "disabled_foreground_color", contrasting!(ButtonDisabledBackground, ButtonForeground, TextColor, SurfaceColor))
/// The outline color of the button.
ButtonOutline(Color, "outline_color", Color::CLEAR_BLACK)
/// The outline color of the button when it is active (depressed).
ButtonActiveOutline(Color, "active_outline_color", contrasting!(ButtonActiveBackground, ButtonOutline, TextColor, SurfaceColor))
/// The outline color of the button when the mouse cursor is hovering over
/// it.
ButtonHoverOutline(Color, "hover_outline_color", contrasting!(ButtonHoverBackground, ButtonOutline, TextColor, SurfaceColor))
/// The outline color of the button when the mouse cursor is hovering over
/// it.
ButtonDisabledOutline(Color, "disabled_outline_color", contrasting!(ButtonDisabledBackground, ButtonOutline, TextColor, SurfaceColor))
}
}

View file

@ -321,10 +321,7 @@ impl Widget for Input {
if window_focused && cursor_state.visible {
context.gfx.draw_shape(
&Shape::filled_rect(
Rect::new(
location,
Size::new(Px(1), line_height),
),
Rect::new(location, Size::new(Px(1), line_height)),
highlight, // TODO cursor should be a bold color, highlight probably not. This should have its own color.
),
padding,