From bf3888e275980dd1e33c61cbc397dcf5f38101db Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sun, 2 Oct 2016 14:55:19 -0700 Subject: [PATCH] Add RadioButton & RadioGroup --- examples/radio.rs | 43 ++++++++++ src/views/checkbox.rs | 31 +------ src/views/mod.rs | 36 ++++++++ src/views/radio.rs | 187 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 268 insertions(+), 29 deletions(-) create mode 100644 examples/radio.rs create mode 100644 src/views/radio.rs diff --git a/examples/radio.rs b/examples/radio.rs new file mode 100644 index 0000000..fb33963 --- /dev/null +++ b/examples/radio.rs @@ -0,0 +1,43 @@ +extern crate cursive; + +use cursive::Cursive; +use cursive::views::{Dialog, DummyView, LinearLayout, RadioGroup}; + +fn main() { + let mut siv = Cursive::new(); + + // We need to pre-create the groups for our RadioButtons. + let mut color_group: RadioGroup = RadioGroup::new(); + let mut size_group: RadioGroup = RadioGroup::new(); + + siv.add_layer(Dialog::empty() + .title("Make your selection") + // We'll have two columns side-by-side + .content(LinearLayout::horizontal() + .child(LinearLayout::vertical() + // The color group uses the label itself as stored value + .child(color_group.button_str("Red")) + .child(color_group.button_str("Green")) + .child(color_group.button_str("Blue"))) + .child(DummyView) + .child(LinearLayout::vertical() + // For the size, we store a number separately + .child(size_group.button(5, "Small")) + .child(size_group.button(15, "Medium").selected()) + // The large size is out of stock, sorry! + .child(size_group.button(25, "Large").disabled()))) + .button("Ok", move |s| { + // We retrieve the stored value for both group. + let color = color_group.selection(); + let size = size_group.selection(); + + s.pop_layer(); + // And we simply print the result. + s.add_layer(Dialog::text(format!("Color: {}\nSize: {}cm", + color, + size)) + .button("Ok", |s| s.quit())); + })); + + siv.run(); +} diff --git a/src/views/checkbox.rs b/src/views/checkbox.rs index e72eb11..138b39f 100644 --- a/src/views/checkbox.rs +++ b/src/views/checkbox.rs @@ -21,6 +21,8 @@ pub struct Checkbox { new_default!(Checkbox); impl Checkbox { + impl_enabled!(self.enabled); + /// Creates a new, unchecked checkbox. pub fn new() -> Self { Checkbox { @@ -30,35 +32,6 @@ impl Checkbox { } } - /// Disables this view. - /// - /// A disabled view cannot be selected. - pub fn disable(&mut self) { - self.enabled = false; - } - - /// Disables this view. - /// - /// Chainable variant. - pub fn disabled(self) -> Self { - self.with(Self::disable) - } - - /// Re-enables this view. - pub fn enable(&mut self) { - self.enabled = true; - } - - /// Enable or disable this view. - pub fn set_enabled(&mut self, enabled: bool) { - self.enabled = enabled; - } - - /// Returns `true` if this view is enabled. - pub fn is_enabled(&self) -> bool { - self.enabled - } - /// Sets a callback to be used when the state changes. pub fn set_on_change(&mut self, on_change: F) { diff --git a/src/views/mod.rs b/src/views/mod.rs index 99b0124..d43f79f 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -1,5 +1,39 @@ //! Various views to use when creating the layout. +macro_rules! impl_enabled { + (self.$x:ident) => { + + /// Disables this view. + /// + /// A disabled view cannot be selected. + pub fn disable(&mut self) { + self.$x = false; + } + + /// Disables this view. + /// + /// Chainable variant. + pub fn disabled(self) -> Self { + self.with(Self::disable) + } + + /// Re-enables this view. + pub fn enable(&mut self) { + self.$x = true; + } + + /// Enable or disable this view. + pub fn set_enabled(&mut self, enabled: bool) { + self.$x = enabled; + } + + /// Returns `true` if this view is enabled. + pub fn is_enabled(&self) -> bool { + self.$x + } + } +} + mod box_view; mod button; mod checkbox; @@ -14,6 +48,7 @@ mod menubar; mod menu_popup; mod panel; mod progress_bar; +mod radio; mod select_view; mod slider_view; mod shadow_view; @@ -37,6 +72,7 @@ pub use self::menubar::Menubar; pub use self::menu_popup::MenuPopup; pub use self::panel::Panel; pub use self::progress_bar::{Counter, ProgressBar}; +pub use self::radio::{RadioGroup, RadioButton}; pub use self::select_view::SelectView; pub use self::slider_view::SliderView; pub use self::shadow_view::ShadowView; diff --git a/src/views/radio.rs b/src/views/radio.rs new file mode 100644 index 0000000..3cdf6f4 --- /dev/null +++ b/src/views/radio.rs @@ -0,0 +1,187 @@ +use {Printer, With}; +use vec::Vec2; +use view::View; +use theme::ColorStyle; +use event::{Event, EventResult, Key}; +use direction::Direction; + +use std::cell::RefCell; +use std::rc::Rc; + +struct SharedState { + selection: usize, + values: Vec>, +} + +impl SharedState { + pub fn selection(&self) -> Rc { + self.values[self.selection].clone() + } +} + +/// Group to coordinate multiple radio buttons. +/// +/// A `RadioGroup` is used to create and manage [`RadioButton`]s. +/// +/// [`RadioButton`]: struct.RadioButton.html +/// +/// # Examples +/// +/// ``` +/// let mut group = RadioGroup::new(); +/// +/// let button_1 = group.button_str(1, "Option 1"); +/// let button_2 = group.button_str(2, "Option 2"); +/// let button_3 = group.button_str(3, "Option 3"); +/// ``` +pub struct RadioGroup { + // Given to every child button + state: Rc>>, + + // Count the number of children already created, + // to give an ID to the next child. + count: usize, +} + +impl RadioGroup { + /// Creates an empty group for radio buttons. + pub fn new() -> Self { + RadioGroup { + state: Rc::new(RefCell::new(SharedState { + selection: 0, + values: Vec::new(), + })), + count: 0, + } + } + + /// Adds a new button to the group. + /// + /// The button will display `label` next to it, and will embed `value`. + pub fn button>(&mut self, value: T, label: S) + -> RadioButton { + self.state.borrow_mut().values.push(Rc::new(value)); + let result = + RadioButton::new(self.state.clone(), self.count, label.into()); + self.count += 1; + result + } + + /// Returns the id of the selected button. + /// + /// Buttons are indexed in the order they are created, starting from 0. + pub fn selected_id(&self) -> usize { + self.state.borrow().selection + } + + /// Returns the value associated with the selected button. + pub fn selection(&self) -> Rc { + self.state.borrow().selection() + } +} + +impl RadioGroup { + /// Adds a button, using the label itself as value. + pub fn button_str>(&mut self, text: S) + -> RadioButton { + let text = text.into(); + self.button(text.clone(), text) + } +} + +/// Variant of `Checkbox` arranged in group. +/// +/// RadioButton are managed by a [`RadioGroup`]. A single group can contain +/// several radio buttons, but only one button per group can be active at a +/// time. +/// +/// `RadioButton`s are not created directly, but through +/// [`RadioGroup::button()`]. +/// +/// [`RadioGroup`]: struct.RadioGroup.html +/// [`RadioGroup::button()`]: struct.RadioGroup.html#method.button +pub struct RadioButton { + state: Rc>>, + id: usize, + enabled: bool, + label: String, +} + + +impl RadioButton { + impl_enabled!(self.enabled); + + fn new(state: Rc>>, id: usize, label: String) -> Self { + RadioButton { + state: state, + id: id, + enabled: true, + label: label, + } + } + + /// Returns `true` if this button is selected. + pub fn is_selected(&self) -> bool { + self.state.borrow().selection == self.id + } + + /// Selects this button, un-selecting any other in the same group. + pub fn select(&mut self) { + self.state.borrow_mut().selection = self.id; + } + + /// Selects this button, un-selecting any other in the same group. + /// + /// Chainable variant. + pub fn selected(self) -> Self { + self.with(Self::select) + } + + fn draw_internal(&self, printer: &Printer) { + printer.print((0, 0), "( )"); + if self.is_selected() { + 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((4, 0), &self.label); + } + } +} + +impl View for RadioButton { + fn get_min_size(&mut self, _: Vec2) -> Vec2 { + if self.label.is_empty() { + Vec2::new(3, 1) + } else { + Vec2::new(3 + 1 + self.label.len(), 1) + } + } + + fn take_focus(&mut self, _: Direction) -> bool { + self.enabled + } + + fn draw(&self, printer: &Printer) { + if self.enabled { + printer.with_selection(printer.focused, + |printer| self.draw_internal(printer)); + } else { + printer.with_color(ColorStyle::Secondary, + |printer| self.draw_internal(printer)); + } + } + + fn on_event(&mut self, event: Event) -> EventResult { + match event { + Event::Key(Key::Enter) | + Event::Char(' ') => { + self.select(); + EventResult::Consumed(None) + } + _ => EventResult::Ignored, + } + } +}