1use crate::_private::NonExhaustive;
24use crate::checkbox::event::CheckOutcome;
25use crate::util::{block_size, revert_style};
26use rat_event::util::MouseFlags;
27use rat_event::{HandleEvent, MouseOnly, Regular, ct_event};
28use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
29use rat_reloc::{RelocatableState, relocate_area};
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, Default, Clone, Copy, PartialEq, Eq)]
43pub enum CheckboxCheck {
44    SingleClick,
45    #[default]
46    DoubleClick,
47}
48
49#[derive(Debug, Clone)]
51pub struct Checkbox<'a> {
52    text: Text<'a>,
53
54    checked: Option<bool>,
56    default: Option<bool>,
57
58    true_str: Span<'a>,
59    false_str: Span<'a>,
60
61    behave_check: CheckboxCheck,
62
63    style: Style,
64    focus_style: Option<Style>,
65    block: Option<Block<'a>>,
66}
67
68#[derive(Debug, Clone)]
70pub struct CheckboxStyle {
71    pub style: Style,
73    pub focus: Option<Style>,
75    pub block: Option<Block<'static>>,
77
78    pub true_str: Option<Span<'static>>,
80    pub false_str: Option<Span<'static>>,
82
83    pub behave_check: Option<CheckboxCheck>,
84
85    pub non_exhaustive: NonExhaustive,
86}
87
88#[derive(Debug)]
90pub struct CheckboxState {
91    pub area: Rect,
94    pub inner: Rect,
97    pub check_area: Rect,
100    pub text_area: Rect,
103    pub behave_check: CheckboxCheck,
106
107    pub checked: bool,
110
111    pub default: bool,
114
115    pub focus: FocusFlag,
118
119    pub mouse: MouseFlags,
122
123    pub non_exhaustive: NonExhaustive,
124}
125
126pub(crate) mod event {
127    use rat_event::{ConsumedEvent, Outcome};
128
129    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
131    pub enum CheckOutcome {
132        Continue,
134        Unchanged,
136        Changed,
138        Value,
140    }
141
142    impl ConsumedEvent for CheckOutcome {
143        fn is_consumed(&self) -> bool {
144            *self != CheckOutcome::Continue
145        }
146    }
147
148    impl From<CheckOutcome> for Outcome {
149        fn from(value: CheckOutcome) -> Self {
150            match value {
151                CheckOutcome::Continue => Outcome::Continue,
152                CheckOutcome::Unchanged => Outcome::Unchanged,
153                CheckOutcome::Changed => Outcome::Changed,
154                CheckOutcome::Value => Outcome::Changed,
155            }
156        }
157    }
158}
159
160impl Default for CheckboxStyle {
161    fn default() -> Self {
162        Self {
163            style: Default::default(),
164            focus: Default::default(),
165            block: Default::default(),
166            true_str: Default::default(),
167            false_str: Default::default(),
168            behave_check: Default::default(),
169            non_exhaustive: NonExhaustive,
170        }
171    }
172}
173
174impl Default for Checkbox<'_> {
175    fn default() -> Self {
176        Self {
177            text: Default::default(),
178            checked: Default::default(),
179            default: Default::default(),
180            true_str: Span::from("[\u{2713}]"),
181            false_str: Span::from("[ ]"),
182            behave_check: Default::default(),
183            style: Default::default(),
184            focus_style: Default::default(),
185            block: Default::default(),
186        }
187    }
188}
189
190impl<'a> Checkbox<'a> {
191    pub fn new() -> Self {
193        Self::default()
194    }
195
196    pub fn styles(mut self, styles: CheckboxStyle) -> Self {
198        self.style = styles.style;
199        if styles.focus.is_some() {
200            self.focus_style = styles.focus;
201        }
202        if let Some(block) = styles.block {
203            self.block = Some(block);
204        }
205        if let Some(true_str) = styles.true_str {
206            self.true_str = true_str;
207        }
208        if let Some(false_str) = styles.false_str {
209            self.false_str = false_str;
210        }
211        if let Some(check) = styles.behave_check {
212            self.behave_check = check;
213        }
214        self.block = self.block.map(|v| v.style(self.style));
215        self
216    }
217
218    #[inline]
220    pub fn style(mut self, style: impl Into<Style>) -> Self {
221        self.style = style.into();
222        self
223    }
224
225    #[inline]
227    pub fn focus_style(mut self, style: impl Into<Style>) -> Self {
228        self.focus_style = Some(style.into());
229        self
230    }
231
232    #[inline]
234    pub fn text(mut self, text: impl Into<Text<'a>>) -> Self {
235        self.text = text.into();
236        self
237    }
238
239    pub fn checked(mut self, checked: bool) -> Self {
241        self.checked = Some(checked);
242        self
243    }
244
245    pub fn default_(mut self, default: bool) -> Self {
247        self.default = Some(default);
248        self
249    }
250
251    #[inline]
253    pub fn block(mut self, block: Block<'a>) -> Self {
254        self.block = Some(block);
255        self.block = self.block.map(|v| v.style(self.style));
256        self
257    }
258
259    pub fn true_str(mut self, str: Span<'a>) -> Self {
261        self.true_str = str;
262        self
263    }
264
265    pub fn false_str(mut self, str: Span<'a>) -> Self {
267        self.false_str = str;
268        self
269    }
270
271    pub fn behave_check(mut self, check: CheckboxCheck) -> Self {
273        self.behave_check = check;
274        self
275    }
276
277    fn check_len(&self) -> u16 {
279        max(
280            self.true_str.content.graphemes(true).count(),
281            self.false_str.content.graphemes(true).count(),
282        ) as u16
283    }
284
285    pub fn width(&self) -> u16 {
287        let chk_len = self.check_len();
288        let txt_len = self.text.width() as u16;
289
290        chk_len + 1 + txt_len + block_size(&self.block).width
291    }
292
293    pub fn height(&self) -> u16 {
295        self.text.height() as u16 + block_size(&self.block).height
296    }
297}
298
299impl<'a> StatefulWidget for &Checkbox<'a> {
300    type State = CheckboxState;
301
302    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
303        render_ref(self, area, buf, state);
304    }
305}
306
307impl StatefulWidget for Checkbox<'_> {
308    type State = CheckboxState;
309
310    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
311        render_ref(&self, area, buf, state);
312    }
313}
314
315fn render_ref(widget: &Checkbox<'_>, area: Rect, buf: &mut Buffer, state: &mut CheckboxState) {
316    state.area = area;
317    state.inner = widget.block.inner_if_some(area);
318    state.behave_check = widget.behave_check;
319
320    let chk_len = widget.check_len();
321    state.check_area = Rect::new(state.inner.x, state.inner.y, chk_len, 1);
322    state.text_area = Rect::new(
323        state.inner.x + chk_len + 1,
324        state.inner.y,
325        state.inner.width.saturating_sub(chk_len + 1),
326        state.inner.height,
327    );
328
329    if let Some(checked) = widget.checked {
330        state.checked = checked;
331    }
332    if let Some(default) = widget.default {
333        state.default = default;
334    }
335
336    let style = widget.style;
337    let focus_style = if let Some(focus_style) = widget.focus_style {
338        style.patch(focus_style)
339    } else {
340        revert_style(style)
341    };
342
343    if let Some(block) = &widget.block {
344        block.render(area, buf);
345        if state.focus.get() {
346            buf.set_style(state.inner, focus_style);
347        }
348    } else {
349        if state.focus.get() {
350            buf.set_style(state.inner, focus_style);
351        } else {
352            buf.set_style(state.inner, widget.style);
353        }
354    }
355
356    let cc = if state.checked {
357        &widget.true_str
358    } else {
359        &widget.false_str
360    };
361    cc.render(state.check_area, buf);
362    (&widget.text).render(state.text_area, buf);
363}
364
365impl Clone for CheckboxState {
366    fn clone(&self) -> Self {
367        Self {
368            area: self.area,
369            inner: self.inner,
370            check_area: self.check_area,
371            text_area: self.text_area,
372            behave_check: self.behave_check,
373            checked: self.checked,
374            default: self.default,
375            focus: FocusFlag::named(self.focus.name()),
376            mouse: Default::default(),
377            non_exhaustive: NonExhaustive,
378        }
379    }
380}
381
382impl Default for CheckboxState {
383    fn default() -> Self {
384        Self {
385            area: Default::default(),
386            inner: Default::default(),
387            check_area: Default::default(),
388            text_area: Default::default(),
389            behave_check: Default::default(),
390            checked: false,
391            default: false,
392            focus: Default::default(),
393            mouse: Default::default(),
394            non_exhaustive: NonExhaustive,
395        }
396    }
397}
398
399impl HasFocus for CheckboxState {
400    fn build(&self, builder: &mut FocusBuilder) {
401        builder.leaf_widget(self);
402    }
403
404    fn focus(&self) -> FocusFlag {
405        self.focus.clone()
406    }
407
408    fn area(&self) -> Rect {
409        self.area
410    }
411}
412
413impl RelocatableState for CheckboxState {
414    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
415        self.area = relocate_area(self.area, shift, clip);
416        self.inner = relocate_area(self.inner, shift, clip);
417    }
418}
419
420impl CheckboxState {
421    pub fn new() -> Self {
422        Self::default()
423    }
424
425    pub fn named(name: &str) -> Self {
426        Self {
427            focus: FocusFlag::named(name),
428            ..Default::default()
429        }
430    }
431
432    pub fn checked(&self) -> bool {
434        self.checked
435    }
436
437    pub fn set_checked(&mut self, checked: bool) -> bool {
439        let old_value = self.checked;
440        self.checked = checked;
441        old_value != self.checked
442    }
443
444    pub fn default_(&self) -> bool {
446        self.default
447    }
448
449    pub fn set_default(&mut self, default: bool) -> bool {
451        let old_value = self.default;
452        self.default = default;
453        old_value != self.default
454    }
455
456    pub fn value(&self) -> bool {
458        self.checked
459    }
460
461    pub fn set_value(&mut self, checked: bool) -> bool {
463        let old_value = self.checked;
464        self.checked = checked;
465        old_value != self.checked
466    }
467
468    pub fn flip_checked(&mut self) {
472        self.checked = !self.checked;
473    }
474}
475
476impl HandleEvent<crossterm::event::Event, Regular, CheckOutcome> for CheckboxState {
477    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> CheckOutcome {
478        let r = if self.is_focused() {
479            match event {
480                ct_event!(keycode press Enter) | ct_event!(key press ' ') => {
481                    self.flip_checked();
482                    CheckOutcome::Value
483                }
484                ct_event!(keycode press Backspace) | ct_event!(keycode press Delete) => {
485                    self.set_value(self.default);
486                    CheckOutcome::Value
487                }
488                _ => CheckOutcome::Continue,
489            }
490        } else {
491            CheckOutcome::Continue
492        };
493
494        if r == CheckOutcome::Continue {
495            HandleEvent::handle(self, event, MouseOnly)
496        } else {
497            r
498        }
499    }
500}
501
502impl HandleEvent<crossterm::event::Event, MouseOnly, CheckOutcome> for CheckboxState {
503    fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> CheckOutcome {
504        match event {
505            ct_event!(mouse any for m)
506                if self.behave_check == CheckboxCheck::DoubleClick
507                    && self.mouse.doubleclick(self.area, m) =>
508            {
509                self.flip_checked();
510                CheckOutcome::Value
511            }
512            ct_event!(mouse down Left for x,y)
513                if self.behave_check == CheckboxCheck::SingleClick
514                    && self.area.contains((*x, *y).into()) =>
515            {
516                self.flip_checked();
517                CheckOutcome::Value
518            }
519            _ => CheckOutcome::Continue,
520        }
521    }
522}
523
524pub fn handle_events(
528    state: &mut CheckboxState,
529    focus: bool,
530    event: &crossterm::event::Event,
531) -> CheckOutcome {
532    state.focus.set(focus);
533    HandleEvent::handle(state, event, Regular)
534}
535
536pub fn handle_mouse_events(
538    state: &mut CheckboxState,
539    event: &crossterm::event::Event,
540) -> CheckOutcome {
541    HandleEvent::handle(state, event, MouseOnly)
542}