1use crate::_private::NonExhaustive;
24use crate::checkbox::event::CheckOutcome;
25use crate::util::{block_size, revert_style};
26use rat_event::util::MouseFlags;
27use rat_event::{ct_event, HandleEvent, MouseOnly, Regular};
28use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
29use rat_reloc::{relocate_area, RelocatableState};
30use ratatui::buffer::Buffer;
31use ratatui::layout::Rect;
32use ratatui::prelude::BlockExt;
33use ratatui::style::Style;
34use ratatui::text::Span;
35use ratatui::text::Text;
36use ratatui::widgets::Block;
37use ratatui::widgets::{StatefulWidget, Widget};
38use std::cmp::max;
39use unicode_segmentation::UnicodeSegmentation;
40
41#[derive(Debug, Clone)]
43pub struct Checkbox<'a> {
44    text: Text<'a>,
45
46    checked: Option<bool>,
48    default: Option<bool>,
49
50    true_str: Span<'a>,
51    false_str: Span<'a>,
52
53    style: Style,
54    focus_style: Option<Style>,
55    block: Option<Block<'a>>,
56}
57
58#[derive(Debug, Clone)]
60pub struct CheckboxStyle {
61    pub style: Style,
63    pub focus: Option<Style>,
65    pub block: Option<Block<'static>>,
67
68    pub true_str: Option<Span<'static>>,
70    pub false_str: Option<Span<'static>>,
72
73    pub non_exhaustive: NonExhaustive,
74}
75
76#[derive(Debug)]
78pub struct CheckboxState {
79    pub area: Rect,
82    pub inner: Rect,
85    pub check_area: Rect,
88    pub text_area: Rect,
91
92    pub checked: bool,
95
96    pub default: bool,
99
100    pub focus: FocusFlag,
103
104    pub mouse: MouseFlags,
107
108    pub non_exhaustive: NonExhaustive,
109}
110
111pub(crate) mod event {
112    use rat_event::{ConsumedEvent, Outcome};
113
114    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
116    pub enum CheckOutcome {
117        Continue,
119        Unchanged,
121        Changed,
123        Value,
125    }
126
127    impl ConsumedEvent for CheckOutcome {
128        fn is_consumed(&self) -> bool {
129            *self != CheckOutcome::Continue
130        }
131    }
132
133    impl From<CheckOutcome> for Outcome {
134        fn from(value: CheckOutcome) -> Self {
135            match value {
136                CheckOutcome::Continue => Outcome::Continue,
137                CheckOutcome::Unchanged => Outcome::Unchanged,
138                CheckOutcome::Changed => Outcome::Changed,
139                CheckOutcome::Value => Outcome::Changed,
140            }
141        }
142    }
143}
144
145impl Default for CheckboxStyle {
146    fn default() -> Self {
147        Self {
148            style: Default::default(),
149            focus: None,
150            block: Default::default(),
151            true_str: None,
152            false_str: None,
153            non_exhaustive: NonExhaustive,
154        }
155    }
156}
157
158impl Default for Checkbox<'_> {
159    fn default() -> Self {
160        Self {
161            text: Default::default(),
162            checked: None,
163            default: None,
164            true_str: Span::from("[\u{2713}]"),
165            false_str: Span::from("[ ]"),
166            style: Default::default(),
167            focus_style: None,
168            block: None,
169        }
170    }
171}
172
173impl<'a> Checkbox<'a> {
174    pub fn new() -> Self {
176        Self::default()
177    }
178
179    pub fn styles(mut self, styles: CheckboxStyle) -> Self {
181        self.style = styles.style;
182        if styles.focus.is_some() {
183            self.focus_style = styles.focus;
184        }
185        if let Some(block) = styles.block {
186            self.block = Some(block);
187        }
188        if let Some(true_str) = styles.true_str {
189            self.true_str = true_str;
190        }
191        if let Some(false_str) = styles.false_str {
192            self.false_str = false_str;
193        }
194        self.block = self.block.map(|v| v.style(self.style));
195        self
196    }
197
198    #[inline]
200    pub fn style(mut self, style: impl Into<Style>) -> Self {
201        self.style = style.into();
202        self
203    }
204
205    #[inline]
207    pub fn focus_style(mut self, style: impl Into<Style>) -> Self {
208        self.focus_style = Some(style.into());
209        self
210    }
211
212    #[inline]
214    pub fn text(mut self, text: impl Into<Text<'a>>) -> Self {
215        self.text = text.into();
216        self
217    }
218
219    pub fn checked(mut self, checked: bool) -> Self {
221        self.checked = Some(checked);
222        self
223    }
224
225    pub fn default_(mut self, default: bool) -> Self {
227        self.default = Some(default);
228        self
229    }
230
231    #[inline]
233    pub fn block(mut self, block: Block<'a>) -> Self {
234        self.block = Some(block);
235        self.block = self.block.map(|v| v.style(self.style));
236        self
237    }
238
239    pub fn true_str(mut self, str: Span<'a>) -> Self {
241        self.true_str = str;
242        self
243    }
244
245    pub fn false_str(mut self, str: Span<'a>) -> Self {
247        self.false_str = str;
248        self
249    }
250
251    fn check_len(&self) -> u16 {
253        max(
254            self.true_str.content.graphemes(true).count(),
255            self.false_str.content.graphemes(true).count(),
256        ) as u16
257    }
258
259    pub fn width(&self) -> u16 {
261        let chk_len = self.check_len();
262        let txt_len = self.text.width() as u16;
263
264        chk_len + 1 + txt_len + block_size(&self.block).width
265    }
266
267    pub fn height(&self) -> u16 {
269        self.text.height() as u16 + block_size(&self.block).height
270    }
271}
272
273impl<'a> StatefulWidget for &Checkbox<'a> {
274    type State = CheckboxState;
275
276    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
277        render_ref(self, area, buf, state);
278    }
279}
280
281impl StatefulWidget for Checkbox<'_> {
282    type State = CheckboxState;
283
284    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
285        render_ref(&self, area, buf, state);
286    }
287}
288
289fn render_ref(widget: &Checkbox<'_>, area: Rect, buf: &mut Buffer, state: &mut CheckboxState) {
290    state.area = area;
291    state.inner = widget.block.inner_if_some(area);
292
293    let chk_len = widget.check_len();
294    state.check_area = Rect::new(state.inner.x, state.inner.y, chk_len, 1);
295    state.text_area = Rect::new(
296        state.inner.x + chk_len + 1,
297        state.inner.y,
298        state.inner.width.saturating_sub(chk_len + 1),
299        state.inner.height,
300    );
301
302    if let Some(checked) = widget.checked {
303        state.checked = checked;
304    }
305    if let Some(default) = widget.default {
306        state.default = default;
307    }
308
309    let style = widget.style;
310    let focus_style = if let Some(focus_style) = widget.focus_style {
311        style.patch(focus_style)
312    } else {
313        revert_style(style)
314    };
315
316    if let Some(block) = &widget.block {
317        block.render(area, buf);
318        if state.focus.get() {
319            buf.set_style(state.inner, focus_style);
320        }
321    } else {
322        if state.focus.get() {
323            buf.set_style(state.inner, focus_style);
324        } else {
325            buf.set_style(state.inner, widget.style);
326        }
327    }
328
329    let cc = if state.checked {
330        &widget.true_str
331    } else {
332        &widget.false_str
333    };
334    cc.render(state.check_area, buf);
335    (&widget.text).render(state.text_area, buf);
336}
337
338impl Clone for CheckboxState {
339    fn clone(&self) -> Self {
340        Self {
341            area: self.area,
342            inner: self.inner,
343            check_area: self.check_area,
344            text_area: self.text_area,
345            checked: self.checked,
346            default: self.default,
347            focus: FocusFlag::named(self.focus.name()),
348            mouse: Default::default(),
349            non_exhaustive: NonExhaustive,
350        }
351    }
352}
353
354impl Default for CheckboxState {
355    fn default() -> Self {
356        Self {
357            area: Default::default(),
358            inner: Default::default(),
359            check_area: Default::default(),
360            text_area: Default::default(),
361            checked: false,
362            default: false,
363            focus: Default::default(),
364            mouse: Default::default(),
365            non_exhaustive: NonExhaustive,
366        }
367    }
368}
369
370impl HasFocus for CheckboxState {
371    fn build(&self, builder: &mut FocusBuilder) {
372        builder.leaf_widget(self);
373    }
374
375    fn focus(&self) -> FocusFlag {
376        self.focus.clone()
377    }
378
379    fn area(&self) -> Rect {
380        self.area
381    }
382}
383
384impl RelocatableState for CheckboxState {
385    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
386        self.area = relocate_area(self.area, shift, clip);
387        self.inner = relocate_area(self.inner, shift, clip);
388    }
389}
390
391impl CheckboxState {
392    pub fn new() -> Self {
393        Self::default()
394    }
395
396    pub fn named(name: &str) -> Self {
397        Self {
398            focus: FocusFlag::named(name),
399            ..Default::default()
400        }
401    }
402
403    pub fn checked(&self) -> bool {
405        self.checked
406    }
407
408    pub fn set_checked(&mut self, checked: bool) -> bool {
410        let old_value = self.checked;
411        self.checked = checked;
412        old_value != self.checked
413    }
414
415    pub fn default_(&self) -> bool {
417        self.default
418    }
419
420    pub fn set_default(&mut self, default: bool) -> bool {
422        let old_value = self.default;
423        self.default = default;
424        old_value != self.default
425    }
426
427    pub fn value(&self) -> bool {
429        self.checked
430    }
431
432    pub fn set_value(&mut self, checked: bool) -> bool {
434        let old_value = self.checked;
435        self.checked = checked;
436        old_value != self.checked
437    }
438
439    pub fn flip_checked(&mut self) {
443        self.checked = !self.checked;
444    }
445}
446
447impl HandleEvent<crossterm::event::Event, Regular, CheckOutcome> for CheckboxState {
448    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> CheckOutcome {
449        let r = if self.is_focused() {
450            match event {
451                ct_event!(keycode press Enter) | ct_event!(key press ' ') => {
452                    self.flip_checked();
453                    CheckOutcome::Value
454                }
455                ct_event!(keycode press Backspace) | ct_event!(keycode press Delete) => {
456                    self.set_value(self.default);
457                    CheckOutcome::Value
458                }
459                _ => CheckOutcome::Continue,
460            }
461        } else {
462            CheckOutcome::Continue
463        };
464
465        if r == CheckOutcome::Continue {
466            HandleEvent::handle(self, event, MouseOnly)
467        } else {
468            r
469        }
470    }
471}
472
473impl HandleEvent<crossterm::event::Event, MouseOnly, CheckOutcome> for CheckboxState {
474    fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> CheckOutcome {
475        match event {
476            ct_event!(mouse any for m) if self.mouse.doubleclick(self.area, m) => {
477                self.flip_checked();
478                CheckOutcome::Value
479            }
480            _ => CheckOutcome::Continue,
481        }
482    }
483}
484
485pub fn handle_events(
489    state: &mut CheckboxState,
490    focus: bool,
491    event: &crossterm::event::Event,
492) -> CheckOutcome {
493    state.focus.set(focus);
494    HandleEvent::handle(state, event, Regular)
495}
496
497pub fn handle_mouse_events(
499    state: &mut CheckboxState,
500    event: &crossterm::event::Event,
501) -> CheckOutcome {
502    HandleEvent::handle(state, event, MouseOnly)
503}