diff --git a/cursive-core/src/views/checkbox.rs b/cursive-core/src/views/checkbox.rs index ba814bea..7f709710 100644 --- a/cursive-core/src/views/checkbox.rs +++ b/cursive-core/src/views/checkbox.rs @@ -3,7 +3,7 @@ use crate::{ event::{Event, EventResult, Key, MouseButton, MouseEvent}, theme::PaletteStyle, view::{CannotFocus, View}, - Cursive, Printer, Vec2, With, + Cursive, Printer, Vec2, With, utils::markup::StyledString, }; use std::rc::Rc; @@ -25,6 +25,8 @@ pub struct Checkbox { enabled: bool, on_change: Option>, + + label: StyledString, } new_default!(Checkbox); @@ -32,12 +34,23 @@ new_default!(Checkbox); impl Checkbox { impl_enabled!(self.enabled); - /// Creates a new, unchecked checkbox. + /// Creates a new, unlabelled, unchecked checkbox. pub fn new() -> Self { Checkbox { checked: false, enabled: true, on_change: None, + label: StyledString::new(), + } + } + + /// Creates a new, labelled, unchecked checkbox. + pub fn labelled(label: StyledString) -> Self { + Checkbox { + checked: false, + enabled: true, + on_change: None, + label } } @@ -134,12 +147,26 @@ impl Checkbox { if self.checked { printer.print((1, 0), "X"); } + + if !self.label.is_empty() { + // We want the space to be highlighted if focused + printer.print((3, 0), " "); + printer.print_styled((4, 0), &self.label); + } + } + + fn req_size(&self) -> Vec2 { + if self.label.is_empty() { + Vec2::new(3, 1) + } else { + Vec2::new(3 + 1 + self.label.width(), 1) + } } } impl View for Checkbox { fn required_size(&mut self, _: Vec2) -> Vec2 { - Vec2::new(3, 1) + self.req_size() } fn take_focus(&mut self, _: Direction) -> Result { diff --git a/cursive/examples/Readme.md b/cursive/examples/Readme.md index 18cd45e0..04ec7130 100644 --- a/cursive/examples/Readme.md +++ b/cursive/examples/Readme.md @@ -111,3 +111,7 @@ A larger example showing an implementation of minesweeper. ## [`window_title`](./window_title.rs) This shows how to change the terminal window title. + +## [`checkbox`](./checkbox.rs) + +This shows how to use `Checkbox`. diff --git a/cursive/examples/checkbox.rs b/cursive/examples/checkbox.rs new file mode 100644 index 00000000..3fca9e65 --- /dev/null +++ b/cursive/examples/checkbox.rs @@ -0,0 +1,81 @@ +use std::{cell::RefCell, collections::HashSet, fmt::Display, rc::Rc}; + +use cursive::views::{Checkbox, Dialog, DummyView, LinearLayout}; + +// This example uses checkboxes. +#[derive(Debug, PartialEq, Eq, Hash)] +enum Toppings { + ChocolateSprinkles, + CrushedAlmonds, + StrawberrySauce, +} + +impl Display for Toppings { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + Toppings::ChocolateSprinkles => write!(f, "Chocolate Sprinkles"), + Toppings::CrushedAlmonds => write!(f, "Crushed Almonds"), + Toppings::StrawberrySauce => write!(f, "Strawberry Sauce"), + } + } +} + +fn main() { + let mut siv = cursive::default(); + + // TODO: placeholder for MultiChoiceGroup. + + // Application wide container w/toppings choices. + let toppings: Rc>> = Rc::new(RefCell::new(HashSet::new())); + + siv.add_layer( + Dialog::new() + .title("Make your selections") + .content( + LinearLayout::vertical() + .child(Checkbox::labelled("Chocolate Sprinkles".into()).on_change({ + let toppings = toppings.clone(); + move |_, checked| { + if checked { + toppings.borrow_mut().insert(Toppings::ChocolateSprinkles); + } else { + toppings.borrow_mut().remove(&Toppings::ChocolateSprinkles); + } + } + })) + .child(Checkbox::labelled("Crushed Almonds".into()).on_change({ + let toppings = toppings.clone(); + move |_, checked| { + if checked { + toppings.borrow_mut().insert(Toppings::CrushedAlmonds); + } else { + toppings.borrow_mut().remove(&Toppings::CrushedAlmonds); + } + } + })) + .child(Checkbox::labelled("Strawberry Sauce".into()).on_change({ + let toppings = toppings.clone(); + move |_, checked| { + if checked { + toppings.borrow_mut().insert(Toppings::StrawberrySauce); + } else { + toppings.borrow_mut().remove(&Toppings::StrawberrySauce); + } + } + })), + ) + .button("Ok", move |s| { + s.pop_layer(); + let toppings = toppings + .borrow() + .iter() + .map(|t| t.to_string()) + .collect::>() + .join(", "); + let text = format!("Toppings: {toppings}"); + s.add_layer(Dialog::text(text).button("Ok", |s| s.quit())); + }), + ); + + siv.run(); +}