rat_widget/
splitter.rs

1//! Vertical or horizontal multiple split.
2//!
3//! ```
4//! # use ratatui::buffer::Buffer;
5//! # use ratatui::layout::{Constraint, Rect};
6//! # use ratatui::text::Line;
7//! # use ratatui::widgets::{Widget, StatefulWidget};
8//! use rat_widget::splitter::{Split, SplitState, SplitType};
9//! # struct State { split: SplitState }
10//! # let mut state = State { split: Default::default() };
11//! # let area = Rect::default();
12//! # let mut buf = Buffer::default();
13//! # let buf = &mut buf;
14//!
15//! let split = Split::horizontal()
16//!     .constraints([
17//!         Constraint::Length(25),
18//!         Constraint::Length(25),
19//!         Constraint::Fill(1),
20//!     ])
21//!     .split_type(SplitType::Scroll)
22//!     .into_widget(area, &mut state.split);
23//!
24//! Line::from("first")
25//!     .render(state.split.widget_areas[0], buf);
26//!
27//! Line::from("second")
28//!     .render(state.split.widget_areas[1], buf);
29//!
30//! Line::from("third")
31//!     .render(state.split.widget_areas[2], buf);
32//!
33//! // render split decorations
34//! split.render(area, buf, &mut state.split);
35//!
36//! ```
37use crate::_private::NonExhaustive;
38use crate::util::{fill_buf_area, revert_style};
39use rat_event::util::MouseFlagsN;
40use rat_event::{HandleEvent, MouseOnly, Outcome, Regular, ct_event, flow};
41use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
42use rat_reloc::{RelocatableState, relocate_area, relocate_areas, relocate_positions};
43use ratatui::buffer::Buffer;
44use ratatui::layout::{Constraint, Direction, Flex, Layout, Position, Rect};
45use ratatui::prelude::BlockExt;
46use ratatui::style::Style;
47use ratatui::widgets::{Block, BorderType, StatefulWidget, Widget};
48use std::cmp::{max, min};
49use std::mem;
50use unicode_segmentation::UnicodeSegmentation;
51
52#[derive(Debug, Default, Clone)]
53/// Splits the area in multiple parts and renders a UI that
54/// allows changing the sizes.
55///
56/// * Can hide/show regions.
57/// * Resize
58///     * neighbours only
59///     * all regions
60/// * Horizontal / vertical split. Choose one.
61///
62/// The widget doesn't hold references to the content widgets,
63/// instead it gives back the regions where the content can be
64/// rendered.
65///
66/// 1. Construct the Split.
67/// 2. Call [Split::into_widget_layout] to create the actual
68///    widget and the layout of the regions.
69/// 3. Render the content areas.
70/// 4. Render the split widget last. There are options that
71///    will render above the widgets and use part of the content
72///    area.
73///
74pub struct Split<'a> {
75    // direction
76    direction: Direction,
77    // start constraints. used when
78    // there are no widths in the state.
79    constraints: Vec<Constraint>,
80    // resize options
81    resize: SplitResize,
82
83    // split rendering
84    split_type: SplitType,
85    // joiner left/top
86    join_0: Option<BorderType>,
87    // joiner right/bottom
88    join_1: Option<BorderType>,
89    // offset from left/top for the split-mark
90    mark_offset: u16,
91    // mark char 1
92    mark_0_char: Option<&'a str>,
93    // mark char 2
94    mark_1_char: Option<&'a str>,
95
96    // styling
97    block: Option<Block<'a>>,
98    style: Style,
99    arrow_style: Option<Style>,
100    drag_style: Option<Style>,
101}
102
103/// Primary widget for rendering the Split.
104#[derive(Debug, Clone)]
105pub struct SplitWidget<'a> {
106    split: Split<'a>,
107    // internal mode:
108    // 0 - legacy
109    // 1 - used for into_widget_layout()
110    mode: u8,
111}
112
113///
114/// Combined styles for the Split.
115///
116#[derive(Debug)]
117pub struct SplitStyle {
118    /// Base style
119    pub style: Style,
120    /// Arrow style.
121    pub arrow_style: Option<Style>,
122    /// Style while dragging.
123    pub drag_style: Option<Style>,
124
125    /// Marker for a horizontal split.
126    /// Only the first 2 chars are used.
127    pub horizontal_mark: Option<&'static str>,
128    /// Marker for a vertical split.
129    /// Only the first 2 chars are used.
130    pub vertical_mark: Option<&'static str>,
131
132    /// Block
133    pub block: Option<Block<'static>>,
134
135    pub non_exhaustive: NonExhaustive,
136}
137
138/// Render variants for the splitter.
139#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
140pub enum SplitType {
141    /// Render a full splitter between the widgets. Reduces the area for
142    /// each widget. Renders a blank border.
143    #[default]
144    FullEmpty,
145    /// Render a full splitter between the widgets. Reduces the area for
146    /// each widget. Renders a plain line border.
147    FullPlain,
148    /// Render a full splitter between the widgets. Reduces the area for
149    /// each widget. Renders a double line border.
150    FullDouble,
151    /// Render a full splitter between the widgets. Reduces the area for
152    /// each widget. Renders a thick line border.
153    FullThick,
154    /// Render a full splitter between the widgets. Reduces the area for
155    /// each widget. Renders a border with a single line on the inside
156    /// of a half block.
157    FullQuadrantInside,
158    /// Render a full splitter between the widgets. Reduces the area for
159    /// each widget. Renders a border with a single line on the outside
160    /// of a half block.
161    FullQuadrantOutside,
162    /// Render a minimal splitter, consisting just the two marker chars
163    /// rendered over the left/top widget.
164    ///
165    /// If the left widget has a Scroll in that area this will integrate
166    /// nicely. You will have to set `start_margin` with Scroll, then
167    /// Scroll can adjust its rendering to leave space for the markers.
168    /// And you want to set a `mark_offset` here.
169    ///
170    /// The widget will get the full area, only the marker is used
171    /// for mouse interactions.
172    Scroll,
173    /// Don't render a splitter, fully manual mode.
174    ///
175    /// The widget will have the full area, but the event-handling will
176    /// use the last column/row of the widget for moving the split.
177    /// This can be adjusted if you change `state.split[n]` which provides
178    /// the active area.
179    Widget,
180}
181
182/// Strategy for resizing the split-areas.
183#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
184pub enum SplitResize {
185    /// When changing a split-position limit resizing to the two
186    /// adjacent neighbours of the split.
187    Neighbours,
188    /// When changing a split-position, allow all positions in the
189    /// widgets area. Minus the minimum space required to draw the
190    /// split itself.
191    #[default]
192    Full,
193}
194
195const SPLIT_WIDTH: u16 = 1;
196
197/// State & event handling.
198#[derive(Debug)]
199pub struct SplitState {
200    /// Total area.
201    /// __readonly__. renewed for each render.
202    pub area: Rect,
203    /// Area inside the border.
204    /// __readonly__. renewed for each render.
205    pub inner: Rect,
206    /// The widget areas.
207    /// Use this after calling layout() to render your widgets.
208    /// __readonly__ renewed for each render.
209    pub widget_areas: Vec<Rect>,
210    /// Area used by the splitter. This is area is used for moving the splitter.
211    /// It might overlap with the widget area.
212    /// __readonly__ renewed for each render.
213    pub splitline_areas: Vec<Rect>,
214    /// Start position for drawing the mark.
215    /// __readonly__ renewed for each render.
216    pub splitline_mark_position: Vec<Position>,
217    /// Offset of the mark from top/left.
218    /// __readonly__ renewed for each render.
219    pub mark_offset: u16,
220
221    /// Direction of the split.
222    /// __readonly__ renewed for each render.
223    pub direction: Direction,
224    /// __readonly__ renewed for each render.
225    pub split_type: SplitType,
226    /// __readonly__ renewed for each render.
227    pub resize: SplitResize,
228
229    /// Layout-widths for the split-areas.
230    ///
231    /// This information is used after the initial render to
232    /// lay out the splitter.
233    area_length: Vec<u16>,
234    /// Saved lengths for hidden splits.
235    hidden_length: Vec<u16>,
236
237    /// Focus.
238    /// __read+write__
239    pub focus: FocusFlag,
240    /// If the splitter has the focus you can navigate between
241    /// the split-markers. This is the currently active split-marker.
242    /// __read+write__
243    pub focus_marker: Option<usize>,
244
245    /// Mouseflags.
246    /// __read+write__
247    pub mouse: MouseFlagsN,
248
249    pub non_exhaustive: NonExhaustive,
250}
251
252impl SplitType {
253    pub fn is_full(&self) -> bool {
254        use SplitType::*;
255        match self {
256            FullEmpty => true,
257            FullPlain => true,
258            FullDouble => true,
259            FullThick => true,
260            FullQuadrantInside => true,
261            FullQuadrantOutside => true,
262            Scroll => false,
263            Widget => false,
264        }
265    }
266}
267
268impl Default for SplitStyle {
269    fn default() -> Self {
270        Self {
271            style: Default::default(),
272            arrow_style: None,
273            drag_style: None,
274            horizontal_mark: None,
275            vertical_mark: None,
276            block: None,
277            non_exhaustive: NonExhaustive,
278        }
279    }
280}
281
282impl<'a> Split<'a> {
283    pub fn new() -> Self {
284        Self {
285            direction: Direction::Horizontal,
286            ..Default::default()
287        }
288    }
289
290    pub fn horizontal() -> Self {
291        Self {
292            direction: Direction::Horizontal,
293            ..Default::default()
294        }
295    }
296
297    pub fn vertical() -> Self {
298        Self {
299            direction: Direction::Horizontal,
300            ..Default::default()
301        }
302    }
303
304    /// Set constraints for the initial area sizes.
305    /// If the window is resized the current widths are used as
306    /// constraints for recalculating.
307    ///
308    /// The number of constraints determines the number of areas.
309    pub fn constraints(mut self, constraints: impl IntoIterator<Item = Constraint>) -> Self {
310        self.constraints = constraints.into_iter().collect();
311        self
312    }
313
314    /// Layout direction of the widgets.
315    /// Direction::Horizontal means the widgets are laid out left to right,
316    /// with a vertical split area in between.
317    pub fn direction(mut self, direction: Direction) -> Self {
318        self.direction = direction;
319        self
320    }
321
322    /// Controls rendering of the splitter.
323    pub fn split_type(mut self, split_type: SplitType) -> Self {
324        self.split_type = split_type;
325        self
326    }
327
328    /// Controls resizing the split areas.
329    pub fn resize(mut self, resize: SplitResize) -> Self {
330        self.resize = resize;
331        self
332    }
333
334    /// Draw a join character between the split and the
335    /// borders. This sets the border type used for the
336    /// surrounding border.
337    pub fn join(mut self, border: BorderType) -> Self {
338        self.join_0 = Some(border);
339        self.join_1 = Some(border);
340        self
341    }
342
343    /// Draw a join character between the split and the
344    /// border on the left/top side. This sets the border type
345    /// used for the left/top border.
346    pub fn join_0(mut self, border: BorderType) -> Self {
347        self.join_0 = Some(border);
348        self
349    }
350
351    /// Draw a join character between the split and the
352    /// border on the right/bottom side. This sets the border type
353    /// used for the right/bottom border.
354    pub fn join_1(mut self, border: BorderType) -> Self {
355        self.join_1 = Some(border);
356        self
357    }
358
359    /// Outer block.
360    pub fn block(mut self, block: Block<'a>) -> Self {
361        self.block = Some(block);
362        self
363    }
364
365    /// Set all styles.
366    pub fn styles(mut self, styles: SplitStyle) -> Self {
367        self.style = styles.style;
368        if styles.drag_style.is_some() {
369            self.drag_style = styles.drag_style;
370        }
371        if styles.arrow_style.is_some() {
372            self.arrow_style = styles.arrow_style;
373        }
374        match self.direction {
375            Direction::Horizontal => {
376                if let Some(mark) = styles.horizontal_mark {
377                    let mut g = mark.graphemes(true);
378                    if let Some(g0) = g.next() {
379                        self.mark_0_char = Some(g0);
380                    }
381                    if let Some(g1) = g.next() {
382                        self.mark_1_char = Some(g1);
383                    }
384                }
385            }
386            Direction::Vertical => {
387                if let Some(mark) = styles.vertical_mark {
388                    let mut g = mark.graphemes(true);
389                    if let Some(g0) = g.next() {
390                        self.mark_0_char = Some(g0);
391                    }
392                    if let Some(g1) = g.next() {
393                        self.mark_1_char = Some(g1);
394                    }
395                }
396            }
397        }
398        if styles.block.is_some() {
399            self.block = styles.block;
400        }
401        self
402    }
403
404    /// Style for the split area.
405    pub fn style(mut self, style: Style) -> Self {
406        self.style = style;
407        self
408    }
409
410    /// Style for the arrows.
411    pub fn arrow_style(mut self, style: Style) -> Self {
412        self.arrow_style = Some(style);
413        self
414    }
415
416    /// Style while dragging the splitter.
417    pub fn drag_style(mut self, style: Style) -> Self {
418        self.drag_style = Some(style);
419        self
420    }
421
422    /// Offset for the split marker from the top/left.
423    pub fn mark_offset(mut self, offset: u16) -> Self {
424        self.mark_offset = offset;
425        self
426    }
427
428    /// First marker char for the splitter.
429    pub fn mark_0(mut self, mark: &'a str) -> Self {
430        self.mark_0_char = Some(mark);
431        self
432    }
433
434    /// Second marker char for the splitter.
435    pub fn mark_1(mut self, mark: &'a str) -> Self {
436        self.mark_1_char = Some(mark);
437        self
438    }
439
440    /// Constructs the widget for rendering.
441    ///
442    /// Returns the SplitWidget that actually renders the split decorations.
443    ///
444    /// Use [SplitState::widget_areas] to render your contents, and
445    /// then render the SplitWidget. This allows it to render some
446    /// decorations on top of your widgets.
447    pub fn into_widget(self, area: Rect, state: &mut SplitState) -> SplitWidget<'a> {
448        self.layout_split(area, state);
449
450        SplitWidget {
451            split: self,
452            mode: 1,
453        }
454    }
455
456    /// Constructs the widgets for rendering.
457    ///
458    /// Returns the SplitWidget that actually renders the split.
459    /// Returns a `Vec<Rect>` with the regions for each split.
460    ///
461    /// Render your content first, using the layout information.
462    /// And the SplitWidget as last to allow rendering over
463    /// the content widgets.
464    pub fn into_widget_layout(
465        self,
466        area: Rect,
467        state: &mut SplitState,
468    ) -> (SplitWidget<'a>, Vec<Rect>) {
469        self.layout_split(area, state);
470
471        (
472            SplitWidget {
473                split: self,
474                mode: 1,
475            },
476            state.widget_areas.clone(),
477        )
478    }
479}
480
481impl Split<'_> {
482    /// Calculates the first layout according to the constraints.
483    /// When a resize is detected, the current widths are used as constraints.
484    fn layout_split(&self, area: Rect, state: &mut SplitState) {
485        state.area = area;
486        state.inner = self.block.inner_if_some(area);
487
488        // use only the inner from here on
489        let inner = state.inner;
490
491        let layout_change = state.area_length.len() != self.constraints.len();
492        let meta_change = state.direction != self.direction
493            || state.split_type != self.split_type
494            || state.mark_offset != self.mark_offset;
495
496        let old_len = |v: &Rect| {
497            // must use the old direction to get a correct value.
498            if state.direction == Direction::Horizontal {
499                v.width
500            } else {
501                v.height
502            }
503        };
504        let new_len = |v: &Rect| {
505            // must use the old direction to get a correct value.
506            if self.direction == Direction::Horizontal {
507                v.width
508            } else {
509                v.height
510            }
511        };
512
513        let new_split_areas = if layout_change {
514            // initial
515            let new_areas = Layout::new(self.direction, self.constraints.clone())
516                .flex(Flex::Legacy)
517                .split(inner);
518            Some(new_areas)
519        } else {
520            let old_length: u16 = state.area_length.iter().sum();
521            if meta_change || old_len(&inner) != old_length {
522                let mut constraints = Vec::new();
523                for i in 0..state.area_length.len() {
524                    constraints.push(Constraint::Fill(state.area_length[i]));
525                }
526                let new_areas = Layout::new(self.direction, constraints).split(inner);
527                Some(new_areas)
528            } else {
529                None
530            }
531        };
532
533        if let Some(new_split_areas) = new_split_areas {
534            state.area_length.clear();
535            for v in new_split_areas.iter() {
536                state.area_length.push(new_len(v));
537            }
538            while state.hidden_length.len() < state.area_length.len() {
539                state.hidden_length.push(0);
540            }
541            while state.hidden_length.len() > state.area_length.len() {
542                state.hidden_length.pop();
543            }
544        }
545
546        state.direction = self.direction;
547        state.split_type = self.split_type;
548        state.resize = self.resize;
549        state.mark_offset = self.mark_offset;
550
551        self.layout_from_widths(state);
552    }
553
554    fn layout_from_widths(&self, state: &mut SplitState) {
555        // Areas changed, create areas and splits.
556        state.widget_areas.clear();
557        state.splitline_areas.clear();
558        state.splitline_mark_position.clear();
559
560        let inner = state.inner;
561
562        let mut total = 0;
563        for length in state
564            .area_length
565            .iter()
566            .take(state.area_length.len().saturating_sub(1))
567            .copied()
568        {
569            let mut area = if self.direction == Direction::Horizontal {
570                Rect::new(inner.x + total, inner.y, length, inner.height)
571            } else {
572                Rect::new(inner.x, inner.y + total, inner.width, length)
573            };
574            let mut split = if self.direction == Direction::Horizontal {
575                Rect::new(
576                    inner.x + total + length.saturating_sub(SPLIT_WIDTH),
577                    inner.y,
578                    min(1, length),
579                    inner.height,
580                )
581            } else {
582                Rect::new(
583                    inner.x,
584                    inner.y + total + length.saturating_sub(SPLIT_WIDTH),
585                    inner.width,
586                    min(1, length),
587                )
588            };
589            let mut mark = if self.direction == Direction::Horizontal {
590                Position::new(
591                    inner.x + total + length.saturating_sub(SPLIT_WIDTH),
592                    inner.y + self.mark_offset,
593                )
594            } else {
595                Position::new(
596                    inner.x + self.mark_offset,
597                    inner.y + total + length.saturating_sub(SPLIT_WIDTH),
598                )
599            };
600
601            adjust_for_split_type(
602                self.direction,
603                self.split_type,
604                &mut area,
605                &mut split,
606                &mut mark,
607            );
608
609            state.widget_areas.push(area);
610            state.splitline_areas.push(split);
611            state.splitline_mark_position.push(mark);
612
613            total += length;
614        }
615        if let Some(length) = state.area_length.last().copied() {
616            let area = if self.direction == Direction::Horizontal {
617                Rect::new(inner.x + total, inner.y, length, inner.height)
618            } else {
619                Rect::new(inner.x, inner.y + total, inner.width, length)
620            };
621
622            state.widget_areas.push(area);
623        }
624
625        // Set 2nd dimension too, if necessary.
626        if let Some(test) = state.widget_areas.first() {
627            if self.direction == Direction::Horizontal {
628                if test.height != state.inner.height {
629                    for r in &mut state.widget_areas {
630                        r.height = state.inner.height;
631                    }
632                    for r in &mut state.splitline_areas {
633                        r.height = state.inner.height;
634                    }
635                }
636            } else {
637                if test.width != state.inner.width {
638                    for r in &mut state.widget_areas {
639                        r.width = state.inner.width;
640                    }
641                    for r in &mut state.splitline_areas {
642                        r.width = state.inner.width;
643                    }
644                }
645            }
646        }
647    }
648}
649
650/// Adjust area and split according to the split_type.
651fn adjust_for_split_type(
652    direction: Direction,
653    split_type: SplitType,
654    area: &mut Rect,
655    split: &mut Rect,
656    mark: &mut Position,
657) {
658    use Direction::*;
659    use SplitType::*;
660
661    match (direction, split_type) {
662        (
663            Horizontal,
664            FullEmpty | FullPlain | FullDouble | FullThick | FullQuadrantInside
665            | FullQuadrantOutside,
666        ) => {
667            area.width = area.width.saturating_sub(SPLIT_WIDTH);
668        }
669        (
670            Vertical,
671            FullEmpty | FullPlain | FullDouble | FullThick | FullQuadrantInside
672            | FullQuadrantOutside,
673        ) => {
674            area.height = area.height.saturating_sub(SPLIT_WIDTH);
675        }
676
677        (Horizontal, Scroll) => {
678            split.y = mark.y;
679            split.height = 2;
680        }
681        (Vertical, Scroll) => {
682            split.x = mark.x;
683            split.width = 2;
684        }
685
686        (Horizontal, Widget) => {}
687        (Vertical, Widget) => {}
688    }
689}
690
691impl Split<'_> {
692    fn get_mark_0(&self) -> &str {
693        if let Some(mark) = self.mark_0_char {
694            mark
695        } else if self.direction == Direction::Horizontal {
696            "<"
697        } else {
698            "^"
699        }
700    }
701
702    fn get_mark_1(&self) -> &str {
703        if let Some(mark) = self.mark_1_char {
704            mark
705        } else if self.direction == Direction::Horizontal {
706            ">"
707        } else {
708            "v"
709        }
710    }
711
712    fn get_fill_char(&self) -> Option<&str> {
713        use Direction::*;
714        use SplitType::*;
715
716        match (self.direction, self.split_type) {
717            (Horizontal, FullEmpty) => Some(" "),
718            (Vertical, FullEmpty) => Some(" "),
719            (Horizontal, FullPlain) => Some("\u{2502}"),
720            (Vertical, FullPlain) => Some("\u{2500}"),
721            (Horizontal, FullDouble) => Some("\u{2551}"),
722            (Vertical, FullDouble) => Some("\u{2550}"),
723            (Horizontal, FullThick) => Some("\u{2503}"),
724            (Vertical, FullThick) => Some("\u{2501}"),
725            (Horizontal, FullQuadrantInside) => Some("\u{258C}"),
726            (Vertical, FullQuadrantInside) => Some("\u{2580}"),
727            (Horizontal, FullQuadrantOutside) => Some("\u{2590}"),
728            (Vertical, FullQuadrantOutside) => Some("\u{2584}"),
729            (_, Scroll) => None,
730            (_, Widget) => None,
731        }
732    }
733
734    #[allow(unreachable_patterns)]
735    fn get_join_0(&self, split_area: Rect, state: &SplitState) -> Option<(Position, &str)> {
736        use Direction::*;
737        use SplitType::*;
738
739        let s: Option<&str> = if let Some(join_0) = self.join_0 {
740            match (self.direction, join_0, self.split_type) {
741                (
742                    Horizontal,
743                    BorderType::Plain | BorderType::Rounded,
744                    FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
745                ) => Some("\u{252C}"),
746                (
747                    Vertical,
748                    BorderType::Plain | BorderType::Rounded,
749                    FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
750                ) => Some("\u{251C}"),
751                (
752                    Horizontal,
753                    BorderType::Plain | BorderType::Rounded | BorderType::Thick,
754                    FullDouble,
755                ) => Some("\u{2565}"),
756                (
757                    Vertical,
758                    BorderType::Plain | BorderType::Rounded | BorderType::Thick,
759                    FullDouble,
760                ) => Some("\u{255E}"),
761                (Horizontal, BorderType::Plain | BorderType::Rounded, FullThick) => {
762                    Some("\u{2530}")
763                }
764                (Vertical, BorderType::Plain | BorderType::Rounded, FullThick) => Some("\u{251D}"),
765
766                (
767                    Horizontal,
768                    BorderType::Double,
769                    FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
770                    | Scroll,
771                ) => Some("\u{2564}"),
772                (
773                    Vertical,
774                    BorderType::Double,
775                    FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
776                    | Scroll,
777                ) => Some("\u{255F}"),
778                (Horizontal, BorderType::Double, FullDouble) => Some("\u{2566}"),
779                (Vertical, BorderType::Double, FullDouble) => Some("\u{2560}"),
780
781                (
782                    Horizontal,
783                    BorderType::Thick,
784                    FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
785                ) => Some("\u{252F}"),
786                (
787                    Vertical,
788                    BorderType::Thick,
789                    FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
790                ) => Some("\u{2520}"),
791                (Horizontal, BorderType::Thick, FullThick) => Some("\u{2533}"),
792                (Vertical, BorderType::Thick, FullThick) => Some("\u{2523}"),
793
794                (Horizontal, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
795                (Vertical, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
796
797                (_, BorderType::QuadrantInside, _) => None,
798                (_, BorderType::QuadrantOutside, _) => None,
799
800                (_, _, Widget) => None,
801                (_, _, _) => None,
802            }
803        } else {
804            None
805        };
806
807        s.map(|s| {
808            (
809                match self.direction {
810                    Horizontal => Position::new(split_area.x, state.area.y),
811                    Vertical => Position::new(state.area.x, split_area.y),
812                },
813                s,
814            )
815        })
816    }
817
818    #[allow(unreachable_patterns)]
819    fn get_join_1(&self, split_area: Rect, state: &SplitState) -> Option<(Position, &str)> {
820        use Direction::*;
821        use SplitType::*;
822
823        let s: Option<&str> = if let Some(join_1) = self.join_1 {
824            match (self.direction, join_1, self.split_type) {
825                (
826                    Horizontal,
827                    BorderType::Plain | BorderType::Rounded,
828                    FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
829                ) => Some("\u{2534}"),
830                (
831                    Vertical,
832                    BorderType::Plain | BorderType::Rounded,
833                    FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
834                ) => Some("\u{2524}"),
835                (
836                    Horizontal,
837                    BorderType::Plain | BorderType::Rounded | BorderType::Thick,
838                    FullDouble,
839                ) => Some("\u{2568}"),
840                (
841                    Vertical,
842                    BorderType::Plain | BorderType::Rounded | BorderType::Thick,
843                    FullDouble,
844                ) => Some("\u{2561}"),
845                (Horizontal, BorderType::Plain | BorderType::Rounded, FullThick) => {
846                    Some("\u{2538}")
847                }
848                (Vertical, BorderType::Plain | BorderType::Rounded, FullThick) => Some("\u{2525}"),
849
850                (
851                    Horizontal,
852                    BorderType::Double,
853                    FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
854                    | Scroll,
855                ) => Some("\u{2567}"),
856                (
857                    Vertical,
858                    BorderType::Double,
859                    FullPlain | FullThick | FullQuadrantInside | FullQuadrantOutside | FullEmpty
860                    | Scroll,
861                ) => Some("\u{2562}"),
862                (Horizontal, BorderType::Double, FullDouble) => Some("\u{2569}"),
863                (Vertical, BorderType::Double, FullDouble) => Some("\u{2563}"),
864
865                (
866                    Horizontal,
867                    BorderType::Thick,
868                    FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
869                ) => Some("\u{2537}"),
870                (
871                    Vertical,
872                    BorderType::Thick,
873                    FullPlain | FullQuadrantInside | FullQuadrantOutside | FullEmpty | Scroll,
874                ) => Some("\u{2528}"),
875                (Horizontal, BorderType::Thick, FullThick) => Some("\u{253B}"),
876                (Vertical, BorderType::Thick, FullThick) => Some("\u{252B}"),
877
878                (Horizontal, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
879                (Vertical, BorderType::QuadrantOutside, FullEmpty) => Some("\u{2588}"),
880
881                (_, BorderType::QuadrantInside, _) => None,
882                (_, BorderType::QuadrantOutside, _) => None,
883
884                (_, _, Widget) => None,
885                (_, _, _) => None,
886            }
887        } else {
888            None
889        };
890
891        s.map(|s| {
892            (
893                match self.direction {
894                    Horizontal => Position::new(split_area.x, state.area.y + state.area.height - 1),
895                    Vertical => Position::new(state.area.x + state.area.width - 1, split_area.y),
896                },
897                s,
898            )
899        })
900    }
901}
902
903impl<'a> StatefulWidget for &SplitWidget<'a> {
904    type State = SplitState;
905
906    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
907        if self.mode == 0 {
908            self.split.layout_split(area, state);
909        } else if self.mode == 1 {
910            // done before
911        } else {
912            unreachable!()
913        }
914
915        if state.is_focused() {
916            if state.focus_marker.is_none() {
917                state.focus_marker = Some(0);
918            }
919        } else {
920            state.focus_marker = None;
921        }
922
923        if self.mode == 0 {
924            if let Some(block) = &self.split.block {
925                block.render(area, buf);
926            } else {
927                buf.set_style(area, self.split.style);
928            }
929        } else if self.mode == 1 {
930            if let Some(mut block) = self.split.block.clone() {
931                block = block.style(Style::default());
932                block.render(area, buf);
933            }
934        } else {
935            unreachable!()
936        }
937
938        if self.mode == 0 {
939            if !matches!(self.split.split_type, SplitType::Widget | SplitType::Scroll) {
940                render_split(&self.split, buf, state);
941            }
942        } else if self.mode == 1 {
943            render_split(&self.split, buf, state);
944        } else {
945            unreachable!()
946        }
947    }
948}
949
950impl StatefulWidget for SplitWidget<'_> {
951    type State = SplitState;
952
953    fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
954        if self.mode == 0 {
955            self.split.layout_split(area, state);
956        } else if self.mode == 1 {
957            // done before
958        } else {
959            unreachable!()
960        }
961
962        if state.is_focused() {
963            if state.focus_marker.is_none() {
964                state.focus_marker = Some(0);
965            }
966        } else {
967            state.focus_marker = None;
968        }
969
970        if self.mode == 0 {
971            if let Some(block) = &self.split.block {
972                block.render(area, buf);
973            } else {
974                buf.set_style(area, self.split.style);
975            }
976        } else if self.mode == 1 {
977            if let Some(mut block) = mem::take(&mut self.split.block) {
978                // can't have the block overwrite all content styles.
979                block = block.style(Style::default());
980                block.render(area, buf);
981            }
982        } else {
983            unreachable!()
984        }
985
986        if self.mode == 0 {
987            if !matches!(self.split.split_type, SplitType::Widget | SplitType::Scroll) {
988                render_split(&self.split, buf, state);
989            }
990        } else if self.mode == 1 {
991            render_split(&self.split, buf, state);
992        } else {
993            unreachable!()
994        }
995    }
996}
997
998fn render_split(split: &Split<'_>, buf: &mut Buffer, state: &mut SplitState) {
999    for (n, split_area) in state.splitline_areas.iter().enumerate() {
1000        // skip 0 width/height
1001        if split.direction == Direction::Horizontal {
1002            if split_area.width == 0 {
1003                continue;
1004            }
1005        } else {
1006            if split_area.height == 0 {
1007                continue;
1008            }
1009        }
1010
1011        let (style, arrow_style) = if Some(n) == state.mouse.drag.get()
1012            || Some(n) == state.focus_marker
1013            || Some(n) == state.mouse.hover.get()
1014        {
1015            if let Some(drag) = split.drag_style {
1016                (drag, drag)
1017            } else {
1018                (revert_style(split.style), revert_style(split.style))
1019            }
1020        } else {
1021            if let Some(arrow) = split.arrow_style {
1022                (split.style, arrow)
1023            } else {
1024                (split.style, split.style)
1025            }
1026        };
1027
1028        if let Some(fill) = split.get_fill_char() {
1029            fill_buf_area(buf, *split_area, fill, style);
1030        }
1031
1032        let mark = state.splitline_mark_position[n];
1033        if split.direction == Direction::Horizontal {
1034            if buf.area.contains((mark.x, mark.y).into()) {
1035                if let Some(cell) = buf.cell_mut((mark.x, mark.y)) {
1036                    cell.set_style(arrow_style);
1037                    cell.set_symbol(split.get_mark_0());
1038                }
1039            }
1040            if buf.area.contains((mark.x, mark.y + 1).into()) {
1041                if let Some(cell) = buf.cell_mut((mark.x, mark.y + 1)) {
1042                    cell.set_style(arrow_style);
1043                    cell.set_symbol(split.get_mark_1());
1044                }
1045            }
1046        } else {
1047            if let Some(cell) = buf.cell_mut((mark.x, mark.y)) {
1048                cell.set_style(arrow_style);
1049                cell.set_symbol(split.get_mark_0());
1050            }
1051            if let Some(cell) = buf.cell_mut((mark.x + 1, mark.y)) {
1052                cell.set_style(arrow_style);
1053                cell.set_symbol(split.get_mark_1());
1054            }
1055        }
1056
1057        if let Some((pos_0, c_0)) = split.get_join_0(*split_area, state) {
1058            if let Some(cell) = buf.cell_mut((pos_0.x, pos_0.y)) {
1059                cell.set_symbol(c_0);
1060            }
1061        }
1062        if let Some((pos_1, c_1)) = split.get_join_1(*split_area, state) {
1063            if let Some(cell) = buf.cell_mut((pos_1.x, pos_1.y)) {
1064                cell.set_symbol(c_1);
1065            }
1066        }
1067    }
1068}
1069
1070impl Default for SplitState {
1071    fn default() -> Self {
1072        Self {
1073            area: Default::default(),
1074            inner: Default::default(),
1075            widget_areas: Default::default(),
1076            splitline_areas: Default::default(),
1077            splitline_mark_position: Default::default(),
1078            mark_offset: Default::default(),
1079            direction: Default::default(),
1080            split_type: Default::default(),
1081            resize: Default::default(),
1082            area_length: Default::default(),
1083            hidden_length: Default::default(),
1084            focus: Default::default(),
1085            focus_marker: Default::default(),
1086            mouse: Default::default(),
1087            non_exhaustive: NonExhaustive,
1088        }
1089    }
1090}
1091
1092impl Clone for SplitState {
1093    fn clone(&self) -> Self {
1094        Self {
1095            area: self.area,
1096            inner: self.inner,
1097            widget_areas: self.widget_areas.clone(),
1098            splitline_areas: self.splitline_areas.clone(),
1099            splitline_mark_position: self.splitline_mark_position.clone(),
1100            mark_offset: self.mark_offset,
1101            direction: self.direction,
1102            split_type: self.split_type,
1103            resize: self.resize,
1104            area_length: self.area_length.clone(),
1105            hidden_length: self.hidden_length.clone(),
1106            focus: FocusFlag::named(self.focus.name()),
1107            focus_marker: self.focus_marker,
1108            mouse: Default::default(),
1109            non_exhaustive: NonExhaustive,
1110        }
1111    }
1112}
1113
1114impl HasFocus for SplitState {
1115    fn build(&self, builder: &mut FocusBuilder) {
1116        builder.leaf_widget(self);
1117    }
1118
1119    fn focus(&self) -> FocusFlag {
1120        self.focus.clone()
1121    }
1122
1123    fn area(&self) -> Rect {
1124        // not mouse focusable
1125        Rect::default()
1126    }
1127
1128    fn navigable(&self) -> Navigation {
1129        Navigation::Leave
1130    }
1131}
1132
1133impl RelocatableState for SplitState {
1134    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1135        self.area = relocate_area(self.area, shift, clip);
1136        self.inner = relocate_area(self.inner, shift, clip);
1137        relocate_areas(self.widget_areas.as_mut_slice(), shift, clip);
1138        relocate_areas(self.splitline_areas.as_mut_slice(), shift, clip);
1139        relocate_positions(self.splitline_mark_position.as_mut_slice(), shift, clip);
1140    }
1141}
1142
1143#[allow(clippy::len_without_is_empty)]
1144impl SplitState {
1145    /// New state.
1146    pub fn new() -> Self {
1147        Self::default()
1148    }
1149
1150    /// New state with a focus-name.
1151    pub fn named(name: &str) -> Self {
1152        Self {
1153            focus: FocusFlag::named(name),
1154            ..Self::default()
1155        }
1156    }
1157
1158    /// Set the position for the nth splitter.
1159    ///
1160    /// The position is limited the combined area of the two adjacent areas.
1161    /// The position is further limited to leave space for rendering the
1162    /// splitter.
1163    ///
1164    pub fn set_screen_split_pos(&mut self, n: usize, pos: (u16, u16)) -> bool {
1165        if self.is_hidden(n) {
1166            return false;
1167        }
1168        if self.direction == Direction::Horizontal {
1169            let pos = if pos.0 < self.inner.left() {
1170                0
1171            } else if pos.0 < self.inner.right() {
1172                pos.0 - self.inner.x
1173            } else {
1174                self.inner.width
1175            };
1176
1177            let split_pos = self.split_pos(n);
1178            self.set_split_pos(n, pos);
1179
1180            split_pos != self.split_pos(n)
1181        } else {
1182            let pos = if pos.1 < self.inner.top() {
1183                0
1184            } else if pos.1 < self.inner.bottom() {
1185                pos.1 - self.inner.y
1186            } else {
1187                self.inner.height
1188            };
1189
1190            let split_pos = self.split_pos(n);
1191            self.set_split_pos(n, pos);
1192
1193            split_pos != self.split_pos(n)
1194        }
1195    }
1196
1197    /// Move the nth split position.
1198    /// If delta is greater than the area length it sets the
1199    /// length to 0.
1200    pub fn move_split_left(&mut self, n: usize, delta: u16) -> bool {
1201        let split_pos = self.split_pos(n);
1202        self.set_split_pos(n, split_pos - delta);
1203
1204        split_pos != self.split_pos(n)
1205    }
1206
1207    /// Move the nth split position.
1208    /// Does nothing if the change is bigger than the length of the split.
1209    pub fn move_split_right(&mut self, n: usize, delta: u16) -> bool {
1210        let split_pos = self.split_pos(n);
1211        self.set_split_pos(n, split_pos + delta);
1212
1213        split_pos != self.split_pos(n)
1214    }
1215
1216    /// Move the nth split position.
1217    /// Does nothing if the change is bigger than the length of the split.
1218    pub fn move_split_up(&mut self, n: usize, delta: u16) -> bool {
1219        self.move_split_left(n, delta)
1220    }
1221
1222    /// Move the nth split position.
1223    /// Does nothing if the change is bigger than the length of the split.
1224    pub fn move_split_down(&mut self, n: usize, delta: u16) -> bool {
1225        self.move_split_right(n, delta)
1226    }
1227
1228    /// Select the next splitter for manual adjustment.
1229    pub fn select_next_split(&mut self) -> bool {
1230        if self.is_focused() {
1231            let n = self.focus_marker.unwrap_or_default();
1232            if n + 1 >= self.area_length.len().saturating_sub(1) {
1233                self.focus_marker = Some(0);
1234            } else {
1235                self.focus_marker = Some(n + 1);
1236            }
1237            true
1238        } else {
1239            false
1240        }
1241    }
1242
1243    /// Select the previous splitter for manual adjustment.
1244    pub fn select_prev_split(&mut self) -> bool {
1245        if self.is_focused() {
1246            let n = self.focus_marker.unwrap_or_default();
1247            if n == 0 {
1248                self.focus_marker =
1249                    Some(self.area_length.len().saturating_sub(1).saturating_sub(1));
1250            } else {
1251                self.focus_marker = Some(n - 1);
1252            }
1253            true
1254        } else {
1255            false
1256        }
1257    }
1258
1259    /// Number of split-areas
1260    pub fn len(&self) -> usize {
1261        self.area_length.len()
1262    }
1263
1264    /// Get all area lengths.
1265    pub fn area_lengths(&self) -> &[u16] {
1266        &self.area_length
1267    }
1268
1269    /// Set all area lengths.
1270    ///
1271    /// This will adjust the list of the hidden splits too.
1272    ///
1273    /// __Caution__
1274    /// If the sum of the lengths doesn't match the display-width
1275    /// this will trigger a layout and will use the given lenghts
1276    /// as Constraint::Fill().
1277    ///
1278    /// __Caution__
1279    ///
1280    /// If a length is 0 it will not display the split at all.
1281    pub fn set_area_lengths(&mut self, lengths: Vec<u16>) {
1282        self.area_length = lengths;
1283        while self.hidden_length.len() < self.area_length.len() {
1284            self.hidden_length.push(0);
1285        }
1286        while self.hidden_length.len() > self.area_length.len() {
1287            self.hidden_length.pop();
1288        }
1289    }
1290
1291    /// Get the value of the hidden lengths.
1292    pub fn hidden_lengths(&self) -> &[u16] {
1293        &self.hidden_length
1294    }
1295
1296    /// Set the value of the hidden lengths.
1297    ///
1298    /// This will take at most area_length.len() items of this Vec.
1299    /// And it will fill missing items as 0.
1300    pub fn set_hidden_lengths(&mut self, hidden: Vec<u16>) {
1301        for i in 0..self.hidden_length.len() {
1302            if let Some(v) = hidden.get(i) {
1303                self.hidden_length[i] = *v;
1304            } else {
1305                self.hidden_length[i] = 0;
1306            }
1307        }
1308    }
1309
1310    /// Length of the nth split.
1311    ///
1312    /// __Caution__
1313    ///
1314    /// This length **includes** the width of the split itself.
1315    /// Which may or may not take some space. Except for the last.
1316    /// So it will be better to use `widget_areas` for anything
1317    /// rendering related.
1318    ///
1319    pub fn area_len(&self, n: usize) -> u16 {
1320        if n >= self.area_length.len() {
1321            return 0;
1322        }
1323        self.area_length[n]
1324    }
1325
1326    /// Sum of all area lengths.
1327    pub fn total_area_len(&self) -> u16 {
1328        self.area_length.iter().sum()
1329    }
1330
1331    /// Set the length of the nth split.
1332    ///
1333    /// This resets any hidden state of the nth split.
1334    ///
1335    /// __Caution__
1336    /// The sum of all lengths must be equal with the width/height of
1337    /// the splitter. If it is not this operation doesn't set the
1338    /// absolute width of the nth split. Instead, it triggers a layout
1339    /// of the widget, takes all the lengths as Constraint::Fill()
1340    /// values and redistributes the size.
1341    ///
1342    /// You can either ensure to change some other len to accommodate
1343    /// for your changes. Or use [set_split_pos](Self::set_split_pos) or
1344    /// [set_screen_split_pos](Self::set_screen_split_pos)
1345    ///
1346    /// __Caution__
1347    ///
1348    /// This length **includes** the width of the split itself.
1349    /// Which may or may not take some space. Except for the last area.
1350    /// Which doesn't have a split.
1351    ///
1352    /// So:
1353    /// - If you set the length to 0 the area will be hidden completely
1354    ///   and no split will be shown.
1355    /// - A value of 1 is fine.
1356    /// - The last area can have a length 0 and that's fine too.
1357    ///
1358    /// __Caution__
1359    ///
1360    /// Before the first render this will do nothing.
1361    /// Use [set_area_lengths](SplitState::set_area_lengths) to initialize the areas.
1362    ///
1363    pub fn set_area_len(&mut self, n: usize, len: u16) {
1364        if n >= self.area_length.len() {
1365            return;
1366        }
1367        self.area_length[n] = len;
1368        self.hidden_length[n] = 0;
1369    }
1370
1371    /// Returns the position of the nth split.
1372    ///
1373    /// __Caution__
1374    ///
1375    /// The numbering for the splitters goes from `0` to `len-1` __exclusive__.
1376    /// Split `n` marks the gap between area `n` and `n+1`.
1377    ///
1378    /// __Caution__
1379    ///
1380    /// This returns the position of the gap between two adjacent
1381    /// split-areas. Use `splitline_areas` for anything rendering related.
1382    pub fn split_pos(&self, n: usize) -> u16 {
1383        if n + 1 >= self.area_length.len() {
1384            return self.area_length.iter().sum();
1385        }
1386        self.area_length[..n + 1].iter().sum()
1387    }
1388
1389    /// Sets the position of the nth split.
1390    ///
1391    /// Depending on the resize strategy this can limit the allowed positions
1392    /// for the split.
1393    ///
1394    /// __Caution__
1395    ///
1396    /// The numbering for the splitters goes from `0` to `len-1` __exclusive__.
1397    /// Split `n` marks the gap between area `n` and `n+1`.
1398    ///
1399    /// __Caution__
1400    ///
1401    /// This marks the position of the gap between two adjacent
1402    /// split-areas. If you start from screen-coordinates it might
1403    /// be easier to use [set_screen_split_pos](Self::set_screen_split_pos)
1404    pub fn set_split_pos(&mut self, n: usize, pos: u16) {
1405        if n + 1 >= self.area_length.len() {
1406            return;
1407        }
1408
1409        match self.resize {
1410            SplitResize::Neighbours => {
1411                self.set_split_pos_neighbour(n, pos);
1412            }
1413            SplitResize::Full => {
1414                self.set_split_pos_full(n, pos);
1415            }
1416        }
1417    }
1418
1419    /// Limits the possible position of the split to the
1420    /// width of the two direct neighbours of the split.
1421    fn set_split_pos_neighbour(&mut self, n: usize, pos: u16) {
1422        assert!(n + 1 < self.area_length.len());
1423
1424        // create dual
1425        let mut pos_vec = Vec::new();
1426        let mut pp = 0;
1427        for len in &self.area_length {
1428            pp += *len;
1429            pos_vec.push(pp);
1430        }
1431        // last is not a split
1432        let pos_count = pos_vec.len();
1433
1434        let (min_pos, max_pos) = if n == 0 {
1435            if n + 2 >= pos_count {
1436                (SPLIT_WIDTH, pos_vec[n + 1])
1437            } else {
1438                (SPLIT_WIDTH, pos_vec[n + 1] - SPLIT_WIDTH)
1439            }
1440        } else if n + 2 < pos_count {
1441            (pos_vec[n - 1] + 1, pos_vec[n + 1] - SPLIT_WIDTH)
1442        } else {
1443            (pos_vec[n - 1] + 1, pos_vec[n + 1])
1444        };
1445
1446        pos_vec[n] = min(max(min_pos, pos), max_pos);
1447
1448        // revert dual
1449        for i in 0..pos_vec.len() {
1450            if i > 0 {
1451                self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1452            } else {
1453                self.area_length[i] = pos_vec[i];
1454            }
1455        }
1456    }
1457
1458    /// Allows the full range for the split-pos.
1459    /// Minus the space needed to render the split itself.
1460    #[allow(clippy::needless_range_loop)]
1461    #[allow(clippy::comparison_chain)]
1462    fn set_split_pos_full(&mut self, n: usize, pos: u16) {
1463        assert!(n + 1 < self.area_length.len());
1464
1465        let total_len = self.total_area_len();
1466
1467        // create dual
1468        let mut pos_vec = Vec::new();
1469        let mut pp = 0;
1470        for len in &self.area_length {
1471            pp += *len;
1472            pos_vec.push(pp);
1473        }
1474        // last is not a split
1475        pos_vec.pop();
1476        let pos_count = pos_vec.len();
1477
1478        let mut min_pos = SPLIT_WIDTH;
1479        for i in 0..pos_vec.len() {
1480            if i < n {
1481                if self.area_length[i] == 0 {
1482                    pos_vec[i] = min_pos;
1483                } else if self.hidden_length[i] != 0 {
1484                    pos_vec[i] = min_pos;
1485                    min_pos += SPLIT_WIDTH;
1486                } else {
1487                    if pos_vec[i] >= pos {
1488                        // how many split between here and there
1489                        let rest_area_count = n - (i + 1);
1490                        let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1491                        // min
1492                        pos_vec[i] = max(
1493                            min_pos,
1494                            pos.saturating_sub(SPLIT_WIDTH)
1495                                .saturating_sub(rest_area_width),
1496                        );
1497                        min_pos += SPLIT_WIDTH;
1498                    } else {
1499                        // unchanged
1500                    }
1501                }
1502            } else if i == n {
1503                // remaining area count with a split
1504                let rest_area_count = pos_count - (i + 1);
1505                let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1506                let rest_len = total_len - (min_pos + 1);
1507                // min for remaining areas
1508                let rest_len = rest_len - rest_area_width;
1509                // last can be 0
1510                let rest_len = rest_len + SPLIT_WIDTH;
1511
1512                let max_pos = min_pos + rest_len;
1513
1514                pos_vec[i] = min(max(min_pos, pos), max_pos);
1515
1516                min_pos = pos_vec[i] + SPLIT_WIDTH;
1517            } else {
1518                if self.area_length[i] == 0 {
1519                    pos_vec[i] = min_pos;
1520                } else if self.hidden_length[i] != 0 {
1521                    pos_vec[i] = min_pos;
1522                    min_pos += SPLIT_WIDTH;
1523                } else {
1524                    if pos_vec[i] <= pos {
1525                        pos_vec[i] = min_pos;
1526                        min_pos += SPLIT_WIDTH;
1527                    } else {
1528                        // unchanged
1529                    }
1530                }
1531            }
1532        }
1533
1534        // revert dual
1535        for i in 0..pos_vec.len() {
1536            if i > 0 {
1537                self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1538            } else {
1539                self.area_length[i] = pos_vec[i];
1540            }
1541        }
1542        self.area_length[pos_count] = total_len - pos_vec[pos_count - 1];
1543    }
1544
1545    /// Is the split hidden?
1546    pub fn is_hidden(&self, n: usize) -> bool {
1547        self.hidden_length[n] > 0
1548    }
1549
1550    /// Hide the split and adds its area to the following split.
1551    /// If there is no following split it will go left/up.
1552    /// Leaves enough space to render the splitter.
1553    pub fn hide_split(&mut self, n: usize) -> bool {
1554        if self.hidden_length[n] == 0 {
1555            let mut hide = if n + 1 == self.area_length.len() {
1556                self.area_length[n]
1557            } else {
1558                self.area_length[n].saturating_sub(SPLIT_WIDTH)
1559            };
1560            for idx in n + 1..self.area_length.len() {
1561                if self.hidden_length[idx] == 0 {
1562                    self.area_length[idx] += hide;
1563                    hide = 0;
1564                    break;
1565                }
1566            }
1567            if hide > 0 {
1568                for idx in (0..n).rev() {
1569                    if self.hidden_length[idx] == 0 {
1570                        self.area_length[idx] += hide;
1571                        hide = 0;
1572                        break;
1573                    }
1574                }
1575            }
1576
1577            if hide > 0 {
1578                // don't hide last split.
1579                self.hidden_length[n] = 0;
1580                false
1581            } else {
1582                if n + 1 == self.area_length.len() {
1583                    self.hidden_length[n] = self.area_length[n];
1584                    self.area_length[n] = 0;
1585                } else {
1586                    self.hidden_length[n] = self.area_length[n].saturating_sub(SPLIT_WIDTH);
1587                    self.area_length[n] = 1;
1588                };
1589                true
1590            }
1591        } else {
1592            false
1593        }
1594    }
1595
1596    /// Show a hidden split.
1597    /// It will first try to reduce the areas to the right,
1598    /// and then the areas to the left to make space.
1599    pub fn show_split(&mut self, n: usize) -> bool {
1600        let mut show = self.hidden_length[n];
1601        if show > 0 {
1602            for idx in n + 1..self.area_length.len() {
1603                if self.hidden_length[idx] == 0 {
1604                    // steal as much as we can
1605                    if self.area_length[idx] > show + SPLIT_WIDTH {
1606                        self.area_length[idx] -= show;
1607                        show = 0;
1608                    } else if self.area_length[idx] > SPLIT_WIDTH {
1609                        show -= self.area_length[idx] - SPLIT_WIDTH;
1610                        self.area_length[idx] = SPLIT_WIDTH;
1611                    }
1612                    if show == 0 {
1613                        break;
1614                    }
1615                }
1616            }
1617            if show > 0 {
1618                for idx in (0..n).rev() {
1619                    if self.hidden_length[idx] == 0 {
1620                        if self.area_length[idx] > show + SPLIT_WIDTH {
1621                            self.area_length[idx] -= show;
1622                            show = 0;
1623                        } else if self.area_length[idx] > SPLIT_WIDTH {
1624                            show -= self.area_length[idx] - SPLIT_WIDTH;
1625                            self.area_length[idx] = SPLIT_WIDTH;
1626                        }
1627                        if show == 0 {
1628                            break;
1629                        }
1630                    }
1631                }
1632            }
1633
1634            self.area_length[n] += self.hidden_length[n] - show;
1635            self.hidden_length[n] = 0;
1636            true
1637        } else {
1638            false
1639        }
1640    }
1641}
1642
1643impl HandleEvent<crossterm::event::Event, Regular, Outcome> for SplitState {
1644    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
1645        flow!(if self.is_focused() {
1646            if let Some(n) = self.focus_marker {
1647                match event {
1648                    ct_event!(keycode press Left) => self.move_split_left(n, 1).into(),
1649                    ct_event!(keycode press Right) => self.move_split_right(n, 1).into(),
1650                    ct_event!(keycode press Up) => self.move_split_up(n, 1).into(),
1651                    ct_event!(keycode press Down) => self.move_split_down(n, 1).into(),
1652
1653                    ct_event!(keycode press ALT-Left) => self.select_prev_split().into(),
1654                    ct_event!(keycode press ALT-Right) => self.select_next_split().into(),
1655                    ct_event!(keycode press ALT-Up) => self.select_prev_split().into(),
1656                    ct_event!(keycode press ALT-Down) => self.select_next_split().into(),
1657                    _ => Outcome::Continue,
1658                }
1659            } else {
1660                Outcome::Continue
1661            }
1662        } else {
1663            Outcome::Continue
1664        });
1665
1666        self.handle(event, MouseOnly)
1667    }
1668}
1669
1670impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for SplitState {
1671    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
1672        match event {
1673            ct_event!(mouse any for m) if self.mouse.hover(&self.splitline_areas, m) => {
1674                Outcome::Changed
1675            }
1676            ct_event!(mouse any for m) => {
1677                let was_drag = self.mouse.drag.get();
1678                if self.mouse.drag(&self.splitline_areas, m) {
1679                    if let Some(n) = self.mouse.drag.get() {
1680                        self.set_screen_split_pos(n, self.mouse.pos_of(m)).into()
1681                    } else {
1682                        Outcome::Continue
1683                    }
1684                } else {
1685                    // repaint after drag is finished. resets the displayed style.
1686                    if was_drag.is_some() {
1687                        Outcome::Changed
1688                    } else {
1689                        Outcome::Continue
1690                    }
1691                }
1692            }
1693            _ => Outcome::Continue,
1694        }
1695    }
1696}