rat_widget/
radio.rs

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