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