1use crate::_private::NonExhaustive;
6use crate::util::{fill_buf_area, revert_style};
7use rat_event::util::MouseFlagsN;
8use rat_event::{HandleEvent, MouseOnly, Outcome, Regular, ct_event, flow};
9use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
10use rat_reloc::{RelocatableState, relocate_area, relocate_areas, relocate_positions};
11use ratatui::buffer::Buffer;
12use ratatui::layout::{Constraint, Direction, Flex, Layout, Position, Rect};
13use ratatui::prelude::BlockExt;
14use ratatui::style::Style;
15use ratatui::widgets::{Block, BorderType, StatefulWidget, Widget};
16use std::cmp::{max, min};
17use std::mem;
18use unicode_segmentation::UnicodeSegmentation;
19
20#[derive(Debug, Default, Clone)]
21pub struct Split<'a> {
43 direction: Direction,
45 constraints: Vec<Constraint>,
48 resize: SplitResize,
50
51 split_type: SplitType,
53 join_0: Option<BorderType>,
55 join_1: Option<BorderType>,
57 mark_offset: u16,
59 mark_0_char: Option<&'a str>,
61 mark_1_char: Option<&'a str>,
63
64 block: Option<Block<'a>>,
66 style: Style,
67 arrow_style: Option<Style>,
68 drag_style: Option<Style>,
69}
70
71#[derive(Debug, Clone)]
73pub struct SplitWidget<'a> {
74 split: Split<'a>,
75 mode: u8,
79}
80
81#[derive(Debug, Clone)]
83#[deprecated(since = "1.0.2", note = "no longer needed")]
84pub struct SplitOverlay<'a> {
85 split: Option<Split<'a>>,
86}
87
88#[derive(Debug)]
92pub struct SplitStyle {
93 pub style: Style,
95 pub arrow_style: Option<Style>,
97 pub drag_style: Option<Style>,
99
100 pub horizontal_mark: Option<&'static str>,
103 pub vertical_mark: Option<&'static str>,
106
107 pub block: Option<Block<'static>>,
109
110 pub non_exhaustive: NonExhaustive,
111}
112
113#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
115pub enum SplitType {
116 #[default]
119 FullEmpty,
120 FullPlain,
123 FullDouble,
126 FullThick,
129 FullQuadrantInside,
133 FullQuadrantOutside,
137 Scroll,
148 Widget,
155}
156
157#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
159pub enum SplitResize {
160 Neighbours,
163 #[default]
167 Full,
168}
169
170const SPLIT_WIDTH: u16 = 1;
171
172#[derive(Debug)]
174pub struct SplitState {
175 pub area: Rect,
178 pub inner: Rect,
181 pub widget_areas: Vec<Rect>,
185 pub splitline_areas: Vec<Rect>,
189 pub splitline_mark_position: Vec<Position>,
192 pub mark_offset: u16,
195
196 pub direction: Direction,
199 pub split_type: SplitType,
201 pub resize: SplitResize,
203
204 area_length: Vec<u16>,
209 hidden_length: Vec<u16>,
211
212 pub focus: FocusFlag,
215 pub focus_marker: Option<usize>,
219
220 pub mouse: MouseFlagsN,
223
224 pub non_exhaustive: NonExhaustive,
225}
226
227impl SplitType {
228 pub fn is_full(&self) -> bool {
229 use SplitType::*;
230 match self {
231 FullEmpty => true,
232 FullPlain => true,
233 FullDouble => true,
234 FullThick => true,
235 FullQuadrantInside => true,
236 FullQuadrantOutside => true,
237 Scroll => false,
238 Widget => false,
239 }
240 }
241}
242
243impl Default for SplitStyle {
244 fn default() -> Self {
245 Self {
246 style: Default::default(),
247 arrow_style: None,
248 drag_style: None,
249 horizontal_mark: None,
250 vertical_mark: None,
251 block: None,
252 non_exhaustive: NonExhaustive,
253 }
254 }
255}
256
257impl<'a> Split<'a> {
258 pub fn new() -> Self {
259 Self {
260 direction: Direction::Horizontal,
261 ..Default::default()
262 }
263 }
264
265 pub fn horizontal() -> Self {
266 Self {
267 direction: Direction::Horizontal,
268 ..Default::default()
269 }
270 }
271
272 pub fn vertical() -> Self {
273 Self {
274 direction: Direction::Horizontal,
275 ..Default::default()
276 }
277 }
278
279 pub fn constraints(mut self, constraints: impl IntoIterator<Item = Constraint>) -> Self {
285 self.constraints = constraints.into_iter().collect();
286 self
287 }
288
289 pub fn direction(mut self, direction: Direction) -> Self {
293 self.direction = direction;
294 self
295 }
296
297 pub fn split_type(mut self, split_type: SplitType) -> Self {
299 self.split_type = split_type;
300 self
301 }
302
303 pub fn resize(mut self, resize: SplitResize) -> Self {
305 self.resize = resize;
306 self
307 }
308
309 pub fn join(mut self, border: BorderType) -> Self {
313 self.join_0 = Some(border);
314 self.join_1 = Some(border);
315 self
316 }
317
318 pub fn join_0(mut self, border: BorderType) -> Self {
322 self.join_0 = Some(border);
323 self
324 }
325
326 pub fn join_1(mut self, border: BorderType) -> Self {
330 self.join_1 = Some(border);
331 self
332 }
333
334 pub fn block(mut self, block: Block<'a>) -> Self {
336 self.block = Some(block);
337 self
338 }
339
340 pub fn styles(mut self, styles: SplitStyle) -> Self {
342 self.style = styles.style;
343 if styles.drag_style.is_some() {
344 self.drag_style = styles.drag_style;
345 }
346 if styles.arrow_style.is_some() {
347 self.arrow_style = styles.arrow_style;
348 }
349 match self.direction {
350 Direction::Horizontal => {
351 if let Some(mark) = styles.horizontal_mark {
352 let mut g = mark.graphemes(true);
353 if let Some(g0) = g.next() {
354 self.mark_0_char = Some(g0);
355 }
356 if let Some(g1) = g.next() {
357 self.mark_1_char = Some(g1);
358 }
359 }
360 }
361 Direction::Vertical => {
362 if let Some(mark) = styles.vertical_mark {
363 let mut g = mark.graphemes(true);
364 if let Some(g0) = g.next() {
365 self.mark_0_char = Some(g0);
366 }
367 if let Some(g1) = g.next() {
368 self.mark_1_char = Some(g1);
369 }
370 }
371 }
372 }
373 if styles.block.is_some() {
374 self.block = styles.block;
375 }
376 self
377 }
378
379 pub fn style(mut self, style: Style) -> Self {
381 self.style = style;
382 self
383 }
384
385 pub fn arrow_style(mut self, style: Style) -> Self {
387 self.arrow_style = Some(style);
388 self
389 }
390
391 pub fn drag_style(mut self, style: Style) -> Self {
393 self.drag_style = Some(style);
394 self
395 }
396
397 pub fn mark_offset(mut self, offset: u16) -> Self {
399 self.mark_offset = offset;
400 self
401 }
402
403 pub fn mark_0(mut self, mark: &'a str) -> Self {
405 self.mark_0_char = Some(mark);
406 self
407 }
408
409 pub fn mark_1(mut self, mark: &'a str) -> Self {
411 self.mark_1_char = Some(mark);
412 self
413 }
414
415 pub fn into_widget_layout(
424 self,
425 area: Rect,
426 state: &mut SplitState,
427 ) -> (SplitWidget<'a>, Vec<Rect>) {
428 self.layout_split(area, state);
429
430 (
431 SplitWidget {
432 split: self,
433 mode: 1,
434 },
435 state.widget_areas.clone(),
436 )
437 }
438
439 #[deprecated(
441 since = "1.0.3",
442 note = "use into_widget_layout(). it has a simpler contract."
443 )]
444 #[allow(deprecated)]
445 pub fn into_widgets(self) -> (SplitWidget<'a>, SplitOverlay<'a>) {
446 if self.split_type == SplitType::Scroll {
447 (
448 SplitWidget {
449 split: self.clone(),
450 mode: 0,
451 },
452 SplitOverlay { split: Some(self) },
453 )
454 } else {
455 (
456 SplitWidget {
457 split: self,
458 mode: 0,
459 },
460 SplitOverlay { split: None },
461 )
462 }
463 }
464}
465
466impl Split<'_> {
467 fn layout_split(&self, area: Rect, state: &mut SplitState) {
470 state.area = area;
471 state.inner = self.block.inner_if_some(area);
472
473 let inner = state.inner;
475
476 let layout_change = state.area_length.len() != self.constraints.len();
477 let meta_change = state.direction != self.direction
478 || state.split_type != self.split_type
479 || state.mark_offset != self.mark_offset;
480
481 let old_len = |v: &Rect| {
482 if state.direction == Direction::Horizontal {
484 v.width
485 } else {
486 v.height
487 }
488 };
489 let new_len = |v: &Rect| {
490 if self.direction == Direction::Horizontal {
492 v.width
493 } else {
494 v.height
495 }
496 };
497
498 let new_split_areas = if layout_change {
499 let new_areas = Layout::new(self.direction, self.constraints.clone())
501 .flex(Flex::Legacy)
502 .split(inner);
503 Some(new_areas)
504 } else {
505 let old_length: u16 = state.area_length.iter().sum();
506 if meta_change || old_len(&inner) != old_length {
507 let mut constraints = Vec::new();
508 for i in 0..state.area_length.len() {
509 constraints.push(Constraint::Fill(state.area_length[i]));
510 }
511 let new_areas = Layout::new(self.direction, constraints).split(inner);
512 Some(new_areas)
513 } else {
514 None
515 }
516 };
517
518 if let Some(new_split_areas) = new_split_areas {
519 state.area_length.clear();
520 for v in new_split_areas.iter() {
521 state.area_length.push(new_len(v));
522 }
523 while state.hidden_length.len() < state.area_length.len() {
524 state.hidden_length.push(0);
525 }
526 while state.hidden_length.len() > state.area_length.len() {
527 state.hidden_length.pop();
528 }
529 }
530
531 state.direction = self.direction;
532 state.split_type = self.split_type;
533 state.resize = self.resize;
534 state.mark_offset = self.mark_offset;
535
536 self.layout_from_widths(state);
537 }
538
539 fn layout_from_widths(&self, state: &mut SplitState) {
540 state.widget_areas.clear();
542 state.splitline_areas.clear();
543 state.splitline_mark_position.clear();
544
545 let inner = state.inner;
546
547 let mut total = 0;
548 for length in state
549 .area_length
550 .iter()
551 .take(state.area_length.len().saturating_sub(1))
552 .copied()
553 {
554 let mut area = if self.direction == Direction::Horizontal {
555 Rect::new(inner.x + total, inner.y, length, inner.height)
556 } else {
557 Rect::new(inner.x, inner.y + total, inner.width, length)
558 };
559 let mut split = if self.direction == Direction::Horizontal {
560 Rect::new(
561 inner.x + total + length.saturating_sub(SPLIT_WIDTH),
562 inner.y,
563 min(1, length),
564 inner.height,
565 )
566 } else {
567 Rect::new(
568 inner.x,
569 inner.y + total + length.saturating_sub(SPLIT_WIDTH),
570 inner.width,
571 min(1, length),
572 )
573 };
574 let mut mark = if self.direction == Direction::Horizontal {
575 Position::new(
576 inner.x + total + length.saturating_sub(SPLIT_WIDTH),
577 inner.y + self.mark_offset,
578 )
579 } else {
580 Position::new(
581 inner.x + self.mark_offset,
582 inner.y + total + length.saturating_sub(SPLIT_WIDTH),
583 )
584 };
585
586 adjust_for_split_type(
587 self.direction,
588 self.split_type,
589 &mut area,
590 &mut split,
591 &mut mark,
592 );
593
594 state.widget_areas.push(area);
595 state.splitline_areas.push(split);
596 state.splitline_mark_position.push(mark);
597
598 total += length;
599 }
600 if let Some(length) = state.area_length.last().copied() {
601 let area = if self.direction == Direction::Horizontal {
602 Rect::new(inner.x + total, inner.y, length, inner.height)
603 } else {
604 Rect::new(inner.x, inner.y + total, inner.width, length)
605 };
606
607 state.widget_areas.push(area);
608 }
609
610 if let Some(test) = state.widget_areas.first() {
612 if self.direction == Direction::Horizontal {
613 if test.height != state.inner.height {
614 for r in &mut state.widget_areas {
615 r.height = state.inner.height;
616 }
617 for r in &mut state.splitline_areas {
618 r.height = state.inner.height;
619 }
620 }
621 } else {
622 if test.width != state.inner.width {
623 for r in &mut state.widget_areas {
624 r.width = state.inner.width;
625 }
626 for r in &mut state.splitline_areas {
627 r.width = state.inner.width;
628 }
629 }
630 }
631 }
632 }
633}
634
635fn adjust_for_split_type(
637 direction: Direction,
638 split_type: SplitType,
639 area: &mut Rect,
640 split: &mut Rect,
641 mark: &mut Position,
642) {
643 use Direction::*;
644 use SplitType::*;
645
646 match (direction, split_type) {
647 (
648 Horizontal,
649 FullEmpty | FullPlain | FullDouble | FullThick | FullQuadrantInside
650 | FullQuadrantOutside,
651 ) => {
652 area.width = area.width.saturating_sub(SPLIT_WIDTH);
653 }
654 (
655 Vertical,
656 FullEmpty | FullPlain | FullDouble | FullThick | FullQuadrantInside
657 | FullQuadrantOutside,
658 ) => {
659 area.height = area.height.saturating_sub(SPLIT_WIDTH);
660 }
661
662 (Horizontal, Scroll) => {
663 split.y = mark.y;
664 split.height = 2;
665 }
666 (Vertical, Scroll) => {
667 split.x = mark.x;
668 split.width = 2;
669 }
670
671 (Horizontal, Widget) => {}
672 (Vertical, Widget) => {}
673 }
674}
675
676impl Split<'_> {
677 fn get_mark_0(&self) -> &str {
678 if let Some(mark) = self.mark_0_char {
679 mark
680 } else if self.direction == Direction::Horizontal {
681 "<"
682 } else {
683 "^"
684 }
685 }
686
687 fn get_mark_1(&self) -> &str {
688 if let Some(mark) = self.mark_1_char {
689 mark
690 } else if self.direction == Direction::Horizontal {
691 ">"
692 } else {
693 "v"
694 }
695 }
696
697 fn get_fill_char(&self) -> Option<&str> {
698 use Direction::*;
699 use SplitType::*;
700
701 match (self.direction, self.split_type) {
702 (Horizontal, FullEmpty) => Some(" "),
703 (Vertical, FullEmpty) => Some(" "),
704 (Horizontal, FullPlain) => Some("\u{2502}"),
705 (Vertical, FullPlain) => Some("\u{2500}"),
706 (Horizontal, FullDouble) => Some("\u{2551}"),
707 (Vertical, FullDouble) => Some("\u{2550}"),
708 (Horizontal, FullThick) => Some("\u{2503}"),
709 (Vertical, FullThick) => Some("\u{2501}"),
710 (Horizontal, FullQuadrantInside) => Some("\u{258C}"),
711 (Vertical, FullQuadrantInside) => Some("\u{2580}"),
712 (Horizontal, FullQuadrantOutside) => Some("\u{2590}"),
713 (Vertical, FullQuadrantOutside) => Some("\u{2584}"),
714 (_, Scroll) => None,
715 (_, Widget) => None,
716 }
717 }
718
719 #[allow(unreachable_patterns)]
720 fn get_join_0(&self, split_area: Rect, state: &SplitState) -> Option<(Position, &str)> {
721 use Direction::*;
722 use SplitType::*;
723
724 let s: Option<&str> = if let Some(join_0) = self.join_0 {
725 match (self.direction, join_0, self.split_type) {
726 (
727 Horizontal,
728 BorderType::Plain | BorderType::Rounded,
729 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
730 ) => Some("\u{252C}"),
731 (
732 Vertical,
733 BorderType::Plain | BorderType::Rounded,
734 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
735 ) => Some("\u{251C}"),
736 (
737 Horizontal,
738 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
739 FullDouble,
740 ) => Some("\u{2565}"),
741 (
742 Vertical,
743 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
744 FullDouble,
745 ) => Some("\u{255E}"),
746 (Horizontal, BorderType::Plain | BorderType::Rounded, FullThick) => {
747 Some("\u{2530}")
748 }
749 (Vertical, BorderType::Plain | BorderType::Rounded, FullThick) => Some("\u{251D}"),
750
751 (
752 Horizontal,
753 BorderType::Double,
754 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
755 | Scroll,
756 ) => Some("\u{2564}"),
757 (
758 Vertical,
759 BorderType::Double,
760 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
761 | Scroll,
762 ) => Some("\u{255F}"),
763 (Horizontal, BorderType::Double, FullDouble) => Some("\u{2566}"),
764 (Vertical, BorderType::Double, FullDouble) => Some("\u{2560}"),
765
766 (
767 Horizontal,
768 BorderType::Thick,
769 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
770 ) => Some("\u{252F}"),
771 (
772 Vertical,
773 BorderType::Thick,
774 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
775 ) => Some("\u{2520}"),
776 (Horizontal, BorderType::Thick, FullThick) => Some("\u{2533}"),
777 (Vertical, BorderType::Thick, FullThick) => Some("\u{2523}"),
778
779 (Horizontal, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
780 (Vertical, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
781
782 (_, BorderType::QuadrantInside, _) => None,
783 (_, BorderType::QuadrantOutside, _) => None,
784
785 (_, _, Widget) => None,
786 (_, _, _) => None,
787 }
788 } else {
789 None
790 };
791
792 s.map(|s| {
793 (
794 match self.direction {
795 Horizontal => Position::new(split_area.x, state.area.y),
796 Vertical => Position::new(state.area.x, split_area.y),
797 },
798 s,
799 )
800 })
801 }
802
803 #[allow(unreachable_patterns)]
804 fn get_join_1(&self, split_area: Rect, state: &SplitState) -> Option<(Position, &str)> {
805 use Direction::*;
806 use SplitType::*;
807
808 let s: Option<&str> = if let Some(join_1) = self.join_1 {
809 match (self.direction, join_1, self.split_type) {
810 (
811 Horizontal,
812 BorderType::Plain | BorderType::Rounded,
813 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
814 ) => Some("\u{2534}"),
815 (
816 Vertical,
817 BorderType::Plain | BorderType::Rounded,
818 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
819 ) => Some("\u{2524}"),
820 (
821 Horizontal,
822 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
823 FullDouble,
824 ) => Some("\u{2568}"),
825 (
826 Vertical,
827 BorderType::Plain | BorderType::Rounded | BorderType::Thick,
828 FullDouble,
829 ) => Some("\u{2561}"),
830 (Horizontal, BorderType::Plain | BorderType::Rounded, FullThick) => {
831 Some("\u{2538}")
832 }
833 (Vertical, BorderType::Plain | BorderType::Rounded, FullThick) => Some("\u{2525}"),
834
835 (
836 Horizontal,
837 BorderType::Double,
838 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
839 | Scroll,
840 ) => Some("\u{2567}"),
841 (
842 Vertical,
843 BorderType::Double,
844 FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
845 | Scroll,
846 ) => Some("\u{2562}"),
847 (Horizontal, BorderType::Double, FullDouble) => Some("\u{2569}"),
848 (Vertical, BorderType::Double, FullDouble) => Some("\u{2563}"),
849
850 (
851 Horizontal,
852 BorderType::Thick,
853 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
854 ) => Some("\u{2537}"),
855 (
856 Vertical,
857 BorderType::Thick,
858 FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
859 ) => Some("\u{2528}"),
860 (Horizontal, BorderType::Thick, FullThick) => Some("\u{253B}"),
861 (Vertical, BorderType::Thick, FullThick) => Some("\u{252B}"),
862
863 (Horizontal, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
864 (Vertical, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
865
866 (_, BorderType::QuadrantInside, _) => None,
867 (_, BorderType::QuadrantOutside, _) => None,
868
869 (_, _, Widget) => None,
870 (_, _, _) => None,
871 }
872 } else {
873 None
874 };
875
876 s.map(|s| {
877 (
878 match self.direction {
879 Horizontal => Position::new(split_area.x, state.area.y + state.area.height - 1),
880 Vertical => Position::new(state.area.x + state.area.width - 1, split_area.y),
881 },
882 s,
883 )
884 })
885 }
886}
887
888impl<'a> StatefulWidget for &SplitWidget<'a> {
889 type State = SplitState;
890
891 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
892 if self.mode == 0 {
893 self.split.layout_split(area, state);
894 } else if self.mode == 1 {
895 } else {
897 unreachable!()
898 }
899
900 if state.is_focused() {
901 if state.focus_marker.is_none() {
902 state.focus_marker = Some(0);
903 }
904 } else {
905 state.focus_marker = None;
906 }
907
908 if self.mode == 0 {
909 if let Some(block) = &self.split.block {
910 block.render(area, buf);
911 } else {
912 buf.set_style(area, self.split.style);
913 }
914 } else if self.mode == 1 {
915 if let Some(mut block) = self.split.block.clone() {
916 block = block.style(Style::default());
917 block.render(area, buf);
918 }
919 } else {
920 unreachable!()
921 }
922
923 if self.mode == 0 {
924 if !matches!(self.split.split_type, SplitType::Widget | SplitType::Scroll) {
925 render_split(&self.split, buf, state);
926 }
927 } else if self.mode == 1 {
928 render_split(&self.split, buf, state);
929 } else {
930 unreachable!()
931 }
932 }
933}
934
935impl StatefulWidget for SplitWidget<'_> {
936 type State = SplitState;
937
938 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
939 if self.mode == 0 {
940 self.split.layout_split(area, state);
941 } else if self.mode == 1 {
942 } else {
944 unreachable!()
945 }
946
947 if state.is_focused() {
948 if state.focus_marker.is_none() {
949 state.focus_marker = Some(0);
950 }
951 } else {
952 state.focus_marker = None;
953 }
954
955 if self.mode == 0 {
956 if let Some(block) = &self.split.block {
957 block.render(area, buf);
958 } else {
959 buf.set_style(area, self.split.style);
960 }
961 } else if self.mode == 1 {
962 if let Some(mut block) = mem::take(&mut self.split.block) {
963 block = block.style(Style::default());
965 block.render(area, buf);
966 }
967 } else {
968 unreachable!()
969 }
970
971 if self.mode == 0 {
972 if !matches!(self.split.split_type, SplitType::Widget | SplitType::Scroll) {
973 render_split(&self.split, buf, state);
974 }
975 } else if self.mode == 1 {
976 render_split(&self.split, buf, state);
977 } else {
978 unreachable!()
979 }
980 }
981}
982
983#[allow(deprecated)]
984impl<'a> StatefulWidget for &SplitOverlay<'a> {
985 type State = SplitState;
986
987 fn render(self, _area: Rect, buf: &mut Buffer, state: &mut Self::State) {
988 if let Some(split) = &self.split {
990 if matches!(split.split_type, SplitType::Scroll) {
991 render_split(split, buf, state);
992 }
993 }
994 }
995}
996
997#[allow(deprecated)]
998impl StatefulWidget for SplitOverlay<'_> {
999 type State = SplitState;
1000
1001 fn render(self, _area: Rect, buf: &mut Buffer, state: &mut Self::State) {
1002 if let Some(split) = &self.split {
1004 if matches!(split.split_type, SplitType::Scroll) {
1005 render_split(split, buf, state);
1006 }
1007 }
1008 }
1009}
1010
1011fn render_split(split: &Split<'_>, buf: &mut Buffer, state: &mut SplitState) {
1012 for (n, split_area) in state.splitline_areas.iter().enumerate() {
1013 if split.direction == Direction::Horizontal {
1015 if split_area.width == 0 {
1016 continue;
1017 }
1018 } else {
1019 if split_area.height == 0 {
1020 continue;
1021 }
1022 }
1023
1024 let (style, arrow_style) = if Some(n) == state.mouse.drag.get()
1025 || Some(n) == state.focus_marker
1026 || Some(n) == state.mouse.hover.get()
1027 {
1028 if let Some(drag) = split.drag_style {
1029 (drag, drag)
1030 } else {
1031 (revert_style(split.style), revert_style(split.style))
1032 }
1033 } else {
1034 if let Some(arrow) = split.arrow_style {
1035 (split.style, arrow)
1036 } else {
1037 (split.style, split.style)
1038 }
1039 };
1040
1041 if let Some(fill) = split.get_fill_char() {
1042 fill_buf_area(buf, *split_area, fill, style);
1043 }
1044
1045 let mark = state.splitline_mark_position[n];
1046 if split.direction == Direction::Horizontal {
1047 if buf.area.contains((mark.x, mark.y).into()) {
1048 if let Some(cell) = buf.cell_mut((mark.x, mark.y)) {
1049 cell.set_style(arrow_style);
1050 cell.set_symbol(split.get_mark_0());
1051 }
1052 }
1053 if buf.area.contains((mark.x, mark.y + 1).into()) {
1054 if let Some(cell) = buf.cell_mut((mark.x, mark.y + 1)) {
1055 cell.set_style(arrow_style);
1056 cell.set_symbol(split.get_mark_1());
1057 }
1058 }
1059 } else {
1060 if let Some(cell) = buf.cell_mut((mark.x, mark.y)) {
1061 cell.set_style(arrow_style);
1062 cell.set_symbol(split.get_mark_0());
1063 }
1064 if let Some(cell) = buf.cell_mut((mark.x + 1, mark.y)) {
1065 cell.set_style(arrow_style);
1066 cell.set_symbol(split.get_mark_1());
1067 }
1068 }
1069
1070 if let Some((pos_0, c_0)) = split.get_join_0(*split_area, state) {
1071 if let Some(cell) = buf.cell_mut((pos_0.x, pos_0.y)) {
1072 cell.set_symbol(c_0);
1073 }
1074 }
1075 if let Some((pos_1, c_1)) = split.get_join_1(*split_area, state) {
1076 if let Some(cell) = buf.cell_mut((pos_1.x, pos_1.y)) {
1077 cell.set_symbol(c_1);
1078 }
1079 }
1080 }
1081}
1082
1083impl Default for SplitState {
1084 fn default() -> Self {
1085 Self {
1086 area: Default::default(),
1087 inner: Default::default(),
1088 widget_areas: Default::default(),
1089 splitline_areas: Default::default(),
1090 splitline_mark_position: Default::default(),
1091 mark_offset: Default::default(),
1092 direction: Default::default(),
1093 split_type: Default::default(),
1094 resize: Default::default(),
1095 area_length: Default::default(),
1096 hidden_length: Default::default(),
1097 focus: Default::default(),
1098 focus_marker: Default::default(),
1099 mouse: Default::default(),
1100 non_exhaustive: NonExhaustive,
1101 }
1102 }
1103}
1104
1105impl Clone for SplitState {
1106 fn clone(&self) -> Self {
1107 Self {
1108 area: self.area,
1109 inner: self.inner,
1110 widget_areas: self.widget_areas.clone(),
1111 splitline_areas: self.splitline_areas.clone(),
1112 splitline_mark_position: self.splitline_mark_position.clone(),
1113 mark_offset: self.mark_offset,
1114 direction: self.direction,
1115 split_type: self.split_type,
1116 resize: self.resize,
1117 area_length: self.area_length.clone(),
1118 hidden_length: self.hidden_length.clone(),
1119 focus: FocusFlag::named(self.focus.name()),
1120 focus_marker: self.focus_marker,
1121 mouse: Default::default(),
1122 non_exhaustive: NonExhaustive,
1123 }
1124 }
1125}
1126
1127impl HasFocus for SplitState {
1128 fn build(&self, builder: &mut FocusBuilder) {
1129 builder.leaf_widget(self);
1130 }
1131
1132 fn focus(&self) -> FocusFlag {
1133 self.focus.clone()
1134 }
1135
1136 fn area(&self) -> Rect {
1137 Rect::default()
1139 }
1140
1141 fn navigable(&self) -> Navigation {
1142 Navigation::Leave
1143 }
1144}
1145
1146impl RelocatableState for SplitState {
1147 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1148 self.area = relocate_area(self.area, shift, clip);
1149 self.inner = relocate_area(self.inner, shift, clip);
1150 relocate_areas(self.widget_areas.as_mut_slice(), shift, clip);
1151 relocate_areas(self.splitline_areas.as_mut_slice(), shift, clip);
1152 relocate_positions(self.splitline_mark_position.as_mut_slice(), shift, clip);
1153 }
1154}
1155
1156#[allow(clippy::len_without_is_empty)]
1157impl SplitState {
1158 pub fn new() -> Self {
1160 Self::default()
1161 }
1162
1163 pub fn named(name: &str) -> Self {
1165 Self {
1166 focus: FocusFlag::named(name),
1167 ..Self::default()
1168 }
1169 }
1170
1171 pub fn set_screen_split_pos(&mut self, n: usize, pos: (u16, u16)) -> bool {
1178 if self.is_hidden(n) {
1179 return false;
1180 }
1181 if self.direction == Direction::Horizontal {
1182 let pos = if pos.0 < self.inner.left() {
1183 0
1184 } else if pos.0 < self.inner.right() {
1185 pos.0 - self.inner.x
1186 } else {
1187 self.inner.width
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 } else {
1195 let pos = if pos.1 < self.inner.top() {
1196 0
1197 } else if pos.1 < self.inner.bottom() {
1198 pos.1 - self.inner.y
1199 } else {
1200 self.inner.height
1201 };
1202
1203 let split_pos = self.split_pos(n);
1204 self.set_split_pos(n, pos);
1205
1206 split_pos != self.split_pos(n)
1207 }
1208 }
1209
1210 pub fn move_split_left(&mut self, n: usize, delta: u16) -> bool {
1214 let split_pos = self.split_pos(n);
1215 self.set_split_pos(n, split_pos - delta);
1216
1217 split_pos != self.split_pos(n)
1218 }
1219
1220 pub fn move_split_right(&mut self, n: usize, delta: u16) -> bool {
1223 let split_pos = self.split_pos(n);
1224 self.set_split_pos(n, split_pos + delta);
1225
1226 split_pos != self.split_pos(n)
1227 }
1228
1229 pub fn move_split_up(&mut self, n: usize, delta: u16) -> bool {
1232 self.move_split_left(n, delta)
1233 }
1234
1235 pub fn move_split_down(&mut self, n: usize, delta: u16) -> bool {
1238 self.move_split_right(n, delta)
1239 }
1240
1241 pub fn select_next_split(&mut self) -> bool {
1243 if self.is_focused() {
1244 let n = self.focus_marker.unwrap_or_default();
1245 if n + 1 >= self.area_length.len().saturating_sub(1) {
1246 self.focus_marker = Some(0);
1247 } else {
1248 self.focus_marker = Some(n + 1);
1249 }
1250 true
1251 } else {
1252 false
1253 }
1254 }
1255
1256 pub fn select_prev_split(&mut self) -> bool {
1258 if self.is_focused() {
1259 let n = self.focus_marker.unwrap_or_default();
1260 if n == 0 {
1261 self.focus_marker =
1262 Some(self.area_length.len().saturating_sub(1).saturating_sub(1));
1263 } else {
1264 self.focus_marker = Some(n - 1);
1265 }
1266 true
1267 } else {
1268 false
1269 }
1270 }
1271
1272 pub fn len(&self) -> usize {
1274 self.area_length.len()
1275 }
1276
1277 pub fn area_lengths(&self) -> &[u16] {
1279 &self.area_length
1280 }
1281
1282 pub fn set_area_lengths(&mut self, lengths: Vec<u16>) {
1295 self.area_length = lengths;
1296 while self.hidden_length.len() < self.area_length.len() {
1297 self.hidden_length.push(0);
1298 }
1299 while self.hidden_length.len() > self.area_length.len() {
1300 self.hidden_length.pop();
1301 }
1302 }
1303
1304 pub fn hidden_lengths(&self) -> &[u16] {
1306 &self.hidden_length
1307 }
1308
1309 pub fn set_hidden_lengths(&mut self, hidden: Vec<u16>) {
1314 for i in 0..self.hidden_length.len() {
1315 if let Some(v) = hidden.get(i) {
1316 self.hidden_length[i] = *v;
1317 } else {
1318 self.hidden_length[i] = 0;
1319 }
1320 }
1321 }
1322
1323 pub fn area_len(&self, n: usize) -> u16 {
1333 if n >= self.area_length.len() {
1334 return 0;
1335 }
1336 self.area_length[n]
1337 }
1338
1339 pub fn total_area_len(&self) -> u16 {
1341 self.area_length.iter().sum()
1342 }
1343
1344 pub fn set_area_len(&mut self, n: usize, len: u16) {
1377 if n >= self.area_length.len() {
1378 return;
1379 }
1380 self.area_length[n] = len;
1381 self.hidden_length[n] = 0;
1382 }
1383
1384 pub fn split_pos(&self, n: usize) -> u16 {
1396 if n + 1 >= self.area_length.len() {
1397 return self.area_length.iter().sum();
1398 }
1399 self.area_length[..n + 1].iter().sum()
1400 }
1401
1402 pub fn set_split_pos(&mut self, n: usize, pos: u16) {
1418 if n + 1 >= self.area_length.len() {
1419 return;
1420 }
1421
1422 match self.resize {
1423 SplitResize::Neighbours => {
1424 self.set_split_pos_neighbour(n, pos);
1425 }
1426 SplitResize::Full => {
1427 self.set_split_pos_full(n, pos);
1428 }
1429 }
1430 }
1431
1432 fn set_split_pos_neighbour(&mut self, n: usize, pos: u16) {
1435 assert!(n + 1 < self.area_length.len());
1436
1437 let mut pos_vec = Vec::new();
1439 let mut pp = 0;
1440 for len in &self.area_length {
1441 pp += *len;
1442 pos_vec.push(pp);
1443 }
1444 let pos_count = pos_vec.len();
1446
1447 let (min_pos, max_pos) = if n == 0 {
1448 if n + 2 >= pos_count {
1449 (SPLIT_WIDTH, pos_vec[n + 1])
1450 } else {
1451 (SPLIT_WIDTH, pos_vec[n + 1] - SPLIT_WIDTH)
1452 }
1453 } else if n + 2 < pos_count {
1454 (pos_vec[n - 1] + 1, pos_vec[n + 1] - SPLIT_WIDTH)
1455 } else {
1456 (pos_vec[n - 1] + 1, pos_vec[n + 1])
1457 };
1458
1459 pos_vec[n] = min(max(min_pos, pos), max_pos);
1460
1461 for i in 0..pos_vec.len() {
1463 if i > 0 {
1464 self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1465 } else {
1466 self.area_length[i] = pos_vec[i];
1467 }
1468 }
1469 }
1470
1471 #[allow(clippy::needless_range_loop)]
1474 #[allow(clippy::comparison_chain)]
1475 fn set_split_pos_full(&mut self, n: usize, pos: u16) {
1476 assert!(n + 1 < self.area_length.len());
1477
1478 let total_len = self.total_area_len();
1479
1480 let mut pos_vec = Vec::new();
1482 let mut pp = 0;
1483 for len in &self.area_length {
1484 pp += *len;
1485 pos_vec.push(pp);
1486 }
1487 pos_vec.pop();
1489 let pos_count = pos_vec.len();
1490
1491 let mut min_pos = SPLIT_WIDTH;
1492 for i in 0..pos_vec.len() {
1493 if i < n {
1494 if self.area_length[i] == 0 {
1495 pos_vec[i] = min_pos;
1496 } else if self.hidden_length[i] != 0 {
1497 pos_vec[i] = min_pos;
1498 min_pos += SPLIT_WIDTH;
1499 } else {
1500 if pos_vec[i] >= pos {
1501 let rest_area_count = n - (i + 1);
1503 let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1504 pos_vec[i] = max(
1506 min_pos,
1507 pos.saturating_sub(SPLIT_WIDTH)
1508 .saturating_sub(rest_area_width),
1509 );
1510 min_pos += SPLIT_WIDTH;
1511 } else {
1512 }
1514 }
1515 } else if i == n {
1516 let rest_area_count = pos_count - (i + 1);
1518 let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1519 let rest_len = total_len - (min_pos + 1);
1520 let rest_len = rest_len - rest_area_width;
1522 let rest_len = rest_len + SPLIT_WIDTH;
1524
1525 let max_pos = min_pos + rest_len;
1526
1527 pos_vec[i] = min(max(min_pos, pos), max_pos);
1528
1529 min_pos = pos_vec[i] + SPLIT_WIDTH;
1530 } else {
1531 if self.area_length[i] == 0 {
1532 pos_vec[i] = min_pos;
1533 } else if self.hidden_length[i] != 0 {
1534 pos_vec[i] = min_pos;
1535 min_pos += SPLIT_WIDTH;
1536 } else {
1537 if pos_vec[i] <= pos {
1538 pos_vec[i] = min_pos;
1539 min_pos += SPLIT_WIDTH;
1540 } else {
1541 }
1543 }
1544 }
1545 }
1546
1547 for i in 0..pos_vec.len() {
1549 if i > 0 {
1550 self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1551 } else {
1552 self.area_length[i] = pos_vec[i];
1553 }
1554 }
1555 self.area_length[pos_count] = total_len - pos_vec[pos_count - 1];
1556 }
1557
1558 pub fn is_hidden(&self, n: usize) -> bool {
1560 self.hidden_length[n] > 0
1561 }
1562
1563 pub fn hide_split(&mut self, n: usize) -> bool {
1567 if self.hidden_length[n] == 0 {
1568 let mut hide = if n + 1 == self.area_length.len() {
1569 self.area_length[n]
1570 } else {
1571 self.area_length[n].saturating_sub(SPLIT_WIDTH)
1572 };
1573 for idx in n + 1..self.area_length.len() {
1574 if self.hidden_length[idx] == 0 {
1575 self.area_length[idx] += hide;
1576 hide = 0;
1577 break;
1578 }
1579 }
1580 if hide > 0 {
1581 for idx in (0..n).rev() {
1582 if self.hidden_length[idx] == 0 {
1583 self.area_length[idx] += hide;
1584 hide = 0;
1585 break;
1586 }
1587 }
1588 }
1589
1590 if hide > 0 {
1591 self.hidden_length[n] = 0;
1593 false
1594 } else {
1595 if n + 1 == self.area_length.len() {
1596 self.hidden_length[n] = self.area_length[n];
1597 self.area_length[n] = 0;
1598 } else {
1599 self.hidden_length[n] = self.area_length[n].saturating_sub(SPLIT_WIDTH);
1600 self.area_length[n] = 1;
1601 };
1602 true
1603 }
1604 } else {
1605 false
1606 }
1607 }
1608
1609 pub fn show_split(&mut self, n: usize) -> bool {
1613 let mut show = self.hidden_length[n];
1614 if show > 0 {
1615 for idx in n + 1..self.area_length.len() {
1616 if self.hidden_length[idx] == 0 {
1617 if self.area_length[idx] > show + SPLIT_WIDTH {
1619 self.area_length[idx] -= show;
1620 show = 0;
1621 } else if self.area_length[idx] > SPLIT_WIDTH {
1622 show -= self.area_length[idx] - SPLIT_WIDTH;
1623 self.area_length[idx] = SPLIT_WIDTH;
1624 }
1625 if show == 0 {
1626 break;
1627 }
1628 }
1629 }
1630 if show > 0 {
1631 for idx in (0..n).rev() {
1632 if self.hidden_length[idx] == 0 {
1633 if self.area_length[idx] > show + SPLIT_WIDTH {
1634 self.area_length[idx] -= show;
1635 show = 0;
1636 } else if self.area_length[idx] > SPLIT_WIDTH {
1637 show -= self.area_length[idx] - SPLIT_WIDTH;
1638 self.area_length[idx] = SPLIT_WIDTH;
1639 }
1640 if show == 0 {
1641 break;
1642 }
1643 }
1644 }
1645 }
1646
1647 self.area_length[n] += self.hidden_length[n] - show;
1648 self.hidden_length[n] = 0;
1649 true
1650 } else {
1651 false
1652 }
1653 }
1654}
1655
1656impl HandleEvent<crossterm::event::Event, Regular, Outcome> for SplitState {
1657 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
1658 flow!(if self.is_focused() {
1659 if let Some(n) = self.focus_marker {
1660 match event {
1661 ct_event!(keycode press Left) => self.move_split_left(n, 1).into(),
1662 ct_event!(keycode press Right) => self.move_split_right(n, 1).into(),
1663 ct_event!(keycode press Up) => self.move_split_up(n, 1).into(),
1664 ct_event!(keycode press Down) => self.move_split_down(n, 1).into(),
1665
1666 ct_event!(keycode press ALT-Left) => self.select_prev_split().into(),
1667 ct_event!(keycode press ALT-Right) => self.select_next_split().into(),
1668 ct_event!(keycode press ALT-Up) => self.select_prev_split().into(),
1669 ct_event!(keycode press ALT-Down) => self.select_next_split().into(),
1670 _ => Outcome::Continue,
1671 }
1672 } else {
1673 Outcome::Continue
1674 }
1675 } else {
1676 Outcome::Continue
1677 });
1678
1679 self.handle(event, MouseOnly)
1680 }
1681}
1682
1683impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for SplitState {
1684 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
1685 match event {
1686 ct_event!(mouse any for m) if self.mouse.hover(&self.splitline_areas, m) => {
1687 Outcome::Changed
1688 }
1689 ct_event!(mouse any for m) => {
1690 let was_drag = self.mouse.drag.get();
1691 if self.mouse.drag(&self.splitline_areas, m) {
1692 if let Some(n) = self.mouse.drag.get() {
1693 self.set_screen_split_pos(n, self.mouse.pos_of(m)).into()
1694 } else {
1695 Outcome::Continue
1696 }
1697 } else {
1698 if was_drag.is_some() {
1700 Outcome::Changed
1701 } else {
1702 Outcome::Continue
1703 }
1704 }
1705 }
1706 _ => Outcome::Continue,
1707 }
1708 }
1709}