use crate::component::{Component, EventCx, MeasureCx};
use crate::event::Event;
use crate::geom::{Rect, Size};
use crate::layout::Constraint;
use crate::render::RenderCx;
use crate::style::Style;
pub struct Checkbox {
label: String,
checked: bool,
focused: bool,
rect: Rect,
style: Style,
checked_style: Style,
}
impl Checkbox {
pub fn new(label: impl Into<String>) -> Self {
Self {
label: label.into(),
checked: false,
focused: false,
rect: Rect::default(),
style: Style::default(),
checked_style: Style::default().fg(crate::style::Color::Green),
}
}
pub fn checked(mut self) -> Self {
self.checked = true;
self
}
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn checked_style(mut self, style: Style) -> Self {
self.checked_style = style;
self
}
pub fn is_checked(&self) -> bool {
self.checked
}
pub fn set_checked(&mut self, checked: bool, cx: &mut EventCx) {
if self.checked != checked {
self.checked = checked;
cx.invalidate_paint();
}
}
pub fn toggle(&mut self, cx: &mut EventCx) {
self.checked = !self.checked;
cx.invalidate_paint();
}
}
impl Component for Checkbox {
fn render(&self, cx: &mut RenderCx) {
let mark = if self.checked { "✓" } else { " " };
let text = format!("[{}] {}", mark, self.label);
if self.focused {
cx.set_style(self.checked_style.clone());
} else if self.checked {
cx.set_style(self.checked_style.clone());
} else {
cx.set_style(self.style.clone());
}
cx.line(&text);
}
fn measure(&self, _constraint: Constraint, _cx: &mut MeasureCx) -> Size {
let w: u16 = self.label.chars().map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0) as u16).sum();
Size { width: 5 + w, height: 1 }
}
fn event(&mut self, event: &Event, cx: &mut EventCx) {
match event {
Event::Focus => { self.focused = true; cx.invalidate_paint(); return; }
Event::Blur => { self.focused = false; cx.invalidate_paint(); return; }
_ => {}
}
if cx.phase() != crate::event::EventPhase::Target { return; }
if let Event::Key(key_event) = event {
match &key_event.key {
crate::event::Key::Char(' ') | crate::event::Key::Enter => {
self.toggle(cx);
}
_ => {}
}
}
}
fn layout(&mut self, rect: Rect, _cx: &mut crate::component::LayoutCx) { self.rect = rect; }
fn focusable(&self) -> bool { true }
fn style(&self) -> Style { self.style.clone() }
}
pub struct RadioGroup {
options: Vec<String>,
selected: usize,
focused: bool,
rect: Rect,
style: Style,
selected_style: Style,
}
impl RadioGroup {
pub fn new(options: Vec<String>) -> Self {
Self {
options,
selected: 0,
focused: false,
rect: Rect::default(),
style: Style::default(),
selected_style: Style::default().fg(crate::style::Color::Green),
}
}
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn selected_style(mut self, style: Style) -> Self {
self.selected_style = style;
self
}
pub fn selected(&self) -> usize {
self.selected
}
pub fn selected_text(&self) -> &str {
self.options.get(self.selected).map(|s| s.as_str()).unwrap_or("")
}
pub fn set_selected(&mut self, index: usize, cx: &mut EventCx) {
if index < self.options.len() && index != self.selected {
self.selected = index;
cx.invalidate_paint();
}
}
}
impl Component for RadioGroup {
fn render(&self, cx: &mut RenderCx) {
for (i, opt) in self.options.iter().enumerate() {
let (mark, style) = if i == self.selected {
if self.focused {
("•", self.selected_style.clone())
} else {
("•", self.selected_style.clone())
}
} else {
(" ", self.style.clone())
};
cx.set_style(style);
cx.line(&format!("({}) {}", mark, opt));
}
}
fn measure(&self, _constraint: Constraint, _cx: &mut MeasureCx) -> Size {
let max_w: u16 = self.options.iter()
.map(|o| 4 + o.chars().map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0) as u16).sum::<u16>())
.max()
.unwrap_or(0);
Size { width: max_w, height: self.options.len() as u16 }
}
fn event(&mut self, event: &Event, cx: &mut EventCx) {
match event {
Event::Focus => { self.focused = true; cx.invalidate_paint(); return; }
Event::Blur => { self.focused = false; cx.invalidate_paint(); return; }
_ => {}
}
if cx.phase() != crate::event::EventPhase::Target { return; }
if self.options.is_empty() { return; }
if let Event::Key(key_event) = event {
match &key_event.key {
crate::event::Key::Up => {
self.selected = if self.selected > 0 { self.selected - 1 } else { self.options.len() - 1 };
cx.invalidate_paint();
}
crate::event::Key::Down => {
self.selected = if self.selected + 1 < self.options.len() { self.selected + 1 } else { 0 };
cx.invalidate_paint();
}
_ => {}
}
}
}
fn layout(&mut self, rect: Rect, _cx: &mut crate::component::LayoutCx) { self.rect = rect; }
fn focusable(&self) -> bool { true }
fn style(&self) -> Style { self.style.clone() }
}