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