1use crate::_private::NonExhaustive;
38use crate::util::{fill_buf_area, revert_style};
39use rat_event::util::MouseFlagsN;
40use rat_event::{HandleEvent, MouseOnly, Outcome, Regular, ct_event, flow};
41use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
42use rat_reloc::{RelocatableState, relocate_area, relocate_areas, relocate_positions};
43use ratatui::buffer::Buffer;
44use ratatui::layout::{Constraint, Direction, Flex, Layout, Position, Rect};
45use ratatui::prelude::BlockExt;
46use ratatui::style::Style;
47use ratatui::widgets::{Block, BorderType, StatefulWidget, Widget};
48use std::cmp::{max, min};
49use std::mem;
50use unicode_segmentation::UnicodeSegmentation;
51
52#[derive(Debug, Default, Clone)]
53pub struct Split<'a> {
75 direction: Direction,
77 constraints: Vec<Constraint>,
80 resize: SplitResize,
82
83 split_type: SplitType,
85 join_0: Option<BorderType>,
87 join_1: Option<BorderType>,
89 mark_offset: u16,
91 mark_0_char: Option<&'a str>,
93 mark_1_char: Option<&'a str>,
95
96 block: Option<Block<'a>>,
98 style: Style,
99 arrow_style: Option<Style>,
100 drag_style: Option<Style>,
101}
102
103#[derive(Debug, Clone)]
105pub struct SplitWidget<'a> {
106 split: Split<'a>,
107 mode: u8,
111}
112
113#[derive(Debug, Clone)]
115#[deprecated(since = "1.0.2", note = "no longer needed")]
116pub struct SplitOverlay<'a> {
117 split: Option<Split<'a>>,
118}
119
120#[derive(Debug)]
124pub struct SplitStyle {
125 pub style: Style,
127 pub arrow_style: Option<Style>,
129 pub drag_style: Option<Style>,
131
132 pub horizontal_mark: Option<&'static str>,
135 pub vertical_mark: Option<&'static str>,
138
139 pub block: Option<Block<'static>>,
141
142 pub non_exhaustive: NonExhaustive,
143}
144
145#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
147pub enum SplitType {
148 #[default]
151 FullEmpty,
152 FullPlain,
155 FullDouble,
158 FullThick,
161 FullQuadrantInside,
165 FullQuadrantOutside,
169 Scroll,
180 Widget,
187}
188
189#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
191pub enum SplitResize {
192 Neighbours,
195 #[default]
199 Full,
200}
201
202const SPLIT_WIDTH: u16 = 1;
203
204#[derive(Debug)]
206pub struct SplitState {
207 pub area: Rect,
210 pub inner: Rect,
213 pub widget_areas: Vec<Rect>,
217 pub splitline_areas: Vec<Rect>,
221 pub splitline_mark_position: Vec<Position>,
224 pub mark_offset: u16,
227
228 pub direction: Direction,
231 pub split_type: SplitType,
233 pub resize: SplitResize,
235
236 area_length: Vec<u16>,
241 hidden_length: Vec<u16>,
243
244 pub focus: FocusFlag,
247 pub focus_marker: Option<usize>,
251
252 pub mouse: MouseFlagsN,
255
256 pub non_exhaustive: NonExhaustive,
257}
258
259impl SplitType {
260 pub fn is_full(&self) -> bool {
261 use SplitType::*;
262 match self {
263 FullEmpty => true,
264 FullPlain => true,
265 FullDouble => true,
266 FullThick => true,
267 FullQuadrantInside => true,
268 FullQuadrantOutside => true,
269 Scroll => false,
270 Widget => false,
271 }
272 }
273}
274
275impl Default for SplitStyle {
276 fn default() -> Self {
277 Self {
278 style: Default::default(),
279 arrow_style: None,
280 drag_style: None,
281 horizontal_mark: None,
282 vertical_mark: None,
283 block: None,
284 non_exhaustive: NonExhaustive,
285 }
286 }
287}
288
289impl<'a> Split<'a> {
290 pub fn new() -> Self {
291 Self {
292 direction: Direction::Horizontal,
293 ..Default::default()
294 }
295 }
296
297 pub fn horizontal() -> Self {
298 Self {
299 direction: Direction::Horizontal,
300 ..Default::default()
301 }
302 }
303
304 pub fn vertical() -> Self {
305 Self {
306 direction: Direction::Horizontal,
307 ..Default::default()
308 }
309 }
310
311 pub fn constraints(mut self, constraints: impl IntoIterator<Item = Constraint>) -> Self {
317 self.constraints = constraints.into_iter().collect();
318 self
319 }
320
321 pub fn direction(mut self, direction: Direction) -> Self {
325 self.direction = direction;
326 self
327 }
328
329 pub fn split_type(mut self, split_type: SplitType) -> Self {
331 self.split_type = split_type;
332 self
333 }
334
335 pub fn resize(mut self, resize: SplitResize) -> Self {
337 self.resize = resize;
338 self
339 }
340
341 pub fn join(mut self, border: BorderType) -> Self {
345 self.join_0 = Some(border);
346 self.join_1 = Some(border);
347 self
348 }
349
350 pub fn join_0(mut self, border: BorderType) -> Self {
354 self.join_0 = Some(border);
355 self
356 }
357
358 pub fn join_1(mut self, border: BorderType) -> Self {
362 self.join_1 = Some(border);
363 self
364 }
365
366 pub fn block(mut self, block: Block<'a>) -> Self {
368 self.block = Some(block);
369 self
370 }
371
372 pub fn styles(mut self, styles: SplitStyle) -> Self {
374 self.style = styles.style;
375 if styles.drag_style.is_some() {
376 self.drag_style = styles.drag_style;
377 }
378 if styles.arrow_style.is_some() {
379 self.arrow_style = styles.arrow_style;
380 }
381 match self.direction {
382 Direction::Horizontal => {
383 if let Some(mark) = styles.horizontal_mark {
384 let mut g = mark.graphemes(true);
385 if let Some(g0) = g.next() {
386 self.mark_0_char = Some(g0);
387 }
388 if let Some(g1) = g.next() {
389 self.mark_1_char = Some(g1);
390 }
391 }
392 }
393 Direction::Vertical => {
394 if let Some(mark) = styles.vertical_mark {
395 let mut g = mark.graphemes(true);
396 if let Some(g0) = g.next() {
397 self.mark_0_char = Some(g0);
398 }
399 if let Some(g1) = g.next() {
400 self.mark_1_char = Some(g1);
401 }
402 }
403 }
404 }
405 if styles.block.is_some() {
406 self.block = styles.block;
407 }
408 self
409 }
410
411 pub fn style(mut self, style: Style) -> Self {
413 self.style = style;
414 self
415 }
416
417 pub fn arrow_style(mut self, style: Style) -> Self {
419 self.arrow_style = Some(style);
420 self
421 }
422
423 pub fn drag_style(mut self, style: Style) -> Self {
425 self.drag_style = Some(style);
426 self
427 }
428
429 pub fn mark_offset(mut self, offset: u16) -> Self {
431 self.mark_offset = offset;
432 self
433 }
434
435 pub fn mark_0(mut self, mark: &'a str) -> Self {
437 self.mark_0_char = Some(mark);
438 self
439 }
440
441 pub fn mark_1(mut self, mark: &'a str) -> Self {
443 self.mark_1_char = Some(mark);
444 self
445 }
446
447 pub fn into_widget(self, area: Rect, state: &mut SplitState) -> SplitWidget<'a> {
455 self.layout_split(area, state);
456
457 SplitWidget {
458 split: self,
459 mode: 1,
460 }
461 }
462
463 pub fn into_widget_layout(
472 self,
473 area: Rect,
474 state: &mut SplitState,
475 ) -> (SplitWidget<'a>, Vec<Rect>) {
476 self.layout_split(area, state);
477
478 (
479 SplitWidget {
480 split: self,
481 mode: 1,
482 },
483 state.widget_areas.clone(),
484 )
485 }
486
487 #[deprecated(since = "1.0.3", note = "use into_widget_layout()")]
489 #[allow(deprecated)]
490 pub fn into_widgets(self) -> (SplitWidget<'a>, SplitOverlay<'a>) {
491 if self.split_type == SplitType::Scroll {
492 (
493 SplitWidget {
494 split: self.clone(),
495 mode: 0,
496 },
497 SplitOverlay { split: Some(self) },
498 )
499 } else {
500 (
501 SplitWidget {
502 split: self,
503 mode: 0,
504 },
505 SplitOverlay { split: None },
506 )
507 }
508 }
509}
510
511impl Split<'_> {
512 fn layout_split(&self, area: Rect, state: &mut SplitState) {
515 state.area = area;
516 state.inner = self.block.inner_if_some(area);
517
518 let inner = state.inner;
520
521 let layout_change = state.area_length.len() != self.constraints.len();
522 let meta_change = state.direction != self.direction
523 || state.split_type != self.split_type
524 || state.mark_offset != self.mark_offset;
525
526 let old_len = |v: &Rect| {
527 if state.direction == Direction::Horizontal {
529 v.width
530 } else {
531 v.height
532 }
533 };
534 let new_len = |v: &Rect| {
535 if self.direction == Direction::Horizontal {
537 v.width
538 } else {
539 v.height
540 }
541 };
542
543 let new_split_areas = if layout_change {
544 let new_areas = Layout::new(self.direction, self.constraints.clone())
546 .flex(Flex::Legacy)
547 .split(inner);
548 Some(new_areas)
549 } else {
550 let old_length: u16 = state.area_length.iter().sum();
551 if meta_change || old_len(&inner) != old_length {
552 let mut constraints = Vec::new();
553 for i in 0..state.area_length.len() {
554 constraints.push(Constraint::Fill(state.area_length[i]));
555 }
556 let new_areas = Layout::new(self.direction, constraints).split(inner);
557 Some(new_areas)
558 } else {
559 None
560 }
561 };
562
563 if let Some(new_split_areas) = new_split_areas {
564 state.area_length.clear();
565 for v in new_split_areas.iter() {
566 state.area_length.push(new_len(v));
567 }
568 while state.hidden_length.len() < state.area_length.len() {
569 state.hidden_length.push(0);
570 }
571 while state.hidden_length.len() > state.area_length.len() {
572 state.hidden_length.pop();
573 }
574 }
575
576 state.direction = self.direction;
577 state.split_type = self.split_type;
578 state.resize = self.resize;
579 state.mark_offset = self.mark_offset;
580
581 self.layout_from_widths(state);
582 }
583
584 fn layout_from_widths(&self, state: &mut SplitState) {
585 state.widget_areas.clear();
587 state.splitline_areas.clear();
588 state.splitline_mark_position.clear();
589
590 let inner = state.inner;
591
592 let mut total = 0;
593 for length in state
594 .area_length
595 .iter()
596 .take(state.area_length.len().saturating_sub(1))
597 .copied()
598 {
599 let mut area = if self.direction == Direction::Horizontal {
600 Rect::new(inner.x + total, inner.y, length, inner.height)
601 } else {
602 Rect::new(inner.x, inner.y + total, inner.width, length)
603 };
604 let mut split = if self.direction == Direction::Horizontal {
605 Rect::new(
606 inner.x + total + length.saturating_sub(SPLIT_WIDTH),
607 inner.y,
608 min(1, length),
609 inner.height,
610 )
611 } else {
612 Rect::new(
613 inner.x,
614 inner.y + total + length.saturating_sub(SPLIT_WIDTH),
615 inner.width,
616 min(1, length),
617 )
618 };
619 let mut mark = if self.direction == Direction::Horizontal {
620 Position::new(
621 inner.x + total + length.saturating_sub(SPLIT_WIDTH),
622 inner.y + self.mark_offset,
623 )
624 } else {
625 Position::new(
626 inner.x + self.mark_offset,
627 inner.y + total + length.saturating_sub(SPLIT_WIDTH),
628 )
629 };
630
631 adjust_for_split_type(
632 self.direction,
633 self.split_type,
634 &mut area,
635 &mut split,
636 &mut mark,
637 );
638
639 state.widget_areas.push(area);
640 state.splitline_areas.push(split);
641 state.splitline_mark_position.push(mark);
642
643 total += length;
644 }
645 if let Some(length) = state.area_length.last().copied() {
646 let area = if self.direction == Direction::Horizontal {
647 Rect::new(inner.x + total, inner.y, length, inner.height)
648 } else {
649 Rect::new(inner.x, inner.y + total, inner.width, length)
650 };
651
652 state.widget_areas.push(area);
653 }
654
655 if let Some(test) = state.widget_areas.first() {
657 if self.direction == Direction::Horizontal {
658 if test.height != state.inner.height {
659 for r in &mut state.widget_areas {
660 r.height = state.inner.height;
661 }
662 for r in &mut state.splitline_areas {
663 r.height = state.inner.height;
664 }
665 }
666 } else {
667 if test.width != state.inner.width {
668 for r in &mut state.widget_areas {
669 r.width = state.inner.width;
670 }
671 for r in &mut state.splitline_areas {
672 r.width = state.inner.width;
673 }
674 }
675 }
676 }
677 }
678}
679
680fn adjust_for_split_type(
682 direction: Direction,
683 split_type: SplitType,
684 area: &mut Rect,
685 split: &mut Rect,
686 mark: &mut Position,
687) {
688 use Direction::*;
689 use SplitType::*;
690
691 match (direction, split_type) {
692 (
693 Horizontal,
694 FullEmpty | FullPlain | FullDouble | FullThick | FullQuadrantInside
695 | FullQuadrantOutside,
696 ) => {
697 area.width = area.width.saturating_sub(SPLIT_WIDTH);
698 }
699 (
700 Vertical,
701 FullEmpty | FullPlain | FullDouble | FullThick | FullQuadrantInside
702 | FullQuadrantOutside,
703 ) => {
704 area.height = area.height.saturating_sub(SPLIT_WIDTH);
705 }
706
707 (Horizontal, Scroll) => {
708 split.y = mark.y;
709 split.height = 2;
710 }
711 (Vertical, Scroll) => {
712 split.x = mark.x;
713 split.width = 2;
714 }
715
716 (Horizontal, Widget) => {}
717 (Vertical, Widget) => {}
718 }
719}
720
721impl Split<'_> {
722 fn get_mark_0(&self) -> &str {
723 if let Some(mark) = self.mark_0_char {
724 mark
725 } else if self.direction == Direction::Horizontal {
726 "<"
727 } else {
728 "^"
729 }
730 }
731
732 fn get_mark_1(&self) -> &str {
733 if let Some(mark) = self.mark_1_char {
734 mark
735 } else if self.direction == Direction::Horizontal {
736 ">"
737 } else {
738 "v"
739 }
740 }
741
742 fn get_fill_char(&self) -> Option<&str> {
743 use Direction::*;
744 use SplitType::*;
745
746 match (self.direction, self.split_type) {
747 (Horizontal, FullEmpty) => Some(" "),
748 (Vertical, FullEmpty) => Some(" "),
749 (Horizontal, FullPlain) => Some("\u{2502}"),
750 (Vertical, FullPlain) => Some("\u{2500}"),
751 (Horizontal, FullDouble) => Some("\u{2551}"),
752 (Vertical, FullDouble) => Some("\u{2550}"),
753 (Horizontal, FullThick) => Some("\u{2503}"),
754 (Vertical, FullThick) => Some("\u{2501}"),
755 (Horizontal, FullQuadrantInside) => Some("\u{258C}"),
756 (Vertical, FullQuadrantInside) => Some("\u{2580}"),
757 (Horizontal, FullQuadrantOutside) => Some("\u{2590}"),
758 (Vertical, FullQuadrantOutside) => Some("\u{2584}"),
759 (_, Scroll) => None,
760 (_, Widget) => None,
761 }
762 }
763
764 #[allow(unreachable_patterns)]
765 fn get_join_0(&self, split_area: Rect, state: &SplitState) -> Option<(Position, &str)> {
766 use Direction::*;
767 use SplitType::*;
768
769 let s: Option<&str> = if let Some(join_0) = self.join_0 {
770 match (self.direction, join_0, self.split_type) {
771 (
772 Horizontal,
773 BorderType::Plain | BorderType::Rounded,
774 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
775 ) => Some("\u{252C}"),
776 (
777 Vertical,
778 BorderType::Plain | BorderType::Rounded,
779 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
780 ) => Some("\u{251C}"),
781 (
782 Horizontal,
783 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
784 FullDouble,
785 ) => Some("\u{2565}"),
786 (
787 Vertical,
788 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
789 FullDouble,
790 ) => Some("\u{255E}"),
791 (Horizontal, BorderType::Plain | BorderType::Rounded, FullThick) => {
792 Some("\u{2530}")
793 }
794 (Vertical, BorderType::Plain | BorderType::Rounded, FullThick) => Some("\u{251D}"),
795
796 (
797 Horizontal,
798 BorderType::Double,
799 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
800 | Scroll,
801 ) => Some("\u{2564}"),
802 (
803 Vertical,
804 BorderType::Double,
805 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
806 | Scroll,
807 ) => Some("\u{255F}"),
808 (Horizontal, BorderType::Double, FullDouble) => Some("\u{2566}"),
809 (Vertical, BorderType::Double, FullDouble) => Some("\u{2560}"),
810
811 (
812 Horizontal,
813 BorderType::Thick,
814 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
815 ) => Some("\u{252F}"),
816 (
817 Vertical,
818 BorderType::Thick,
819 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
820 ) => Some("\u{2520}"),
821 (Horizontal, BorderType::Thick, FullThick) => Some("\u{2533}"),
822 (Vertical, BorderType::Thick, FullThick) => Some("\u{2523}"),
823
824 (Horizontal, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
825 (Vertical, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
826
827 (_, BorderType::QuadrantInside, _) => None,
828 (_, BorderType::QuadrantOutside, _) => None,
829
830 (_, _, Widget) => None,
831 (_, _, _) => None,
832 }
833 } else {
834 None
835 };
836
837 s.map(|s| {
838 (
839 match self.direction {
840 Horizontal => Position::new(split_area.x, state.area.y),
841 Vertical => Position::new(state.area.x, split_area.y),
842 },
843 s,
844 )
845 })
846 }
847
848 #[allow(unreachable_patterns)]
849 fn get_join_1(&self, split_area: Rect, state: &SplitState) -> Option<(Position, &str)> {
850 use Direction::*;
851 use SplitType::*;
852
853 let s: Option<&str> = if let Some(join_1) = self.join_1 {
854 match (self.direction, join_1, self.split_type) {
855 (
856 Horizontal,
857 BorderType::Plain | BorderType::Rounded,
858 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
859 ) => Some("\u{2534}"),
860 (
861 Vertical,
862 BorderType::Plain | BorderType::Rounded,
863 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
864 ) => Some("\u{2524}"),
865 (
866 Horizontal,
867 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
868 FullDouble,
869 ) => Some("\u{2568}"),
870 (
871 Vertical,
872 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
873 FullDouble,
874 ) => Some("\u{2561}"),
875 (Horizontal, BorderType::Plain | BorderType::Rounded, FullThick) => {
876 Some("\u{2538}")
877 }
878 (Vertical, BorderType::Plain | BorderType::Rounded, FullThick) => Some("\u{2525}"),
879
880 (
881 Horizontal,
882 BorderType::Double,
883 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
884 | Scroll,
885 ) => Some("\u{2567}"),
886 (
887 Vertical,
888 BorderType::Double,
889 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
890 | Scroll,
891 ) => Some("\u{2562}"),
892 (Horizontal, BorderType::Double, FullDouble) => Some("\u{2569}"),
893 (Vertical, BorderType::Double, FullDouble) => Some("\u{2563}"),
894
895 (
896 Horizontal,
897 BorderType::Thick,
898 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
899 ) => Some("\u{2537}"),
900 (
901 Vertical,
902 BorderType::Thick,
903 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
904 ) => Some("\u{2528}"),
905 (Horizontal, BorderType::Thick, FullThick) => Some("\u{253B}"),
906 (Vertical, BorderType::Thick, FullThick) => Some("\u{252B}"),
907
908 (Horizontal, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
909 (Vertical, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
910
911 (_, BorderType::QuadrantInside, _) => None,
912 (_, BorderType::QuadrantOutside, _) => None,
913
914 (_, _, Widget) => None,
915 (_, _, _) => None,
916 }
917 } else {
918 None
919 };
920
921 s.map(|s| {
922 (
923 match self.direction {
924 Horizontal => Position::new(split_area.x, state.area.y + state.area.height - 1),
925 Vertical => Position::new(state.area.x + state.area.width - 1, split_area.y),
926 },
927 s,
928 )
929 })
930 }
931}
932
933impl<'a> StatefulWidget for &SplitWidget<'a> {
934 type State = SplitState;
935
936 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
937 if self.mode == 0 {
938 self.split.layout_split(area, state);
939 } else if self.mode == 1 {
940 } else {
942 unreachable!()
943 }
944
945 if state.is_focused() {
946 if state.focus_marker.is_none() {
947 state.focus_marker = Some(0);
948 }
949 } else {
950 state.focus_marker = None;
951 }
952
953 if self.mode == 0 {
954 if let Some(block) = &self.split.block {
955 block.render(area, buf);
956 } else {
957 buf.set_style(area, self.split.style);
958 }
959 } else if self.mode == 1 {
960 if let Some(mut block) = self.split.block.clone() {
961 block = block.style(Style::default());
962 block.render(area, buf);
963 }
964 } else {
965 unreachable!()
966 }
967
968 if self.mode == 0 {
969 if !matches!(self.split.split_type, SplitType::Widget | SplitType::Scroll) {
970 render_split(&self.split, buf, state);
971 }
972 } else if self.mode == 1 {
973 render_split(&self.split, buf, state);
974 } else {
975 unreachable!()
976 }
977 }
978}
979
980impl StatefulWidget for SplitWidget<'_> {
981 type State = SplitState;
982
983 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
984 if self.mode == 0 {
985 self.split.layout_split(area, state);
986 } else if self.mode == 1 {
987 } else {
989 unreachable!()
990 }
991
992 if state.is_focused() {
993 if state.focus_marker.is_none() {
994 state.focus_marker = Some(0);
995 }
996 } else {
997 state.focus_marker = None;
998 }
999
1000 if self.mode == 0 {
1001 if let Some(block) = &self.split.block {
1002 block.render(area, buf);
1003 } else {
1004 buf.set_style(area, self.split.style);
1005 }
1006 } else if self.mode == 1 {
1007 if let Some(mut block) = mem::take(&mut self.split.block) {
1008 block = block.style(Style::default());
1010 block.render(area, buf);
1011 }
1012 } else {
1013 unreachable!()
1014 }
1015
1016 if self.mode == 0 {
1017 if !matches!(self.split.split_type, SplitType::Widget | SplitType::Scroll) {
1018 render_split(&self.split, buf, state);
1019 }
1020 } else if self.mode == 1 {
1021 render_split(&self.split, buf, state);
1022 } else {
1023 unreachable!()
1024 }
1025 }
1026}
1027
1028#[allow(deprecated)]
1029impl<'a> StatefulWidget for &SplitOverlay<'a> {
1030 type State = SplitState;
1031
1032 fn render(self, _area: Rect, buf: &mut Buffer, state: &mut Self::State) {
1033 if let Some(split) = &self.split {
1035 if matches!(split.split_type, SplitType::Scroll) {
1036 render_split(split, buf, state);
1037 }
1038 }
1039 }
1040}
1041
1042#[allow(deprecated)]
1043impl StatefulWidget for SplitOverlay<'_> {
1044 type State = SplitState;
1045
1046 fn render(self, _area: Rect, buf: &mut Buffer, state: &mut Self::State) {
1047 if let Some(split) = &self.split {
1049 if matches!(split.split_type, SplitType::Scroll) {
1050 render_split(split, buf, state);
1051 }
1052 }
1053 }
1054}
1055
1056fn render_split(split: &Split<'_>, buf: &mut Buffer, state: &mut SplitState) {
1057 for (n, split_area) in state.splitline_areas.iter().enumerate() {
1058 if split.direction == Direction::Horizontal {
1060 if split_area.width == 0 {
1061 continue;
1062 }
1063 } else {
1064 if split_area.height == 0 {
1065 continue;
1066 }
1067 }
1068
1069 let (style, arrow_style) = if Some(n) == state.mouse.drag.get()
1070 || Some(n) == state.focus_marker
1071 || Some(n) == state.mouse.hover.get()
1072 {
1073 if let Some(drag) = split.drag_style {
1074 (drag, drag)
1075 } else {
1076 (revert_style(split.style), revert_style(split.style))
1077 }
1078 } else {
1079 if let Some(arrow) = split.arrow_style {
1080 (split.style, arrow)
1081 } else {
1082 (split.style, split.style)
1083 }
1084 };
1085
1086 if let Some(fill) = split.get_fill_char() {
1087 fill_buf_area(buf, *split_area, fill, style);
1088 }
1089
1090 let mark = state.splitline_mark_position[n];
1091 if split.direction == Direction::Horizontal {
1092 if buf.area.contains((mark.x, mark.y).into()) {
1093 if let Some(cell) = buf.cell_mut((mark.x, mark.y)) {
1094 cell.set_style(arrow_style);
1095 cell.set_symbol(split.get_mark_0());
1096 }
1097 }
1098 if buf.area.contains((mark.x, mark.y + 1).into()) {
1099 if let Some(cell) = buf.cell_mut((mark.x, mark.y + 1)) {
1100 cell.set_style(arrow_style);
1101 cell.set_symbol(split.get_mark_1());
1102 }
1103 }
1104 } else {
1105 if let Some(cell) = buf.cell_mut((mark.x, mark.y)) {
1106 cell.set_style(arrow_style);
1107 cell.set_symbol(split.get_mark_0());
1108 }
1109 if let Some(cell) = buf.cell_mut((mark.x + 1, mark.y)) {
1110 cell.set_style(arrow_style);
1111 cell.set_symbol(split.get_mark_1());
1112 }
1113 }
1114
1115 if let Some((pos_0, c_0)) = split.get_join_0(*split_area, state) {
1116 if let Some(cell) = buf.cell_mut((pos_0.x, pos_0.y)) {
1117 cell.set_symbol(c_0);
1118 }
1119 }
1120 if let Some((pos_1, c_1)) = split.get_join_1(*split_area, state) {
1121 if let Some(cell) = buf.cell_mut((pos_1.x, pos_1.y)) {
1122 cell.set_symbol(c_1);
1123 }
1124 }
1125 }
1126}
1127
1128impl Default for SplitState {
1129 fn default() -> Self {
1130 Self {
1131 area: Default::default(),
1132 inner: Default::default(),
1133 widget_areas: Default::default(),
1134 splitline_areas: Default::default(),
1135 splitline_mark_position: Default::default(),
1136 mark_offset: Default::default(),
1137 direction: Default::default(),
1138 split_type: Default::default(),
1139 resize: Default::default(),
1140 area_length: Default::default(),
1141 hidden_length: Default::default(),
1142 focus: Default::default(),
1143 focus_marker: Default::default(),
1144 mouse: Default::default(),
1145 non_exhaustive: NonExhaustive,
1146 }
1147 }
1148}
1149
1150impl Clone for SplitState {
1151 fn clone(&self) -> Self {
1152 Self {
1153 area: self.area,
1154 inner: self.inner,
1155 widget_areas: self.widget_areas.clone(),
1156 splitline_areas: self.splitline_areas.clone(),
1157 splitline_mark_position: self.splitline_mark_position.clone(),
1158 mark_offset: self.mark_offset,
1159 direction: self.direction,
1160 split_type: self.split_type,
1161 resize: self.resize,
1162 area_length: self.area_length.clone(),
1163 hidden_length: self.hidden_length.clone(),
1164 focus: FocusFlag::named(self.focus.name()),
1165 focus_marker: self.focus_marker,
1166 mouse: Default::default(),
1167 non_exhaustive: NonExhaustive,
1168 }
1169 }
1170}
1171
1172impl HasFocus for SplitState {
1173 fn build(&self, builder: &mut FocusBuilder) {
1174 builder.leaf_widget(self);
1175 }
1176
1177 fn focus(&self) -> FocusFlag {
1178 self.focus.clone()
1179 }
1180
1181 fn area(&self) -> Rect {
1182 Rect::default()
1184 }
1185
1186 fn navigable(&self) -> Navigation {
1187 Navigation::Leave
1188 }
1189}
1190
1191impl RelocatableState for SplitState {
1192 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1193 self.area = relocate_area(self.area, shift, clip);
1194 self.inner = relocate_area(self.inner, shift, clip);
1195 relocate_areas(self.widget_areas.as_mut_slice(), shift, clip);
1196 relocate_areas(self.splitline_areas.as_mut_slice(), shift, clip);
1197 relocate_positions(self.splitline_mark_position.as_mut_slice(), shift, clip);
1198 }
1199}
1200
1201#[allow(clippy::len_without_is_empty)]
1202impl SplitState {
1203 pub fn new() -> Self {
1205 Self::default()
1206 }
1207
1208 pub fn named(name: &str) -> Self {
1210 Self {
1211 focus: FocusFlag::named(name),
1212 ..Self::default()
1213 }
1214 }
1215
1216 pub fn set_screen_split_pos(&mut self, n: usize, pos: (u16, u16)) -> bool {
1223 if self.is_hidden(n) {
1224 return false;
1225 }
1226 if self.direction == Direction::Horizontal {
1227 let pos = if pos.0 < self.inner.left() {
1228 0
1229 } else if pos.0 < self.inner.right() {
1230 pos.0 - self.inner.x
1231 } else {
1232 self.inner.width
1233 };
1234
1235 let split_pos = self.split_pos(n);
1236 self.set_split_pos(n, pos);
1237
1238 split_pos != self.split_pos(n)
1239 } else {
1240 let pos = if pos.1 < self.inner.top() {
1241 0
1242 } else if pos.1 < self.inner.bottom() {
1243 pos.1 - self.inner.y
1244 } else {
1245 self.inner.height
1246 };
1247
1248 let split_pos = self.split_pos(n);
1249 self.set_split_pos(n, pos);
1250
1251 split_pos != self.split_pos(n)
1252 }
1253 }
1254
1255 pub fn move_split_left(&mut self, n: usize, delta: u16) -> bool {
1259 let split_pos = self.split_pos(n);
1260 self.set_split_pos(n, split_pos - delta);
1261
1262 split_pos != self.split_pos(n)
1263 }
1264
1265 pub fn move_split_right(&mut self, n: usize, delta: u16) -> bool {
1268 let split_pos = self.split_pos(n);
1269 self.set_split_pos(n, split_pos + delta);
1270
1271 split_pos != self.split_pos(n)
1272 }
1273
1274 pub fn move_split_up(&mut self, n: usize, delta: u16) -> bool {
1277 self.move_split_left(n, delta)
1278 }
1279
1280 pub fn move_split_down(&mut self, n: usize, delta: u16) -> bool {
1283 self.move_split_right(n, delta)
1284 }
1285
1286 pub fn select_next_split(&mut self) -> bool {
1288 if self.is_focused() {
1289 let n = self.focus_marker.unwrap_or_default();
1290 if n + 1 >= self.area_length.len().saturating_sub(1) {
1291 self.focus_marker = Some(0);
1292 } else {
1293 self.focus_marker = Some(n + 1);
1294 }
1295 true
1296 } else {
1297 false
1298 }
1299 }
1300
1301 pub fn select_prev_split(&mut self) -> bool {
1303 if self.is_focused() {
1304 let n = self.focus_marker.unwrap_or_default();
1305 if n == 0 {
1306 self.focus_marker =
1307 Some(self.area_length.len().saturating_sub(1).saturating_sub(1));
1308 } else {
1309 self.focus_marker = Some(n - 1);
1310 }
1311 true
1312 } else {
1313 false
1314 }
1315 }
1316
1317 pub fn len(&self) -> usize {
1319 self.area_length.len()
1320 }
1321
1322 pub fn area_lengths(&self) -> &[u16] {
1324 &self.area_length
1325 }
1326
1327 pub fn set_area_lengths(&mut self, lengths: Vec<u16>) {
1340 self.area_length = lengths;
1341 while self.hidden_length.len() < self.area_length.len() {
1342 self.hidden_length.push(0);
1343 }
1344 while self.hidden_length.len() > self.area_length.len() {
1345 self.hidden_length.pop();
1346 }
1347 }
1348
1349 pub fn hidden_lengths(&self) -> &[u16] {
1351 &self.hidden_length
1352 }
1353
1354 pub fn set_hidden_lengths(&mut self, hidden: Vec<u16>) {
1359 for i in 0..self.hidden_length.len() {
1360 if let Some(v) = hidden.get(i) {
1361 self.hidden_length[i] = *v;
1362 } else {
1363 self.hidden_length[i] = 0;
1364 }
1365 }
1366 }
1367
1368 pub fn area_len(&self, n: usize) -> u16 {
1378 if n >= self.area_length.len() {
1379 return 0;
1380 }
1381 self.area_length[n]
1382 }
1383
1384 pub fn total_area_len(&self) -> u16 {
1386 self.area_length.iter().sum()
1387 }
1388
1389 pub fn set_area_len(&mut self, n: usize, len: u16) {
1422 if n >= self.area_length.len() {
1423 return;
1424 }
1425 self.area_length[n] = len;
1426 self.hidden_length[n] = 0;
1427 }
1428
1429 pub fn split_pos(&self, n: usize) -> u16 {
1441 if n + 1 >= self.area_length.len() {
1442 return self.area_length.iter().sum();
1443 }
1444 self.area_length[..n + 1].iter().sum()
1445 }
1446
1447 pub fn set_split_pos(&mut self, n: usize, pos: u16) {
1463 if n + 1 >= self.area_length.len() {
1464 return;
1465 }
1466
1467 match self.resize {
1468 SplitResize::Neighbours => {
1469 self.set_split_pos_neighbour(n, pos);
1470 }
1471 SplitResize::Full => {
1472 self.set_split_pos_full(n, pos);
1473 }
1474 }
1475 }
1476
1477 fn set_split_pos_neighbour(&mut self, n: usize, pos: u16) {
1480 assert!(n + 1 < self.area_length.len());
1481
1482 let mut pos_vec = Vec::new();
1484 let mut pp = 0;
1485 for len in &self.area_length {
1486 pp += *len;
1487 pos_vec.push(pp);
1488 }
1489 let pos_count = pos_vec.len();
1491
1492 let (min_pos, max_pos) = if n == 0 {
1493 if n + 2 >= pos_count {
1494 (SPLIT_WIDTH, pos_vec[n + 1])
1495 } else {
1496 (SPLIT_WIDTH, pos_vec[n + 1] - SPLIT_WIDTH)
1497 }
1498 } else if n + 2 < pos_count {
1499 (pos_vec[n - 1] + 1, pos_vec[n + 1] - SPLIT_WIDTH)
1500 } else {
1501 (pos_vec[n - 1] + 1, pos_vec[n + 1])
1502 };
1503
1504 pos_vec[n] = min(max(min_pos, pos), max_pos);
1505
1506 for i in 0..pos_vec.len() {
1508 if i > 0 {
1509 self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1510 } else {
1511 self.area_length[i] = pos_vec[i];
1512 }
1513 }
1514 }
1515
1516 #[allow(clippy::needless_range_loop)]
1519 #[allow(clippy::comparison_chain)]
1520 fn set_split_pos_full(&mut self, n: usize, pos: u16) {
1521 assert!(n + 1 < self.area_length.len());
1522
1523 let total_len = self.total_area_len();
1524
1525 let mut pos_vec = Vec::new();
1527 let mut pp = 0;
1528 for len in &self.area_length {
1529 pp += *len;
1530 pos_vec.push(pp);
1531 }
1532 pos_vec.pop();
1534 let pos_count = pos_vec.len();
1535
1536 let mut min_pos = SPLIT_WIDTH;
1537 for i in 0..pos_vec.len() {
1538 if i < n {
1539 if self.area_length[i] == 0 {
1540 pos_vec[i] = min_pos;
1541 } else if self.hidden_length[i] != 0 {
1542 pos_vec[i] = min_pos;
1543 min_pos += SPLIT_WIDTH;
1544 } else {
1545 if pos_vec[i] >= pos {
1546 let rest_area_count = n - (i + 1);
1548 let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1549 pos_vec[i] = max(
1551 min_pos,
1552 pos.saturating_sub(SPLIT_WIDTH)
1553 .saturating_sub(rest_area_width),
1554 );
1555 min_pos += SPLIT_WIDTH;
1556 } else {
1557 }
1559 }
1560 } else if i == n {
1561 let rest_area_count = pos_count - (i + 1);
1563 let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1564 let rest_len = total_len - (min_pos + 1);
1565 let rest_len = rest_len - rest_area_width;
1567 let rest_len = rest_len + SPLIT_WIDTH;
1569
1570 let max_pos = min_pos + rest_len;
1571
1572 pos_vec[i] = min(max(min_pos, pos), max_pos);
1573
1574 min_pos = pos_vec[i] + SPLIT_WIDTH;
1575 } else {
1576 if self.area_length[i] == 0 {
1577 pos_vec[i] = min_pos;
1578 } else if self.hidden_length[i] != 0 {
1579 pos_vec[i] = min_pos;
1580 min_pos += SPLIT_WIDTH;
1581 } else {
1582 if pos_vec[i] <= pos {
1583 pos_vec[i] = min_pos;
1584 min_pos += SPLIT_WIDTH;
1585 } else {
1586 }
1588 }
1589 }
1590 }
1591
1592 for i in 0..pos_vec.len() {
1594 if i > 0 {
1595 self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1596 } else {
1597 self.area_length[i] = pos_vec[i];
1598 }
1599 }
1600 self.area_length[pos_count] = total_len - pos_vec[pos_count - 1];
1601 }
1602
1603 pub fn is_hidden(&self, n: usize) -> bool {
1605 self.hidden_length[n] > 0
1606 }
1607
1608 pub fn hide_split(&mut self, n: usize) -> bool {
1612 if self.hidden_length[n] == 0 {
1613 let mut hide = if n + 1 == self.area_length.len() {
1614 self.area_length[n]
1615 } else {
1616 self.area_length[n].saturating_sub(SPLIT_WIDTH)
1617 };
1618 for idx in n + 1..self.area_length.len() {
1619 if self.hidden_length[idx] == 0 {
1620 self.area_length[idx] += hide;
1621 hide = 0;
1622 break;
1623 }
1624 }
1625 if hide > 0 {
1626 for idx in (0..n).rev() {
1627 if self.hidden_length[idx] == 0 {
1628 self.area_length[idx] += hide;
1629 hide = 0;
1630 break;
1631 }
1632 }
1633 }
1634
1635 if hide > 0 {
1636 self.hidden_length[n] = 0;
1638 false
1639 } else {
1640 if n + 1 == self.area_length.len() {
1641 self.hidden_length[n] = self.area_length[n];
1642 self.area_length[n] = 0;
1643 } else {
1644 self.hidden_length[n] = self.area_length[n].saturating_sub(SPLIT_WIDTH);
1645 self.area_length[n] = 1;
1646 };
1647 true
1648 }
1649 } else {
1650 false
1651 }
1652 }
1653
1654 pub fn show_split(&mut self, n: usize) -> bool {
1658 let mut show = self.hidden_length[n];
1659 if show > 0 {
1660 for idx in n + 1..self.area_length.len() {
1661 if self.hidden_length[idx] == 0 {
1662 if self.area_length[idx] > show + SPLIT_WIDTH {
1664 self.area_length[idx] -= show;
1665 show = 0;
1666 } else if self.area_length[idx] > SPLIT_WIDTH {
1667 show -= self.area_length[idx] - SPLIT_WIDTH;
1668 self.area_length[idx] = SPLIT_WIDTH;
1669 }
1670 if show == 0 {
1671 break;
1672 }
1673 }
1674 }
1675 if show > 0 {
1676 for idx in (0..n).rev() {
1677 if self.hidden_length[idx] == 0 {
1678 if self.area_length[idx] > show + SPLIT_WIDTH {
1679 self.area_length[idx] -= show;
1680 show = 0;
1681 } else if self.area_length[idx] > SPLIT_WIDTH {
1682 show -= self.area_length[idx] - SPLIT_WIDTH;
1683 self.area_length[idx] = SPLIT_WIDTH;
1684 }
1685 if show == 0 {
1686 break;
1687 }
1688 }
1689 }
1690 }
1691
1692 self.area_length[n] += self.hidden_length[n] - show;
1693 self.hidden_length[n] = 0;
1694 true
1695 } else {
1696 false
1697 }
1698 }
1699}
1700
1701impl HandleEvent<crossterm::event::Event, Regular, Outcome> for SplitState {
1702 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
1703 flow!(if self.is_focused() {
1704 if let Some(n) = self.focus_marker {
1705 match event {
1706 ct_event!(keycode press Left) => self.move_split_left(n, 1).into(),
1707 ct_event!(keycode press Right) => self.move_split_right(n, 1).into(),
1708 ct_event!(keycode press Up) => self.move_split_up(n, 1).into(),
1709 ct_event!(keycode press Down) => self.move_split_down(n, 1).into(),
1710
1711 ct_event!(keycode press ALT-Left) => self.select_prev_split().into(),
1712 ct_event!(keycode press ALT-Right) => self.select_next_split().into(),
1713 ct_event!(keycode press ALT-Up) => self.select_prev_split().into(),
1714 ct_event!(keycode press ALT-Down) => self.select_next_split().into(),
1715 _ => Outcome::Continue,
1716 }
1717 } else {
1718 Outcome::Continue
1719 }
1720 } else {
1721 Outcome::Continue
1722 });
1723
1724 self.handle(event, MouseOnly)
1725 }
1726}
1727
1728impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for SplitState {
1729 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
1730 match event {
1731 ct_event!(mouse any for m) if self.mouse.hover(&self.splitline_areas, m) => {
1732 Outcome::Changed
1733 }
1734 ct_event!(mouse any for m) => {
1735 let was_drag = self.mouse.drag.get();
1736 if self.mouse.drag(&self.splitline_areas, m) {
1737 if let Some(n) = self.mouse.drag.get() {
1738 self.set_screen_split_pos(n, self.mouse.pos_of(m)).into()
1739 } else {
1740 Outcome::Continue
1741 }
1742 } else {
1743 if was_drag.is_some() {
1745 Outcome::Changed
1746 } else {
1747 Outcome::Continue
1748 }
1749 }
1750 }
1751 _ => Outcome::Continue,
1752 }
1753 }
1754}