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(20, 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);
153 assert_eq!(layout.full_area.width, 11); });
155 }
156
157 #[test]
158 fn test_toggle_unchecked() {
159 test_frame(20, 1, |frame, area| {
160 let state = ToggleState::new(false, "Enable");
161 let colors = ToggleColors::default();
162 let layout = render_toggle(frame, area, &state, &colors);
163
164 assert_eq!(layout.checkbox_area.width, 3);
165 });
166 }
167
168 #[test]
169 fn test_toggle_click_detection() {
170 test_frame(20, 1, |frame, area| {
171 let state = ToggleState::new(true, "Enable");
172 let colors = ToggleColors::default();
173 let layout = render_toggle(frame, area, &state, &colors);
174
175 assert!(layout.contains(0, 0));
177 assert!(layout.contains(2, 0));
178
179 assert!(layout.contains(5, 0));
181
182 assert!(!layout.contains(15, 0));
184 });
185 }
186
187 #[test]
188 fn test_toggle_state_toggle() {
189 let mut state = ToggleState::new(false, "Test");
190 assert!(!state.checked);
191
192 state.toggle();
193 assert!(state.checked);
194
195 state.toggle();
196 assert!(!state.checked);
197 }
198
199 #[test]
200 fn test_toggle_disabled_no_toggle() {
201 let mut state = ToggleState::new(false, "Test").with_focus(FocusState::Disabled);
202 state.toggle();
203 assert!(!state.checked); }
205
206 #[test]
207 fn test_toggle_narrow_area() {
208 test_frame(2, 1, |frame, area| {
209 let state = ToggleState::new(true, "Enable");
210 let colors = ToggleColors::default();
211 let layout = render_toggle(frame, area, &state, &colors);
212
213 assert!(layout.full_area.width <= area.width);
215 });
216 }
217}