1use crate::_private::NonExhaustive;
38use crate::util::{fill_buf_area, revert_style};
39use rat_event::util::MouseFlagsN;
40use rat_event::{HandleEvent, MouseOnly, Outcome, Regular, ct_event, flow};
41use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
42use rat_reloc::{RelocatableState, relocate_area, relocate_areas, relocate_positions};
43use ratatui::buffer::Buffer;
44use ratatui::layout::{Constraint, Direction, Flex, Layout, Position, Rect};
45use ratatui::prelude::BlockExt;
46use ratatui::style::Style;
47use ratatui::widgets::{Block, BorderType, StatefulWidget, Widget};
48use std::cmp::{max, min};
49use std::mem;
50use unicode_segmentation::UnicodeSegmentation;
51
52#[derive(Debug, Default, Clone)]
53pub struct Split<'a> {
75 direction: Direction,
77 constraints: Vec<Constraint>,
80 resize: SplitResize,
82
83 split_type: SplitType,
85 join_0: Option<BorderType>,
87 join_1: Option<BorderType>,
89 mark_offset: u16,
91 mark_0_char: Option<&'a str>,
93 mark_1_char: Option<&'a str>,
95
96 block: Option<Block<'a>>,
98 style: Style,
99 arrow_style: Option<Style>,
100 drag_style: Option<Style>,
101}
102
103#[derive(Debug, Clone)]
105pub struct SplitWidget<'a> {
106 split: Split<'a>,
107 mode: u8,
111}
112
113#[derive(Debug, Clone)]
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: self.focus.new_instance(),
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 let mut z = Self::default();
1153 z.focus = z.focus.with_name(name);
1154 z
1155 }
1156
1157 pub fn set_screen_split_pos(&mut self, n: usize, pos: (u16, u16)) -> bool {
1164 if self.is_hidden(n) {
1165 return false;
1166 }
1167 if self.direction == Direction::Horizontal {
1168 let pos = if pos.0 < self.inner.left() {
1169 0
1170 } else if pos.0 < self.inner.right() {
1171 pos.0 - self.inner.x
1172 } else {
1173 self.inner.width
1174 };
1175
1176 let split_pos = self.split_pos(n);
1177 self.set_split_pos(n, pos);
1178
1179 split_pos != self.split_pos(n)
1180 } else {
1181 let pos = if pos.1 < self.inner.top() {
1182 0
1183 } else if pos.1 < self.inner.bottom() {
1184 pos.1 - self.inner.y
1185 } else {
1186 self.inner.height
1187 };
1188
1189 let split_pos = self.split_pos(n);
1190 self.set_split_pos(n, pos);
1191
1192 split_pos != self.split_pos(n)
1193 }
1194 }
1195
1196 pub fn move_split_left(&mut self, n: usize, delta: u16) -> bool {
1200 let split_pos = self.split_pos(n);
1201 self.set_split_pos(n, split_pos - delta);
1202
1203 split_pos != self.split_pos(n)
1204 }
1205
1206 pub fn move_split_right(&mut self, n: usize, delta: u16) -> bool {
1209 let split_pos = self.split_pos(n);
1210 self.set_split_pos(n, split_pos + delta);
1211
1212 split_pos != self.split_pos(n)
1213 }
1214
1215 pub fn move_split_up(&mut self, n: usize, delta: u16) -> bool {
1218 self.move_split_left(n, delta)
1219 }
1220
1221 pub fn move_split_down(&mut self, n: usize, delta: u16) -> bool {
1224 self.move_split_right(n, delta)
1225 }
1226
1227 pub fn select_next_split(&mut self) -> bool {
1229 if self.is_focused() {
1230 let n = self.focus_marker.unwrap_or_default();
1231 if n + 1 >= self.area_length.len().saturating_sub(1) {
1232 self.focus_marker = Some(0);
1233 } else {
1234 self.focus_marker = Some(n + 1);
1235 }
1236 true
1237 } else {
1238 false
1239 }
1240 }
1241
1242 pub fn select_prev_split(&mut self) -> bool {
1244 if self.is_focused() {
1245 let n = self.focus_marker.unwrap_or_default();
1246 if n == 0 {
1247 self.focus_marker =
1248 Some(self.area_length.len().saturating_sub(1).saturating_sub(1));
1249 } else {
1250 self.focus_marker = Some(n - 1);
1251 }
1252 true
1253 } else {
1254 false
1255 }
1256 }
1257
1258 pub fn len(&self) -> usize {
1260 self.area_length.len()
1261 }
1262
1263 pub fn area_lengths(&self) -> &[u16] {
1265 &self.area_length
1266 }
1267
1268 pub fn set_area_lengths(&mut self, lengths: Vec<u16>) {
1281 self.area_length = lengths;
1282 while self.hidden_length.len() < self.area_length.len() {
1283 self.hidden_length.push(0);
1284 }
1285 while self.hidden_length.len() > self.area_length.len() {
1286 self.hidden_length.pop();
1287 }
1288 }
1289
1290 pub fn hidden_lengths(&self) -> &[u16] {
1292 &self.hidden_length
1293 }
1294
1295 pub fn set_hidden_lengths(&mut self, hidden: Vec<u16>) {
1300 for i in 0..self.hidden_length.len() {
1301 if let Some(v) = hidden.get(i) {
1302 self.hidden_length[i] = *v;
1303 } else {
1304 self.hidden_length[i] = 0;
1305 }
1306 }
1307 }
1308
1309 pub fn area_len(&self, n: usize) -> u16 {
1319 if n >= self.area_length.len() {
1320 return 0;
1321 }
1322 self.area_length[n]
1323 }
1324
1325 pub fn total_area_len(&self) -> u16 {
1327 self.area_length.iter().sum()
1328 }
1329
1330 pub fn set_area_len(&mut self, n: usize, len: u16) {
1363 if n >= self.area_length.len() {
1364 return;
1365 }
1366 self.area_length[n] = len;
1367 self.hidden_length[n] = 0;
1368 }
1369
1370 pub fn split_pos(&self, n: usize) -> u16 {
1382 if n + 1 >= self.area_length.len() {
1383 return self.area_length.iter().sum();
1384 }
1385 self.area_length[..n + 1].iter().sum()
1386 }
1387
1388 pub fn set_split_pos(&mut self, n: usize, pos: u16) {
1404 if n + 1 >= self.area_length.len() {
1405 return;
1406 }
1407
1408 match self.resize {
1409 SplitResize::Neighbours => {
1410 self.set_split_pos_neighbour(n, pos);
1411 }
1412 SplitResize::Full => {
1413 self.set_split_pos_full(n, pos);
1414 }
1415 }
1416 }
1417
1418 fn set_split_pos_neighbour(&mut self, n: usize, pos: u16) {
1421 assert!(n + 1 < self.area_length.len());
1422
1423 let mut pos_vec = Vec::new();
1425 let mut pp = 0;
1426 for len in &self.area_length {
1427 pp += *len;
1428 pos_vec.push(pp);
1429 }
1430 let pos_count = pos_vec.len();
1432
1433 let (min_pos, max_pos) = if n == 0 {
1434 if n + 2 >= pos_count {
1435 (SPLIT_WIDTH, pos_vec[n + 1])
1436 } else {
1437 (SPLIT_WIDTH, pos_vec[n + 1] - SPLIT_WIDTH)
1438 }
1439 } else if n + 2 < pos_count {
1440 (pos_vec[n - 1] + 1, pos_vec[n + 1] - SPLIT_WIDTH)
1441 } else {
1442 (pos_vec[n - 1] + 1, pos_vec[n + 1])
1443 };
1444
1445 pos_vec[n] = min(max(min_pos, pos), max_pos);
1446
1447 for i in 0..pos_vec.len() {
1449 if i > 0 {
1450 self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1451 } else {
1452 self.area_length[i] = pos_vec[i];
1453 }
1454 }
1455 }
1456
1457 #[allow(clippy::needless_range_loop)]
1460 #[allow(clippy::comparison_chain)]
1461 fn set_split_pos_full(&mut self, n: usize, pos: u16) {
1462 assert!(n + 1 < self.area_length.len());
1463
1464 let total_len = self.total_area_len();
1465
1466 let mut pos_vec = Vec::new();
1468 let mut pp = 0;
1469 for len in &self.area_length {
1470 pp += *len;
1471 pos_vec.push(pp);
1472 }
1473 pos_vec.pop();
1475 let pos_count = pos_vec.len();
1476
1477 let mut min_pos = SPLIT_WIDTH;
1478 for i in 0..pos_vec.len() {
1479 if i < n {
1480 if self.area_length[i] == 0 {
1481 pos_vec[i] = min_pos;
1482 } else if self.hidden_length[i] != 0 {
1483 pos_vec[i] = min_pos;
1484 min_pos += SPLIT_WIDTH;
1485 } else {
1486 if pos_vec[i] >= pos {
1487 let rest_area_count = n - (i + 1);
1489 let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1490 pos_vec[i] = max(
1492 min_pos,
1493 pos.saturating_sub(SPLIT_WIDTH)
1494 .saturating_sub(rest_area_width),
1495 );
1496 min_pos += SPLIT_WIDTH;
1497 } else {
1498 }
1500 }
1501 } else if i == n {
1502 let rest_area_count = pos_count - (i + 1);
1504 let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1505 let rest_len = total_len - (min_pos + 1);
1506 let rest_len = rest_len - rest_area_width;
1508 let rest_len = rest_len + SPLIT_WIDTH;
1510
1511 let max_pos = min_pos + rest_len;
1512
1513 pos_vec[i] = min(max(min_pos, pos), max_pos);
1514
1515 min_pos = pos_vec[i] + SPLIT_WIDTH;
1516 } else {
1517 if self.area_length[i] == 0 {
1518 pos_vec[i] = min_pos;
1519 } else if self.hidden_length[i] != 0 {
1520 pos_vec[i] = min_pos;
1521 min_pos += SPLIT_WIDTH;
1522 } else {
1523 if pos_vec[i] <= pos {
1524 pos_vec[i] = min_pos;
1525 min_pos += SPLIT_WIDTH;
1526 } else {
1527 }
1529 }
1530 }
1531 }
1532
1533 for i in 0..pos_vec.len() {
1535 if i > 0 {
1536 self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1537 } else {
1538 self.area_length[i] = pos_vec[i];
1539 }
1540 }
1541 self.area_length[pos_count] = total_len - pos_vec[pos_count - 1];
1542 }
1543
1544 pub fn is_hidden(&self, n: usize) -> bool {
1546 self.hidden_length[n] > 0
1547 }
1548
1549 pub fn hide_split(&mut self, n: usize) -> bool {
1553 if self.hidden_length[n] == 0 {
1554 let mut hide = if n + 1 == self.area_length.len() {
1555 self.area_length[n]
1556 } else {
1557 self.area_length[n].saturating_sub(SPLIT_WIDTH)
1558 };
1559 for idx in n + 1..self.area_length.len() {
1560 if self.hidden_length[idx] == 0 {
1561 self.area_length[idx] += hide;
1562 hide = 0;
1563 break;
1564 }
1565 }
1566 if hide > 0 {
1567 for idx in (0..n).rev() {
1568 if self.hidden_length[idx] == 0 {
1569 self.area_length[idx] += hide;
1570 hide = 0;
1571 break;
1572 }
1573 }
1574 }
1575
1576 if hide > 0 {
1577 self.hidden_length[n] = 0;
1579 false
1580 } else {
1581 if n + 1 == self.area_length.len() {
1582 self.hidden_length[n] = self.area_length[n];
1583 self.area_length[n] = 0;
1584 } else {
1585 self.hidden_length[n] = self.area_length[n].saturating_sub(SPLIT_WIDTH);
1586 self.area_length[n] = 1;
1587 };
1588 true
1589 }
1590 } else {
1591 false
1592 }
1593 }
1594
1595 pub fn show_split(&mut self, n: usize) -> bool {
1599 let mut show = self.hidden_length[n];
1600 if show > 0 {
1601 for idx in n + 1..self.area_length.len() {
1602 if self.hidden_length[idx] == 0 {
1603 if self.area_length[idx] > show + SPLIT_WIDTH {
1605 self.area_length[idx] -= show;
1606 show = 0;
1607 } else if self.area_length[idx] > SPLIT_WIDTH {
1608 show -= self.area_length[idx] - SPLIT_WIDTH;
1609 self.area_length[idx] = SPLIT_WIDTH;
1610 }
1611 if show == 0 {
1612 break;
1613 }
1614 }
1615 }
1616 if show > 0 {
1617 for idx in (0..n).rev() {
1618 if self.hidden_length[idx] == 0 {
1619 if self.area_length[idx] > show + SPLIT_WIDTH {
1620 self.area_length[idx] -= show;
1621 show = 0;
1622 } else if self.area_length[idx] > SPLIT_WIDTH {
1623 show -= self.area_length[idx] - SPLIT_WIDTH;
1624 self.area_length[idx] = SPLIT_WIDTH;
1625 }
1626 if show == 0 {
1627 break;
1628 }
1629 }
1630 }
1631 }
1632
1633 self.area_length[n] += self.hidden_length[n] - show;
1634 self.hidden_length[n] = 0;
1635 true
1636 } else {
1637 false
1638 }
1639 }
1640}
1641
1642impl HandleEvent<crossterm::event::Event, Regular, Outcome> for SplitState {
1643 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
1644 flow!(if self.is_focused() {
1645 if let Some(n) = self.focus_marker {
1646 match event {
1647 ct_event!(keycode press Left) => self.move_split_left(n, 1).into(),
1648 ct_event!(keycode press Right) => self.move_split_right(n, 1).into(),
1649 ct_event!(keycode press Up) => self.move_split_up(n, 1).into(),
1650 ct_event!(keycode press Down) => self.move_split_down(n, 1).into(),
1651
1652 ct_event!(keycode press ALT-Left) => self.select_prev_split().into(),
1653 ct_event!(keycode press ALT-Right) => self.select_next_split().into(),
1654 ct_event!(keycode press ALT-Up) => self.select_prev_split().into(),
1655 ct_event!(keycode press ALT-Down) => self.select_next_split().into(),
1656 _ => Outcome::Continue,
1657 }
1658 } else {
1659 Outcome::Continue
1660 }
1661 } else {
1662 Outcome::Continue
1663 });
1664
1665 self.handle(event, MouseOnly)
1666 }
1667}
1668
1669impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for SplitState {
1670 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
1671 match event {
1672 ct_event!(mouse any for m) if self.mouse.hover(&self.splitline_areas, m) => {
1673 Outcome::Changed
1674 }
1675 ct_event!(mouse any for m) => {
1676 let was_drag = self.mouse.drag.get();
1677 if self.mouse.drag(&self.splitline_areas, m) {
1678 if let Some(n) = self.mouse.drag.get() {
1679 self.set_screen_split_pos(n, self.mouse.pos_of(m)).into()
1680 } else {
1681 Outcome::Continue
1682 }
1683 } else {
1684 if was_drag.is_some() {
1686 Outcome::Changed
1687 } else {
1688 Outcome::Continue
1689 }
1690 }
1691 }
1692 _ => Outcome::Continue,
1693 }
1694 }
1695}
1696
1697pub fn handle_events(
1701 state: &mut SplitState,
1702 focus: bool,
1703 event: &crossterm::event::Event,
1704) -> Outcome {
1705 state.focus.set(focus);
1706 HandleEvent::handle(state, event, Regular)
1707}
1708
1709pub fn handle_mouse_events(state: &mut SplitState, event: &crossterm::event::Event) -> Outcome {
1711 HandleEvent::handle(state, event, MouseOnly)
1712}