tui_lipan/widgets/checkbox/
mod.rs1mod layout;
4mod node;
5mod reconcile;
6
7pub use layout::measure_checkbox;
8pub use node::CheckboxNode;
9pub use reconcile::reconcile_checkbox;
10
11use std::sync::Arc;
12
13use crate::callback::{Callback, KeyHandler};
14use crate::core::element::{Element, ElementKind};
15use crate::core::event::MouseEvent;
16use crate::style::{Length, Padding, Style, StyleSlot};
17
18#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
20pub enum CheckboxVariant {
21 #[default]
23 Bracket,
24 Circle,
26 Box,
28 Switch,
30 Custom {
32 checked: &'static str,
34 unchecked: &'static str,
36 indeterminate: &'static str,
38 },
39}
40
41impl CheckboxVariant {
42 pub fn checked_str(self) -> &'static str {
44 match self {
45 Self::Bracket => "[x]",
46 Self::Circle => "◉",
47 Self::Box => "✓",
48 Self::Switch => "●",
49 Self::Custom { checked, .. } => checked,
50 }
51 }
52
53 pub fn unchecked_str(self) -> &'static str {
55 match self {
56 Self::Bracket => "[ ]",
57 Self::Circle => "○",
58 Self::Box => "☐",
59 Self::Switch => "○",
60 Self::Custom { unchecked, .. } => unchecked,
61 }
62 }
63
64 pub fn indeterminate_str(self) -> &'static str {
66 match self {
67 Self::Bracket => "[-]",
68 Self::Circle => "◍",
69 Self::Box => "▣",
70 Self::Switch => "◐",
71 Self::Custom { indeterminate, .. } => indeterminate,
72 }
73 }
74
75 pub fn width(self) -> u16 {
77 use unicode_width::UnicodeWidthStr;
78 match self {
79 Self::Bracket => 3,
80 Self::Circle | Self::Box | Self::Switch => 1,
81 Self::Custom {
82 checked,
83 unchecked,
84 indeterminate,
85 } => UnicodeWidthStr::width(checked)
86 .max(UnicodeWidthStr::width(unchecked))
87 .max(UnicodeWidthStr::width(indeterminate)) as u16,
88 }
89 }
90}
91
92#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
94pub enum CheckboxState {
95 Unchecked,
97 Checked,
99 Indeterminate,
101}
102
103impl CheckboxState {
104 pub fn is_checked(self) -> bool {
106 matches!(self, Self::Checked)
107 }
108
109 pub fn is_indeterminate(self) -> bool {
111 matches!(self, Self::Indeterminate)
112 }
113
114 pub fn toggle(self) -> Self {
116 match self {
117 Self::Checked => Self::Unchecked,
118 Self::Unchecked | Self::Indeterminate => Self::Checked,
119 }
120 }
121}
122
123#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
125pub struct CheckboxEvent {
126 pub state: CheckboxState,
128}
129
130#[derive(Clone)]
132pub struct Checkbox {
133 pub state: CheckboxState,
135 pub label: Option<Arc<str>>,
137 pub variant: CheckboxVariant,
139 pub gap: u16,
141 pub style: Style,
143 pub hover_style: StyleSlot,
145 pub focus_style: StyleSlot,
147 pub checked_style: Style,
149 pub unchecked_style: Style,
151 pub indeterminate_style: Style,
153 pub label_style: Style,
155 pub padding: Padding,
158 pub disabled: bool,
160 pub disabled_style: Style,
162 pub width: Length,
165 pub height: Length,
168 pub on_toggle: Option<Callback<CheckboxEvent>>,
170 pub on_click: Option<Callback<MouseEvent>>,
172 pub on_key: Option<KeyHandler>,
174 pub focusable: bool,
176 pub tab_stop: bool,
178 pub on_focus: Option<Callback<()>>,
180 pub on_blur: Option<Callback<()>>,
182}
183
184impl Checkbox {
185 pub fn new(checked: bool) -> Self {
187 Self {
188 state: if checked {
189 CheckboxState::Checked
190 } else {
191 CheckboxState::Unchecked
192 },
193 label: None,
194 variant: CheckboxVariant::Bracket,
195 gap: 1,
196 style: Style::default(),
197 hover_style: StyleSlot::Inherit,
198 focus_style: StyleSlot::Inherit,
199 checked_style: Style::default(),
200 unchecked_style: Style::default(),
201 indeterminate_style: Style::default(),
202 label_style: Style::default(),
203 padding: Padding::default(),
204 disabled: false,
205 disabled_style: Style::default(),
206 width: Length::Auto,
207 height: Length::Auto,
208 on_toggle: None,
209 on_click: None,
210 on_key: None,
211 focusable: true,
212 tab_stop: true,
213 on_focus: None,
214 on_blur: None,
215 }
216 }
217
218 pub fn state(mut self, state: CheckboxState) -> Self {
220 self.state = state;
221 self
222 }
223
224 pub fn checked(mut self, checked: bool) -> Self {
226 self.state = if checked {
227 CheckboxState::Checked
228 } else {
229 CheckboxState::Unchecked
230 };
231 self
232 }
233
234 pub fn indeterminate(mut self, indeterminate: bool) -> Self {
236 if indeterminate {
237 self.state = CheckboxState::Indeterminate;
238 } else if self.state.is_indeterminate() {
239 self.state = CheckboxState::Unchecked;
240 }
241 self
242 }
243
244 pub fn label(mut self, label: impl Into<Arc<str>>) -> Self {
246 self.label = Some(label.into());
247 self
248 }
249
250 pub fn variant(mut self, variant: CheckboxVariant) -> Self {
252 self.variant = variant;
253 self
254 }
255
256 pub fn gap(mut self, gap: u16) -> Self {
258 self.gap = gap;
259 self
260 }
261
262 pub fn style(mut self, style: Style) -> Self {
264 self.style = style;
265 self
266 }
267
268 pub fn hover_style(mut self, style: Style) -> Self {
270 self.hover_style = StyleSlot::Replace(style);
271 self
272 }
273
274 pub fn extend_hover_style(mut self, style: Style) -> Self {
276 self.hover_style = StyleSlot::Extend(style);
277 self
278 }
279
280 pub fn inherit_hover_style(mut self) -> Self {
282 self.hover_style = StyleSlot::Inherit;
283 self
284 }
285
286 pub fn hover_style_slot(mut self, slot: StyleSlot) -> Self {
288 self.hover_style = slot;
289 self
290 }
291
292 pub fn focus_style(mut self, style: Style) -> Self {
294 self.focus_style = StyleSlot::Replace(style);
295 self
296 }
297
298 pub fn extend_focus_style(mut self, style: Style) -> Self {
300 self.focus_style = StyleSlot::Extend(style);
301 self
302 }
303
304 pub fn inherit_focus_style(mut self) -> Self {
306 self.focus_style = StyleSlot::Inherit;
307 self
308 }
309
310 pub fn focus_style_slot(mut self, slot: StyleSlot) -> Self {
312 self.focus_style = slot;
313 self
314 }
315
316 pub fn checked_style(mut self, style: Style) -> Self {
318 self.checked_style = style;
319 self
320 }
321
322 pub fn unchecked_style(mut self, style: Style) -> Self {
324 self.unchecked_style = style;
325 self
326 }
327
328 pub fn indeterminate_style(mut self, style: Style) -> Self {
330 self.indeterminate_style = style;
331 self
332 }
333
334 pub fn label_style(mut self, style: Style) -> Self {
336 self.label_style = style;
337 self
338 }
339
340 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
342 self.padding = padding.into();
343 self
344 }
345
346 pub fn disabled(mut self, disabled: bool) -> Self {
348 self.disabled = disabled;
349 self
350 }
351
352 pub fn disabled_style(mut self, style: Style) -> Self {
354 self.disabled_style = style;
355 self
356 }
357
358 pub fn width(mut self, width: Length) -> Self {
360 self.width = width;
361 self
362 }
363
364 pub fn height(mut self, height: Length) -> Self {
366 self.height = height;
367 self
368 }
369
370 pub fn on_toggle(mut self, cb: Callback<CheckboxEvent>) -> Self {
372 self.on_toggle = Some(cb);
373 self
374 }
375
376 pub fn on_click(mut self, cb: Callback<MouseEvent>) -> Self {
378 self.on_click = Some(cb);
379 self
380 }
381
382 pub fn on_key(mut self, handler: KeyHandler) -> Self {
384 self.on_key = Some(handler);
385 self
386 }
387
388 pub fn focusable(mut self, focusable: bool) -> Self {
390 self.focusable = focusable;
391 self
392 }
393
394 pub fn tab_stop(mut self, tab_stop: bool) -> Self {
396 self.tab_stop = tab_stop;
397 self
398 }
399
400 pub fn on_focus(mut self, cb: Callback<()>) -> Self {
402 self.on_focus = Some(cb);
403 self
404 }
405
406 pub fn on_blur(mut self, cb: Callback<()>) -> Self {
408 self.on_blur = Some(cb);
409 self
410 }
411}
412
413impl From<Checkbox> for Element {
414 fn from(value: Checkbox) -> Self {
415 Element::new(ElementKind::Checkbox(value))
416 }
417}
418
419impl crate::layout::hash::LayoutHash for Checkbox {
420 fn layout_hash(
421 &self,
422 hasher: &mut impl std::hash::Hasher,
423 _recurse: &dyn Fn(&Element) -> Option<u64>,
424 ) -> Option<()> {
425 use std::hash::Hash;
426 self.width.hash(hasher);
427 self.height.hash(hasher);
428 self.variant.hash(hasher);
429 self.gap.hash(hasher);
430 self.padding.hash(hasher);
431 self.label.hash(hasher);
432 Some(())
433 }
434}