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