rat_widget/
radio.rs

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