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)]
117pub struct SplitStyle {
118 pub style: Style,
120 pub arrow_style: Option<Style>,
122 pub drag_style: Option<Style>,
124
125 pub horizontal_mark: Option<&'static str>,
128 pub vertical_mark: Option<&'static str>,
131
132 pub block: Option<Block<'static>>,
134
135 pub non_exhaustive: NonExhaustive,
136}
137
138#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
140pub enum SplitType {
141 #[default]
144 FullEmpty,
145 FullPlain,
148 FullDouble,
151 FullThick,
154 FullQuadrantInside,
158 FullQuadrantOutside,
162 Scroll,
173 Widget,
180}
181
182#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
184pub enum SplitResize {
185 Neighbours,
188 #[default]
192 Full,
193}
194
195const SPLIT_WIDTH: u16 = 1;
196
197#[derive(Debug)]
199pub struct SplitState {
200 pub area: Rect,
203 pub inner: Rect,
206 pub widget_areas: Vec<Rect>,
210 pub splitline_areas: Vec<Rect>,
214 pub splitline_mark_position: Vec<Position>,
217 pub mark_offset: u16,
220
221 pub direction: Direction,
224 pub split_type: SplitType,
226 pub resize: SplitResize,
228
229 area_length: Vec<u16>,
234 hidden_length: Vec<u16>,
236
237 pub focus: FocusFlag,
240 pub focus_marker: Option<usize>,
244
245 pub mouse: MouseFlagsN,
248
249 pub non_exhaustive: NonExhaustive,
250}
251
252impl SplitType {
253 pub fn is_full(&self) -> bool {
254 use SplitType::*;
255 match self {
256 FullEmpty => true,
257 FullPlain => true,
258 FullDouble => true,
259 FullThick => true,
260 FullQuadrantInside => true,
261 FullQuadrantOutside => true,
262 Scroll => false,
263 Widget => false,
264 }
265 }
266}
267
268impl Default for SplitStyle {
269 fn default() -> Self {
270 Self {
271 style: Default::default(),
272 arrow_style: None,
273 drag_style: None,
274 horizontal_mark: None,
275 vertical_mark: None,
276 block: None,
277 non_exhaustive: NonExhaustive,
278 }
279 }
280}
281
282impl<'a> Split<'a> {
283 pub fn new() -> Self {
284 Self {
285 direction: Direction::Horizontal,
286 ..Default::default()
287 }
288 }
289
290 pub fn horizontal() -> Self {
291 Self {
292 direction: Direction::Horizontal,
293 ..Default::default()
294 }
295 }
296
297 pub fn vertical() -> Self {
298 Self {
299 direction: Direction::Horizontal,
300 ..Default::default()
301 }
302 }
303
304 pub fn constraints(mut self, constraints: impl IntoIterator<Item = Constraint>) -> Self {
310 self.constraints = constraints.into_iter().collect();
311 self
312 }
313
314 pub fn direction(mut self, direction: Direction) -> Self {
318 self.direction = direction;
319 self
320 }
321
322 pub fn split_type(mut self, split_type: SplitType) -> Self {
324 self.split_type = split_type;
325 self
326 }
327
328 pub fn resize(mut self, resize: SplitResize) -> Self {
330 self.resize = resize;
331 self
332 }
333
334 pub fn join(mut self, border: BorderType) -> Self {
338 self.join_0 = Some(border);
339 self.join_1 = Some(border);
340 self
341 }
342
343 pub fn join_0(mut self, border: BorderType) -> Self {
347 self.join_0 = Some(border);
348 self
349 }
350
351 pub fn join_1(mut self, border: BorderType) -> Self {
355 self.join_1 = Some(border);
356 self
357 }
358
359 pub fn block(mut self, block: Block<'a>) -> Self {
361 self.block = Some(block);
362 self
363 }
364
365 pub fn styles(mut self, styles: SplitStyle) -> Self {
367 self.style = styles.style;
368 if styles.drag_style.is_some() {
369 self.drag_style = styles.drag_style;
370 }
371 if styles.arrow_style.is_some() {
372 self.arrow_style = styles.arrow_style;
373 }
374 match self.direction {
375 Direction::Horizontal => {
376 if let Some(mark) = styles.horizontal_mark {
377 let mut g = mark.graphemes(true);
378 if let Some(g0) = g.next() {
379 self.mark_0_char = Some(g0);
380 }
381 if let Some(g1) = g.next() {
382 self.mark_1_char = Some(g1);
383 }
384 }
385 }
386 Direction::Vertical => {
387 if let Some(mark) = styles.vertical_mark {
388 let mut g = mark.graphemes(true);
389 if let Some(g0) = g.next() {
390 self.mark_0_char = Some(g0);
391 }
392 if let Some(g1) = g.next() {
393 self.mark_1_char = Some(g1);
394 }
395 }
396 }
397 }
398 if styles.block.is_some() {
399 self.block = styles.block;
400 }
401 self
402 }
403
404 pub fn style(mut self, style: Style) -> Self {
406 self.style = style;
407 self
408 }
409
410 pub fn arrow_style(mut self, style: Style) -> Self {
412 self.arrow_style = Some(style);
413 self
414 }
415
416 pub fn drag_style(mut self, style: Style) -> Self {
418 self.drag_style = Some(style);
419 self
420 }
421
422 pub fn mark_offset(mut self, offset: u16) -> Self {
424 self.mark_offset = offset;
425 self
426 }
427
428 pub fn mark_0(mut self, mark: &'a str) -> Self {
430 self.mark_0_char = Some(mark);
431 self
432 }
433
434 pub fn mark_1(mut self, mark: &'a str) -> Self {
436 self.mark_1_char = Some(mark);
437 self
438 }
439
440 pub fn into_widget(self, area: Rect, state: &mut SplitState) -> SplitWidget<'a> {
448 self.layout_split(area, state);
449
450 SplitWidget {
451 split: self,
452 mode: 1,
453 }
454 }
455
456 pub fn into_widget_layout(
465 self,
466 area: Rect,
467 state: &mut SplitState,
468 ) -> (SplitWidget<'a>, Vec<Rect>) {
469 self.layout_split(area, state);
470
471 (
472 SplitWidget {
473 split: self,
474 mode: 1,
475 },
476 state.widget_areas.clone(),
477 )
478 }
479}
480
481impl Split<'_> {
482 fn layout_split(&self, area: Rect, state: &mut SplitState) {
485 state.area = area;
486 state.inner = self.block.inner_if_some(area);
487
488 let inner = state.inner;
490
491 let layout_change = state.area_length.len() != self.constraints.len();
492 let meta_change = state.direction != self.direction
493 || state.split_type != self.split_type
494 || state.mark_offset != self.mark_offset;
495
496 let old_len = |v: &Rect| {
497 if state.direction == Direction::Horizontal {
499 v.width
500 } else {
501 v.height
502 }
503 };
504 let new_len = |v: &Rect| {
505 if self.direction == Direction::Horizontal {
507 v.width
508 } else {
509 v.height
510 }
511 };
512
513 let new_split_areas = if layout_change {
514 let new_areas = Layout::new(self.direction, self.constraints.clone())
516 .flex(Flex::Legacy)
517 .split(inner);
518 Some(new_areas)
519 } else {
520 let old_length: u16 = state.area_length.iter().sum();
521 if meta_change || old_len(&inner) != old_length {
522 let mut constraints = Vec::new();
523 for i in 0..state.area_length.len() {
524 constraints.push(Constraint::Fill(state.area_length[i]));
525 }
526 let new_areas = Layout::new(self.direction, constraints).split(inner);
527 Some(new_areas)
528 } else {
529 None
530 }
531 };
532
533 if let Some(new_split_areas) = new_split_areas {
534 state.area_length.clear();
535 for v in new_split_areas.iter() {
536 state.area_length.push(new_len(v));
537 }
538 while state.hidden_length.len() < state.area_length.len() {
539 state.hidden_length.push(0);
540 }
541 while state.hidden_length.len() > state.area_length.len() {
542 state.hidden_length.pop();
543 }
544 }
545
546 state.direction = self.direction;
547 state.split_type = self.split_type;
548 state.resize = self.resize;
549 state.mark_offset = self.mark_offset;
550
551 self.layout_from_widths(state);
552 }
553
554 fn layout_from_widths(&self, state: &mut SplitState) {
555 state.widget_areas.clear();
557 state.splitline_areas.clear();
558 state.splitline_mark_position.clear();
559
560 let inner = state.inner;
561
562 let mut total = 0;
563 for length in state
564 .area_length
565 .iter()
566 .take(state.area_length.len().saturating_sub(1))
567 .copied()
568 {
569 let mut area = if self.direction == Direction::Horizontal {
570 Rect::new(inner.x + total, inner.y, length, inner.height)
571 } else {
572 Rect::new(inner.x, inner.y + total, inner.width, length)
573 };
574 let mut split = if self.direction == Direction::Horizontal {
575 Rect::new(
576 inner.x + total + length.saturating_sub(SPLIT_WIDTH),
577 inner.y,
578 min(1, length),
579 inner.height,
580 )
581 } else {
582 Rect::new(
583 inner.x,
584 inner.y + total + length.saturating_sub(SPLIT_WIDTH),
585 inner.width,
586 min(1, length),
587 )
588 };
589 let mut mark = if self.direction == Direction::Horizontal {
590 Position::new(
591 inner.x + total + length.saturating_sub(SPLIT_WIDTH),
592 inner.y + self.mark_offset,
593 )
594 } else {
595 Position::new(
596 inner.x + self.mark_offset,
597 inner.y + total + length.saturating_sub(SPLIT_WIDTH),
598 )
599 };
600
601 adjust_for_split_type(
602 self.direction,
603 self.split_type,
604 &mut area,
605 &mut split,
606 &mut mark,
607 );
608
609 state.widget_areas.push(area);
610 state.splitline_areas.push(split);
611 state.splitline_mark_position.push(mark);
612
613 total += length;
614 }
615 if let Some(length) = state.area_length.last().copied() {
616 let area = if self.direction == Direction::Horizontal {
617 Rect::new(inner.x + total, inner.y, length, inner.height)
618 } else {
619 Rect::new(inner.x, inner.y + total, inner.width, length)
620 };
621
622 state.widget_areas.push(area);
623 }
624
625 if let Some(test) = state.widget_areas.first() {
627 if self.direction == Direction::Horizontal {
628 if test.height != state.inner.height {
629 for r in &mut state.widget_areas {
630 r.height = state.inner.height;
631 }
632 for r in &mut state.splitline_areas {
633 r.height = state.inner.height;
634 }
635 }
636 } else {
637 if test.width != state.inner.width {
638 for r in &mut state.widget_areas {
639 r.width = state.inner.width;
640 }
641 for r in &mut state.splitline_areas {
642 r.width = state.inner.width;
643 }
644 }
645 }
646 }
647 }
648}
649
650fn adjust_for_split_type(
652 direction: Direction,
653 split_type: SplitType,
654 area: &mut Rect,
655 split: &mut Rect,
656 mark: &mut Position,
657) {
658 use Direction::*;
659 use SplitType::*;
660
661 match (direction, split_type) {
662 (
663 Horizontal,
664 FullEmpty | FullPlain | FullDouble | FullThick | FullQuadrantInside
665 | FullQuadrantOutside,
666 ) => {
667 area.width = area.width.saturating_sub(SPLIT_WIDTH);
668 }
669 (
670 Vertical,
671 FullEmpty | FullPlain | FullDouble | FullThick | FullQuadrantInside
672 | FullQuadrantOutside,
673 ) => {
674 area.height = area.height.saturating_sub(SPLIT_WIDTH);
675 }
676
677 (Horizontal, Scroll) => {
678 split.y = mark.y;
679 split.height = 2;
680 }
681 (Vertical, Scroll) => {
682 split.x = mark.x;
683 split.width = 2;
684 }
685
686 (Horizontal, Widget) => {}
687 (Vertical, Widget) => {}
688 }
689}
690
691impl Split<'_> {
692 fn get_mark_0(&self) -> &str {
693 if let Some(mark) = self.mark_0_char {
694 mark
695 } else if self.direction == Direction::Horizontal {
696 "<"
697 } else {
698 "^"
699 }
700 }
701
702 fn get_mark_1(&self) -> &str {
703 if let Some(mark) = self.mark_1_char {
704 mark
705 } else if self.direction == Direction::Horizontal {
706 ">"
707 } else {
708 "v"
709 }
710 }
711
712 fn get_fill_char(&self) -> Option<&str> {
713 use Direction::*;
714 use SplitType::*;
715
716 match (self.direction, self.split_type) {
717 (Horizontal, FullEmpty) => Some(" "),
718 (Vertical, FullEmpty) => Some(" "),
719 (Horizontal, FullPlain) => Some("\u{2502}"),
720 (Vertical, FullPlain) => Some("\u{2500}"),
721 (Horizontal, FullDouble) => Some("\u{2551}"),
722 (Vertical, FullDouble) => Some("\u{2550}"),
723 (Horizontal, FullThick) => Some("\u{2503}"),
724 (Vertical, FullThick) => Some("\u{2501}"),
725 (Horizontal, FullQuadrantInside) => Some("\u{258C}"),
726 (Vertical, FullQuadrantInside) => Some("\u{2580}"),
727 (Horizontal, FullQuadrantOutside) => Some("\u{2590}"),
728 (Vertical, FullQuadrantOutside) => Some("\u{2584}"),
729 (_, Scroll) => None,
730 (_, Widget) => None,
731 }
732 }
733
734 #[allow(unreachable_patterns)]
735 fn get_join_0(&self, split_area: Rect, state: &SplitState) -> Option<(Position, &str)> {
736 use Direction::*;
737 use SplitType::*;
738
739 let s: Option<&str> = if let Some(join_0) = self.join_0 {
740 match (self.direction, join_0, self.split_type) {
741 (
742 Horizontal,
743 BorderType::Plain | BorderType::Rounded,
744 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
745 ) => Some("\u{252C}"),
746 (
747 Vertical,
748 BorderType::Plain | BorderType::Rounded,
749 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
750 ) => Some("\u{251C}"),
751 (
752 Horizontal,
753 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
754 FullDouble,
755 ) => Some("\u{2565}"),
756 (
757 Vertical,
758 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
759 FullDouble,
760 ) => Some("\u{255E}"),
761 (Horizontal, BorderType::Plain | BorderType::Rounded, FullThick) => {
762 Some("\u{2530}")
763 }
764 (Vertical, BorderType::Plain | BorderType::Rounded, FullThick) => Some("\u{251D}"),
765
766 (
767 Horizontal,
768 BorderType::Double,
769 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
770 | Scroll,
771 ) => Some("\u{2564}"),
772 (
773 Vertical,
774 BorderType::Double,
775 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
776 | Scroll,
777 ) => Some("\u{255F}"),
778 (Horizontal, BorderType::Double, FullDouble) => Some("\u{2566}"),
779 (Vertical, BorderType::Double, FullDouble) => Some("\u{2560}"),
780
781 (
782 Horizontal,
783 BorderType::Thick,
784 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
785 ) => Some("\u{252F}"),
786 (
787 Vertical,
788 BorderType::Thick,
789 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
790 ) => Some("\u{2520}"),
791 (Horizontal, BorderType::Thick, FullThick) => Some("\u{2533}"),
792 (Vertical, BorderType::Thick, FullThick) => Some("\u{2523}"),
793
794 (Horizontal, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
795 (Vertical, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
796
797 (_, BorderType::QuadrantInside, _) => None,
798 (_, BorderType::QuadrantOutside, _) => None,
799
800 (_, _, Widget) => None,
801 (_, _, _) => None,
802 }
803 } else {
804 None
805 };
806
807 s.map(|s| {
808 (
809 match self.direction {
810 Horizontal => Position::new(split_area.x, state.area.y),
811 Vertical => Position::new(state.area.x, split_area.y),
812 },
813 s,
814 )
815 })
816 }
817
818 #[allow(unreachable_patterns)]
819 fn get_join_1(&self, split_area: Rect, state: &SplitState) -> Option<(Position, &str)> {
820 use Direction::*;
821 use SplitType::*;
822
823 let s: Option<&str> = if let Some(join_1) = self.join_1 {
824 match (self.direction, join_1, self.split_type) {
825 (
826 Horizontal,
827 BorderType::Plain | BorderType::Rounded,
828 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
829 ) => Some("\u{2534}"),
830 (
831 Vertical,
832 BorderType::Plain | BorderType::Rounded,
833 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
834 ) => Some("\u{2524}"),
835 (
836 Horizontal,
837 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
838 FullDouble,
839 ) => Some("\u{2568}"),
840 (
841 Vertical,
842 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
843 FullDouble,
844 ) => Some("\u{2561}"),
845 (Horizontal, BorderType::Plain | BorderType::Rounded, FullThick) => {
846 Some("\u{2538}")
847 }
848 (Vertical, BorderType::Plain | BorderType::Rounded, FullThick) => Some("\u{2525}"),
849
850 (
851 Horizontal,
852 BorderType::Double,
853 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
854 | Scroll,
855 ) => Some("\u{2567}"),
856 (
857 Vertical,
858 BorderType::Double,
859 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
860 | Scroll,
861 ) => Some("\u{2562}"),
862 (Horizontal, BorderType::Double, FullDouble) => Some("\u{2569}"),
863 (Vertical, BorderType::Double, FullDouble) => Some("\u{2563}"),
864
865 (
866 Horizontal,
867 BorderType::Thick,
868 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
869 ) => Some("\u{2537}"),
870 (
871 Vertical,
872 BorderType::Thick,
873 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
874 ) => Some("\u{2528}"),
875 (Horizontal, BorderType::Thick, FullThick) => Some("\u{253B}"),
876 (Vertical, BorderType::Thick, FullThick) => Some("\u{252B}"),
877
878 (Horizontal, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
879 (Vertical, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
880
881 (_, BorderType::QuadrantInside, _) => None,
882 (_, BorderType::QuadrantOutside, _) => None,
883
884 (_, _, Widget) => None,
885 (_, _, _) => None,
886 }
887 } else {
888 None
889 };
890
891 s.map(|s| {
892 (
893 match self.direction {
894 Horizontal => Position::new(split_area.x, state.area.y + state.area.height - 1),
895 Vertical => Position::new(state.area.x + state.area.width - 1, split_area.y),
896 },
897 s,
898 )
899 })
900 }
901}
902
903impl<'a> StatefulWidget for &SplitWidget<'a> {
904 type State = SplitState;
905
906 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
907 if self.mode == 0 {
908 self.split.layout_split(area, state);
909 } else if self.mode == 1 {
910 } else {
912 unreachable!()
913 }
914
915 if state.is_focused() {
916 if state.focus_marker.is_none() {
917 state.focus_marker = Some(0);
918 }
919 } else {
920 state.focus_marker = None;
921 }
922
923 if self.mode == 0 {
924 if let Some(block) = &self.split.block {
925 block.render(area, buf);
926 } else {
927 buf.set_style(area, self.split.style);
928 }
929 } else if self.mode == 1 {
930 if let Some(mut block) = self.split.block.clone() {
931 block = block.style(Style::default());
932 block.render(area, buf);
933 }
934 } else {
935 unreachable!()
936 }
937
938 if self.mode == 0 {
939 if !matches!(self.split.split_type, SplitType::Widget | SplitType::Scroll) {
940 render_split(&self.split, buf, state);
941 }
942 } else if self.mode == 1 {
943 render_split(&self.split, buf, state);
944 } else {
945 unreachable!()
946 }
947 }
948}
949
950impl StatefulWidget for SplitWidget<'_> {
951 type State = SplitState;
952
953 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
954 if self.mode == 0 {
955 self.split.layout_split(area, state);
956 } else if self.mode == 1 {
957 } else {
959 unreachable!()
960 }
961
962 if state.is_focused() {
963 if state.focus_marker.is_none() {
964 state.focus_marker = Some(0);
965 }
966 } else {
967 state.focus_marker = None;
968 }
969
970 if self.mode == 0 {
971 if let Some(block) = &self.split.block {
972 block.render(area, buf);
973 } else {
974 buf.set_style(area, self.split.style);
975 }
976 } else if self.mode == 1 {
977 if let Some(mut block) = mem::take(&mut self.split.block) {
978 block = block.style(Style::default());
980 block.render(area, buf);
981 }
982 } else {
983 unreachable!()
984 }
985
986 if self.mode == 0 {
987 if !matches!(self.split.split_type, SplitType::Widget | SplitType::Scroll) {
988 render_split(&self.split, buf, state);
989 }
990 } else if self.mode == 1 {
991 render_split(&self.split, buf, state);
992 } else {
993 unreachable!()
994 }
995 }
996}
997
998fn render_split(split: &Split<'_>, buf: &mut Buffer, state: &mut SplitState) {
999 for (n, split_area) in state.splitline_areas.iter().enumerate() {
1000 if split.direction == Direction::Horizontal {
1002 if split_area.width == 0 {
1003 continue;
1004 }
1005 } else {
1006 if split_area.height == 0 {
1007 continue;
1008 }
1009 }
1010
1011 let (style, arrow_style) = if Some(n) == state.mouse.drag.get()
1012 || Some(n) == state.focus_marker
1013 || Some(n) == state.mouse.hover.get()
1014 {
1015 if let Some(drag) = split.drag_style {
1016 (drag, drag)
1017 } else {
1018 (revert_style(split.style), revert_style(split.style))
1019 }
1020 } else {
1021 if let Some(arrow) = split.arrow_style {
1022 (split.style, arrow)
1023 } else {
1024 (split.style, split.style)
1025 }
1026 };
1027
1028 if let Some(fill) = split.get_fill_char() {
1029 fill_buf_area(buf, *split_area, fill, style);
1030 }
1031
1032 let mark = state.splitline_mark_position[n];
1033 if split.direction == Direction::Horizontal {
1034 if buf.area.contains((mark.x, mark.y).into()) {
1035 if let Some(cell) = buf.cell_mut((mark.x, mark.y)) {
1036 cell.set_style(arrow_style);
1037 cell.set_symbol(split.get_mark_0());
1038 }
1039 }
1040 if buf.area.contains((mark.x, mark.y + 1).into()) {
1041 if let Some(cell) = buf.cell_mut((mark.x, mark.y + 1)) {
1042 cell.set_style(arrow_style);
1043 cell.set_symbol(split.get_mark_1());
1044 }
1045 }
1046 } else {
1047 if let Some(cell) = buf.cell_mut((mark.x, mark.y)) {
1048 cell.set_style(arrow_style);
1049 cell.set_symbol(split.get_mark_0());
1050 }
1051 if let Some(cell) = buf.cell_mut((mark.x + 1, mark.y)) {
1052 cell.set_style(arrow_style);
1053 cell.set_symbol(split.get_mark_1());
1054 }
1055 }
1056
1057 if let Some((pos_0, c_0)) = split.get_join_0(*split_area, state) {
1058 if let Some(cell) = buf.cell_mut((pos_0.x, pos_0.y)) {
1059 cell.set_symbol(c_0);
1060 }
1061 }
1062 if let Some((pos_1, c_1)) = split.get_join_1(*split_area, state) {
1063 if let Some(cell) = buf.cell_mut((pos_1.x, pos_1.y)) {
1064 cell.set_symbol(c_1);
1065 }
1066 }
1067 }
1068}
1069
1070impl Default for SplitState {
1071 fn default() -> Self {
1072 Self {
1073 area: Default::default(),
1074 inner: Default::default(),
1075 widget_areas: Default::default(),
1076 splitline_areas: Default::default(),
1077 splitline_mark_position: Default::default(),
1078 mark_offset: Default::default(),
1079 direction: Default::default(),
1080 split_type: Default::default(),
1081 resize: Default::default(),
1082 area_length: Default::default(),
1083 hidden_length: Default::default(),
1084 focus: Default::default(),
1085 focus_marker: Default::default(),
1086 mouse: Default::default(),
1087 non_exhaustive: NonExhaustive,
1088 }
1089 }
1090}
1091
1092impl Clone for SplitState {
1093 fn clone(&self) -> Self {
1094 Self {
1095 area: self.area,
1096 inner: self.inner,
1097 widget_areas: self.widget_areas.clone(),
1098 splitline_areas: self.splitline_areas.clone(),
1099 splitline_mark_position: self.splitline_mark_position.clone(),
1100 mark_offset: self.mark_offset,
1101 direction: self.direction,
1102 split_type: self.split_type,
1103 resize: self.resize,
1104 area_length: self.area_length.clone(),
1105 hidden_length: self.hidden_length.clone(),
1106 focus: FocusFlag::named(self.focus.name()),
1107 focus_marker: self.focus_marker,
1108 mouse: Default::default(),
1109 non_exhaustive: NonExhaustive,
1110 }
1111 }
1112}
1113
1114impl HasFocus for SplitState {
1115 fn build(&self, builder: &mut FocusBuilder) {
1116 builder.leaf_widget(self);
1117 }
1118
1119 fn focus(&self) -> FocusFlag {
1120 self.focus.clone()
1121 }
1122
1123 fn area(&self) -> Rect {
1124 Rect::default()
1126 }
1127
1128 fn navigable(&self) -> Navigation {
1129 Navigation::Leave
1130 }
1131}
1132
1133impl RelocatableState for SplitState {
1134 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1135 self.area = relocate_area(self.area, shift, clip);
1136 self.inner = relocate_area(self.inner, shift, clip);
1137 relocate_areas(self.widget_areas.as_mut_slice(), shift, clip);
1138 relocate_areas(self.splitline_areas.as_mut_slice(), shift, clip);
1139 relocate_positions(self.splitline_mark_position.as_mut_slice(), shift, clip);
1140 }
1141}
1142
1143#[allow(clippy::len_without_is_empty)]
1144impl SplitState {
1145 pub fn new() -> Self {
1147 Self::default()
1148 }
1149
1150 pub fn named(name: &str) -> Self {
1152 Self {
1153 focus: FocusFlag::named(name),
1154 ..Self::default()
1155 }
1156 }
1157
1158 pub fn set_screen_split_pos(&mut self, n: usize, pos: (u16, u16)) -> bool {
1165 if self.is_hidden(n) {
1166 return false;
1167 }
1168 if self.direction == Direction::Horizontal {
1169 let pos = if pos.0 < self.inner.left() {
1170 0
1171 } else if pos.0 < self.inner.right() {
1172 pos.0 - self.inner.x
1173 } else {
1174 self.inner.width
1175 };
1176
1177 let split_pos = self.split_pos(n);
1178 self.set_split_pos(n, pos);
1179
1180 split_pos != self.split_pos(n)
1181 } else {
1182 let pos = if pos.1 < self.inner.top() {
1183 0
1184 } else if pos.1 < self.inner.bottom() {
1185 pos.1 - self.inner.y
1186 } else {
1187 self.inner.height
1188 };
1189
1190 let split_pos = self.split_pos(n);
1191 self.set_split_pos(n, pos);
1192
1193 split_pos != self.split_pos(n)
1194 }
1195 }
1196
1197 pub fn move_split_left(&mut self, n: usize, delta: u16) -> bool {
1201 let split_pos = self.split_pos(n);
1202 self.set_split_pos(n, split_pos - delta);
1203
1204 split_pos != self.split_pos(n)
1205 }
1206
1207 pub fn move_split_right(&mut self, n: usize, delta: u16) -> bool {
1210 let split_pos = self.split_pos(n);
1211 self.set_split_pos(n, split_pos + delta);
1212
1213 split_pos != self.split_pos(n)
1214 }
1215
1216 pub fn move_split_up(&mut self, n: usize, delta: u16) -> bool {
1219 self.move_split_left(n, delta)
1220 }
1221
1222 pub fn move_split_down(&mut self, n: usize, delta: u16) -> bool {
1225 self.move_split_right(n, delta)
1226 }
1227
1228 pub fn select_next_split(&mut self) -> bool {
1230 if self.is_focused() {
1231 let n = self.focus_marker.unwrap_or_default();
1232 if n + 1 >= self.area_length.len().saturating_sub(1) {
1233 self.focus_marker = Some(0);
1234 } else {
1235 self.focus_marker = Some(n + 1);
1236 }
1237 true
1238 } else {
1239 false
1240 }
1241 }
1242
1243 pub fn select_prev_split(&mut self) -> bool {
1245 if self.is_focused() {
1246 let n = self.focus_marker.unwrap_or_default();
1247 if n == 0 {
1248 self.focus_marker =
1249 Some(self.area_length.len().saturating_sub(1).saturating_sub(1));
1250 } else {
1251 self.focus_marker = Some(n - 1);
1252 }
1253 true
1254 } else {
1255 false
1256 }
1257 }
1258
1259 pub fn len(&self) -> usize {
1261 self.area_length.len()
1262 }
1263
1264 pub fn area_lengths(&self) -> &[u16] {
1266 &self.area_length
1267 }
1268
1269 pub fn set_area_lengths(&mut self, lengths: Vec<u16>) {
1282 self.area_length = lengths;
1283 while self.hidden_length.len() < self.area_length.len() {
1284 self.hidden_length.push(0);
1285 }
1286 while self.hidden_length.len() > self.area_length.len() {
1287 self.hidden_length.pop();
1288 }
1289 }
1290
1291 pub fn hidden_lengths(&self) -> &[u16] {
1293 &self.hidden_length
1294 }
1295
1296 pub fn set_hidden_lengths(&mut self, hidden: Vec<u16>) {
1301 for i in 0..self.hidden_length.len() {
1302 if let Some(v) = hidden.get(i) {
1303 self.hidden_length[i] = *v;
1304 } else {
1305 self.hidden_length[i] = 0;
1306 }
1307 }
1308 }
1309
1310 pub fn area_len(&self, n: usize) -> u16 {
1320 if n >= self.area_length.len() {
1321 return 0;
1322 }
1323 self.area_length[n]
1324 }
1325
1326 pub fn total_area_len(&self) -> u16 {
1328 self.area_length.iter().sum()
1329 }
1330
1331 pub fn set_area_len(&mut self, n: usize, len: u16) {
1364 if n >= self.area_length.len() {
1365 return;
1366 }
1367 self.area_length[n] = len;
1368 self.hidden_length[n] = 0;
1369 }
1370
1371 pub fn split_pos(&self, n: usize) -> u16 {
1383 if n + 1 >= self.area_length.len() {
1384 return self.area_length.iter().sum();
1385 }
1386 self.area_length[..n + 1].iter().sum()
1387 }
1388
1389 pub fn set_split_pos(&mut self, n: usize, pos: u16) {
1405 if n + 1 >= self.area_length.len() {
1406 return;
1407 }
1408
1409 match self.resize {
1410 SplitResize::Neighbours => {
1411 self.set_split_pos_neighbour(n, pos);
1412 }
1413 SplitResize::Full => {
1414 self.set_split_pos_full(n, pos);
1415 }
1416 }
1417 }
1418
1419 fn set_split_pos_neighbour(&mut self, n: usize, pos: u16) {
1422 assert!(n + 1 < self.area_length.len());
1423
1424 let mut pos_vec = Vec::new();
1426 let mut pp = 0;
1427 for len in &self.area_length {
1428 pp += *len;
1429 pos_vec.push(pp);
1430 }
1431 let pos_count = pos_vec.len();
1433
1434 let (min_pos, max_pos) = if n == 0 {
1435 if n + 2 >= pos_count {
1436 (SPLIT_WIDTH, pos_vec[n + 1])
1437 } else {
1438 (SPLIT_WIDTH, pos_vec[n + 1] - SPLIT_WIDTH)
1439 }
1440 } else if n + 2 < pos_count {
1441 (pos_vec[n - 1] + 1, pos_vec[n + 1] - SPLIT_WIDTH)
1442 } else {
1443 (pos_vec[n - 1] + 1, pos_vec[n + 1])
1444 };
1445
1446 pos_vec[n] = min(max(min_pos, pos), max_pos);
1447
1448 for i in 0..pos_vec.len() {
1450 if i > 0 {
1451 self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1452 } else {
1453 self.area_length[i] = pos_vec[i];
1454 }
1455 }
1456 }
1457
1458 #[allow(clippy::needless_range_loop)]
1461 #[allow(clippy::comparison_chain)]
1462 fn set_split_pos_full(&mut self, n: usize, pos: u16) {
1463 assert!(n + 1 < self.area_length.len());
1464
1465 let total_len = self.total_area_len();
1466
1467 let mut pos_vec = Vec::new();
1469 let mut pp = 0;
1470 for len in &self.area_length {
1471 pp += *len;
1472 pos_vec.push(pp);
1473 }
1474 pos_vec.pop();
1476 let pos_count = pos_vec.len();
1477
1478 let mut min_pos = SPLIT_WIDTH;
1479 for i in 0..pos_vec.len() {
1480 if i < n {
1481 if self.area_length[i] == 0 {
1482 pos_vec[i] = min_pos;
1483 } else if self.hidden_length[i] != 0 {
1484 pos_vec[i] = min_pos;
1485 min_pos += SPLIT_WIDTH;
1486 } else {
1487 if pos_vec[i] >= pos {
1488 let rest_area_count = n - (i + 1);
1490 let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1491 pos_vec[i] = max(
1493 min_pos,
1494 pos.saturating_sub(SPLIT_WIDTH)
1495 .saturating_sub(rest_area_width),
1496 );
1497 min_pos += SPLIT_WIDTH;
1498 } else {
1499 }
1501 }
1502 } else if i == n {
1503 let rest_area_count = pos_count - (i + 1);
1505 let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1506 let rest_len = total_len - (min_pos + 1);
1507 let rest_len = rest_len - rest_area_width;
1509 let rest_len = rest_len + SPLIT_WIDTH;
1511
1512 let max_pos = min_pos + rest_len;
1513
1514 pos_vec[i] = min(max(min_pos, pos), max_pos);
1515
1516 min_pos = pos_vec[i] + SPLIT_WIDTH;
1517 } else {
1518 if self.area_length[i] == 0 {
1519 pos_vec[i] = min_pos;
1520 } else if self.hidden_length[i] != 0 {
1521 pos_vec[i] = min_pos;
1522 min_pos += SPLIT_WIDTH;
1523 } else {
1524 if pos_vec[i] <= pos {
1525 pos_vec[i] = min_pos;
1526 min_pos += SPLIT_WIDTH;
1527 } else {
1528 }
1530 }
1531 }
1532 }
1533
1534 for i in 0..pos_vec.len() {
1536 if i > 0 {
1537 self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1538 } else {
1539 self.area_length[i] = pos_vec[i];
1540 }
1541 }
1542 self.area_length[pos_count] = total_len - pos_vec[pos_count - 1];
1543 }
1544
1545 pub fn is_hidden(&self, n: usize) -> bool {
1547 self.hidden_length[n] > 0
1548 }
1549
1550 pub fn hide_split(&mut self, n: usize) -> bool {
1554 if self.hidden_length[n] == 0 {
1555 let mut hide = if n + 1 == self.area_length.len() {
1556 self.area_length[n]
1557 } else {
1558 self.area_length[n].saturating_sub(SPLIT_WIDTH)
1559 };
1560 for idx in n + 1..self.area_length.len() {
1561 if self.hidden_length[idx] == 0 {
1562 self.area_length[idx] += hide;
1563 hide = 0;
1564 break;
1565 }
1566 }
1567 if hide > 0 {
1568 for idx in (0..n).rev() {
1569 if self.hidden_length[idx] == 0 {
1570 self.area_length[idx] += hide;
1571 hide = 0;
1572 break;
1573 }
1574 }
1575 }
1576
1577 if hide > 0 {
1578 self.hidden_length[n] = 0;
1580 false
1581 } else {
1582 if n + 1 == self.area_length.len() {
1583 self.hidden_length[n] = self.area_length[n];
1584 self.area_length[n] = 0;
1585 } else {
1586 self.hidden_length[n] = self.area_length[n].saturating_sub(SPLIT_WIDTH);
1587 self.area_length[n] = 1;
1588 };
1589 true
1590 }
1591 } else {
1592 false
1593 }
1594 }
1595
1596 pub fn show_split(&mut self, n: usize) -> bool {
1600 let mut show = self.hidden_length[n];
1601 if show > 0 {
1602 for idx in n + 1..self.area_length.len() {
1603 if self.hidden_length[idx] == 0 {
1604 if self.area_length[idx] > show + SPLIT_WIDTH {
1606 self.area_length[idx] -= show;
1607 show = 0;
1608 } else if self.area_length[idx] > SPLIT_WIDTH {
1609 show -= self.area_length[idx] - SPLIT_WIDTH;
1610 self.area_length[idx] = SPLIT_WIDTH;
1611 }
1612 if show == 0 {
1613 break;
1614 }
1615 }
1616 }
1617 if show > 0 {
1618 for idx in (0..n).rev() {
1619 if self.hidden_length[idx] == 0 {
1620 if self.area_length[idx] > show + SPLIT_WIDTH {
1621 self.area_length[idx] -= show;
1622 show = 0;
1623 } else if self.area_length[idx] > SPLIT_WIDTH {
1624 show -= self.area_length[idx] - SPLIT_WIDTH;
1625 self.area_length[idx] = SPLIT_WIDTH;
1626 }
1627 if show == 0 {
1628 break;
1629 }
1630 }
1631 }
1632 }
1633
1634 self.area_length[n] += self.hidden_length[n] - show;
1635 self.hidden_length[n] = 0;
1636 true
1637 } else {
1638 false
1639 }
1640 }
1641}
1642
1643impl HandleEvent<crossterm::event::Event, Regular, Outcome> for SplitState {
1644 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
1645 flow!(if self.is_focused() {
1646 if let Some(n) = self.focus_marker {
1647 match event {
1648 ct_event!(keycode press Left) => self.move_split_left(n, 1).into(),
1649 ct_event!(keycode press Right) => self.move_split_right(n, 1).into(),
1650 ct_event!(keycode press Up) => self.move_split_up(n, 1).into(),
1651 ct_event!(keycode press Down) => self.move_split_down(n, 1).into(),
1652
1653 ct_event!(keycode press ALT-Left) => self.select_prev_split().into(),
1654 ct_event!(keycode press ALT-Right) => self.select_next_split().into(),
1655 ct_event!(keycode press ALT-Up) => self.select_prev_split().into(),
1656 ct_event!(keycode press ALT-Down) => self.select_next_split().into(),
1657 _ => Outcome::Continue,
1658 }
1659 } else {
1660 Outcome::Continue
1661 }
1662 } else {
1663 Outcome::Continue
1664 });
1665
1666 self.handle(event, MouseOnly)
1667 }
1668}
1669
1670impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for SplitState {
1671 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
1672 match event {
1673 ct_event!(mouse any for m) if self.mouse.hover(&self.splitline_areas, m) => {
1674 Outcome::Changed
1675 }
1676 ct_event!(mouse any for m) => {
1677 let was_drag = self.mouse.drag.get();
1678 if self.mouse.drag(&self.splitline_areas, m) {
1679 if let Some(n) = self.mouse.drag.get() {
1680 self.set_screen_split_pos(n, self.mouse.pos_of(m)).into()
1681 } else {
1682 Outcome::Continue
1683 }
1684 } else {
1685 if was_drag.is_some() {
1687 Outcome::Changed
1688 } else {
1689 Outcome::Continue
1690 }
1691 }
1692 }
1693 _ => Outcome::Continue,
1694 }
1695 }
1696}