rat_widget/
splitter.rs

1//!
2//! Vertical and horizontal multiple split.
3//!
4
5use 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)]
21/// Splits the area in multiple parts and renders an UI that
22/// allows changing the sizes.
23///
24/// * Can hide/show regions.
25/// * Resize
26///     * neighbours only
27///     * all regions
28/// * Horizontal / vertical split. Choose one.
29///
30/// The widget doesn't hold references to the content widgets,
31/// instead it gives back the regions where the content can be
32/// rendered.
33///
34/// 1. Construct the Split.
35/// 2. Call [Split::into_widget_layout] to create the actual
36///    widget and the layout of the regions.
37/// 3. Render the content areas.
38/// 4. Render the split widget last. There are options that
39///    will render above the widgets and use part of the content
40///    area.
41///
42pub struct Split<'a> {
43    // direction
44    direction: Direction,
45    // start constraints. used when
46    // there are no widths in the state.
47    constraints: Vec<Constraint>,
48    // resize options
49    resize: SplitResize,
50
51    // split rendering
52    split_type: SplitType,
53    // joiner left/top
54    join_0: Option<BorderType>,
55    // joiner right/bottom
56    join_1: Option<BorderType>,
57    // offset from left/top for the split-mark
58    mark_offset: u16,
59    // mark char 1
60    mark_0_char: Option<&'a str>,
61    // mark char 2
62    mark_1_char: Option<&'a str>,
63
64    // styling
65    block: Option<Block<'a>>,
66    style: Style,
67    arrow_style: Option<Style>,
68    drag_style: Option<Style>,
69}
70
71/// Primary widget for rendering the Split.
72#[derive(Debug, Clone)]
73pub struct SplitWidget<'a> {
74    split: Split<'a>,
75    // internal mode:
76    // 0 - legacy
77    // 1 - used for into_widget_layout()
78    mode: u8,
79}
80
81/// Secondary struct for rendering the overlay parts of the Split.
82#[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///
89/// Combined styles for the Split.
90///
91#[derive(Debug)]
92pub struct SplitStyle {
93    /// Base style
94    pub style: Style,
95    /// Arrow style.
96    pub arrow_style: Option<Style>,
97    /// Style while dragging.
98    pub drag_style: Option<Style>,
99
100    /// Marker for a horizontal split.
101    /// Only the first 2 chars are used.
102    pub horizontal_mark: Option<&'static str>,
103    /// Marker for a vertical split.
104    /// Only the first 2 chars are used.
105    pub vertical_mark: Option<&'static str>,
106
107    /// Block
108    pub block: Option<Block<'static>>,
109
110    pub non_exhaustive: NonExhaustive,
111}
112
113/// Render variants for the splitter.
114#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
115pub enum SplitType {
116    /// Render a full splitter between the widgets. Reduces the area for
117    /// each widget. Renders a blank border.
118    #[default]
119    FullEmpty,
120    /// Render a full splitter between the widgets. Reduces the area for
121    /// each widget. Renders a plain line border.
122    FullPlain,
123    /// Render a full splitter between the widgets. Reduces the area for
124    /// each widget. Renders a double line border.
125    FullDouble,
126    /// Render a full splitter between the widgets. Reduces the area for
127    /// each widget. Renders a thick line border.
128    FullThick,
129    /// Render a full splitter between the widgets. Reduces the area for
130    /// each widget. Renders a border with a single line on the inside
131    /// of a half block.
132    FullQuadrantInside,
133    /// Render a full splitter between the widgets. Reduces the area for
134    /// each widget. Renders a border with a single line on the outside
135    /// of a half block.
136    FullQuadrantOutside,
137    /// Render a minimal splitter, consisting just the two marker chars
138    /// rendered over the left/top widget.
139    ///
140    /// If the left widget has a Scroll in that area this will integrate
141    /// nicely. You will have to set `start_margin` with Scroll, then
142    /// Scroll can adjust its rendering to leave space for the markers.
143    /// And you want to set a `mark_offset` here.
144    ///
145    /// The widget will get the full area, only the marker is used
146    /// for mouse interactions.
147    Scroll,
148    /// Don't render a splitter, fully manual mode.
149    ///
150    /// The widget will have the full area, but the event-handling will
151    /// use the last column/row of the widget for moving the split.
152    /// This can be adjusted if you change `state.split[n]` which provides
153    /// the active area.
154    Widget,
155}
156
157/// Strategy for resizing the split-areas.
158#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
159pub enum SplitResize {
160    /// When changing a split-position limit resizing to the two
161    /// adjacent neighbours of the split.
162    Neighbours,
163    /// When changing a split-position, allow all positions in the
164    /// widgets area. Minus the minimum space required to draw the
165    /// split itself.
166    #[default]
167    Full,
168}
169
170const SPLIT_WIDTH: u16 = 1;
171
172/// State & event handling.
173#[derive(Debug)]
174pub struct SplitState {
175    /// Total area.
176    /// __readonly__. renewed for each render.
177    pub area: Rect,
178    /// Area inside the border.
179    /// __readonly__. renewed for each render.
180    pub inner: Rect,
181    /// The widget areas.
182    /// Use this after calling layout() to render your widgets.
183    /// __readonly__ renewed for each render.
184    pub widget_areas: Vec<Rect>,
185    /// Area used by the splitter. This is area is used for moving the splitter.
186    /// It might overlap with the widget area.
187    /// __readonly__ renewed for each render.
188    pub splitline_areas: Vec<Rect>,
189    /// Start position for drawing the mark.
190    /// __readonly__ renewed for each render.
191    pub splitline_mark_position: Vec<Position>,
192    /// Offset of the mark from top/left.
193    /// __readonly__ renewed for each render.
194    pub mark_offset: u16,
195
196    /// Direction of the split.
197    /// __readonly__ renewed for each render.
198    pub direction: Direction,
199    /// __readonly__ renewed for each render.
200    pub split_type: SplitType,
201    /// __readonly__ renewed for each render.
202    pub resize: SplitResize,
203
204    /// Layout-widths for the split-areas.
205    ///
206    /// This information is used after the initial render to
207    /// lay out the splitter.
208    area_length: Vec<u16>,
209    /// Saved lengths for hidden splits.
210    hidden_length: Vec<u16>,
211
212    /// Focus.
213    /// __read+write__
214    pub focus: FocusFlag,
215    /// If the splitter has the focus you can navigate between
216    /// the split-markers. This is the currently active split-marker.
217    /// __read+write__
218    pub focus_marker: Option<usize>,
219
220    /// Mouseflags.
221    /// __read+write__
222    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    /// Set constraints for the initial area sizes.
280    /// If the window is resized the current widths are used as
281    /// constraints for recalculating.
282    ///
283    /// The number of constraints determines the number of areas.
284    pub fn constraints(mut self, constraints: impl IntoIterator<Item = Constraint>) -> Self {
285        self.constraints = constraints.into_iter().collect();
286        self
287    }
288
289    /// Layout direction of the widgets.
290    /// Direction::Horizontal means the widgets are laid out left to right,
291    /// with a vertical split area in between.
292    pub fn direction(mut self, direction: Direction) -> Self {
293        self.direction = direction;
294        self
295    }
296
297    /// Controls rendering of the splitter.
298    pub fn split_type(mut self, split_type: SplitType) -> Self {
299        self.split_type = split_type;
300        self
301    }
302
303    /// Controls resizing the split areas.
304    pub fn resize(mut self, resize: SplitResize) -> Self {
305        self.resize = resize;
306        self
307    }
308
309    /// Draw a join character between the split and the
310    /// border on the left/top side. This sets the border type
311    /// used for the left/top border.
312    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    /// Draw a join character between the split and the
319    /// border on the left/top side. This sets the border type
320    /// used for the left/top border.
321    pub fn join_0(mut self, border: BorderType) -> Self {
322        self.join_0 = Some(border);
323        self
324    }
325
326    /// Draw a join character between the split and the
327    /// border on the right/bottom side. This sets the border type
328    /// used for the right/bottom border.
329    pub fn join_1(mut self, border: BorderType) -> Self {
330        self.join_1 = Some(border);
331        self
332    }
333
334    /// Outer block.
335    pub fn block(mut self, block: Block<'a>) -> Self {
336        self.block = Some(block);
337        self
338    }
339
340    /// Set all styles.
341    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    /// Style for the split area.
380    pub fn style(mut self, style: Style) -> Self {
381        self.style = style;
382        self
383    }
384
385    /// Style for the arrows.
386    pub fn arrow_style(mut self, style: Style) -> Self {
387        self.arrow_style = Some(style);
388        self
389    }
390
391    /// Style while dragging the splitter.
392    pub fn drag_style(mut self, style: Style) -> Self {
393        self.drag_style = Some(style);
394        self
395    }
396
397    /// Offset for the split marker from the top/left.
398    pub fn mark_offset(mut self, offset: u16) -> Self {
399        self.mark_offset = offset;
400        self
401    }
402
403    /// First marker char for the splitter.
404    pub fn mark_0(mut self, mark: &'a str) -> Self {
405        self.mark_0_char = Some(mark);
406        self
407    }
408
409    /// Second marker char for the splitter.
410    pub fn mark_1(mut self, mark: &'a str) -> Self {
411        self.mark_1_char = Some(mark);
412        self
413    }
414
415    /// Constructs the widgets for rendering.
416    ///
417    /// Returns the SplitWidget that actually renders the split.
418    /// Returns a `Vec<Rect>` with the regions for each split.
419    ///
420    /// Render your content first, using the layout information.
421    /// And the SplitWidget as last to allow rendering over
422    /// the content widgets.
423    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    /// Constructs the widgets for rendering.
440    #[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    /// Calculates the first layout according to the constraints.
468    /// When a resize is detected, the current widths are used as constraints.
469    fn layout_split(&self, area: Rect, state: &mut SplitState) {
470        state.area = area;
471        state.inner = self.block.inner_if_some(area);
472
473        // use only the inner from here on
474        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            // must use the old direction to get a correct value.
483            if state.direction == Direction::Horizontal {
484                v.width
485            } else {
486                v.height
487            }
488        };
489        let new_len = |v: &Rect| {
490            // must use the old direction to get a correct value.
491            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            // initial
500            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        // Areas changed, create areas and splits.
541        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        // Set 2nd dimension too, if necessary.
611        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
635/// Adjust area and split according to the split_type.
636fn 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            // done before
896        } 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            // done before
943        } 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                // can't have the block overwrite all content styles.
964                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        // rely on layout already happened.
989        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        // rely on layout already happened.
1003        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        // skip 0 width/height
1014        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        // not mouse focusable
1138        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    /// New state.
1159    pub fn new() -> Self {
1160        Self::default()
1161    }
1162
1163    /// New state with a focus-name.
1164    pub fn named(name: &str) -> Self {
1165        Self {
1166            focus: FocusFlag::named(name),
1167            ..Self::default()
1168        }
1169    }
1170
1171    /// Set the position for the nth splitter.
1172    ///
1173    /// The position is limited the combined area of the two adjacent areas.
1174    /// The position is further limited to leave space for rendering the
1175    /// splitter.
1176    ///
1177    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    /// Move the nth split position.
1211    /// If delta is greater than the area length it sets the
1212    /// length to 0.
1213    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    /// Move the nth split position.
1221    /// Does nothing if the change is bigger than the length of the split.
1222    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    /// Move the nth split position.
1230    /// Does nothing if the change is bigger than the length of the split.
1231    pub fn move_split_up(&mut self, n: usize, delta: u16) -> bool {
1232        self.move_split_left(n, delta)
1233    }
1234
1235    /// Move the nth split position.
1236    /// Does nothing if the change is bigger than the length of the split.
1237    pub fn move_split_down(&mut self, n: usize, delta: u16) -> bool {
1238        self.move_split_right(n, delta)
1239    }
1240
1241    /// Select the next splitter for manual adjustment.
1242    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    /// Select the previous splitter for manual adjustment.
1257    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    /// Number of split-areas
1273    pub fn len(&self) -> usize {
1274        self.area_length.len()
1275    }
1276
1277    /// Get all area lengths.
1278    pub fn area_lengths(&self) -> &[u16] {
1279        &self.area_length
1280    }
1281
1282    /// Set all area lengths.
1283    ///
1284    /// This will adjust the list of the hidden splits too.
1285    ///
1286    /// __Caution__
1287    /// If the sum of the lengths doesn't match the display-width
1288    /// this will trigger a layout and will use the given lenghts
1289    /// as Constraint::Fill().
1290    ///
1291    /// __Caution__
1292    ///
1293    /// If a length is 0 it will not display the split at all.
1294    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    /// Get the value of the hidden lengths.
1305    pub fn hidden_lengths(&self) -> &[u16] {
1306        &self.hidden_length
1307    }
1308
1309    /// Set the value of the hidden lengths.
1310    ///
1311    /// This will take at most area_length.len() items of this Vec.
1312    /// And it will fill missing items as 0.
1313    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    /// Length of the nth split.
1324    ///
1325    /// __Caution__
1326    ///
1327    /// This length **includes** the width of the split itself.
1328    /// Which may or may not take some space. Except for the last.
1329    /// So it will be better to use `widget_areas` for anything
1330    /// rendering related.
1331    ///
1332    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    /// Sum of all area lengths.
1340    pub fn total_area_len(&self) -> u16 {
1341        self.area_length.iter().sum()
1342    }
1343
1344    /// Set the length of the nth split.
1345    ///
1346    /// This resets any hidden state of the nth split.
1347    ///
1348    /// __Caution__
1349    /// The sum of all lengths must be equal with the width/height of
1350    /// the splitter. If it is not this operation doesn't set the
1351    /// absolute width of the nth split. Instead, it triggers a layout
1352    /// of the widget, takes all the lengths as Constraint::Fill()
1353    /// values and redistributes the size.
1354    ///
1355    /// You can either ensure to change some other len to accommodate
1356    /// for your changes. Or use [set_split_pos](Self::set_split_pos) or
1357    /// [set_screen_split_pos](Self::set_screen_split_pos)
1358    ///
1359    /// __Caution__
1360    ///
1361    /// This length **includes** the width of the split itself.
1362    /// Which may or may not take some space. Except for the last area.
1363    /// Which doesn't have a split.
1364    ///
1365    /// So:
1366    /// - If you set the length to 0 the area will be hidden completely
1367    ///   and no split will be shown.
1368    /// - A value of 1 is fine.
1369    /// - The last area can have a length 0 and that's fine too.
1370    ///
1371    /// __Caution__
1372    ///
1373    /// Before the first render this will do nothing.
1374    /// Use [set_area_lengths](SplitState::set_area_lengths) to initialize the areas.
1375    ///
1376    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    /// Returns the position of the nth split.
1385    ///
1386    /// __Caution__
1387    ///
1388    /// The numbering for the splitters goes from `0` to `len-1` __exclusive__.
1389    /// Split `n` marks the gap between area `n` and `n+1`.
1390    ///
1391    /// __Caution__
1392    ///
1393    /// This returns the position of the gap between two adjacent
1394    /// split-areas. Use `splitline_areas` for anything rendering related.
1395    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    /// Sets the position of the nth split.
1403    ///
1404    /// Depending on the resize strategy this can limit the allowed positions
1405    /// for the split.
1406    ///
1407    /// __Caution__
1408    ///
1409    /// The numbering for the splitters goes from `0` to `len-1` __exclusive__.
1410    /// Split `n` marks the gap between area `n` and `n+1`.
1411    ///
1412    /// __Caution__
1413    ///
1414    /// This marks the position of the gap between two adjacent
1415    /// split-areas. If you start from screen-coordinates it might
1416    /// be easier to use [set_screen_split_pos](Self::set_screen_split_pos)
1417    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    /// Limits the possible position of the split to the
1433    /// width of the two direct neighbours of the split.
1434    fn set_split_pos_neighbour(&mut self, n: usize, pos: u16) {
1435        assert!(n + 1 < self.area_length.len());
1436
1437        // create dual
1438        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        // last is not a split
1445        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        // revert dual
1462        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    /// Allows the full range for the split-pos.
1472    /// Minus the space needed to render the split itself.
1473    #[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        // create dual
1481        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        // last is not a split
1488        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                        // how many split between here and there
1502                        let rest_area_count = n - (i + 1);
1503                        let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1504                        // min
1505                        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                        // unchanged
1513                    }
1514                }
1515            } else if i == n {
1516                // remaining area count with a split
1517                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                // min for remaining areas
1521                let rest_len = rest_len - rest_area_width;
1522                // last can be 0
1523                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                        // unchanged
1542                    }
1543                }
1544            }
1545        }
1546
1547        // revert dual
1548        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    /// Is the split hidden?
1559    pub fn is_hidden(&self, n: usize) -> bool {
1560        self.hidden_length[n] > 0
1561    }
1562
1563    /// Hide the split and adds its area to the following split.
1564    /// If there is no following split it will go left/up.
1565    /// Leaves enough space to render the splitter.
1566    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                // don't hide last split.
1592                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    /// Show a hidden split.
1610    /// It will first try to reduce the areas to the right,
1611    /// and then the areas to the left to make space.
1612    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                    // steal as much as we can
1618                    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                    // repaint after drag is finished. resets the displayed style.
1699                    if was_drag.is_some() {
1700                        Outcome::Changed
1701                    } else {
1702                        Outcome::Continue
1703                    }
1704                }
1705            }
1706            _ => Outcome::Continue,
1707        }
1708    }
1709}