diff --git a/examples/login.rs b/examples/login.rs index c80cbd5..9617e01 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -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, password: &Dynamic) -> Dynamic { + // 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 +} diff --git a/src/widgets/button.rs b/src/widgets/button.rs index f8dfa36..c4c0d48 100644 --- a/src/widgets/button.rs +++ b/src/widgets/button.rs @@ -28,6 +28,9 @@ pub struct Button { pub label: Value, /// The callback that is invoked when the button is clicked. pub on_click: Option>, + /// The enabled state of the button. + pub enabled: Value, + currently_enabled: bool, buttons_pressed: usize, background_color: Option>, 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) -> 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::