rat_widget/
radio.rs

1//!
2//! Radiobutton widget.
3//!
4//! ```
5//! # use ratatui::buffer::Buffer;
6//! # use ratatui::layout::{Direction, Rect};
7//! # use ratatui::widgets::StatefulWidget;
8//! use rat_event::{HandleEvent, Regular};
9//! use rat_widget::event::RadioOutcome;
10//! use rat_widget::radio::{Radio, RadioLayout, RadioState};
11//! # struct State { radio1: RadioState<&'static str> }
12//! # let mut state = State {radio1: Default::default()};
13//! # let mut buf = Buffer::default();
14//! # let buf = &mut buf;
15//! # let area = Rect::default();
16//!
17//! Radio::new()
18//!     .direction(Direction::Horizontal)
19//!     .layout(RadioLayout::Stacked)
20//!     .item("C", "🥕Carrots")
21//!     .item("P", "🥔Potatoes")
22//!     .item("O", "🧅Onions")
23//!     .default_value("C")
24//!     .render(area, buf, &mut state.radio1);
25//!
26//!  // ...
27//!
28//!  # let event = crossterm::event::Event::FocusGained;
29//!  # let event = &event;
30//!  match state.radio1.handle(event, Regular){
31//!      RadioOutcome::Value => {
32//!          // value changed ..
33//!      }
34//!      _ => {}
35//!  }
36//!
37//! ```
38//!
39use crate::_private::NonExhaustive;
40use crate::choice::core::ChoiceCore;
41use crate::event::RadioOutcome;
42use crate::text::HasScreenCursor;
43use crate::util::{block_size, fill_buf_area, revert_style, union_non_empty};
44use rat_event::util::{MouseFlags, item_at};
45use rat_event::{HandleEvent, MouseOnly, Regular, ct_event};
46use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
47use rat_reloc::{RelocatableState, relocate_areas};
48use ratatui::buffer::Buffer;
49use ratatui::layout::{Direction, Rect, Size};
50use ratatui::prelude::BlockExt;
51use ratatui::style::{Style, Stylize};
52use ratatui::text::{Span, Text};
53use ratatui::widgets::StatefulWidget;
54use ratatui::widgets::{Block, Widget};
55use std::cmp::max;
56use unicode_segmentation::UnicodeSegmentation;
57
58/// How will the radio items fill the given area.
59#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
60pub enum RadioLayout {
61    /// Stacks one item directly next to another
62    /// and leave the remaining space free.
63    #[default]
64    Stacked,
65    /// Spaced fills all the area with the items and
66    /// adds space in between.
67    Spaced,
68}
69
70/// Render a list of radio buttons.
71#[derive(Debug, Clone)]
72pub struct Radio<'a, T = usize>
73where
74    T: PartialEq + Clone + Default,
75{
76    values: Vec<T>,
77    default_value: Option<T>,
78    items: Vec<Text<'a>>,
79    direction: Direction,
80    layout: RadioLayout,
81
82    true_str: Span<'a>,
83    false_str: Span<'a>,
84    continue_str: Span<'a>,
85
86    style: Style,
87    block: Option<Block<'a>>,
88    select_style: Option<Style>,
89    focus_style: Option<Style>,
90}
91
92/// Composite style.
93#[derive(Debug, Clone)]
94pub struct RadioStyle {
95    /// Radio layout
96    pub layout: Option<RadioLayout>,
97
98    /// Base style.
99    pub style: Style,
100    /// Border
101    pub block: Option<Block<'static>>,
102    pub border_style: Option<Style>,
103    pub title_style: Option<Style>,
104
105    /// Selected style.
106    pub select: Option<Style>,
107    /// Focused style
108    pub focus: Option<Style>,
109
110    /// Display text for 'true'
111    pub true_str: Option<Span<'static>>,
112    /// Display text for 'false'
113    pub false_str: Option<Span<'static>>,
114    /// Continue text.
115    pub continue_str: Option<Span<'static>>,
116
117    pub non_exhaustive: NonExhaustive,
118}
119
120/// Widget state.
121#[derive(Debug)]
122pub struct RadioState<T = usize>
123where
124    T: PartialEq + Clone + Default,
125{
126    /// Complete area
127    /// __read only__. renewed for each render.
128    pub area: Rect,
129    /// Area inside the block.
130    /// __read only__. renewed for each render.
131    pub inner: Rect,
132
133    /// Area for the focus marker.
134    /// __read only__. renewed for each render.
135    pub marker_area: Rect,
136    /// Area for a continue marker.
137    /// This is displayed if not all items can be displayed.
138    pub continue_area: Rect,
139    /// __read only__. renewed for each render.
140    /// Area of the check marks.
141    /// __read only__. renewed for each render.
142    pub check_areas: Vec<Rect>,
143    /// Area for the texts.
144    /// __read only__. renewed for each render.
145    pub text_areas: Vec<Rect>,
146
147    /// Core
148    pub core: ChoiceCore<T>,
149
150    /// Current focus state.
151    /// __read+write__
152    pub focus: FocusFlag,
153
154    /// Mouse helper
155    /// __read+write__
156    pub mouse: MouseFlags,
157
158    pub non_exhaustive: NonExhaustive,
159}
160
161pub(crate) mod event {
162    use rat_event::{ConsumedEvent, Outcome};
163
164    /// Result value for event-handling.
165    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
166    pub enum RadioOutcome {
167        /// The given event was not handled at all.
168        Continue,
169        /// The event was handled, no repaint necessary.
170        Unchanged,
171        /// The event was handled, repaint necessary.
172        Changed,
173        /// An item has been selected.
174        Value,
175    }
176
177    impl ConsumedEvent for RadioOutcome {
178        fn is_consumed(&self) -> bool {
179            *self != RadioOutcome::Continue
180        }
181    }
182
183    impl From<RadioOutcome> for Outcome {
184        fn from(value: RadioOutcome) -> Self {
185            match value {
186                RadioOutcome::Continue => Outcome::Continue,
187                RadioOutcome::Unchanged => Outcome::Unchanged,
188                RadioOutcome::Changed => Outcome::Changed,
189                RadioOutcome::Value => Outcome::Changed,
190            }
191        }
192    }
193}
194
195impl Default for RadioStyle {
196    fn default() -> Self {
197        Self {
198            layout: Default::default(),
199            style: Default::default(),
200            block: Default::default(),
201            border_style: Default::default(),
202            title_style: Default::default(),
203            select: Default::default(),
204            focus: Default::default(),
205            true_str: Default::default(),
206            false_str: Default::default(),
207            continue_str: Default::default(),
208            non_exhaustive: NonExhaustive,
209        }
210    }
211}
212
213impl<T> Default for Radio<'_, T>
214where
215    T: PartialEq + Clone + Default,
216{
217    fn default() -> Self {
218        Self {
219            values: Default::default(),
220            items: Default::default(),
221            direction: Default::default(),
222            layout: Default::default(),
223            default_value: Default::default(),
224            true_str: Span::from("\u{2B24}"),
225            false_str: Span::from("\u{25EF}"),
226            continue_str: Span::from("...").on_yellow(),
227            style: Default::default(),
228            select_style: None,
229            focus_style: None,
230            block: None,
231        }
232    }
233}
234
235impl<'a> Radio<'a, usize> {
236    /// Add items with auto-generated values.
237    #[inline]
238    pub fn auto_items<V: Into<Text<'a>>>(mut self, items: impl IntoIterator<Item = V>) -> Self {
239        {
240            self.values.clear();
241            self.items.clear();
242
243            for (k, v) in items.into_iter().enumerate() {
244                self.values.push(k);
245                self.items.push(v.into());
246            }
247        }
248
249        self
250    }
251
252    /// Add an item with an auto generated value.
253    pub fn auto_item(mut self, item: impl Into<Text<'a>>) -> Self {
254        let idx = self.values.len();
255        self.values.push(idx);
256        self.items.push(item.into());
257        self
258    }
259}
260
261impl<'a, T> Radio<'a, T>
262where
263    T: PartialEq + Clone + Default,
264{
265    /// New.
266    pub fn new() -> Self {
267        Self::default()
268    }
269
270    /// Set all styles.
271    pub fn styles(mut self, styles: RadioStyle) -> Self {
272        self.style = styles.style;
273        if styles.block.is_some() {
274            self.block = styles.block;
275        }
276        if let Some(border_style) = styles.border_style {
277            self.block = self.block.map(|v| v.border_style(border_style));
278        }
279        if let Some(title_style) = styles.title_style {
280            self.block = self.block.map(|v| v.title_style(title_style));
281        }
282        self.block = self.block.map(|v| v.style(self.style));
283        if let Some(layout) = styles.layout {
284            self.layout = layout;
285        }
286        if styles.focus.is_some() {
287            self.focus_style = styles.focus;
288        }
289        if styles.select.is_some() {
290            self.select_style = styles.focus;
291        }
292        if let Some(true_str) = styles.true_str {
293            self.true_str = true_str;
294        }
295        if let Some(false_str) = styles.false_str {
296            self.false_str = false_str;
297        }
298        self
299    }
300
301    /// Set the base-style.
302    #[inline]
303    pub fn style(mut self, style: impl Into<Style>) -> Self {
304        let style = style.into();
305        self.style = style.clone();
306        self.block = self.block.map(|v| v.style(style));
307        self
308    }
309
310    /// Style when selected.
311    #[inline]
312    pub fn select_style(mut self, style: impl Into<Style>) -> Self {
313        self.select_style = Some(style.into());
314        self
315    }
316
317    /// Style when focused.
318    #[inline]
319    pub fn focus_style(mut self, style: impl Into<Style>) -> Self {
320        self.focus_style = Some(style.into());
321        self
322    }
323
324    /// Radio direction
325    #[inline]
326    pub fn direction(mut self, direction: Direction) -> Self {
327        self.direction = direction;
328        self
329    }
330
331    /// Layout type, stacked or evenly spaced.
332    #[inline]
333    pub fn layout(mut self, layout: RadioLayout) -> Self {
334        self.layout = layout;
335        self
336    }
337
338    /// Button text.
339    #[inline]
340    pub fn items<V: Into<Text<'a>>>(mut self, items: impl IntoIterator<Item = (T, V)>) -> Self {
341        {
342            self.values.clear();
343            self.items.clear();
344
345            for (k, v) in items.into_iter() {
346                self.values.push(k);
347                self.items.push(v.into());
348            }
349        }
350
351        self
352    }
353
354    /// Add an item.
355    pub fn item(mut self, value: T, item: impl Into<Text<'a>>) -> Self {
356        self.values.push(value);
357        self.items.push(item.into());
358        self
359    }
360
361    /// Can return to default with user interaction.
362    pub fn default_value(mut self, default: T) -> Self {
363        self.default_value = Some(default);
364        self
365    }
366
367    /// Block.
368    #[inline]
369    pub fn block(mut self, block: Block<'a>) -> Self {
370        self.block = Some(block);
371        self.block = self.block.map(|v| v.style(self.style));
372        self
373    }
374
375    /// Text for true
376    pub fn true_str(mut self, str: Span<'a>) -> Self {
377        self.true_str = str;
378        self
379    }
380
381    /// Text for false
382    pub fn false_str(mut self, str: Span<'a>) -> Self {
383        self.false_str = str;
384        self
385    }
386
387    /// Inherent size
388    pub fn size(&self) -> Size {
389        if self.direction == Direction::Horizontal {
390            self.horizontal_size()
391        } else {
392            self.vertical_size()
393        }
394    }
395
396    /// Inherent width.
397    pub fn width(&self) -> u16 {
398        self.size().width
399    }
400
401    /// Inherent height.
402    pub fn height(&self) -> u16 {
403        self.size().height
404    }
405}
406
407impl<T> Radio<'_, T>
408where
409    T: PartialEq + Clone + Default,
410{
411    /// Length of the check
412    fn check_len(&self) -> u16 {
413        max(
414            self.true_str.content.graphemes(true).count(),
415            self.false_str.content.graphemes(true).count(),
416        ) as u16
417    }
418
419    fn horizontal_size(&self) -> Size {
420        let block_size = block_size(&self.block);
421        let check_len = self.check_len();
422        let marker_len = 2;
423
424        if self.layout == RadioLayout::Spaced {
425            let (max_width, max_height) = self
426                .items
427                .iter()
428                .map(|v| (v.width() as u16, v.height() as u16))
429                .max()
430                .unwrap_or_default();
431            let n = self.items.len() as u16;
432            let spacing = n.saturating_sub(1);
433
434            Size::new(
435                marker_len + n * (check_len + 1 + max_width) + spacing + block_size.width,
436                max_height + block_size.height,
437            )
438        } else {
439            let sum_width = self
440                .items //
441                .iter()
442                .map(|v| v.width() as u16)
443                .sum::<u16>();
444            let max_height = self
445                .items
446                .iter()
447                .map(|v| v.height() as u16)
448                .max()
449                .unwrap_or_default();
450
451            let n = self.items.len() as u16;
452            let spacing = n.saturating_sub(1);
453
454            Size::new(
455                marker_len + n * (check_len + 1) + sum_width + spacing + block_size.width,
456                max_height + block_size.height,
457            )
458        }
459    }
460
461    fn vertical_size(&self) -> Size {
462        let block_size = block_size(&self.block);
463        let check_len = self.check_len();
464        let marker_len = 2;
465
466        if self.layout == RadioLayout::Spaced {
467            let (max_width, max_height) = self
468                .items
469                .iter()
470                .map(|v| (v.width() as u16, v.height() as u16))
471                .max()
472                .unwrap_or_default();
473
474            let n = self.items.len() as u16;
475
476            Size::new(
477                marker_len + check_len + 1 + max_width + block_size.width,
478                n * max_height + block_size.width,
479            )
480        } else {
481            let max_width = self
482                .items
483                .iter()
484                .map(|v| v.width() as u16)
485                .max()
486                .unwrap_or_default();
487
488            let sum_height = self
489                .items //
490                .iter()
491                .map(|v| v.height() as u16)
492                .sum::<u16>();
493
494            Size::new(
495                marker_len + check_len + 1 + max_width + block_size.width,
496                sum_height + block_size.height,
497            )
498        }
499    }
500
501    fn horizontal_spaced_layout(&self, area: Rect, state: &mut RadioState<T>) {
502        state.inner = self.block.inner_if_some(area);
503
504        let check_len = self.check_len();
505        let continue_len = self.continue_str.width() as u16;
506        let n = self.items.len() as u16;
507
508        let text_width = max(
509            7,
510            (state.inner.width.saturating_sub(n * check_len) / n).saturating_sub(1),
511        );
512        let item_width = text_width + check_len + 1;
513
514        state.continue_area = Rect::new(
515            state.inner.right().saturating_sub(continue_len), //
516            state.inner.y,
517            continue_len,
518            1,
519        )
520        .intersection(state.inner);
521
522        state.marker_area = Rect::new(
523            state.inner.x, //
524            state.inner.y,
525            1,
526            state.inner.height,
527        )
528        .intersection(state.inner);
529
530        state.check_areas.clear();
531        state.text_areas.clear();
532
533        let mut need_continue = false;
534        for (i, item) in self.items.iter().enumerate() {
535            let i = i as u16;
536
537            state.check_areas.push(
538                Rect::new(
539                    state.inner.x + 2 + (i * item_width),
540                    state.inner.y,
541                    check_len,
542                    item.height() as u16,
543                )
544                .intersection(state.inner),
545            );
546
547            state.text_areas.push(
548                Rect::new(
549                    state.inner.x + 2 + (i * item_width) + check_len + 1,
550                    state.inner.y,
551                    item.width() as u16,
552                    item.height() as u16,
553                )
554                .intersection(state.inner),
555            );
556
557            need_continue = state.text_areas.last().expect("area").is_empty()
558        }
559
560        if !need_continue {
561            state.continue_area = Rect::new(state.inner.x, state.inner.y, 0, 0);
562        }
563    }
564
565    fn horizontal_stack_layout(&self, area: Rect, state: &mut RadioState<T>) {
566        state.inner = self.block.inner_if_some(area);
567
568        let check_len = self.check_len();
569        let continue_len = self.continue_str.width() as u16;
570
571        state.check_areas.clear();
572        state.text_areas.clear();
573
574        let mut x = state.inner.x;
575
576        state.continue_area = Rect::new(
577            state.inner.right().saturating_sub(continue_len), //
578            state.inner.y,
579            continue_len,
580            1,
581        )
582        .intersection(state.inner);
583
584        state.marker_area = Rect::new(
585            x, //
586            state.inner.y,
587            1,
588            state.inner.height,
589        )
590        .intersection(state.inner);
591        x += 2;
592
593        let mut need_continue = false;
594        for item in self.items.iter() {
595            state.check_areas.push(
596                Rect::new(
597                    x, //
598                    state.inner.y,
599                    check_len,
600                    item.height() as u16,
601                )
602                .intersection(state.inner),
603            );
604
605            x += check_len + 1;
606
607            state.text_areas.push(
608                Rect::new(
609                    x, //
610                    state.inner.y,
611                    item.width() as u16,
612                    item.height() as u16,
613                )
614                .intersection(state.inner),
615            );
616
617            x += item.width() as u16 + 1;
618
619            need_continue = state.text_areas.last().expect("area").is_empty()
620        }
621
622        if !need_continue {
623            state.continue_area = Rect::new(state.inner.x, state.inner.y, 0, 0);
624        }
625    }
626
627    fn vertical_spaced_layout(&self, area: Rect, state: &mut RadioState<T>) {
628        state.inner = self.block.inner_if_some(area);
629
630        let check_len = self.check_len();
631        let n = self.items.len() as u16;
632
633        let text_height = max(1, state.inner.height / n);
634
635        state.continue_area = Rect::new(
636            state.inner.x + 2,
637            state.inner.bottom().saturating_sub(1),
638            state.inner.width.saturating_sub(2),
639            1,
640        )
641        .intersection(state.inner);
642
643        state.marker_area = Rect::new(
644            state.inner.x, //
645            state.inner.y,
646            1,
647            state.inner.height,
648        )
649        .intersection(state.inner);
650
651        state.check_areas.clear();
652        state.text_areas.clear();
653
654        let mut need_continue = false;
655        for (i, item) in self.items.iter().enumerate() {
656            let i = i as u16;
657
658            state.check_areas.push(
659                Rect::new(
660                    state.inner.x + 2,
661                    state.inner.y + (i * text_height),
662                    check_len,
663                    item.height() as u16,
664                )
665                .intersection(state.inner),
666            );
667
668            state.text_areas.push(
669                Rect::new(
670                    state.inner.x + 2 + check_len + 1,
671                    state.inner.y + (i * text_height),
672                    item.width() as u16,
673                    item.height() as u16,
674                )
675                .intersection(state.inner),
676            );
677
678            need_continue = state.text_areas.last().expect("area").is_empty()
679        }
680
681        if !need_continue {
682            state.continue_area = Rect::new(state.inner.x, state.inner.y, 0, 0);
683        }
684    }
685
686    fn vertical_stack_layout(&self, area: Rect, state: &mut RadioState<T>) {
687        state.inner = self.block.inner_if_some(area);
688
689        let check_len = self.check_len();
690
691        state.continue_area = Rect::new(
692            state.inner.x + 2,
693            state.inner.bottom().saturating_sub(1),
694            state.inner.width.saturating_sub(2),
695            1,
696        )
697        .intersection(state.inner);
698
699        state.marker_area = Rect::new(
700            state.inner.x, //
701            state.inner.y,
702            1,
703            state.inner.height,
704        )
705        .intersection(state.inner);
706
707        state.check_areas.clear();
708        state.text_areas.clear();
709
710        let mut need_continue = false;
711        let mut y = state.inner.y;
712        for item in self.items.iter() {
713            state.check_areas.push(
714                Rect::new(
715                    state.inner.x + 2, //
716                    y,
717                    check_len,
718                    item.height() as u16,
719                )
720                .intersection(state.inner),
721            );
722
723            state.text_areas.push(
724                Rect::new(
725                    state.inner.x + 2 + check_len + 1,
726                    y,
727                    item.width() as u16,
728                    item.height() as u16,
729                )
730                .intersection(state.inner),
731            );
732
733            y += item.height() as u16;
734
735            need_continue = state.text_areas.last().expect("area").is_empty()
736        }
737
738        if !need_continue {
739            state.continue_area = Rect::new(state.inner.x, state.inner.y, 0, 0);
740        }
741    }
742}
743
744impl<T> StatefulWidget for Radio<'_, T>
745where
746    T: PartialEq + Clone + Default,
747{
748    type State = RadioState<T>;
749
750    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
751        state.area = area;
752
753        match (self.direction, self.layout) {
754            (Direction::Horizontal, RadioLayout::Stacked) => {
755                self.horizontal_stack_layout(area, state);
756            }
757            (Direction::Horizontal, RadioLayout::Spaced) => {
758                self.horizontal_spaced_layout(area, state);
759            }
760            (Direction::Vertical, RadioLayout::Stacked) => {
761                self.vertical_stack_layout(area, state);
762            }
763            (Direction::Vertical, RadioLayout::Spaced) => {
764                self.vertical_spaced_layout(area, state);
765            }
766        }
767
768        state.core.set_values(self.values);
769        if let Some(default_value) = self.default_value {
770            state.core.set_default_value(Some(default_value));
771        }
772
773        let style = self.style;
774        let focus_style = if let Some(focus_style) = self.focus_style {
775            style.patch(focus_style)
776        } else {
777            revert_style(self.style)
778        };
779        let select_style = if let Some(select_style) = self.select_style {
780            style.patch(select_style)
781        } else {
782            style
783        };
784
785        if self.block.is_some() {
786            self.block.render(area, buf);
787        } else {
788            buf.set_style(state.area, style);
789        }
790
791        if state.is_focused() {
792            buf.set_style(state.marker_area, focus_style);
793        }
794
795        for (i, item) in self.items.iter().enumerate() {
796            if Some(i) == state.core.selected() {
797                buf.set_style(
798                    union_non_empty(state.check_areas[i], state.text_areas[i]),
799                    if state.is_focused() {
800                        focus_style
801                    } else {
802                        select_style
803                    },
804                );
805                (&self.true_str).render(state.check_areas[i], buf);
806            } else {
807                (&self.false_str).render(state.check_areas[i], buf);
808            }
809            item.render(state.text_areas[i], buf);
810        }
811
812        if !state.continue_area.is_empty() {
813            fill_buf_area(buf, state.continue_area, " ", self.style);
814            self.continue_str.render(state.continue_area, buf);
815        }
816    }
817}
818
819impl<T> Clone for RadioState<T>
820where
821    T: PartialEq + Clone + Default,
822{
823    fn clone(&self) -> Self {
824        Self {
825            area: self.area,
826            inner: self.inner,
827            marker_area: self.marker_area,
828            continue_area: self.continue_area,
829            check_areas: self.check_areas.clone(),
830            text_areas: self.text_areas.clone(),
831            core: self.core.clone(),
832            focus: self.focus.new_instance(),
833            mouse: Default::default(),
834            non_exhaustive: NonExhaustive,
835        }
836    }
837}
838
839impl<T> Default for RadioState<T>
840where
841    T: PartialEq + Clone + Default,
842{
843    fn default() -> Self {
844        Self {
845            area: Default::default(),
846            inner: Default::default(),
847            marker_area: Default::default(),
848            continue_area: Default::default(),
849            check_areas: Default::default(),
850            text_areas: Default::default(),
851            core: Default::default(),
852            focus: Default::default(),
853            mouse: Default::default(),
854            non_exhaustive: NonExhaustive,
855        }
856    }
857}
858
859impl<T> HasFocus for RadioState<T>
860where
861    T: PartialEq + Clone + Default,
862{
863    fn build(&self, builder: &mut FocusBuilder) {
864        builder.leaf_widget(self);
865    }
866
867    fn focus(&self) -> FocusFlag {
868        self.focus.clone()
869    }
870
871    fn area(&self) -> Rect {
872        self.area
873    }
874}
875
876impl<T> HasScreenCursor for RadioState<T>
877where
878    T: PartialEq + Clone + Default,
879{
880    fn screen_cursor(&self) -> Option<(u16, u16)> {
881        None
882    }
883}
884
885impl<T> RelocatableState for RadioState<T>
886where
887    T: PartialEq + Clone + Default,
888{
889    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
890        self.area.relocate(shift, clip);
891        self.inner.relocate(shift, clip);
892        self.marker_area.relocate(shift, clip);
893        self.continue_area.relocate(shift, clip);
894        relocate_areas(self.check_areas.as_mut_slice(), shift, clip);
895        relocate_areas(self.text_areas.as_mut_slice(), shift, clip);
896    }
897}
898
899impl<T> RadioState<T>
900where
901    T: PartialEq + Clone + Default,
902{
903    pub fn new() -> Self {
904        Self::default()
905    }
906
907    pub fn named(name: &str) -> Self {
908        let mut z = Self::default();
909        z.focus = z.focus.with_name(name);
910        z
911    }
912
913    pub fn is_empty(&self) -> bool {
914        self.text_areas.is_empty()
915    }
916
917    pub fn len(&self) -> usize {
918        self.text_areas.len()
919    }
920
921    /// Set a default-value other than T::default()
922    ///
923    /// The starting value will still be T::default()
924    /// after this. You must call clear() to use this
925    /// default.
926    ///
927    /// This default will be overridden by a default set
928    /// on the widget.
929    pub fn set_default_value(&mut self, default_value: Option<T>) {
930        self.core.set_default_value(default_value);
931    }
932
933    /// A default value.
934    pub fn default_value(&self) -> &Option<T> {
935        self.core.default_value()
936    }
937
938    /// Set the given value.
939    ///
940    /// If the value doesn't exist in the list or the list is
941    /// empty the value will still be set, but selected will be
942    /// None. The list will be empty before the first render, but
943    /// the first thing render will do is set the list of values.
944    /// This will adjust the selected index if possible.
945    /// It's still ok to set a value here that can not be represented.
946    /// As long as there is no user interaction, the same value
947    /// will be returned by value().
948    pub fn set_value(&mut self, value: T) -> bool {
949        self.core.set_value(value)
950    }
951
952    /// Get the selected value or None if no value
953    /// is selected or there are no options.
954    pub fn value(&self) -> T {
955        self.core.value()
956    }
957
958    /// Returns the selected index or None if the
959    /// value is not in the list or the list is empty.
960    ///
961    /// You can still get the value set with set_value() though.
962    pub fn selected(&self) -> Option<usize> {
963        self.core.selected()
964    }
965
966    /// Select the value at index. This will set the value
967    /// to the given index in the value-list. If the index is
968    /// out of bounds or the value-list is empty it will
969    /// set selected to None and leave the value as is.
970    /// The list is empty before the first render so this
971    /// may not work as expected.
972    ///
973    /// The selected index is a best effort artefact, the main
974    /// thing is the value itself.
975    ///
976    /// Use of set_value() is preferred.
977    pub fn select(&mut self, select: usize) -> bool {
978        self.core.set_selected(select)
979    }
980
981    /// Set the default value or T::default()
982    pub fn clear(&mut self) -> bool {
983        self.core.clear()
984    }
985
986    /// Select the next item.
987    #[allow(clippy::should_implement_trait)]
988    pub fn next(&mut self) -> bool {
989        if self.core.values().is_empty() {
990            false // noop
991        } else {
992            if let Some(selected) = self.core.selected() {
993                if selected + 1 >= self.core.values().len() {
994                    self.core.set_selected(0)
995                } else {
996                    self.core.set_selected(selected + 1)
997                }
998            } else {
999                self.core.set_selected(0)
1000            }
1001        }
1002    }
1003
1004    /// Select the previous item.
1005    pub fn prev(&mut self) -> bool {
1006        if self.core.values().is_empty() {
1007            false // noop
1008        } else {
1009            if let Some(selected) = self.core.selected() {
1010                if selected == 0 {
1011                    self.core.set_selected(self.core.values().len() - 1)
1012                } else {
1013                    self.core.set_selected(selected - 1)
1014                }
1015            } else {
1016                self.core.set_selected(self.core.values().len() - 1)
1017            }
1018        }
1019    }
1020}
1021
1022impl<T> HandleEvent<crossterm::event::Event, Regular, RadioOutcome> for RadioState<T>
1023where
1024    T: PartialEq + Clone + Default,
1025{
1026    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> RadioOutcome {
1027        let r = if self.is_focused() {
1028            match event {
1029                ct_event!(keycode press Left) => {
1030                    if self.prev() {
1031                        RadioOutcome::Value
1032                    } else {
1033                        RadioOutcome::Unchanged
1034                    }
1035                }
1036                ct_event!(keycode press Right) => {
1037                    if self.next() {
1038                        RadioOutcome::Value
1039                    } else {
1040                        RadioOutcome::Unchanged
1041                    }
1042                }
1043                ct_event!(keycode press Up) => {
1044                    if self.prev() {
1045                        RadioOutcome::Value
1046                    } else {
1047                        RadioOutcome::Unchanged
1048                    }
1049                }
1050                ct_event!(keycode press Down) => {
1051                    if self.next() {
1052                        RadioOutcome::Value
1053                    } else {
1054                        RadioOutcome::Unchanged
1055                    }
1056                }
1057                ct_event!(keycode press Home) => {
1058                    if self.select(0) {
1059                        RadioOutcome::Value
1060                    } else {
1061                        RadioOutcome::Unchanged
1062                    }
1063                }
1064                ct_event!(keycode press End) => {
1065                    if !self.is_empty() {
1066                        if self.select(self.len() - 1) {
1067                            RadioOutcome::Value
1068                        } else {
1069                            RadioOutcome::Unchanged
1070                        }
1071                    } else {
1072                        RadioOutcome::Unchanged
1073                    }
1074                }
1075                ct_event!(keycode press Delete) | ct_event!(keycode press Backspace) => {
1076                    if self.clear() {
1077                        RadioOutcome::Value
1078                    } else {
1079                        RadioOutcome::Unchanged
1080                    }
1081                }
1082                _ => RadioOutcome::Continue,
1083            }
1084        } else {
1085            RadioOutcome::Continue
1086        };
1087
1088        if r == RadioOutcome::Continue {
1089            HandleEvent::handle(self, event, MouseOnly)
1090        } else {
1091            r
1092        }
1093    }
1094}
1095
1096impl<T> HandleEvent<crossterm::event::Event, MouseOnly, RadioOutcome> for RadioState<T>
1097where
1098    T: PartialEq + Clone + Default,
1099{
1100    fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> RadioOutcome {
1101        match event {
1102            ct_event!(mouse any for m) if self.mouse.drag(self.area, m) => {
1103                if let Some(sel) = item_at(self.text_areas.as_slice(), m.column, m.row)
1104                    .or_else(|| item_at(self.check_areas.as_slice(), m.column, m.row))
1105                {
1106                    if self.select(sel) {
1107                        RadioOutcome::Value
1108                    } else {
1109                        RadioOutcome::Unchanged
1110                    }
1111                } else {
1112                    RadioOutcome::Unchanged
1113                }
1114            }
1115            ct_event!(mouse down Left for x,y) if self.area.contains((*x, *y).into()) => {
1116                if let Some(sel) = item_at(self.text_areas.as_slice(), *x, *y)
1117                    .or_else(|| item_at(self.check_areas.as_slice(), *x, *y))
1118                {
1119                    if self.select(sel) {
1120                        RadioOutcome::Value
1121                    } else {
1122                        RadioOutcome::Unchanged
1123                    }
1124                } else {
1125                    RadioOutcome::Unchanged
1126                }
1127            }
1128            _ => RadioOutcome::Continue,
1129        }
1130    }
1131}
1132
1133/// Handle all events.
1134/// Text events are only processed if focus is true.
1135/// Mouse events are processed if they are in range.
1136pub fn handle_events<T: PartialEq + Clone + Default>(
1137    state: &mut RadioState<T>,
1138    focus: bool,
1139    event: &crossterm::event::Event,
1140) -> RadioOutcome {
1141    state.focus.set(focus);
1142    HandleEvent::handle(state, event, Regular)
1143}
1144
1145/// Handle only mouse-events.
1146pub fn handle_mouse_events<T: PartialEq + Clone + Default>(
1147    state: &mut RadioState<T>,
1148    event: &crossterm::event::Event,
1149) -> RadioOutcome {
1150    HandleEvent::handle(state, event, MouseOnly)
1151}