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