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