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 pub inherited: bool,
37}
38
39impl ToggleState {
40 pub fn new(checked: bool, label: impl Into<String>) -> Self {
42 Self {
43 checked,
44 label: label.into(),
45 focus: FocusState::Normal,
46 inherited: false,
47 }
48 }
49
50 pub fn with_focus(mut self, focus: FocusState) -> Self {
52 self.focus = focus;
53 self
54 }
55
56 pub fn with_inherited(mut self, inherited: bool) -> Self {
58 self.inherited = inherited;
59 self
60 }
61
62 pub fn is_enabled(&self) -> bool {
64 self.focus != FocusState::Disabled
65 }
66
67 pub fn toggle(&mut self) {
69 if self.is_enabled() {
70 self.checked = !self.checked;
71 self.inherited = false;
74 }
75 }
76}
77
78#[derive(Debug, Clone, Copy)]
80pub struct ToggleColors {
81 pub bracket: Color,
83 pub checkmark: Color,
85 pub label: Color,
87 pub focused: Color,
89 pub focused_fg: Color,
91 pub disabled: Color,
93}
94
95impl Default for ToggleColors {
96 fn default() -> Self {
97 Self {
98 bracket: Color::Gray,
99 checkmark: Color::Green,
100 label: Color::White,
101 focused: Color::Cyan,
102 focused_fg: Color::Black,
103 disabled: Color::DarkGray,
104 }
105 }
106}
107
108impl ToggleColors {
109 pub fn from_theme(theme: &crate::view::theme::Theme) -> Self {
111 Self {
112 bracket: theme.line_number_fg,
113 checkmark: theme.diagnostic_info_fg,
114 label: theme.editor_fg,
115 focused: theme.settings_selected_bg,
116 focused_fg: theme.settings_selected_fg,
117 disabled: theme.line_number_fg,
118 }
119 }
120}
121
122#[derive(Debug, Clone, Copy, Default)]
124pub struct ToggleLayout {
125 pub checkbox_area: Rect,
127 pub full_area: Rect,
129}
130
131impl ToggleLayout {
132 pub fn contains(&self, x: u16, y: u16) -> bool {
134 x >= self.full_area.x
135 && x < self.full_area.x + self.full_area.width
136 && y >= self.full_area.y
137 && y < self.full_area.y + self.full_area.height
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use ratatui::backend::TestBackend;
145 use ratatui::Terminal;
146
147 fn test_frame<F>(width: u16, height: u16, f: F)
148 where
149 F: FnOnce(&mut ratatui::Frame, Rect),
150 {
151 let backend = TestBackend::new(width, height);
152 let mut terminal = Terminal::new(backend).unwrap();
153 terminal
154 .draw(|frame| {
155 let area = Rect::new(0, 0, width, height);
156 f(frame, area);
157 })
158 .unwrap();
159 }
160
161 #[test]
162 fn test_toggle_checked() {
163 test_frame(40, 1, |frame, area| {
164 let state = ToggleState::new(true, "Enable");
165 let colors = ToggleColors::default();
166 let layout = render_toggle(frame, area, &state, &colors);
167
168 assert_eq!(layout.checkbox_area.width, 3);
170 assert_eq!(layout.full_area.width, 11);
172 });
173 }
174
175 #[test]
176 fn test_toggle_unchecked() {
177 test_frame(40, 1, |frame, area| {
178 let state = ToggleState::new(false, "Enable");
179 let colors = ToggleColors::default();
180 let layout = render_toggle(frame, area, &state, &colors);
181
182 assert_eq!(layout.checkbox_area.width, 3);
184 });
185 }
186
187 #[test]
188 fn test_toggle_click_detection() {
189 test_frame(40, 1, |frame, area| {
190 let state = ToggleState::new(true, "Enable");
191 let colors = ToggleColors::default();
192 let layout = render_toggle(frame, area, &state, &colors);
193
194 assert!(layout.contains(8, 0));
196 assert!(layout.contains(10, 0));
197
198 assert!(layout.contains(0, 0));
200 assert!(layout.contains(5, 0));
201
202 assert!(!layout.contains(15, 0));
204 });
205 }
206
207 #[test]
208 fn test_toggle_state_toggle() {
209 let mut state = ToggleState::new(false, "Test");
210 assert!(!state.checked);
211
212 state.toggle();
213 assert!(state.checked);
214
215 state.toggle();
216 assert!(!state.checked);
217 }
218
219 #[test]
220 fn test_toggle_disabled_no_toggle() {
221 let mut state = ToggleState::new(false, "Test").with_focus(FocusState::Disabled);
222 state.toggle();
223 assert!(!state.checked); }
225
226 #[test]
227 fn test_toggle_narrow_area() {
228 test_frame(2, 1, |frame, area| {
229 let state = ToggleState::new(true, "Enable");
230 let colors = ToggleColors::default();
231 let layout = render_toggle(frame, area, &state, &colors);
232
233 assert!(layout.full_area.width <= area.width);
235 });
236 }
237}