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, 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 style: Style,
98 block: Option<Block<'a>>,
99 arrow_style: Option<Style>,
100 drag_style: Option<Style>,
101}
102
103#[derive(Debug, Clone)]
105pub struct LayoutWidget<'a> {
106 split: Split<'a>,
107}
108
109#[derive(Debug, Clone)]
111pub struct SplitWidget<'a> {
112 split: Split<'a>,
113 mode: u8,
117}
118
119#[derive(Debug, Clone)]
123pub struct SplitStyle {
124 pub style: Style,
126 pub block: Option<Block<'static>>,
128 pub border_style: Option<Style>,
129 pub title_style: Option<Style>,
130 pub arrow_style: Option<Style>,
132 pub drag_style: Option<Style>,
134
135 pub horizontal_mark: Option<&'static str>,
138 pub vertical_mark: Option<&'static str>,
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 block: Default::default(),
280 border_style: Default::default(),
281 title_style: Default::default(),
282 arrow_style: Default::default(),
283 drag_style: Default::default(),
284 horizontal_mark: Default::default(),
285 vertical_mark: Default::default(),
286 non_exhaustive: NonExhaustive,
287 }
288 }
289}
290
291impl<'a> Split<'a> {
292 pub fn new() -> Self {
297 Self {
298 direction: Direction::Horizontal,
299 ..Default::default()
300 }
301 }
302
303 pub fn horizontal() -> Self {
305 Self {
306 direction: Direction::Horizontal,
307 ..Default::default()
308 }
309 }
310
311 pub fn vertical() -> Self {
313 Self {
314 direction: Direction::Horizontal,
315 ..Default::default()
316 }
317 }
318
319 pub fn constraints(mut self, constraints: impl IntoIterator<Item = Constraint>) -> Self {
325 self.constraints = constraints.into_iter().collect();
326 self
327 }
328
329 pub fn direction(mut self, direction: Direction) -> Self {
333 self.direction = direction;
334 self
335 }
336
337 pub fn split_type(mut self, split_type: SplitType) -> Self {
339 self.split_type = split_type;
340 self
341 }
342
343 pub fn resize(mut self, resize: SplitResize) -> Self {
345 self.resize = resize;
346 self
347 }
348
349 pub fn join(mut self, border: BorderType) -> Self {
353 self.join_0 = Some(border);
354 self.join_1 = Some(border);
355 self
356 }
357
358 pub fn join_0(mut self, border: BorderType) -> Self {
362 self.join_0 = Some(border);
363 self
364 }
365
366 pub fn join_1(mut self, border: BorderType) -> Self {
370 self.join_1 = Some(border);
371 self
372 }
373
374 pub fn block(mut self, block: Block<'a>) -> Self {
376 self.block = Some(block);
377 self
378 }
379
380 pub fn styles(mut self, styles: SplitStyle) -> Self {
382 self.style = styles.style;
383 if styles.block.is_some() {
384 self.block = styles.block;
385 }
386 if let Some(border_style) = styles.border_style {
387 self.block = self.block.map(|v| v.border_style(border_style));
388 }
389 if let Some(title_style) = styles.title_style {
390 self.block = self.block.map(|v| v.title_style(title_style));
391 }
392 self.block = self.block.map(|v| v.style(self.style));
393
394 if styles.drag_style.is_some() {
395 self.drag_style = styles.drag_style;
396 }
397 if styles.arrow_style.is_some() {
398 self.arrow_style = styles.arrow_style;
399 }
400 match self.direction {
401 Direction::Horizontal => {
402 if let Some(mark) = styles.horizontal_mark {
403 let mut g = mark.graphemes(true);
404 if let Some(g0) = g.next() {
405 self.mark_0_char = Some(g0);
406 }
407 if let Some(g1) = g.next() {
408 self.mark_1_char = Some(g1);
409 }
410 }
411 }
412 Direction::Vertical => {
413 if let Some(mark) = styles.vertical_mark {
414 let mut g = mark.graphemes(true);
415 if let Some(g0) = g.next() {
416 self.mark_0_char = Some(g0);
417 }
418 if let Some(g1) = g.next() {
419 self.mark_1_char = Some(g1);
420 }
421 }
422 }
423 }
424 self
425 }
426
427 pub fn style(mut self, style: Style) -> Self {
429 self.style = style;
430 self.block = self.block.map(|v| v.style(style));
431 self
432 }
433
434 pub fn arrow_style(mut self, style: Style) -> Self {
436 self.arrow_style = Some(style);
437 self
438 }
439
440 pub fn drag_style(mut self, style: Style) -> Self {
442 self.drag_style = Some(style);
443 self
444 }
445
446 pub fn mark_offset(mut self, offset: u16) -> Self {
448 self.mark_offset = offset;
449 self
450 }
451
452 pub fn mark_0(mut self, mark: &'a str) -> Self {
454 self.mark_0_char = Some(mark);
455 self
456 }
457
458 pub fn mark_1(mut self, mark: &'a str) -> Self {
460 self.mark_1_char = Some(mark);
461 self
462 }
463
464 #[deprecated(since = "2.4.0", note = "use into_widgets() instead")]
472 pub fn into_widget(self, area: Rect, state: &mut SplitState) -> SplitWidget<'a> {
473 self.layout_split(area, state);
474
475 SplitWidget {
476 split: self,
477 mode: 1,
478 }
479 }
480
481 #[deprecated(since = "2.4.0", note = "use into_widgets() instead")]
490 pub fn into_widget_layout(
491 self,
492 area: Rect,
493 state: &mut SplitState,
494 ) -> (SplitWidget<'a>, Vec<Rect>) {
495 self.layout_split(area, state);
496
497 (
498 SplitWidget {
499 split: self,
500 mode: 1,
501 },
502 state.widget_areas.clone(),
503 )
504 }
505
506 pub fn into_widgets(self) -> (LayoutWidget<'a>, SplitWidget<'a>) {
518 (
519 LayoutWidget {
520 split: self.clone(),
521 },
522 SplitWidget {
523 split: self,
524 mode: 1,
525 },
526 )
527 }
528}
529
530impl Split<'_> {
531 fn layout_split(&self, area: Rect, state: &mut SplitState) {
534 state.area = area;
535 state.inner = self.block.inner_if_some(area);
536
537 let inner = state.inner;
539
540 let layout_change = state.area_length.len() != self.constraints.len();
541 let meta_change = state.direction != self.direction
542 || state.split_type != self.split_type
543 || state.mark_offset != self.mark_offset;
544
545 let old_len = |v: &Rect| {
546 if state.direction == Direction::Horizontal {
548 v.width
549 } else {
550 v.height
551 }
552 };
553 let new_len = |v: &Rect| {
554 if self.direction == Direction::Horizontal {
556 v.width
557 } else {
558 v.height
559 }
560 };
561
562 let new_split_areas = if layout_change {
563 let new_areas = Layout::new(self.direction, self.constraints.clone())
565 .flex(Flex::Legacy)
566 .split(inner);
567 Some(new_areas)
568 } else {
569 let old_length: u16 = state.area_length.iter().sum();
570 if meta_change || old_len(&inner) != old_length {
571 let mut constraints = Vec::new();
572 for i in 0..state.area_length.len() {
573 constraints.push(Constraint::Fill(state.area_length[i]));
574 }
575 let new_areas = Layout::new(self.direction, constraints).split(inner);
576 Some(new_areas)
577 } else {
578 None
579 }
580 };
581
582 if let Some(new_split_areas) = new_split_areas {
583 state.area_length.clear();
584 for v in new_split_areas.iter() {
585 state.area_length.push(new_len(v));
586 }
587 while state.hidden_length.len() < state.area_length.len() {
588 state.hidden_length.push(0);
589 }
590 while state.hidden_length.len() > state.area_length.len() {
591 state.hidden_length.pop();
592 }
593 }
594
595 state.direction = self.direction;
596 state.split_type = self.split_type;
597 state.resize = self.resize;
598 state.mark_offset = self.mark_offset;
599
600 self.layout_from_widths(state);
601 }
602
603 fn layout_from_widths(&self, state: &mut SplitState) {
604 state.widget_areas.clear();
606 state.splitline_areas.clear();
607 state.splitline_mark_position.clear();
608
609 let inner = state.inner;
610
611 let mut total = 0;
612 for length in state
613 .area_length
614 .iter()
615 .take(state.area_length.len().saturating_sub(1))
616 .copied()
617 {
618 let mut area = if self.direction == Direction::Horizontal {
619 Rect::new(inner.x + total, inner.y, length, inner.height)
620 } else {
621 Rect::new(inner.x, inner.y + total, inner.width, length)
622 };
623 let mut split = if self.direction == Direction::Horizontal {
624 Rect::new(
625 inner.x + total + length.saturating_sub(SPLIT_WIDTH),
626 inner.y,
627 min(1, length),
628 inner.height,
629 )
630 } else {
631 Rect::new(
632 inner.x,
633 inner.y + total + length.saturating_sub(SPLIT_WIDTH),
634 inner.width,
635 min(1, length),
636 )
637 };
638 let mut mark = if self.direction == Direction::Horizontal {
639 Position::new(
640 inner.x + total + length.saturating_sub(SPLIT_WIDTH),
641 inner.y + self.mark_offset,
642 )
643 } else {
644 Position::new(
645 inner.x + self.mark_offset,
646 inner.y + total + length.saturating_sub(SPLIT_WIDTH),
647 )
648 };
649
650 adjust_for_split_type(
651 self.direction,
652 self.split_type,
653 &mut area,
654 &mut split,
655 &mut mark,
656 );
657
658 state.widget_areas.push(area);
659 state.splitline_areas.push(split);
660 state.splitline_mark_position.push(mark);
661
662 total += length;
663 }
664 if let Some(length) = state.area_length.last().copied() {
665 let area = if self.direction == Direction::Horizontal {
666 Rect::new(inner.x + total, inner.y, length, inner.height)
667 } else {
668 Rect::new(inner.x, inner.y + total, inner.width, length)
669 };
670
671 state.widget_areas.push(area);
672 }
673
674 if let Some(test) = state.widget_areas.first() {
676 if self.direction == Direction::Horizontal {
677 if test.height != state.inner.height {
678 for r in &mut state.widget_areas {
679 r.height = state.inner.height;
680 }
681 for r in &mut state.splitline_areas {
682 r.height = state.inner.height;
683 }
684 }
685 } else {
686 if test.width != state.inner.width {
687 for r in &mut state.widget_areas {
688 r.width = state.inner.width;
689 }
690 for r in &mut state.splitline_areas {
691 r.width = state.inner.width;
692 }
693 }
694 }
695 }
696 }
697}
698
699fn adjust_for_split_type(
701 direction: Direction,
702 split_type: SplitType,
703 area: &mut Rect,
704 split: &mut Rect,
705 mark: &mut Position,
706) {
707 use Direction::*;
708 use SplitType::*;
709
710 match (direction, split_type) {
711 (
712 Horizontal,
713 FullEmpty | FullPlain | FullDouble | FullThick | FullQuadrantInside
714 | FullQuadrantOutside,
715 ) => {
716 area.width = area.width.saturating_sub(SPLIT_WIDTH);
717 }
718 (
719 Vertical,
720 FullEmpty | FullPlain | FullDouble | FullThick | FullQuadrantInside
721 | FullQuadrantOutside,
722 ) => {
723 area.height = area.height.saturating_sub(SPLIT_WIDTH);
724 }
725
726 (Horizontal, Scroll) => {
727 split.y = mark.y;
728 split.height = 2;
729 }
730 (Vertical, Scroll) => {
731 split.x = mark.x;
732 split.width = 2;
733 }
734
735 (Horizontal, Widget) => {}
736 (Vertical, Widget) => {}
737 }
738}
739
740impl Split<'_> {
741 fn get_mark_0(&self) -> &str {
742 if let Some(mark) = self.mark_0_char {
743 mark
744 } else if self.direction == Direction::Horizontal {
745 "<"
746 } else {
747 "^"
748 }
749 }
750
751 fn get_mark_1(&self) -> &str {
752 if let Some(mark) = self.mark_1_char {
753 mark
754 } else if self.direction == Direction::Horizontal {
755 ">"
756 } else {
757 "v"
758 }
759 }
760
761 fn get_fill_char(&self) -> Option<&str> {
762 use Direction::*;
763 use SplitType::*;
764
765 match (self.direction, self.split_type) {
766 (Horizontal, FullEmpty) => Some(" "),
767 (Vertical, FullEmpty) => Some(" "),
768 (Horizontal, FullPlain) => Some("\u{2502}"),
769 (Vertical, FullPlain) => Some("\u{2500}"),
770 (Horizontal, FullDouble) => Some("\u{2551}"),
771 (Vertical, FullDouble) => Some("\u{2550}"),
772 (Horizontal, FullThick) => Some("\u{2503}"),
773 (Vertical, FullThick) => Some("\u{2501}"),
774 (Horizontal, FullQuadrantInside) => Some("\u{258C}"),
775 (Vertical, FullQuadrantInside) => Some("\u{2580}"),
776 (Horizontal, FullQuadrantOutside) => Some("\u{2590}"),
777 (Vertical, FullQuadrantOutside) => Some("\u{2584}"),
778 (_, Scroll) => None,
779 (_, Widget) => None,
780 }
781 }
782
783 #[allow(unreachable_patterns)]
784 fn get_join_0(&self, split_area: Rect, state: &SplitState) -> Option<(Position, &str)> {
785 use Direction::*;
786 use SplitType::*;
787
788 let s: Option<&str> = if let Some(join_0) = self.join_0 {
789 match (self.direction, join_0, self.split_type) {
790 (
791 Horizontal,
792 BorderType::Plain | BorderType::Rounded,
793 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
794 ) => Some("\u{252C}"),
795 (
796 Vertical,
797 BorderType::Plain | BorderType::Rounded,
798 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
799 ) => Some("\u{251C}"),
800 (
801 Horizontal,
802 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
803 FullDouble,
804 ) => Some("\u{2565}"),
805 (
806 Vertical,
807 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
808 FullDouble,
809 ) => Some("\u{255E}"),
810 (Horizontal, BorderType::Plain | BorderType::Rounded, FullThick) => {
811 Some("\u{2530}")
812 }
813 (Vertical, BorderType::Plain | BorderType::Rounded, FullThick) => Some("\u{251D}"),
814
815 (
816 Horizontal,
817 BorderType::Double,
818 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
819 | Scroll,
820 ) => Some("\u{2564}"),
821 (
822 Vertical,
823 BorderType::Double,
824 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
825 | Scroll,
826 ) => Some("\u{255F}"),
827 (Horizontal, BorderType::Double, FullDouble) => Some("\u{2566}"),
828 (Vertical, BorderType::Double, FullDouble) => Some("\u{2560}"),
829
830 (
831 Horizontal,
832 BorderType::Thick,
833 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
834 ) => Some("\u{252F}"),
835 (
836 Vertical,
837 BorderType::Thick,
838 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
839 ) => Some("\u{2520}"),
840 (Horizontal, BorderType::Thick, FullThick) => Some("\u{2533}"),
841 (Vertical, BorderType::Thick, FullThick) => Some("\u{2523}"),
842
843 (Horizontal, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
844 (Vertical, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
845
846 (_, BorderType::QuadrantInside, _) => None,
847 (_, BorderType::QuadrantOutside, _) => None,
848
849 (_, _, Widget) => None,
850 (_, _, _) => None,
851 }
852 } else {
853 None
854 };
855
856 s.map(|s| {
857 (
858 match self.direction {
859 Horizontal => Position::new(split_area.x, state.area.y),
860 Vertical => Position::new(state.area.x, split_area.y),
861 },
862 s,
863 )
864 })
865 }
866
867 #[allow(unreachable_patterns)]
868 fn get_join_1(&self, split_area: Rect, state: &SplitState) -> Option<(Position, &str)> {
869 use Direction::*;
870 use SplitType::*;
871
872 let s: Option<&str> = if let Some(join_1) = self.join_1 {
873 match (self.direction, join_1, self.split_type) {
874 (
875 Horizontal,
876 BorderType::Plain | BorderType::Rounded,
877 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
878 ) => Some("\u{2534}"),
879 (
880 Vertical,
881 BorderType::Plain | BorderType::Rounded,
882 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
883 ) => Some("\u{2524}"),
884 (
885 Horizontal,
886 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
887 FullDouble,
888 ) => Some("\u{2568}"),
889 (
890 Vertical,
891 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
892 FullDouble,
893 ) => Some("\u{2561}"),
894 (Horizontal, BorderType::Plain | BorderType::Rounded, FullThick) => {
895 Some("\u{2538}")
896 }
897 (Vertical, BorderType::Plain | BorderType::Rounded, FullThick) => Some("\u{2525}"),
898
899 (
900 Horizontal,
901 BorderType::Double,
902 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
903 | Scroll,
904 ) => Some("\u{2567}"),
905 (
906 Vertical,
907 BorderType::Double,
908 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
909 | Scroll,
910 ) => Some("\u{2562}"),
911 (Horizontal, BorderType::Double, FullDouble) => Some("\u{2569}"),
912 (Vertical, BorderType::Double, FullDouble) => Some("\u{2563}"),
913
914 (
915 Horizontal,
916 BorderType::Thick,
917 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
918 ) => Some("\u{2537}"),
919 (
920 Vertical,
921 BorderType::Thick,
922 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
923 ) => Some("\u{2528}"),
924 (Horizontal, BorderType::Thick, FullThick) => Some("\u{253B}"),
925 (Vertical, BorderType::Thick, FullThick) => Some("\u{252B}"),
926
927 (Horizontal, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
928 (Vertical, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
929
930 (_, BorderType::QuadrantInside, _) => None,
931 (_, BorderType::QuadrantOutside, _) => None,
932
933 (_, _, Widget) => None,
934 (_, _, _) => None,
935 }
936 } else {
937 None
938 };
939
940 s.map(|s| {
941 (
942 match self.direction {
943 Horizontal => Position::new(split_area.x, state.area.y + state.area.height - 1),
944 Vertical => Position::new(state.area.x + state.area.width - 1, split_area.y),
945 },
946 s,
947 )
948 })
949 }
950}
951
952impl<'a> StatefulWidget for &LayoutWidget<'a> {
953 type State = SplitState;
954
955 fn render(self, area: Rect, _buf: &mut Buffer, state: &mut Self::State) {
956 self.split.layout_split(area, state);
958 }
959}
960
961impl<'a> StatefulWidget for LayoutWidget<'a> {
962 type State = SplitState;
963
964 fn render(self, area: Rect, _buf: &mut Buffer, state: &mut Self::State) {
965 self.split.layout_split(area, state);
967 }
968}
969
970impl<'a> StatefulWidget for &SplitWidget<'a> {
971 type State = SplitState;
972
973 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
974 if self.mode == 0 {
975 self.split.layout_split(area, state);
976 } else if self.mode == 1 {
977 } else {
979 unreachable!()
980 }
981
982 if state.is_focused() {
983 if state.focus_marker.is_none() {
984 state.focus_marker = Some(0);
985 }
986 } else {
987 state.focus_marker = None;
988 }
989
990 if self.mode == 0 {
991 if let Some(block) = &self.split.block {
992 block.render(area, buf);
993 } else {
994 buf.set_style(area, self.split.style);
995 }
996 } else if self.mode == 1 {
997 if let Some(mut block) = self.split.block.clone() {
998 block = block.style(Style::default());
999 block.render(area, buf);
1000 }
1001 } else {
1002 unreachable!()
1003 }
1004
1005 if self.mode == 0 {
1006 if !matches!(self.split.split_type, SplitType::Widget | SplitType::Scroll) {
1007 render_split(&self.split, buf, state);
1008 }
1009 } else if self.mode == 1 {
1010 render_split(&self.split, buf, state);
1011 } else {
1012 unreachable!()
1013 }
1014 }
1015}
1016
1017impl StatefulWidget for SplitWidget<'_> {
1018 type State = SplitState;
1019
1020 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
1021 if self.mode == 0 {
1022 self.split.layout_split(area, state);
1023 } else if self.mode == 1 {
1024 } else {
1026 unreachable!()
1027 }
1028
1029 if state.is_focused() {
1030 if state.focus_marker.is_none() {
1031 state.focus_marker = Some(0);
1032 }
1033 } else {
1034 state.focus_marker = None;
1035 }
1036
1037 if self.mode == 0 {
1038 if let Some(block) = &self.split.block {
1039 block.render(area, buf);
1040 } else {
1041 buf.set_style(area, self.split.style);
1042 }
1043 } else if self.mode == 1 {
1044 if let Some(mut block) = mem::take(&mut self.split.block) {
1045 block = block.style(Style::default());
1047 block.render(area, buf);
1048 }
1049 } else {
1050 unreachable!()
1051 }
1052
1053 if self.mode == 0 {
1054 if !matches!(self.split.split_type, SplitType::Widget | SplitType::Scroll) {
1055 render_split(&self.split, buf, state);
1056 }
1057 } else if self.mode == 1 {
1058 render_split(&self.split, buf, state);
1059 } else {
1060 unreachable!()
1061 }
1062 }
1063}
1064
1065fn render_split(split: &Split<'_>, buf: &mut Buffer, state: &mut SplitState) {
1066 for (n, split_area) in state.splitline_areas.iter().enumerate() {
1067 if split.direction == Direction::Horizontal {
1069 if split_area.width == 0 {
1070 continue;
1071 }
1072 } else {
1073 if split_area.height == 0 {
1074 continue;
1075 }
1076 }
1077
1078 let (style, arrow_style) = if Some(n) == state.mouse.drag.get()
1079 || Some(n) == state.focus_marker
1080 || Some(n) == state.mouse.hover.get()
1081 {
1082 if let Some(drag) = split.drag_style {
1083 (drag, drag)
1084 } else {
1085 (revert_style(split.style), revert_style(split.style))
1086 }
1087 } else {
1088 if let Some(arrow) = split.arrow_style {
1089 (split.style, arrow)
1090 } else {
1091 (split.style, split.style)
1092 }
1093 };
1094
1095 if let Some(fill) = split.get_fill_char() {
1096 fill_buf_area(buf, *split_area, fill, style);
1097 }
1098
1099 let mark = state.splitline_mark_position[n];
1100 if split.direction == Direction::Horizontal {
1101 if buf.area.contains((mark.x, mark.y).into()) {
1102 if let Some(cell) = buf.cell_mut((mark.x, mark.y)) {
1103 cell.set_style(arrow_style);
1104 cell.set_symbol(split.get_mark_0());
1105 }
1106 }
1107 if buf.area.contains((mark.x, mark.y + 1).into()) {
1108 if let Some(cell) = buf.cell_mut((mark.x, mark.y + 1)) {
1109 cell.set_style(arrow_style);
1110 cell.set_symbol(split.get_mark_1());
1111 }
1112 }
1113 } else {
1114 if let Some(cell) = buf.cell_mut((mark.x, mark.y)) {
1115 cell.set_style(arrow_style);
1116 cell.set_symbol(split.get_mark_0());
1117 }
1118 if let Some(cell) = buf.cell_mut((mark.x + 1, mark.y)) {
1119 cell.set_style(arrow_style);
1120 cell.set_symbol(split.get_mark_1());
1121 }
1122 }
1123
1124 if let Some((pos_0, c_0)) = split.get_join_0(*split_area, state) {
1125 if let Some(cell) = buf.cell_mut((pos_0.x, pos_0.y)) {
1126 cell.set_symbol(c_0);
1127 }
1128 }
1129 if let Some((pos_1, c_1)) = split.get_join_1(*split_area, state) {
1130 if let Some(cell) = buf.cell_mut((pos_1.x, pos_1.y)) {
1131 cell.set_symbol(c_1);
1132 }
1133 }
1134 }
1135}
1136
1137impl Default for SplitState {
1138 fn default() -> Self {
1139 Self {
1140 area: Default::default(),
1141 inner: Default::default(),
1142 widget_areas: Default::default(),
1143 splitline_areas: Default::default(),
1144 splitline_mark_position: Default::default(),
1145 mark_offset: Default::default(),
1146 direction: Default::default(),
1147 split_type: Default::default(),
1148 resize: Default::default(),
1149 area_length: Default::default(),
1150 hidden_length: Default::default(),
1151 focus: Default::default(),
1152 focus_marker: Default::default(),
1153 mouse: Default::default(),
1154 non_exhaustive: NonExhaustive,
1155 }
1156 }
1157}
1158
1159impl Clone for SplitState {
1160 fn clone(&self) -> Self {
1161 Self {
1162 area: self.area,
1163 inner: self.inner,
1164 widget_areas: self.widget_areas.clone(),
1165 splitline_areas: self.splitline_areas.clone(),
1166 splitline_mark_position: self.splitline_mark_position.clone(),
1167 mark_offset: self.mark_offset,
1168 direction: self.direction,
1169 split_type: self.split_type,
1170 resize: self.resize,
1171 area_length: self.area_length.clone(),
1172 hidden_length: self.hidden_length.clone(),
1173 focus: self.focus.new_instance(),
1174 focus_marker: self.focus_marker,
1175 mouse: Default::default(),
1176 non_exhaustive: NonExhaustive,
1177 }
1178 }
1179}
1180
1181impl HasFocus for SplitState {
1182 fn build(&self, builder: &mut FocusBuilder) {
1183 builder.leaf_widget(self);
1184 }
1185
1186 fn focus(&self) -> FocusFlag {
1187 self.focus.clone()
1188 }
1189
1190 fn area(&self) -> Rect {
1191 Rect::default()
1193 }
1194
1195 fn navigable(&self) -> Navigation {
1196 Navigation::Leave
1197 }
1198}
1199
1200impl RelocatableState for SplitState {
1201 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1202 self.area = relocate_area(self.area, shift, clip);
1203 self.inner = relocate_area(self.inner, shift, clip);
1204 relocate_areas(self.widget_areas.as_mut_slice(), shift, clip);
1205 relocate_areas(self.splitline_areas.as_mut_slice(), shift, clip);
1206 relocate_positions(self.splitline_mark_position.as_mut_slice(), shift, clip);
1207 }
1208}
1209
1210#[allow(clippy::len_without_is_empty)]
1211impl SplitState {
1212 pub fn new() -> Self {
1214 Self::default()
1215 }
1216
1217 pub fn named(name: &str) -> Self {
1219 let mut z = Self::default();
1220 z.focus = z.focus.with_name(name);
1221 z
1222 }
1223
1224 pub fn set_screen_split_pos(&mut self, n: usize, pos: (u16, u16)) -> bool {
1231 if self.is_hidden(n) {
1232 return false;
1233 }
1234 if self.direction == Direction::Horizontal {
1235 let pos = if pos.0 < self.inner.left() {
1236 0
1237 } else if pos.0 < self.inner.right() {
1238 pos.0 - self.inner.x
1239 } else {
1240 self.inner.width
1241 };
1242
1243 let split_pos = self.split_pos(n);
1244 self.set_split_pos(n, pos);
1245
1246 split_pos != self.split_pos(n)
1247 } else {
1248 let pos = if pos.1 < self.inner.top() {
1249 0
1250 } else if pos.1 < self.inner.bottom() {
1251 pos.1 - self.inner.y
1252 } else {
1253 self.inner.height
1254 };
1255
1256 let split_pos = self.split_pos(n);
1257 self.set_split_pos(n, pos);
1258
1259 split_pos != self.split_pos(n)
1260 }
1261 }
1262
1263 pub fn move_split_left(&mut self, n: usize, delta: u16) -> bool {
1267 let split_pos = self.split_pos(n);
1268 self.set_split_pos(n, split_pos - delta);
1269
1270 split_pos != self.split_pos(n)
1271 }
1272
1273 pub fn move_split_right(&mut self, n: usize, delta: u16) -> bool {
1276 let split_pos = self.split_pos(n);
1277 self.set_split_pos(n, split_pos + delta);
1278
1279 split_pos != self.split_pos(n)
1280 }
1281
1282 pub fn move_split_up(&mut self, n: usize, delta: u16) -> bool {
1285 self.move_split_left(n, delta)
1286 }
1287
1288 pub fn move_split_down(&mut self, n: usize, delta: u16) -> bool {
1291 self.move_split_right(n, delta)
1292 }
1293
1294 pub fn select_next_split(&mut self) -> bool {
1296 if self.is_focused() {
1297 let n = self.focus_marker.unwrap_or_default();
1298 if n + 1 >= self.area_length.len().saturating_sub(1) {
1299 self.focus_marker = Some(0);
1300 } else {
1301 self.focus_marker = Some(n + 1);
1302 }
1303 true
1304 } else {
1305 false
1306 }
1307 }
1308
1309 pub fn select_prev_split(&mut self) -> bool {
1311 if self.is_focused() {
1312 let n = self.focus_marker.unwrap_or_default();
1313 if n == 0 {
1314 self.focus_marker =
1315 Some(self.area_length.len().saturating_sub(1).saturating_sub(1));
1316 } else {
1317 self.focus_marker = Some(n - 1);
1318 }
1319 true
1320 } else {
1321 false
1322 }
1323 }
1324
1325 pub fn len(&self) -> usize {
1327 self.area_length.len()
1328 }
1329
1330 pub fn area_lengths(&self) -> &[u16] {
1332 &self.area_length
1333 }
1334
1335 pub fn set_area_lengths(&mut self, lengths: Vec<u16>) {
1348 self.area_length = lengths;
1349 while self.hidden_length.len() < self.area_length.len() {
1350 self.hidden_length.push(0);
1351 }
1352 while self.hidden_length.len() > self.area_length.len() {
1353 self.hidden_length.pop();
1354 }
1355 }
1356
1357 pub fn hidden_lengths(&self) -> &[u16] {
1359 &self.hidden_length
1360 }
1361
1362 pub fn set_hidden_lengths(&mut self, hidden: Vec<u16>) {
1367 for i in 0..self.hidden_length.len() {
1368 if let Some(v) = hidden.get(i) {
1369 self.hidden_length[i] = *v;
1370 } else {
1371 self.hidden_length[i] = 0;
1372 }
1373 }
1374 }
1375
1376 pub fn area_len(&self, n: usize) -> u16 {
1386 if n >= self.area_length.len() {
1387 return 0;
1388 }
1389 self.area_length[n]
1390 }
1391
1392 pub fn total_area_len(&self) -> u16 {
1394 self.area_length.iter().sum()
1395 }
1396
1397 pub fn set_area_len(&mut self, n: usize, len: u16) {
1430 if n >= self.area_length.len() {
1431 return;
1432 }
1433 self.area_length[n] = len;
1434 self.hidden_length[n] = 0;
1435 }
1436
1437 pub fn split_pos(&self, n: usize) -> u16 {
1449 if n + 1 >= self.area_length.len() {
1450 return self.area_length.iter().sum();
1451 }
1452 self.area_length[..n + 1].iter().sum()
1453 }
1454
1455 pub fn set_split_pos(&mut self, n: usize, pos: u16) {
1471 if n + 1 >= self.area_length.len() {
1472 return;
1473 }
1474
1475 match self.resize {
1476 SplitResize::Neighbours => {
1477 self.set_split_pos_neighbour(n, pos);
1478 }
1479 SplitResize::Full => {
1480 self.set_split_pos_full(n, pos);
1481 }
1482 }
1483 }
1484
1485 fn set_split_pos_neighbour(&mut self, n: usize, pos: u16) {
1488 assert!(n + 1 < self.area_length.len());
1489
1490 let mut pos_vec = Vec::new();
1492 let mut pp = 0;
1493 for len in &self.area_length {
1494 pp += *len;
1495 pos_vec.push(pp);
1496 }
1497 let pos_count = pos_vec.len();
1499
1500 let (min_pos, max_pos) = if n == 0 {
1501 if n + 2 >= pos_count {
1502 (SPLIT_WIDTH, pos_vec[n + 1])
1503 } else {
1504 (SPLIT_WIDTH, pos_vec[n + 1] - SPLIT_WIDTH)
1505 }
1506 } else if n + 2 < pos_count {
1507 (pos_vec[n - 1] + 1, pos_vec[n + 1] - SPLIT_WIDTH)
1508 } else {
1509 (pos_vec[n - 1] + 1, pos_vec[n + 1])
1510 };
1511
1512 pos_vec[n] = min(max(min_pos, pos), max_pos);
1513
1514 for i in 0..pos_vec.len() {
1516 if i > 0 {
1517 self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1518 } else {
1519 self.area_length[i] = pos_vec[i];
1520 }
1521 }
1522 }
1523
1524 #[allow(clippy::needless_range_loop)]
1527 #[allow(clippy::comparison_chain)]
1528 fn set_split_pos_full(&mut self, n: usize, pos: u16) {
1529 assert!(n + 1 < self.area_length.len());
1530
1531 let total_len = self.total_area_len();
1532
1533 let mut pos_vec = Vec::new();
1535 let mut pp = 0;
1536 for len in &self.area_length {
1537 pp += *len;
1538 pos_vec.push(pp);
1539 }
1540 pos_vec.pop();
1542 let pos_count = pos_vec.len();
1543
1544 let mut min_pos = SPLIT_WIDTH;
1545 for i in 0..pos_vec.len() {
1546 if i < n {
1547 if self.area_length[i] == 0 {
1548 pos_vec[i] = min_pos;
1549 } else if self.hidden_length[i] != 0 {
1550 pos_vec[i] = min_pos;
1551 min_pos += SPLIT_WIDTH;
1552 } else {
1553 if pos_vec[i] >= pos {
1554 let rest_area_count = n - (i + 1);
1556 let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1557 pos_vec[i] = max(
1559 min_pos,
1560 pos.saturating_sub(SPLIT_WIDTH)
1561 .saturating_sub(rest_area_width),
1562 );
1563 min_pos += SPLIT_WIDTH;
1564 } else {
1565 }
1567 }
1568 } else if i == n {
1569 let rest_area_count = pos_count - (i + 1);
1571 let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1572 let rest_len = total_len - (min_pos + 1);
1573 let rest_len = rest_len - rest_area_width;
1575 let rest_len = rest_len + SPLIT_WIDTH;
1577
1578 let max_pos = min_pos + rest_len;
1579
1580 pos_vec[i] = min(max(min_pos, pos), max_pos);
1581
1582 min_pos = pos_vec[i] + SPLIT_WIDTH;
1583 } else {
1584 if self.area_length[i] == 0 {
1585 pos_vec[i] = min_pos;
1586 } else if self.hidden_length[i] != 0 {
1587 pos_vec[i] = min_pos;
1588 min_pos += SPLIT_WIDTH;
1589 } else {
1590 if pos_vec[i] <= pos {
1591 pos_vec[i] = min_pos;
1592 min_pos += SPLIT_WIDTH;
1593 } else {
1594 }
1596 }
1597 }
1598 }
1599
1600 for i in 0..pos_vec.len() {
1602 if i > 0 {
1603 self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1604 } else {
1605 self.area_length[i] = pos_vec[i];
1606 }
1607 }
1608 self.area_length[pos_count] = total_len - pos_vec[pos_count - 1];
1609 }
1610
1611 pub fn is_hidden(&self, n: usize) -> bool {
1613 self.hidden_length[n] > 0
1614 }
1615
1616 pub fn hide_split(&mut self, n: usize) -> bool {
1620 if self.hidden_length[n] == 0 {
1621 let mut hide = if n + 1 == self.area_length.len() {
1622 self.area_length[n]
1623 } else {
1624 self.area_length[n].saturating_sub(SPLIT_WIDTH)
1625 };
1626 for idx in n + 1..self.area_length.len() {
1627 if self.hidden_length[idx] == 0 {
1628 self.area_length[idx] += hide;
1629 hide = 0;
1630 break;
1631 }
1632 }
1633 if hide > 0 {
1634 for idx in (0..n).rev() {
1635 if self.hidden_length[idx] == 0 {
1636 self.area_length[idx] += hide;
1637 hide = 0;
1638 break;
1639 }
1640 }
1641 }
1642
1643 if hide > 0 {
1644 self.hidden_length[n] = 0;
1646 false
1647 } else {
1648 if n + 1 == self.area_length.len() {
1649 self.hidden_length[n] = self.area_length[n];
1650 self.area_length[n] = 0;
1651 } else {
1652 self.hidden_length[n] = self.area_length[n].saturating_sub(SPLIT_WIDTH);
1653 self.area_length[n] = 1;
1654 };
1655 true
1656 }
1657 } else {
1658 false
1659 }
1660 }
1661
1662 pub fn show_split(&mut self, n: usize) -> bool {
1666 let mut show = self.hidden_length[n];
1667 if show > 0 {
1668 for idx in n + 1..self.area_length.len() {
1669 if self.hidden_length[idx] == 0 {
1670 if self.area_length[idx] > show + SPLIT_WIDTH {
1672 self.area_length[idx] -= show;
1673 show = 0;
1674 } else if self.area_length[idx] > SPLIT_WIDTH {
1675 show -= self.area_length[idx] - SPLIT_WIDTH;
1676 self.area_length[idx] = SPLIT_WIDTH;
1677 }
1678 if show == 0 {
1679 break;
1680 }
1681 }
1682 }
1683 if show > 0 {
1684 for idx in (0..n).rev() {
1685 if self.hidden_length[idx] == 0 {
1686 if self.area_length[idx] > show + SPLIT_WIDTH {
1687 self.area_length[idx] -= show;
1688 show = 0;
1689 } else if self.area_length[idx] > SPLIT_WIDTH {
1690 show -= self.area_length[idx] - SPLIT_WIDTH;
1691 self.area_length[idx] = SPLIT_WIDTH;
1692 }
1693 if show == 0 {
1694 break;
1695 }
1696 }
1697 }
1698 }
1699
1700 self.area_length[n] += self.hidden_length[n] - show;
1701 self.hidden_length[n] = 0;
1702 true
1703 } else {
1704 false
1705 }
1706 }
1707}
1708
1709impl HandleEvent<crossterm::event::Event, Regular, Outcome> for SplitState {
1710 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
1711 event_flow!(
1712 return if self.is_focused() {
1713 if let Some(n) = self.focus_marker {
1714 match event {
1715 ct_event!(keycode press Left) => self.select_prev_split().into(),
1716 ct_event!(keycode press Right) => self.select_next_split().into(),
1717 ct_event!(keycode press Up) => self.select_prev_split().into(),
1718 ct_event!(keycode press Down) => self.select_next_split().into(),
1719
1720 ct_event!(keycode press CONTROL-Left) => self.move_split_left(n, 1).into(),
1721 ct_event!(keycode press CONTROL-Right) => {
1722 self.move_split_right(n, 1).into()
1723 }
1724 ct_event!(keycode press CONTROL-Up) => self.move_split_up(n, 1).into(),
1725 ct_event!(keycode press CONTROL-Down) => self.move_split_down(n, 1).into(),
1726 _ => Outcome::Continue,
1727 }
1728 } else {
1729 Outcome::Continue
1730 }
1731 } else {
1732 Outcome::Continue
1733 }
1734 );
1735
1736 self.handle(event, MouseOnly)
1737 }
1738}
1739
1740impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for SplitState {
1741 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
1742 match event {
1743 ct_event!(mouse any for m) if self.mouse.hover(&self.splitline_areas, m) => {
1744 Outcome::Changed
1745 }
1746 ct_event!(mouse any for m) => {
1747 let was_drag = self.mouse.drag.get();
1748 if self.mouse.drag(&self.splitline_areas, m) {
1749 if let Some(n) = self.mouse.drag.get() {
1750 self.set_screen_split_pos(n, self.mouse.pos_of(m)).into()
1751 } else {
1752 Outcome::Continue
1753 }
1754 } else {
1755 if was_drag.is_some() {
1757 Outcome::Changed
1758 } else {
1759 Outcome::Continue
1760 }
1761 }
1762 }
1763 _ => Outcome::Continue,
1764 }
1765 }
1766}
1767
1768pub fn handle_events(
1772 state: &mut SplitState,
1773 focus: bool,
1774 event: &crossterm::event::Event,
1775) -> Outcome {
1776 state.focus.set(focus);
1777 HandleEvent::handle(state, event, Regular)
1778}
1779
1780pub fn handle_mouse_events(state: &mut SplitState, event: &crossterm::event::Event) -> Outcome {
1782 HandleEvent::handle(state, event, MouseOnly)
1783}