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