fresh/view/controls/toggle/
mod.rs1mod input;
12mod render;
13
14use ratatui::layout::Rect;
15use ratatui::style::Color;
16
17pub use input::ToggleEvent;
18pub use render::{render_toggle, render_toggle_aligned};
19
20use super::FocusState;
21
22#[derive(Debug, Clone)]
24pub struct ToggleState {
25 pub checked: bool,
27 pub label: String,
29 pub focus: FocusState,
31}
32
33impl ToggleState {
34 pub fn new(checked: bool, label: impl Into<String>) -> Self {
36 Self {
37 checked,
38 label: label.into(),
39 focus: FocusState::Normal,
40 }
41 }
42
43 pub fn with_focus(mut self, focus: FocusState) -> Self {
45 self.focus = focus;
46 self
47 }
48
49 pub fn is_enabled(&self) -> bool {
51 self.focus != FocusState::Disabled
52 }
53
54 pub fn toggle(&mut self) {
56 if self.is_enabled() {
57 self.checked = !self.checked;
58 }
59 }
60}
61
62#[derive(Debug, Clone, Copy)]
64pub struct ToggleColors {
65 pub bracket: Color,
67 pub checkmark: Color,
69 pub label: Color,
71 pub focused: Color,
73 pub disabled: Color,
75}
76
77impl Default for ToggleColors {
78 fn default() -> Self {
79 Self {
80 bracket: Color::Gray,
81 checkmark: Color::Green,
82 label: Color::White,
83 focused: Color::Cyan,
84 disabled: Color::DarkGray,
85 }
86 }
87}
88
89impl ToggleColors {
90 pub fn from_theme(theme: &crate::view::theme::Theme) -> Self {
92 Self {
93 bracket: theme.line_number_fg,
94 checkmark: theme.diagnostic_info_fg,
95 label: theme.editor_fg,
96 focused: theme.selection_bg,
97 disabled: theme.line_number_fg,
98 }
99 }
100}
101
102#[derive(Debug, Clone, Copy, Default)]
104pub struct ToggleLayout {
105 pub checkbox_area: Rect,
107 pub full_area: Rect,
109}
110
111impl ToggleLayout {
112 pub fn contains(&self, x: u16, y: u16) -> bool {
114 x >= self.full_area.x
115 && x < self.full_area.x + self.full_area.width
116 && y >= self.full_area.y
117 && y < self.full_area.y + self.full_area.height
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use ratatui::backend::TestBackend;
125 use ratatui::Terminal;
126
127 fn test_frame<F>(width: u16, height: u16, f: F)
128 where
129 F: FnOnce(&mut ratatui::Frame, Rect),
130 {
131 let backend = TestBackend::new(width, height);
132 let mut terminal = Terminal::new(backend).unwrap();
133 terminal
134 .draw(|frame| {
135 let area = Rect::new(0, 0, width, height);
136 f(frame, area);
137 })
138 .unwrap();
139 }
140
141 #[test]
142 fn test_toggle_checked() {
143 test_frame(20, 1, |frame, area| {
144 let state = ToggleState::new(true, "Enable");
145 let colors = ToggleColors::default();
146 let layout = render_toggle(frame, area, &state, &colors);
147
148 assert_eq!(layout.checkbox_area.width, 3);
149 assert_eq!(layout.full_area.width, 11); });
151 }
152
153 #[test]
154 fn test_toggle_unchecked() {
155 test_frame(20, 1, |frame, area| {
156 let state = ToggleState::new(false, "Enable");
157 let colors = ToggleColors::default();
158 let layout = render_toggle(frame, area, &state, &colors);
159
160 assert_eq!(layout.checkbox_area.width, 3);
161 });
162 }
163
164 #[test]
165 fn test_toggle_click_detection() {
166 test_frame(20, 1, |frame, area| {
167 let state = ToggleState::new(true, "Enable");
168 let colors = ToggleColors::default();
169 let layout = render_toggle(frame, area, &state, &colors);
170
171 assert!(layout.contains(0, 0));
173 assert!(layout.contains(2, 0));
174
175 assert!(layout.contains(5, 0));
177
178 assert!(!layout.contains(15, 0));
180 });
181 }
182
183 #[test]
184 fn test_toggle_state_toggle() {
185 let mut state = ToggleState::new(false, "Test");
186 assert!(!state.checked);
187
188 state.toggle();
189 assert!(state.checked);
190
191 state.toggle();
192 assert!(!state.checked);
193 }
194
195 #[test]
196 fn test_toggle_disabled_no_toggle() {
197 let mut state = ToggleState::new(false, "Test").with_focus(FocusState::Disabled);
198 state.toggle();
199 assert!(!state.checked); }
201
202 #[test]
203 fn test_toggle_narrow_area() {
204 test_frame(2, 1, |frame, area| {
205 let state = ToggleState::new(true, "Enable");
206 let colors = ToggleColors::default();
207 let layout = render_toggle(frame, area, &state, &colors);
208
209 assert!(layout.full_area.width <= area.width);
211 });
212 }
213}