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 focused_fg: Color,
75 pub disabled: Color,
77}
78
79impl Default for ToggleColors {
80 fn default() -> Self {
81 Self {
82 bracket: Color::Gray,
83 checkmark: Color::Green,
84 label: Color::White,
85 focused: Color::Cyan,
86 focused_fg: Color::Black,
87 disabled: Color::DarkGray,
88 }
89 }
90}
91
92impl ToggleColors {
93 pub fn from_theme(theme: &crate::view::theme::Theme) -> Self {
95 Self {
96 bracket: theme.line_number_fg,
97 checkmark: theme.diagnostic_info_fg,
98 label: theme.editor_fg,
99 focused: theme.settings_selected_bg,
100 focused_fg: theme.settings_selected_fg,
101 disabled: theme.line_number_fg,
102 }
103 }
104}
105
106#[derive(Debug, Clone, Copy, Default)]
108pub struct ToggleLayout {
109 pub checkbox_area: Rect,
111 pub full_area: Rect,
113}
114
115impl ToggleLayout {
116 pub fn contains(&self, x: u16, y: u16) -> bool {
118 x >= self.full_area.x
119 && x < self.full_area.x + self.full_area.width
120 && y >= self.full_area.y
121 && y < self.full_area.y + self.full_area.height
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use ratatui::backend::TestBackend;
129 use ratatui::Terminal;
130
131 fn test_frame<F>(width: u16, height: u16, f: F)
132 where
133 F: FnOnce(&mut ratatui::Frame, Rect),
134 {
135 let backend = TestBackend::new(width, height);
136 let mut terminal = Terminal::new(backend).unwrap();
137 terminal
138 .draw(|frame| {
139 let area = Rect::new(0, 0, width, height);
140 f(frame, area);
141 })
142 .unwrap();
143 }
144
145 #[test]
146 fn test_toggle_checked() {
147 test_frame(40, 1, |frame, area| {
148 let state = ToggleState::new(true, "Enable");
149 let colors = ToggleColors::default();
150 let layout = render_toggle(frame, area, &state, &colors);
151
152 assert_eq!(layout.checkbox_area.width, 3);
154 assert_eq!(layout.full_area.width, 11);
156 });
157 }
158
159 #[test]
160 fn test_toggle_unchecked() {
161 test_frame(40, 1, |frame, area| {
162 let state = ToggleState::new(false, "Enable");
163 let colors = ToggleColors::default();
164 let layout = render_toggle(frame, area, &state, &colors);
165
166 assert_eq!(layout.checkbox_area.width, 3);
168 });
169 }
170
171 #[test]
172 fn test_toggle_click_detection() {
173 test_frame(40, 1, |frame, area| {
174 let state = ToggleState::new(true, "Enable");
175 let colors = ToggleColors::default();
176 let layout = render_toggle(frame, area, &state, &colors);
177
178 assert!(layout.contains(8, 0));
180 assert!(layout.contains(10, 0));
181
182 assert!(layout.contains(0, 0));
184 assert!(layout.contains(5, 0));
185
186 assert!(!layout.contains(15, 0));
188 });
189 }
190
191 #[test]
192 fn test_toggle_state_toggle() {
193 let mut state = ToggleState::new(false, "Test");
194 assert!(!state.checked);
195
196 state.toggle();
197 assert!(state.checked);
198
199 state.toggle();
200 assert!(!state.checked);
201 }
202
203 #[test]
204 fn test_toggle_disabled_no_toggle() {
205 let mut state = ToggleState::new(false, "Test").with_focus(FocusState::Disabled);
206 state.toggle();
207 assert!(!state.checked); }
209
210 #[test]
211 fn test_toggle_narrow_area() {
212 test_frame(2, 1, |frame, area| {
213 let state = ToggleState::new(true, "Enable");
214 let colors = ToggleColors::default();
215 let layout = render_toggle(frame, area, &state, &colors);
216
217 assert!(layout.full_area.width <= area.width);
219 });
220 }
221}