use crate::core::buffer::Buffer;
use crate::core::color::Color;
use crate::core::rect::Rect;
use crate::widgets::Widget;
#[derive(Debug, Clone)]
pub struct Toggle {
pub label: String,
pub active: bool,
pub active_color: Color,
pub inactive_color: Color,
pub label_color: Color,
pub style: ToggleStyle,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ToggleStyle {
Slider,
Checkbox,
Radio,
Text,
Block,
}
impl Toggle {
pub fn new(label: &str, active: bool) -> Self {
Self {
label: label.to_string(),
active,
active_color: Color::rgb(63, 185, 80),
inactive_color: Color::rgb(110, 118, 129),
label_color: Color::rgb(201, 209, 217),
style: ToggleStyle::Slider,
}
}
pub fn with_style(mut self, style: ToggleStyle) -> Self {
self.style = style;
self
}
pub fn with_active_color(mut self, c: Color) -> Self {
self.active_color = c;
self
}
pub fn with_inactive_color(mut self, c: Color) -> Self {
self.inactive_color = c;
self
}
pub fn with_label_color(mut self, c: Color) -> Self {
self.label_color = c;
self
}
pub fn toggle(&mut self) {
self.active = !self.active;
}
pub fn set_active(&mut self, v: bool) {
self.active = v;
}
}
impl Widget for Toggle {
fn render(&self, buffer: &mut Buffer, area: Rect) {
if area.width < 4 || area.height < 1 {
return;
}
let (indicator, indicator_color) = match self.style {
ToggleStyle::Slider => {
let indicator_width = (area.width.saturating_sub(4)).min(12);
let filled = if self.active { indicator_width } else { 0 };
let mut slider = String::from("[");
for i in 0..indicator_width {
if i < filled {
slider.push('━');
} else {
slider.push('─');
}
}
slider.push(']');
if self.active {
let last = slider.len() - 1;
slider.replace_range(last.., "●]");
}
(
format!("{} {}", slider, self.label),
if self.active {
self.active_color
} else {
self.inactive_color
},
)
}
ToggleStyle::Checkbox => {
let ch = if self.active { "●" } else { "○" };
(
format!("[{}] {}", ch, self.label),
if self.active {
self.active_color
} else {
self.inactive_color
},
)
}
ToggleStyle::Radio => {
let ch = if self.active { "●" } else { "○" };
(
format!("{} {}", ch, self.label),
if self.active {
self.active_color
} else {
self.inactive_color
},
)
}
ToggleStyle::Text => {
let txt = if self.active { "ON" } else { "OFF" };
(
format!("[{}] {}", txt, self.label),
if self.active {
self.active_color
} else {
self.inactive_color
},
)
}
ToggleStyle::Block => {
let block_width = (area.width.saturating_sub(4)).min(10) as usize;
let filled = if self.active { block_width } else { 0 };
let mut blocks = String::new();
for i in 0..block_width {
if i < filled {
blocks.push('█');
} else {
blocks.push('░');
}
}
(
format!("{} {}", blocks, self.label),
if self.active {
self.active_color
} else {
self.inactive_color
},
)
}
};
let display: String = indicator.chars().take(area.width as usize).collect();
buffer.set_str(
area.x as usize,
area.y as usize,
&display,
indicator_color,
None,
);
if !self.label.is_empty() {
if let Some(byte_idx) = display.find(&self.label) {
let label_col = display[..byte_idx].chars().count();
if label_col < area.width as usize {
let label: String = self
.label
.chars()
.take(area.width as usize - label_col)
.collect();
buffer.set_str(
area.x as usize + label_col,
area.y as usize,
&label,
self.label_color,
None,
);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_toggle_creation() {
let t = Toggle::new("Light", false);
assert!(!t.active);
assert_eq!(t.label, "Light");
}
#[test]
fn test_toggle_switch() {
let mut t = Toggle::new("Dark", false);
t.toggle();
assert!(t.active);
t.toggle();
assert!(!t.active);
}
#[test]
fn test_toggle_render_no_panic() {
let mut buf = Buffer::new(40, 5);
let toggle = Toggle::new("Test", true).with_style(ToggleStyle::Checkbox);
toggle.render(&mut buf, Rect::new(0, 0, 40, 1));
assert_eq!(buf.get(0, 0).unwrap().ch, '[');
}
#[test]
fn test_toggle_all_styles_render() {
let mut buf = Buffer::new(40, 5);
let styles = [
ToggleStyle::Slider,
ToggleStyle::Checkbox,
ToggleStyle::Radio,
ToggleStyle::Text,
ToggleStyle::Block,
];
for (i, style) in styles.iter().enumerate() {
let toggle = Toggle::new("Test", true).with_style(*style);
toggle.render(&mut buf, Rect::new(0, i as u16, 40, 1));
}
}
#[test]
fn test_toggle_render_too_small() {
let mut buf = Buffer::new(2, 1);
let toggle = Toggle::new("Test", true);
toggle.render(&mut buf, Rect::new(0, 0, 2, 1));
}
#[test]
fn test_toggle_uses_label_color() {
let mut buf = Buffer::new(20, 1);
let label_color = Color::rgb(255, 0, 128);
let toggle = Toggle::new("Label", true)
.with_style(ToggleStyle::Checkbox)
.with_label_color(label_color);
toggle.render(&mut buf, Rect::new(0, 0, 20, 1));
assert_eq!(buf.get(4, 0).unwrap().fg, label_color);
}
}