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