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