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 select_style: Option<Style>,
88 focus_style: Option<Style>,
89 block: Option<Block<'a>>,
90}
91
92#[derive(Debug, Clone)]
94pub struct RadioStyle {
95 pub layout: Option<RadioLayout>,
97
98 pub style: Style,
100 pub select: Option<Style>,
102 pub focus: Option<Style>,
104 pub block: Option<Block<'static>>,
106
107 pub true_str: Option<Span<'static>>,
109 pub false_str: Option<Span<'static>>,
111 pub continue_str: Option<Span<'static>>,
113
114 pub non_exhaustive: NonExhaustive,
115}
116
117#[derive(Debug)]
119pub struct RadioState<T = usize>
120where
121 T: PartialEq + Clone + Default,
122{
123 pub area: Rect,
126 pub inner: Rect,
129
130 pub marker_area: Rect,
133 pub continue_area: Rect,
136 pub check_areas: Vec<Rect>,
140 pub text_areas: Vec<Rect>,
143
144 pub core: ChoiceCore<T>,
146
147 pub focus: FocusFlag,
150
151 pub mouse: MouseFlags,
154
155 pub non_exhaustive: NonExhaustive,
156}
157
158pub(crate) mod event {
159 use rat_event::{ConsumedEvent, Outcome};
160
161 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
163 pub enum RadioOutcome {
164 Continue,
166 Unchanged,
168 Changed,
170 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 #[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 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 pub fn new() -> Self {
262 Self::default()
263 }
264
265 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 #[inline]
292 pub fn style(mut self, style: impl Into<Style>) -> Self {
293 self.style = style.into();
294 self
295 }
296
297 #[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 #[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 #[inline]
313 pub fn direction(mut self, direction: Direction) -> Self {
314 self.direction = direction;
315 self
316 }
317
318 #[inline]
320 pub fn layout(mut self, layout: RadioLayout) -> Self {
321 self.layout = layout;
322 self
323 }
324
325 #[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 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 pub fn default_value(mut self, default: T) -> Self {
350 self.default_value = Some(default);
351 self
352 }
353
354 #[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 pub fn true_str(mut self, str: Span<'a>) -> Self {
364 self.true_str = str;
365 self
366 }
367
368 pub fn false_str(mut self, str: Span<'a>) -> Self {
370 self.false_str = str;
371 self
372 }
373
374 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 pub fn width(&self) -> u16 {
385 self.size().width
386 }
387
388 pub fn height(&self) -> u16 {
390 self.size().height
391 }
392}
393
394impl<T> Radio<'_, T>
395where
396 T: PartialEq + Clone + Default,
397{
398 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 .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 .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), state.inner.y,
504 continue_len,
505 1,
506 )
507 .intersection(state.inner);
508
509 state.marker_area = Rect::new(
510 state.inner.x, 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), state.inner.y,
566 continue_len,
567 1,
568 )
569 .intersection(state.inner);
570
571 state.marker_area = Rect::new(
572 x, 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, 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, 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, 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, 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, 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 pub fn set_default_value(&mut self, default_value: Option<T>) {
917 self.core.set_default_value(default_value);
918 }
919
920 pub fn default_value(&self) -> &Option<T> {
922 self.core.default_value()
923 }
924
925 pub fn set_value(&mut self, value: T) -> bool {
936 self.core.set_value(value)
937 }
938
939 pub fn value(&self) -> T {
942 self.core.value()
943 }
944
945 pub fn selected(&self) -> Option<usize> {
950 self.core.selected()
951 }
952
953 pub fn select(&mut self, select: usize) -> bool {
965 self.core.set_selected(select)
966 }
967
968 pub fn clear(&mut self) -> bool {
970 self.core.clear()
971 }
972
973 #[allow(clippy::should_implement_trait)]
975 pub fn next(&mut self) -> bool {
976 if self.core.values().is_empty() {
977 false } 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 pub fn prev(&mut self) -> bool {
993 if self.core.values().is_empty() {
994 false } 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
1120pub 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
1132pub 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}