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