Button.enabled

This commit is contained in:
Jonathan Johnson 2023-11-08 12:37:58 -08:00
parent a8cd3df22c
commit 747813f6b8
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
2 changed files with 75 additions and 27 deletions

View file

@ -10,23 +10,7 @@ fn main() -> gooey::Result {
let username = Dynamic::default();
let password = Dynamic::default();
// TODO This is absolutely horrible. The problem is that within for_each,
// the value is still locked. Thus, we can't have a generic callback that
// tries to lock the value that is being mapped in for_each.
//
// We might be able to make a genericized implementation for_each for
// tuples, ie, (&Dynamic, &Dynamic).for_each(|(a, b)| ..).
let valid = Dynamic::default();
username.for_each((&valid, &password).with_clone(|(valid, password)| {
move |username: &String| {
password.map_ref(|password| valid.update(validate(username, password)))
}
}));
password.for_each((&valid, &username).with_clone(|(valid, username)| {
move |password: &String| {
username.map_ref(|username| valid.update(validate(username, password)))
}
}));
let valid = setup_validation(&username, &password);
Expand::new(Align::centered(Resize::width(
// TODO We need a min/max range for the Resize widget
@ -55,13 +39,10 @@ fn main() -> gooey::Result {
.into_escape(),
Expand::empty(),
Button::new("Log In")
.enabled(valid)
.on_click(move |_| {
if valid.get() {
println!("Welcome, {}", username.get());
exit(0);
} else {
eprintln!("Enter a username and password")
}
println!("Welcome, {}", username.get());
exit(0);
})
.into_default(), // TODO enable/disable based on valid
]),
@ -73,3 +54,24 @@ fn main() -> gooey::Result {
fn validate(username: &String, password: &String) -> bool {
!username.is_empty() && !password.is_empty()
}
fn setup_validation(username: &Dynamic<String>, password: &Dynamic<String>) -> Dynamic<bool> {
// TODO This is absolutely horrible. The problem is that within for_each,
// the value is still locked. Thus, we can't have a generic callback that
// tries to lock the value that is being mapped in for_each.
//
// We might be able to make a genericized implementation for_each for
// tuples, ie, (&Dynamic, &Dynamic).for_each(|(a, b)| ..).
let valid = Dynamic::default();
username.for_each((&valid, password).with_clone(|(valid, password)| {
move |username: &String| {
password.map_ref(|password| valid.update(validate(username, password)))
}
}));
password.for_each((&valid, username).with_clone(|(valid, username)| {
move |password: &String| {
username.map_ref(|username| valid.update(validate(username, password)))
}
}));
valid
}

View file

@ -28,6 +28,9 @@ pub struct Button {
pub label: Value<String>,
/// The callback that is invoked when the button is clicked.
pub on_click: Option<Callback<()>>,
/// The enabled state of the button.
pub enabled: Value<bool>,
currently_enabled: bool,
buttons_pressed: usize,
background_color: Option<Dynamic<Color>>,
background_color_animation: AnimationHandle,
@ -39,6 +42,8 @@ impl Button {
Self {
label: label.into_value(),
on_click: None,
enabled: Value::Constant(true),
currently_enabled: true,
buttons_pressed: 0,
background_color: None,
background_color_animation: AnimationHandle::default(),
@ -57,9 +62,19 @@ impl Button {
self
}
/// Sets the value to use for the button's enabled status.
#[must_use]
pub fn enabled(mut self, enabled: impl IntoValue<bool>) -> Self {
self.enabled = enabled.into_value();
self.currently_enabled = self.enabled.get();
self
}
fn invoke_on_click(&mut self) {
if let Some(on_click) = self.on_click.as_mut() {
on_click.invoke(());
if self.enabled.get() {
if let Some(on_click) = self.on_click.as_mut() {
on_click.invoke(());
}
}
}
@ -68,10 +83,13 @@ impl Button {
&ButtonActiveBackground,
&ButtonBackground,
&ButtonHoverBackground,
&ButtonDisabledBackground,
&PrimaryColor,
&Easing,
]);
let background_color = if context.active() {
let background_color = if !self.enabled.get() {
styles.get_or_default(&ButtonDisabledBackground)
} else if context.active() {
styles.get_or_default(&ButtonActiveBackground)
} else if context.hovered() {
styles.get_or_default(&ButtonHoverBackground)
@ -113,9 +131,18 @@ impl Button {
impl Widget for Button {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let enabled = self.enabled.get();
// TODO This seems ugly. It needs context, so it can't be moved into the
// dynamic system.
if self.currently_enabled != enabled {
self.update_background_color(context, false);
self.currently_enabled = enabled;
}
let size = context.graphics.region().size;
let center = Point::from(size) / 2;
self.label.redraw_when_changed(context);
self.enabled.redraw_when_changed(context);
let styles = context.query_styles(&[
&TextColor,
@ -155,7 +182,7 @@ impl Widget for Button {
fn accept_focus(&mut self, _context: &mut EventContext<'_, '_>) -> bool {
// TODO this should be driven by a "focus_all_widgets" setting that hopefully can be queried from the OS.
true
self.enabled.get()
}
fn mouse_down(
@ -354,3 +381,22 @@ impl ComponentDefinition for ButtonHoverBackground {
Color::new(40, 40, 40, 255)
}
}
/// The background color of the button when the mouse cursor is hovering over
/// it.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct ButtonDisabledBackground;
impl NamedComponent for ButtonDisabledBackground {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Button>("disabled_background_color"))
}
}
impl ComponentDefinition for ButtonDisabledBackground {
type ComponentType = Color;
fn default_value(&self) -> Color {
Color::new(50, 30, 30, 255)
}
}