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, } } }