Skip to main content

rat_widget/
splitter.rs

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