flux_tui/components/
checkbox.rs1use super::*;
2use crate::canvas::*;
3
4use std::num::NonZero;
5use std::rc::Rc;
6
7use crossterm::event::{Event, KeyCode};
8use unicode_width::UnicodeWidthStr;
9
10pub type StateChangedCallback = dyn Fn(&mut TextCheckbox);
11pub type CheckboxKeyHandler = fn(&mut Checkbox, event: &mut WindowEvent);
12
13pub struct TextCheckbox {
23 checkbox: Checkbox,
24 text: String,
25 callback: Rc<StateChangedCallback>,
26}
27
28impl TextCheckbox {
29 pub fn default_callback(_: &mut Self) {}
30
31 pub fn set_state_changed_callback<F: Fn(&mut Self) + 'static>(&mut self, callback: F) {
32 self.callback = Rc::new(callback);
33 }
34
35 pub fn set_text(&mut self, text: &str) {
36 self.text.clear();
37 self.text.push_str(text);
38 let min = 3 + !self.text.is_empty() as TSize * 2;
39 let max = 3 + self.text.width() as TSize + !self.text.is_empty() as TSize;
40 self.checkbox.base.constraints.x.max = Size::Fixed(NonZero::new(max).unwrap());
41 self.checkbox.base.constraints.x.min = NonZero::new(min).unwrap();
42 }
43
44 pub fn get_text(&self) -> &str {
46 &self.text
47 }
48
49 pub fn get_state(&self) -> bool {
51 self.checkbox.state
52 }
53
54 pub fn set_state(&mut self, state: bool) {
56 self.checkbox.state = state;
57 }
58
59 pub fn set_key_handler(&mut self, f: CheckboxKeyHandler) {
60 self.checkbox.set_key_handler(f);
61 }
62}
63
64impl Default for TextCheckbox {
65 fn default() -> Self {
66 let mut s = Self {
67 checkbox: Checkbox::default(),
68 text: String::new(),
69 callback: Rc::new(Self::default_callback),
70 };
71 s.checkbox.base.constraints.x.min = NonZero::new(3).unwrap();
72 s.checkbox.base.constraints.x.max = Size::Relative(Percent::from_int(100));
73 s
74 }
75}
76
77impl Window for TextCheckbox {
78 fn render(&self, canvas: &mut Canvas) {
79 self.checkbox.render(canvas);
80 let mut row = canvas.get_row_variable_width(0).unwrap();
81 row.skip(4);
82 row.add_string(
83 &self.text,
84 None,
85 None,
86 Style::None,
87 Some(VariableWidthGlyphRow::DOT3_REPLACEMENT),
88 )
89 .unwrap();
90 }
91
92 fn handle_event(&mut self, event: &mut WindowEvent) {
93 if self.checkbox.handle_event(event) {
94 let cb = self.callback.clone();
95 cb(self);
96 }
97 }
98
99 fn is_enabled(&self) -> bool {
100 self.checkbox.base.enabled
101 }
102}
103
104impl WindowLayout for TextCheckbox {
105 fn desired_size(&self, available_size: TPoint) -> TPoint {
106 self.checkbox.base.desired_size(available_size)
107 }
108
109 fn border(&self) -> BorderStyle {
110 self.checkbox.base.border
111 }
112
113 fn alignment(&self) -> (HorizontalAlignment, VerticalAlignment) {
114 self.checkbox.base.alignment
115 }
116
117 fn is_visible(&self) -> bool {
118 self.checkbox.base.visibility
119 }
120
121 fn margin(&self) -> Thickness {
122 self.checkbox.base.margin
123 }
124}
125
126impl HasWindowUID for TextCheckbox {
127 fn uid(&self) -> WindowUID {
128 self.checkbox.base.uid
129 }
130}
131
132impl Widget for TextCheckbox {
133 fn set_visibility(&mut self, visibility: bool) {
134 self.checkbox.base.visibility = visibility;
135 self.provoke_changed_property(WindowProperty::IsVisible);
136 }
137
138 fn set_width(&mut self, _: Size) {}
139
140 fn set_height(&mut self, _: Size) {}
141
142 fn set_margin(&mut self, margin: Thickness) {
143 self.checkbox.base.margin = margin;
144 self.provoke_changed_property(WindowProperty::Margin);
145 }
146
147 fn set_border(&mut self, border: BorderStyle) {
148 self.checkbox.base.border = border;
149 self.provoke_changed_property(WindowProperty::Border);
150 }
151
152 fn set_alignment(&mut self, horizontal: HorizontalAlignment, vertical: VerticalAlignment) {
153 self.checkbox.base.alignment = (horizontal, vertical);
154 self.provoke_changed_property(WindowProperty::Alignment);
155 }
156
157 fn set_enabled_state(&mut self, is_enabled: bool) {
158 self.checkbox.base.enabled = is_enabled;
159 }
160
161 fn set_width_constraint(&mut self, _: SizeConstraint) {}
162
163 fn set_height_constraint(&mut self, _: SizeConstraint) {}
164}
165
166impl WidgetColors for TextCheckbox {
167 fn set_disabled_color(&mut self, color: Option<Color>) {
168 self.checkbox.base.colors.disabled = color;
169 }
170
171 fn set_base_fg_color(&mut self, color: Option<Color>) {
172 self.checkbox.base.colors.base_fg = color;
173 }
174
175 fn set_base_bg_color(&mut self, color: Option<Color>) {
176 self.checkbox.base.colors.base_bg = color;
177 }
178}
179
180pub struct Checkbox {
182 has_focus: bool,
183 base: WidgetBase,
184 state: bool,
185 key_handler: CheckboxKeyHandler,
186}
187
188impl Default for Checkbox {
189 fn default() -> Self {
190 let mut s = Self {
191 state: false,
192 has_focus: false,
193 base: WidgetBase::default(),
194 key_handler: Self::default_key_handler,
195 };
196 s.base.constraints.x.min = NonZero::new(3).unwrap();
197 s.base.constraints.x.max = Size::Fixed(NonZero::new(3).unwrap());
198 s.base.constraints.y.min = NonZero::<TSize>::MIN;
199 s.base.constraints.y.max = Size::Fixed(NonZero::<TSize>::MIN);
200 s
201 }
202}
203
204impl Checkbox {
205 pub const CROSS_MARK: Grapheme = Grapheme::new_unchecked("x", GlyphWidth::Half);
206 pub const OPEN_BOX: Grapheme = Grapheme::new_unchecked("[", GlyphWidth::Half);
207 pub const CLOSE_BOX: Grapheme = Grapheme::new_unchecked("]", GlyphWidth::Half);
208
209 pub fn default_key_handler(&mut self, event: &mut WindowEvent) {
210 if let Event::Key(k) = event.raw() {
211 if k.code == KeyCode::Char(' ') {
212 self.state = !self.state;
213 event.handled = true;
214 }
215 }
216 }
217
218 pub fn set_key_handler(&mut self, f: CheckboxKeyHandler) {
219 self.key_handler = f;
220 }
221
222 pub fn get_state(&self) -> bool {
224 self.state
225 }
226
227 pub fn set_state(&mut self, state: bool) {
229 self.state = state;
230 }
231
232 pub fn render(&self, canvas: &mut Canvas) {
233 let mut row = canvas.get_row(0, GlyphWidth::Half).unwrap();
234 row.get(0).unwrap().set_grapheme(Self::OPEN_BOX).unwrap();
235 if self.state {
236 row.get(1).unwrap().set_grapheme(Self::CROSS_MARK).unwrap();
237 }
238 row.get(2).unwrap().set_grapheme(Self::CLOSE_BOX).unwrap();
239
240 if self.has_focus {
241 row.get(0).unwrap().set_style(Style::Reverse);
242 row.get(2).unwrap().set_style(Style::ResetAfter);
243 }
244
245 if let Some(color) = self.base.colors.base_fg {
246 row.fill_foreground(color);
247 }
248 if let Some(color) = self.base.colors.base_bg {
249 row.fill_background(color);
250 }
251
252 if let Some(color) = self.base.colors.disabled
253 && !self.base.enabled
254 {
255 row.fill_foreground(color);
256 }
257 }
258
259 pub fn handle_event(&mut self, event: &mut WindowEvent) -> bool {
260 let old = self.state;
261 match event.raw() {
262 Event::FocusGained => self.has_focus = true,
263 Event::FocusLost => self.has_focus = false,
264 _ => (self.key_handler)(self, event),
265 }
266
267 self.state != old
268 }
269}