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