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