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::splitter::split_impl::{get_fill_char, get_join_0, get_join_1, get_mark_0, get_mark_1};
39use crate::splitter::split_layout::layout_split;
40use crate::util::{fill_buf_area, revert_style};
41use rat_event::util::MouseFlagsN;
42use rat_event::{HandleEvent, MouseOnly, Outcome, Regular, ct_event, event_flow};
43use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
44use rat_reloc::{RelocatableState, relocate_area, relocate_areas, relocate_positions};
45use ratatui::buffer::Buffer;
46use ratatui::layout::{Constraint, Direction, Position, Rect};
47use ratatui::style::Style;
48use ratatui::widgets::{Block, BorderType, StatefulWidget, Widget};
49use std::cmp::{max, min};
50use std::iter;
51use std::rc::Rc;
52use unicode_segmentation::UnicodeSegmentation;
53
54mod split_impl;
55mod split_layout;
56
57#[derive(Debug, Default, Clone)]
58/// Splits the area in multiple parts and renders a UI that
59/// allows changing the sizes.
60///
61/// * Can hide/show regions.
62/// * Resize
63///     * neighbours only
64///     * all regions
65/// * Horizontal / vertical split. Choose one.
66///
67/// The widget doesn't hold references to the content widgets,
68/// instead it gives back the regions where the content can be
69/// rendered.
70///
71/// 1. Construct the Split.
72/// 2. Call [Split::into_widgets] to create the actual
73///    widget and the layout of the regions.
74/// 3. Render the content areas.
75/// 4. Render the split widget last. There are options that
76///    will render above the widgets and use part of the content
77///    area.
78///
79pub struct Split<'a> {
80    // direction
81    direction: Direction,
82    // start constraints. used when
83    // there are no widths in the state.
84    constraints: Vec<Constraint>,
85    // resize constraints for each area.
86    resize_constraints: Vec<ResizeConstraint>,
87    // resize options
88    resize: SplitResize,
89
90    // split rendering
91    split_type: SplitType,
92    // joiner left/top
93    join_0: Option<BorderType>,
94    // joiner right/bottom
95    join_1: Option<BorderType>,
96    // offset from left/top for the split-mar-k
97    mark_offset: u16,
98    // mar-k char 1
99    mark_0_char: Option<&'a str>,
100    // mar-k char 2
101    mark_1_char: Option<&'a str>,
102
103    // styling
104    style: Style,
105    block: Option<Block<'a>>,
106    arrow_style: Option<Style>,
107    drag_style: Option<Style>,
108}
109
110/// Widget for the Layout of the split.
111#[derive(Debug, Clone)]
112pub struct LayoutWidget<'a> {
113    split: Rc<Split<'a>>,
114}
115
116/// Primary widget for rendering the Split.
117#[derive(Debug, Clone)]
118pub struct SplitWidget<'a> {
119    split: Rc<Split<'a>>,
120}
121
122///
123/// Combined styles for the Split.
124///
125#[derive(Debug, Clone)]
126pub struct SplitStyle {
127    /// Base style
128    pub style: Style,
129    /// Block
130    pub block: Option<Block<'static>>,
131    pub border_style: Option<Style>,
132    pub title_style: Option<Style>,
133    /// Arrow style.
134    pub arrow_style: Option<Style>,
135    /// Style while dragging.
136    pub drag_style: Option<Style>,
137
138    /// Marker for a horizontal split.
139    /// Only the first 2 chars are used.
140    pub horizontal_mark: Option<&'static str>,
141    /// Marker for a vertical split.
142    /// Only the first 2 chars are used.
143    pub vertical_mark: Option<&'static str>,
144
145    pub non_exhaustive: NonExhaustive,
146}
147
148/// Render variants for the splitter.
149#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
150pub enum SplitType {
151    /// Render a full splitter between the widgets. Reduces the area for
152    /// each widget. Renders a blank border.
153    #[default]
154    FullEmpty,
155    /// Render a full splitter between the widgets. Reduces the area for
156    /// each widget. Renders a plain line border.
157    FullPlain,
158    /// Render a full splitter between the widgets. Reduces the area for
159    /// each widget. Renders a double line border.
160    FullDouble,
161    /// Render a full splitter between the widgets. Reduces the area for
162    /// each widget. Renders a thick line border.
163    FullThick,
164    /// Render a full splitter between the widgets. Reduces the area for
165    /// each widget. Renders a border with a single line on the inside
166    /// of a half block.
167    FullQuadrantInside,
168    /// Render a full splitter between the widgets. Reduces the area for
169    /// each widget. Renders a border with a single line on the outside
170    /// of a half block.
171    FullQuadrantOutside,
172    /// Render a minimal splitter, consisting just the two marker chars
173    /// rendered over the left/top widget.
174    ///
175    /// If the left widget has a Scroll in that area this will integrate
176    /// nicely. You will have to set `start_margin` with Scroll, then
177    /// Scroll can adjust its rendering to leave space for the markers.
178    /// And you want to set a `mark_offset` here.
179    ///
180    /// The widget will get the full area, only the marker is used
181    /// for mouse interactions.
182    Scroll,
183    /// Don't render a splitter, fully manual mode.
184    ///
185    /// The widget will have the full area, but the event-handling will
186    /// use the last column/row of the widget for moving the split.
187    /// This can be adjusted if you change `state.split[n]` which provides
188    /// the active area.
189    Widget,
190}
191
192/// Strategy for resizing the split-areas.
193#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
194pub enum SplitResize {
195    /// When changing a split-position limit resizing to the two
196    /// adjacent neighbours of the split.
197    Neighbours,
198    /// When changing a split-position, allow all positions in the
199    /// widgets area. Minus the minimum space required to draw the
200    /// split itself.
201    #[default]
202    Full,
203}
204
205/// How will one split area be resized when resizing the whole split-widget.
206#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
207pub enum ResizeConstraint {
208    /// The length of the split-area will stay fixed, if possible.
209    Fixed,
210    /// The length of the split-area will scale with the widget.
211    /// Each area will get a proportional part according to its current length.  
212    #[default]
213    ScaleProportional,
214    /// The length of the split-area will scale with the widget.
215    /// Each area will get the same proportion of the change.
216    ///
217    /// If you mix this with ScaleProportional this will not work,
218    /// because I don't know what you would expect to happen.
219    ScaleEqual,
220}
221
222const SPLIT_WIDTH: u16 = 1;
223
224/// State & event handling.
225#[derive(Debug)]
226pub struct SplitState {
227    /// Total area.
228    /// __readonly__. renewed for each render.
229    pub area: Rect,
230    /// Area inside the border.
231    /// __readonly__. renewed for each render.
232    pub inner: Rect,
233    /// The widget areas.
234    /// Use this after calling layout() to render your widgets.
235    /// __readonly__ renewed for each render.
236    pub widget_areas: Vec<Rect>,
237    /// Area used by the splitter. This is area is used for moving the splitter.
238    /// It might overlap with the widget area.
239    /// __readonly__ renewed for each render.
240    pub splitline_areas: Vec<Rect>,
241    /// Start position for drawing the mar-k.
242    /// __readonly__ renewed for each render.
243    pub splitline_mark_position: Vec<Position>,
244    /// Offset of the mar-k from top/left.
245    /// __readonly__ renewed for each render.
246    pub mark_offset: u16,
247
248    /// Direction of the split.
249    /// __readonly__ renewed for each render.
250    pub direction: Direction,
251    /// __readonly__ renewed for each render.
252    pub split_type: SplitType,
253    /// __readonly__ renewed for each render.
254    pub resize: SplitResize,
255
256    /// Layout-widths for the split-areas.
257    ///
258    /// This information is used after the initial render to
259    /// lay out the splitter.
260    area_length: Vec<u16>,
261    area_constraint: Vec<Constraint>,
262    /// Saved lengths for hidden splits.
263    hidden_length: Vec<u16>,
264
265    /// Focus.
266    /// __read+write__
267    pub focus: FocusFlag,
268    /// If the splitter has the focus you can navigate between
269    /// the split-markers. This is the currently active split-marker.
270    /// __read+write__
271    pub focus_marker: Option<usize>,
272
273    /// Mouseflags.
274    /// __read+write__
275    pub mouse: MouseFlagsN,
276
277    /// Rendering is split into base-widget and menu-popup.
278    /// Relocate after rendering the popup.
279    relocate_split: bool,
280
281    pub non_exhaustive: NonExhaustive,
282}
283
284impl SplitType {
285    pub fn is_full(&self) -> bool {
286        use SplitType::*;
287        match self {
288            FullEmpty => true,
289            FullPlain => true,
290            FullDouble => true,
291            FullThick => true,
292            FullQuadrantInside => true,
293            FullQuadrantOutside => true,
294            Scroll => false,
295            Widget => false,
296        }
297    }
298}
299
300impl Default for SplitStyle {
301    fn default() -> Self {
302        Self {
303            style: Default::default(),
304            block: Default::default(),
305            border_style: Default::default(),
306            title_style: Default::default(),
307            arrow_style: Default::default(),
308            drag_style: Default::default(),
309            horizontal_mark: Default::default(),
310            vertical_mark: Default::default(),
311            non_exhaustive: NonExhaustive,
312        }
313    }
314}
315
316impl<'a> Split<'a> {
317    /// Create a new split.
318    ///
319    /// To have any split-areas you must set a list of [constraints]
320    /// for both the number and initial sizes of the areas.
321    pub fn new() -> Self {
322        Self {
323            direction: Direction::Horizontal,
324            ..Default::default()
325        }
326    }
327
328    /// Horizontal split.
329    pub fn horizontal() -> Self {
330        Self {
331            direction: Direction::Horizontal,
332            ..Default::default()
333        }
334    }
335
336    /// Vertical split
337    pub fn vertical() -> Self {
338        Self {
339            direction: Direction::Horizontal,
340            ..Default::default()
341        }
342    }
343
344    /// Set constraints for the initial area sizes.
345    /// If the window is resized the current widths are used as
346    /// constraints for recalculating.
347    ///
348    /// The number of constraints determines the number of areas.
349    pub fn constraints(mut self, constraints: impl IntoIterator<Item = Constraint>) -> Self {
350        self.constraints = constraints.into_iter().collect();
351        self.resize_constraints = iter::from_fn(|| Some(ResizeConstraint::ScaleProportional))
352            .take(self.constraints.len())
353            .collect();
354        self
355    }
356
357    /// Set the behaviour for each area when resizing the split-widget itself.
358    pub fn resize_constraint(mut self, n: usize, constraint: ResizeConstraint) -> Self {
359        self.resize_constraints[n] = constraint;
360        self
361    }
362
363    /// Layout direction of the widgets.
364    /// Direction::Horizontal means the widgets are laid out left to right,
365    /// with a vertical split area in between.
366    pub fn direction(mut self, direction: Direction) -> Self {
367        self.direction = direction;
368        self
369    }
370
371    /// Controls rendering of the splitter.
372    pub fn split_type(mut self, split_type: SplitType) -> Self {
373        self.split_type = split_type;
374        self
375    }
376
377    /// Controls resizing the split areas.
378    pub fn resize(mut self, resize: SplitResize) -> Self {
379        self.resize = resize;
380        self
381    }
382
383    /// Draw a join character between the split and the
384    /// borders. This sets the border type used for the
385    /// surrounding border.
386    pub fn join(mut self, border: BorderType) -> Self {
387        self.join_0 = Some(border);
388        self.join_1 = Some(border);
389        self
390    }
391
392    /// Draw a join character between the split and the
393    /// border on the left/top side. This sets the border type
394    /// used for the left/top border.
395    pub fn join_0(mut self, border: BorderType) -> Self {
396        self.join_0 = Some(border);
397        self
398    }
399
400    /// Draw a join character between the split and the
401    /// border on the right/bottom side. This sets the border type
402    /// used for the right/bottom border.
403    pub fn join_1(mut self, border: BorderType) -> Self {
404        self.join_1 = Some(border);
405        self
406    }
407
408    /// Outer block.
409    pub fn block(mut self, block: Block<'a>) -> Self {
410        self.block = Some(block);
411        self
412    }
413
414    /// Set all styles.
415    pub fn styles(mut self, styles: SplitStyle) -> Self {
416        self.style = styles.style;
417        if styles.block.is_some() {
418            self.block = styles.block;
419        }
420        if let Some(border_style) = styles.border_style {
421            self.block = self.block.map(|v| v.border_style(border_style));
422        }
423        if let Some(title_style) = styles.title_style {
424            self.block = self.block.map(|v| v.title_style(title_style));
425        }
426        self.block = self.block.map(|v| v.style(self.style));
427
428        if styles.drag_style.is_some() {
429            self.drag_style = styles.drag_style;
430        }
431        if styles.arrow_style.is_some() {
432            self.arrow_style = styles.arrow_style;
433        }
434        match self.direction {
435            Direction::Horizontal => {
436                if let Some(mark) = styles.horizontal_mark {
437                    let mut g = mark.graphemes(true);
438                    if let Some(g0) = g.next() {
439                        self.mark_0_char = Some(g0);
440                    }
441                    if let Some(g1) = g.next() {
442                        self.mark_1_char = Some(g1);
443                    }
444                }
445            }
446            Direction::Vertical => {
447                if let Some(mark) = styles.vertical_mark {
448                    let mut g = mark.graphemes(true);
449                    if let Some(g0) = g.next() {
450                        self.mark_0_char = Some(g0);
451                    }
452                    if let Some(g1) = g.next() {
453                        self.mark_1_char = Some(g1);
454                    }
455                }
456            }
457        }
458        self
459    }
460
461    /// Style for the split area.
462    pub fn style(mut self, style: Style) -> Self {
463        self.style = style;
464        self.block = self.block.map(|v| v.style(style));
465        self
466    }
467
468    /// Style for the arrows.
469    pub fn arrow_style(mut self, style: Style) -> Self {
470        self.arrow_style = Some(style);
471        self
472    }
473
474    /// Style while dragging the splitter.
475    pub fn drag_style(mut self, style: Style) -> Self {
476        self.drag_style = Some(style);
477        self
478    }
479
480    /// Offset for the split marker from the top/left.
481    pub fn mark_offset(mut self, offset: u16) -> Self {
482        self.mark_offset = offset;
483        self
484    }
485
486    /// First marker char for the splitter.
487    pub fn mark_0(mut self, mark: &'a str) -> Self {
488        self.mark_0_char = Some(mark);
489        self
490    }
491
492    /// Second marker char for the splitter.
493    pub fn mark_1(mut self, mark: &'a str) -> Self {
494        self.mark_1_char = Some(mark);
495        self
496    }
497
498    /// Constructs the widget for rendering.
499    ///
500    /// Returns the SplitWidget that actually renders the split decorations.
501    ///
502    /// Use [SplitState::widget_areas] to render your contents, and
503    /// then render the SplitWidget. This allows it to render some
504    /// decorations on top of your widgets.
505    #[deprecated(since = "2.4.0", note = "use into_widgets() instead")]
506    pub fn into_widget(self, area: Rect, state: &mut SplitState) -> SplitWidget<'a> {
507        layout_split(&self, area, state);
508        SplitWidget {
509            split: Rc::new(self),
510        }
511    }
512
513    /// Constructs the widgets for rendering.
514    ///
515    /// Returns the SplitWidget that actually renders the split.
516    /// Returns a `Vec<Rect>` with the regions for each split.
517    ///
518    /// Render your content first, using the layout information.
519    /// And the SplitWidget as last to allow rendering over
520    /// the content widgets.
521    #[deprecated(since = "2.4.0", note = "use into_widgets() instead")]
522    pub fn into_widget_layout(
523        self,
524        area: Rect,
525        state: &mut SplitState,
526    ) -> (SplitWidget<'a>, Vec<Rect>) {
527        layout_split(&self, area, state);
528        (
529            SplitWidget {
530                split: Rc::new(self),
531            },
532            state.widget_areas.clone(),
533        )
534    }
535
536    /// Constructs the widgets for rendering.
537    ///
538    /// Returns the LayoutWidget that must run first. It
539    /// doesn't actually render anything, it just calculates
540    /// the layout for the split regions.
541    ///
542    /// Use the layout in [SplitState::widget_areas] to render
543    /// your widgets.
544    ///
545    /// The SplitWidget actually renders the split itself.
546    /// Render it after you finished with the content.
547    pub fn into_widgets(self) -> (LayoutWidget<'a>, SplitWidget<'a>) {
548        let split = Rc::new(self);
549        (
550            LayoutWidget {
551                split: split.clone(), //
552            },
553            SplitWidget {
554                split, //
555            },
556        )
557    }
558}
559
560impl<'a> StatefulWidget for &Split<'a> {
561    type State = SplitState;
562
563    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
564        layout_split(self, area, state);
565        render_split(self, buf, state);
566        state.relocate_split = false;
567    }
568}
569
570impl<'a> StatefulWidget for Split<'a> {
571    type State = SplitState;
572
573    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
574        layout_split(&self, area, state);
575        render_split(&self, buf, state);
576        state.relocate_split = false;
577    }
578}
579
580impl<'a> StatefulWidget for &LayoutWidget<'a> {
581    type State = SplitState;
582
583    fn render(self, area: Rect, _buf: &mut Buffer, state: &mut Self::State) {
584        // just run the layout here. SplitWidget renders the rest.
585        layout_split(&self.split, area, state);
586    }
587}
588
589impl<'a> StatefulWidget for LayoutWidget<'a> {
590    type State = SplitState;
591
592    fn render(self, area: Rect, _buf: &mut Buffer, state: &mut Self::State) {
593        // just run the layout here. SplitWidget renders the rest.
594        layout_split(&self.split, area, state);
595    }
596}
597
598impl<'a> StatefulWidget for &SplitWidget<'a> {
599    type State = SplitState;
600
601    fn render(self, _area: Rect, buf: &mut Buffer, state: &mut Self::State) {
602        render_split(&self.split, buf, state);
603    }
604}
605
606impl StatefulWidget for SplitWidget<'_> {
607    type State = SplitState;
608
609    fn render(self, _area: Rect, buf: &mut Buffer, state: &mut Self::State) {
610        render_split(&self.split, buf, state);
611    }
612}
613
614fn render_split(split: &Split<'_>, buf: &mut Buffer, state: &mut SplitState) {
615    let area = state.area;
616    if state.is_focused() {
617        if state.focus_marker.is_none() {
618            state.focus_marker = Some(0);
619        }
620    } else {
621        state.focus_marker = None;
622    }
623
624    split.block.render(area, buf);
625
626    for (n, split_area) in state.splitline_areas.iter().enumerate() {
627        // skip 0 width/height
628        if split.direction == Direction::Horizontal {
629            if split_area.width == 0 {
630                continue;
631            }
632        } else {
633            if split_area.height == 0 {
634                continue;
635            }
636        }
637
638        let (style, arrow_style) = if Some(n) == state.mouse.drag.get()
639            || Some(n) == state.focus_marker
640            || Some(n) == state.mouse.hover.get()
641        {
642            if let Some(drag) = split.drag_style {
643                (drag, drag)
644            } else {
645                (revert_style(split.style), revert_style(split.style))
646            }
647        } else {
648            if let Some(arrow) = split.arrow_style {
649                (split.style, arrow)
650            } else {
651                (split.style, split.style)
652            }
653        };
654
655        if let Some(fill) = get_fill_char(split) {
656            fill_buf_area(buf, *split_area, fill, style);
657        }
658
659        let mark = state.splitline_mark_position[n];
660        if split.direction == Direction::Horizontal {
661            if buf.area.contains((mark.x, mark.y).into()) {
662                if let Some(cell) = buf.cell_mut((mark.x, mark.y)) {
663                    cell.set_style(arrow_style);
664                    cell.set_symbol(get_mark_0(split));
665                }
666            }
667            if buf.area.contains((mark.x, mark.y + 1).into()) {
668                if let Some(cell) = buf.cell_mut((mark.x, mark.y + 1)) {
669                    cell.set_style(arrow_style);
670                    cell.set_symbol(get_mark_1(split));
671                }
672            }
673        } else {
674            if let Some(cell) = buf.cell_mut((mark.x, mark.y)) {
675                cell.set_style(arrow_style);
676                cell.set_symbol(get_mark_0(split));
677            }
678            if let Some(cell) = buf.cell_mut((mark.x + 1, mark.y)) {
679                cell.set_style(arrow_style);
680                cell.set_symbol(get_mark_1(split));
681            }
682        }
683
684        if let Some((pos_0, c_0)) = get_join_0(split, *split_area, state) {
685            if let Some(cell) = buf.cell_mut((pos_0.x, pos_0.y)) {
686                cell.set_symbol(c_0);
687            }
688        }
689        if let Some((pos_1, c_1)) = get_join_1(split, *split_area, state) {
690            if let Some(cell) = buf.cell_mut((pos_1.x, pos_1.y)) {
691                cell.set_symbol(c_1);
692            }
693        }
694    }
695}
696
697impl Default for SplitState {
698    fn default() -> Self {
699        Self {
700            area: Default::default(),
701            inner: Default::default(),
702            widget_areas: Default::default(),
703            splitline_areas: Default::default(),
704            splitline_mark_position: Default::default(),
705            mark_offset: Default::default(),
706            direction: Default::default(),
707            split_type: Default::default(),
708            resize: Default::default(),
709            area_length: Default::default(),
710            area_constraint: Default::default(),
711            hidden_length: Default::default(),
712            focus: Default::default(),
713            focus_marker: Default::default(),
714            mouse: Default::default(),
715            relocate_split: Default::default(),
716            non_exhaustive: NonExhaustive,
717        }
718    }
719}
720
721impl Clone for SplitState {
722    fn clone(&self) -> Self {
723        Self {
724            area: self.area,
725            inner: self.inner,
726            widget_areas: self.widget_areas.clone(),
727            splitline_areas: self.splitline_areas.clone(),
728            splitline_mark_position: self.splitline_mark_position.clone(),
729            mark_offset: self.mark_offset,
730            direction: self.direction,
731            split_type: self.split_type,
732            resize: self.resize,
733            area_length: self.area_length.clone(),
734            area_constraint: self.area_constraint.clone(),
735            hidden_length: self.hidden_length.clone(),
736            focus: self.focus.new_instance(),
737            focus_marker: self.focus_marker,
738            mouse: Default::default(),
739            relocate_split: self.relocate_split,
740            non_exhaustive: NonExhaustive,
741        }
742    }
743}
744
745impl HasFocus for SplitState {
746    fn build(&self, builder: &mut FocusBuilder) {
747        builder.leaf_widget(self);
748    }
749
750    fn focus(&self) -> FocusFlag {
751        self.focus.clone()
752    }
753
754    fn area(&self) -> Rect {
755        // not mouse focusable
756        Rect::default()
757    }
758
759    fn navigable(&self) -> Navigation {
760        Navigation::Leave
761    }
762}
763
764impl RelocatableState for SplitState {
765    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
766        // relocate after the popup/split itself is rendered.
767        if !self.relocate_split {
768            self.area = relocate_area(self.area, shift, clip);
769            self.inner = relocate_area(self.inner, shift, clip);
770            relocate_areas(self.widget_areas.as_mut_slice(), shift, clip);
771            relocate_areas(self.splitline_areas.as_mut_slice(), shift, clip);
772            relocate_positions(self.splitline_mark_position.as_mut_slice(), shift, clip);
773        }
774    }
775
776    fn relocate_popup(&mut self, shift: (i16, i16), clip: Rect) {
777        if self.relocate_split {
778            self.relocate_split = false;
779            self.area = relocate_area(self.area, shift, clip);
780            self.inner = relocate_area(self.inner, shift, clip);
781            relocate_areas(self.widget_areas.as_mut_slice(), shift, clip);
782            relocate_areas(self.splitline_areas.as_mut_slice(), shift, clip);
783            relocate_positions(self.splitline_mark_position.as_mut_slice(), shift, clip);
784        }
785    }
786}
787
788#[allow(clippy::len_without_is_empty)]
789impl SplitState {
790    /// New state.
791    pub fn new() -> Self {
792        Self::default()
793    }
794
795    /// New state with a focus-name.
796    pub fn named(name: &str) -> Self {
797        let mut z = Self::default();
798        z.focus = z.focus.with_name(name);
799        z
800    }
801
802    /// Set the position for the nth splitter.
803    ///
804    /// The position is limited the combined area of the two adjacent areas.
805    /// The position is further limited to leave space for rendering the
806    /// splitter.
807    ///
808    pub fn set_screen_split_pos(&mut self, n: usize, pos: (u16, u16)) -> bool {
809        if self.is_hidden(n) {
810            return false;
811        }
812        if self.direction == Direction::Horizontal {
813            let pos = if pos.0 < self.inner.left() {
814                0
815            } else if pos.0 < self.inner.right() {
816                pos.0 - self.inner.x
817            } else {
818                self.inner.width
819            };
820
821            let split_pos = self.split_pos(n);
822            self.set_split_pos(n, pos);
823
824            split_pos != self.split_pos(n)
825        } else {
826            let pos = if pos.1 < self.inner.top() {
827                0
828            } else if pos.1 < self.inner.bottom() {
829                pos.1 - self.inner.y
830            } else {
831                self.inner.height
832            };
833
834            let split_pos = self.split_pos(n);
835            self.set_split_pos(n, pos);
836
837            split_pos != self.split_pos(n)
838        }
839    }
840
841    /// Move the nth split position.
842    /// If delta is greater than the area length it sets the
843    /// length to 0.
844    pub fn move_split_left(&mut self, n: usize, delta: u16) -> bool {
845        let split_pos = self.split_pos(n);
846        self.set_split_pos(n, split_pos - delta);
847
848        split_pos != self.split_pos(n)
849    }
850
851    /// Move the nth split position.
852    /// Does nothing if the change is bigger than the length of the split.
853    pub fn move_split_right(&mut self, n: usize, delta: u16) -> bool {
854        let split_pos = self.split_pos(n);
855        self.set_split_pos(n, split_pos + delta);
856
857        split_pos != self.split_pos(n)
858    }
859
860    /// Move the nth split position.
861    /// Does nothing if the change is bigger than the length of the split.
862    pub fn move_split_up(&mut self, n: usize, delta: u16) -> bool {
863        self.move_split_left(n, delta)
864    }
865
866    /// Move the nth split position.
867    /// Does nothing if the change is bigger than the length of the split.
868    pub fn move_split_down(&mut self, n: usize, delta: u16) -> bool {
869        self.move_split_right(n, delta)
870    }
871
872    /// Select the next splitter for manual adjustment.
873    pub fn select_next_split(&mut self) -> bool {
874        if self.is_focused() {
875            let n = self.focus_marker.unwrap_or_default();
876            if n + 1 >= self.area_length.len().saturating_sub(1) {
877                self.focus_marker = Some(0);
878            } else {
879                self.focus_marker = Some(n + 1);
880            }
881            true
882        } else {
883            false
884        }
885    }
886
887    /// Select the previous splitter for manual adjustment.
888    pub fn select_prev_split(&mut self) -> bool {
889        if self.is_focused() {
890            let n = self.focus_marker.unwrap_or_default();
891            if n == 0 {
892                self.focus_marker =
893                    Some(self.area_length.len().saturating_sub(1).saturating_sub(1));
894            } else {
895                self.focus_marker = Some(n - 1);
896            }
897            true
898        } else {
899            false
900        }
901    }
902
903    /// Number of split-areas
904    pub fn len(&self) -> usize {
905        self.area_length.len()
906    }
907
908    /// Get all area lengths.
909    pub fn area_lengths(&self) -> &[u16] {
910        &self.area_length
911    }
912
913    /// Set all area lengths.
914    ///
915    /// This will adjust the list of the hidden splits too.
916    ///
917    /// __Caution__
918    /// If the sum of the lengths doesn't match the display-width
919    /// this will trigger a layout and will use the given lenghts
920    /// as Constraint::Fill().
921    ///
922    /// __Caution__
923    ///
924    /// If a length is 0 it will not display the split at all.
925    pub fn set_area_lengths(&mut self, lengths: Vec<u16>) {
926        self.area_length = lengths;
927        self.area_constraint.clear();
928        while self.hidden_length.len() < self.area_length.len() {
929            self.hidden_length.push(0);
930        }
931        while self.hidden_length.len() > self.area_length.len() {
932            self.hidden_length.pop();
933        }
934    }
935
936    /// Get the value of the hidden lengths.
937    pub fn hidden_lengths(&self) -> &[u16] {
938        &self.hidden_length
939    }
940
941    /// Set the value of the hidden lengths.
942    ///
943    /// This will take at most area_length.len() items of this Vec.
944    /// And it will fill missing items as 0.
945    pub fn set_hidden_lengths(&mut self, hidden: Vec<u16>) {
946        for i in 0..self.hidden_length.len() {
947            if let Some(v) = hidden.get(i) {
948                self.hidden_length[i] = *v;
949            } else {
950                self.hidden_length[i] = 0;
951            }
952        }
953    }
954
955    /// Length of the nth split.
956    ///
957    /// __Caution__
958    ///
959    /// This length **includes** the width of the split itself.
960    /// Which may or may not take some space. Except for the last.
961    /// So it will be better to use `widget_areas` for anything
962    /// rendering related.
963    ///
964    pub fn area_len(&self, n: usize) -> u16 {
965        if n >= self.area_length.len() {
966            return 0;
967        }
968        self.area_length[n]
969    }
970
971    /// Sum of all area lengths.
972    pub fn total_area_len(&self) -> u16 {
973        self.area_length.iter().sum()
974    }
975
976    /// Set the length of the nth split.
977    ///
978    /// This resets any hidden state of the nth split.
979    ///
980    /// __Caution__
981    /// The sum of all lengths must be equal with the width/height of
982    /// the splitter. If it is not this operation doesn't set the
983    /// absolute width of the nth split. Instead, it triggers a layout
984    /// of the widget, takes all the lengths as Constraint::Fill()
985    /// values and redistributes the size.
986    ///
987    /// You can either ensure to change some other len to accommodate
988    /// for your changes. Or use [set_split_pos](Self::set_split_pos) or
989    /// [set_screen_split_pos](Self::set_screen_split_pos)
990    ///
991    /// __Caution__
992    ///
993    /// This length **includes** the width of the split itself.
994    /// Which may or may not take some space. Except for the last area.
995    /// Which doesn't have a split.
996    ///
997    /// So:
998    /// - If you set the length to 0 the area will be hidden completely
999    ///   and no split will be shown.
1000    /// - A value of 1 is fine.
1001    /// - The last area can have a length 0 and that's fine too.
1002    ///
1003    /// __Caution__
1004    ///
1005    /// Before the first render this will do nothing.
1006    /// Use [set_area_lengths](SplitState::set_area_lengths) to initialize the areas.
1007    ///
1008    pub fn set_area_len(&mut self, n: usize, len: u16) {
1009        if n >= self.area_length.len() {
1010            return;
1011        }
1012        self.area_length[n] = len;
1013        self.area_constraint.clear();
1014        self.hidden_length[n] = 0;
1015    }
1016
1017    /// Returns the position of the nth split.
1018    ///
1019    /// __Caution__
1020    ///
1021    /// The numbering for the splitters goes from `0` to `len-1` __exclusive__.
1022    /// Split `n` marks the gap between area `n` and `n+1`.
1023    ///
1024    /// __Caution__
1025    ///
1026    /// This returns the position of the gap between two adjacent
1027    /// split-areas. Use `splitline_areas` for anything rendering related.
1028    pub fn split_pos(&self, n: usize) -> u16 {
1029        if n + 1 >= self.area_length.len() {
1030            return self.area_length.iter().sum();
1031        }
1032        self.area_length[..n + 1].iter().sum()
1033    }
1034
1035    /// Sets the position of the nth split.
1036    ///
1037    /// Depending on the resize strategy this can limit the allowed positions
1038    /// for the split.
1039    ///
1040    /// __Caution__
1041    ///
1042    /// The numbering for the splitters goes from `0` to `len-1` __exclusive__.
1043    /// Split `n` marks the gap between area `n` and `n+1`.
1044    ///
1045    /// __Caution__
1046    ///
1047    /// This marks the position of the gap between two adjacent
1048    /// split-areas. If you start from screen-coordinates it might
1049    /// be easier to use [set_screen_split_pos](Self::set_screen_split_pos)
1050    pub fn set_split_pos(&mut self, n: usize, pos: u16) {
1051        if n + 1 >= self.area_length.len() {
1052            return;
1053        }
1054
1055        match self.resize {
1056            SplitResize::Neighbours => {
1057                self.set_split_pos_neighbour(n, pos);
1058            }
1059            SplitResize::Full => {
1060                self.set_split_pos_full(n, pos);
1061            }
1062        }
1063    }
1064
1065    /// Limits the possible position of the split to the
1066    /// width of the two direct neighbours of the split.
1067    fn set_split_pos_neighbour(&mut self, n: usize, pos: u16) {
1068        assert!(n + 1 < self.area_length.len());
1069
1070        // create dual
1071        let mut pos_vec = Vec::new();
1072        let mut pp = 0;
1073        for len in &self.area_length {
1074            pp += *len;
1075            pos_vec.push(pp);
1076        }
1077        // last is not a split
1078        let pos_count = pos_vec.len();
1079
1080        let (min_pos, max_pos) = if n == 0 {
1081            if n + 2 >= pos_count {
1082                (SPLIT_WIDTH, pos_vec[n + 1])
1083            } else {
1084                (SPLIT_WIDTH, pos_vec[n + 1] - SPLIT_WIDTH)
1085            }
1086        } else if n + 2 < pos_count {
1087            (pos_vec[n - 1] + 1, pos_vec[n + 1] - SPLIT_WIDTH)
1088        } else {
1089            (pos_vec[n - 1] + 1, pos_vec[n + 1])
1090        };
1091
1092        pos_vec[n] = min(max(min_pos, pos), max_pos);
1093
1094        // revert dual
1095        for i in 0..pos_vec.len() {
1096            if i > 0 {
1097                self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1098            } else {
1099                self.area_length[i] = pos_vec[i];
1100            }
1101        }
1102        self.area_constraint.clear();
1103    }
1104
1105    /// Allows the full range for the split-pos.
1106    /// Minus the space needed to render the split itself.
1107    #[allow(clippy::needless_range_loop)]
1108    #[allow(clippy::comparison_chain)]
1109    fn set_split_pos_full(&mut self, n: usize, pos: u16) {
1110        assert!(n + 1 < self.area_length.len());
1111
1112        let total_len = self.total_area_len();
1113
1114        // create dual
1115        let mut pos_vec = Vec::new();
1116        let mut pp = 0;
1117        for len in &self.area_length {
1118            pp += *len;
1119            pos_vec.push(pp);
1120        }
1121        // last is not a split
1122        pos_vec.pop();
1123        let pos_count = pos_vec.len();
1124
1125        let mut min_pos = SPLIT_WIDTH;
1126        for i in 0..pos_vec.len() {
1127            if i < n {
1128                if self.area_length[i] == 0 {
1129                    pos_vec[i] = min_pos;
1130                } else if self.hidden_length[i] != 0 {
1131                    pos_vec[i] = min_pos;
1132                    min_pos += SPLIT_WIDTH;
1133                } else {
1134                    if pos_vec[i] >= pos {
1135                        // how many split between here and there
1136                        let rest_area_count = n - (i + 1);
1137                        let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1138                        // min
1139                        pos_vec[i] = max(
1140                            min_pos,
1141                            pos.saturating_sub(SPLIT_WIDTH)
1142                                .saturating_sub(rest_area_width),
1143                        );
1144                        min_pos += SPLIT_WIDTH;
1145                    } else {
1146                        // unchanged
1147                    }
1148                }
1149            } else if i == n {
1150                // remaining area count with a split
1151                let rest_area_count = pos_count - (i + 1);
1152                let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1153                let rest_len = total_len - (min_pos + 1);
1154                // min for remaining areas
1155                let rest_len = rest_len - rest_area_width;
1156                // last can be 0
1157                let rest_len = rest_len + SPLIT_WIDTH;
1158
1159                let max_pos = min_pos + rest_len;
1160
1161                pos_vec[i] = min(max(min_pos, pos), max_pos);
1162
1163                min_pos = pos_vec[i] + SPLIT_WIDTH;
1164            } else {
1165                if self.area_length[i] == 0 {
1166                    pos_vec[i] = min_pos;
1167                } else if self.hidden_length[i] != 0 {
1168                    pos_vec[i] = min_pos;
1169                    min_pos += SPLIT_WIDTH;
1170                } else {
1171                    if pos_vec[i] <= pos {
1172                        pos_vec[i] = min_pos;
1173                        min_pos += SPLIT_WIDTH;
1174                    } else {
1175                        // unchanged
1176                    }
1177                }
1178            }
1179        }
1180
1181        // revert dual
1182        for i in 0..pos_vec.len() {
1183            if i > 0 {
1184                self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1185            } else {
1186                self.area_length[i] = pos_vec[i];
1187            }
1188        }
1189        self.area_length[pos_count] = total_len - pos_vec[pos_count - 1];
1190        self.area_constraint.clear();
1191    }
1192
1193    /// Is the split hidden?
1194    pub fn is_hidden(&self, n: usize) -> bool {
1195        self.hidden_length[n] > 0
1196    }
1197
1198    /// Hide the split and adds its area to the following split.
1199    /// If there is no following split it will go left/up.
1200    /// Leaves enough space to render the splitter.
1201    pub fn hide_split(&mut self, n: usize) -> bool {
1202        if self.hidden_length[n] == 0 {
1203            self.area_constraint.clear();
1204
1205            let mut hide = if n + 1 == self.area_length.len() {
1206                self.area_length[n]
1207            } else {
1208                self.area_length[n].saturating_sub(SPLIT_WIDTH)
1209            };
1210            for idx in n + 1..self.area_length.len() {
1211                if self.hidden_length[idx] == 0 {
1212                    self.area_length[idx] += hide;
1213                    hide = 0;
1214                    break;
1215                }
1216            }
1217            if hide > 0 {
1218                for idx in (0..n).rev() {
1219                    if self.hidden_length[idx] == 0 {
1220                        self.area_length[idx] += hide;
1221                        hide = 0;
1222                        break;
1223                    }
1224                }
1225            }
1226
1227            if hide > 0 {
1228                // don't hide last split.
1229                self.hidden_length[n] = 0;
1230                false
1231            } else {
1232                if n + 1 == self.area_length.len() {
1233                    self.hidden_length[n] = self.area_length[n];
1234                    self.area_length[n] = 0;
1235                } else {
1236                    self.hidden_length[n] = self.area_length[n].saturating_sub(SPLIT_WIDTH);
1237                    self.area_length[n] = 1;
1238                };
1239                true
1240            }
1241        } else {
1242            false
1243        }
1244    }
1245
1246    /// Show a hidden split.
1247    /// It will first try to reduce the areas to the right,
1248    /// and then the areas to the left to make space.
1249    pub fn show_split(&mut self, n: usize) -> bool {
1250        let mut show = self.hidden_length[n];
1251        if show > 0 {
1252            for idx in n + 1..self.area_length.len() {
1253                if self.hidden_length[idx] == 0 {
1254                    // steal as much as we can
1255                    if self.area_length[idx] > show + SPLIT_WIDTH {
1256                        self.area_length[idx] -= show;
1257                        show = 0;
1258                    } else if self.area_length[idx] > SPLIT_WIDTH {
1259                        show -= self.area_length[idx] - SPLIT_WIDTH;
1260                        self.area_length[idx] = SPLIT_WIDTH;
1261                    }
1262                    if show == 0 {
1263                        break;
1264                    }
1265                }
1266            }
1267            if show > 0 {
1268                for idx in (0..n).rev() {
1269                    if self.hidden_length[idx] == 0 {
1270                        if self.area_length[idx] > show + SPLIT_WIDTH {
1271                            self.area_length[idx] -= show;
1272                            show = 0;
1273                        } else if self.area_length[idx] > SPLIT_WIDTH {
1274                            show -= self.area_length[idx] - SPLIT_WIDTH;
1275                            self.area_length[idx] = SPLIT_WIDTH;
1276                        }
1277                        if show == 0 {
1278                            break;
1279                        }
1280                    }
1281                }
1282            }
1283
1284            self.area_length[n] += self.hidden_length[n] - show;
1285            self.hidden_length[n] = 0;
1286            self.area_constraint.clear();
1287            true
1288        } else {
1289            false
1290        }
1291    }
1292}
1293
1294impl HandleEvent<crossterm::event::Event, Regular, Outcome> for SplitState {
1295    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
1296        event_flow!(
1297            return if self.is_focused() {
1298                if let Some(n) = self.focus_marker {
1299                    match event {
1300                        ct_event!(keycode press Left) => self.select_prev_split().into(),
1301                        ct_event!(keycode press Right) => self.select_next_split().into(),
1302                        ct_event!(keycode press Up) => self.select_prev_split().into(),
1303                        ct_event!(keycode press Down) => self.select_next_split().into(),
1304
1305                        ct_event!(keycode press CONTROL-Left) => self.move_split_left(n, 1).into(),
1306                        ct_event!(keycode press CONTROL-Right) => {
1307                            self.move_split_right(n, 1).into()
1308                        }
1309                        ct_event!(keycode press CONTROL-Up) => self.move_split_up(n, 1).into(),
1310                        ct_event!(keycode press CONTROL-Down) => self.move_split_down(n, 1).into(),
1311                        _ => Outcome::Continue,
1312                    }
1313                } else {
1314                    Outcome::Continue
1315                }
1316            } else {
1317                Outcome::Continue
1318            }
1319        );
1320
1321        self.handle(event, MouseOnly)
1322    }
1323}
1324
1325impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for SplitState {
1326    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
1327        match event {
1328            ct_event!(mouse any for m) if self.mouse.hover(&self.splitline_areas, m) => {
1329                Outcome::Changed
1330            }
1331            ct_event!(mouse any for m) => {
1332                let was_drag = self.mouse.drag.get();
1333                if self.mouse.drag(&self.splitline_areas, m) {
1334                    if let Some(n) = self.mouse.drag.get() {
1335                        self.set_screen_split_pos(n, self.mouse.pos_of(m)).into()
1336                    } else {
1337                        Outcome::Continue
1338                    }
1339                } else {
1340                    // repaint after drag is finished. resets the displayed style.
1341                    if was_drag.is_some() {
1342                        Outcome::Changed
1343                    } else {
1344                        Outcome::Continue
1345                    }
1346                }
1347            }
1348            _ => Outcome::Continue,
1349        }
1350    }
1351}
1352
1353/// Handle all events.
1354/// Text events are only processed if focus is true.
1355/// Mouse events are processed if they are in range.
1356pub fn handle_events(
1357    state: &mut SplitState,
1358    focus: bool,
1359    event: &crossterm::event::Event,
1360) -> Outcome {
1361    state.focus.set(focus);
1362    HandleEvent::handle(state, event, Regular)
1363}
1364
1365/// Handle only mouse-events.
1366pub fn handle_mouse_events(state: &mut SplitState, event: &crossterm::event::Event) -> Outcome {
1367    HandleEvent::handle(state, event, MouseOnly)
1368}