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
323    /// [constraints](Split::constraints) for both the number
324    /// and initial sizes of the areas.
325    pub fn new() -> Self {
326        Self {
327            direction: Direction::Horizontal,
328            ..Default::default()
329        }
330    }
331
332    /// Horizontal split.
333    pub fn horizontal() -> Self {
334        Self {
335            direction: Direction::Horizontal,
336            ..Default::default()
337        }
338    }
339
340    /// Vertical split
341    pub fn vertical() -> Self {
342        Self {
343            direction: Direction::Horizontal,
344            ..Default::default()
345        }
346    }
347
348    /// Set constraints for the initial area sizes.
349    /// If the window is resized the current widths are used as
350    /// constraints for recalculating.
351    ///
352    /// The number of constraints determines the number of areas.
353    pub fn constraints(mut self, constraints: impl IntoIterator<Item = Constraint>) -> Self {
354        self.constraints = constraints.into_iter().collect();
355        self.resize_constraints = iter::from_fn(|| Some(ResizeConstraint::ScaleProportional))
356            .take(self.constraints.len())
357            .collect();
358        self
359    }
360
361    /// Set the behaviour for each area when resizing the split-widget itself.
362    pub fn resize_constraint(mut self, n: usize, constraint: ResizeConstraint) -> Self {
363        self.resize_constraints[n] = constraint;
364        self
365    }
366
367    /// Layout direction of the widgets.
368    /// Direction::Horizontal means the widgets are laid out left to right,
369    /// with a vertical split area in between.
370    pub fn direction(mut self, direction: Direction) -> Self {
371        self.direction = direction;
372        self
373    }
374
375    /// Controls rendering of the splitter.
376    pub fn split_type(mut self, split_type: SplitType) -> Self {
377        self.split_type = split_type;
378        self
379    }
380
381    /// Controls resizing the split areas.
382    pub fn resize(mut self, resize: SplitResize) -> Self {
383        self.resize = resize;
384        self
385    }
386
387    /// Draw a join character between the split and the
388    /// borders. This sets the border type used for the
389    /// surrounding border.
390    pub fn join(mut self, border: BorderType) -> Self {
391        self.join_0 = Some(border);
392        self.join_1 = Some(border);
393        self
394    }
395
396    /// Draw a join character between the split and the
397    /// border on the left/top side. This sets the border type
398    /// used for the left/top border.
399    pub fn join_0(mut self, border: BorderType) -> Self {
400        self.join_0 = Some(border);
401        self
402    }
403
404    /// Draw a join character between the split and the
405    /// border on the right/bottom side. This sets the border type
406    /// used for the right/bottom border.
407    pub fn join_1(mut self, border: BorderType) -> Self {
408        self.join_1 = Some(border);
409        self
410    }
411
412    /// Outer block.
413    pub fn block(mut self, block: Block<'a>) -> Self {
414        self.block = Some(block);
415        self
416    }
417
418    /// Set all styles.
419    pub fn styles(mut self, styles: SplitStyle) -> Self {
420        self.style = styles.style;
421        if styles.block.is_some() {
422            self.block = styles.block;
423        }
424        if let Some(border_style) = styles.border_style {
425            self.block = self.block.map(|v| v.border_style(border_style));
426        }
427        if let Some(title_style) = styles.title_style {
428            self.block = self.block.map(|v| v.title_style(title_style));
429        }
430        self.block = self.block.map(|v| v.style(self.style));
431
432        if styles.drag_style.is_some() {
433            self.drag_style = styles.drag_style;
434        }
435        if styles.arrow_style.is_some() {
436            self.arrow_style = styles.arrow_style;
437        }
438        match self.direction {
439            Direction::Horizontal => {
440                if let Some(mark) = styles.horizontal_mark {
441                    let mut g = mark.graphemes(true);
442                    if let Some(g0) = g.next() {
443                        self.mark_0_char = Some(g0);
444                    }
445                    if let Some(g1) = g.next() {
446                        self.mark_1_char = Some(g1);
447                    }
448                }
449            }
450            Direction::Vertical => {
451                if let Some(mark) = styles.vertical_mark {
452                    let mut g = mark.graphemes(true);
453                    if let Some(g0) = g.next() {
454                        self.mark_0_char = Some(g0);
455                    }
456                    if let Some(g1) = g.next() {
457                        self.mark_1_char = Some(g1);
458                    }
459                }
460            }
461        }
462        self
463    }
464
465    /// Style for the split area.
466    pub fn style(mut self, style: Style) -> Self {
467        self.style = style;
468        self.block = self.block.map(|v| v.style(style));
469        self
470    }
471
472    /// Style for the arrows.
473    pub fn arrow_style(mut self, style: Style) -> Self {
474        self.arrow_style = Some(style);
475        self
476    }
477
478    /// Style while dragging the splitter.
479    pub fn drag_style(mut self, style: Style) -> Self {
480        self.drag_style = Some(style);
481        self
482    }
483
484    /// Offset for the split marker from the top/left.
485    pub fn mark_offset(mut self, offset: u16) -> Self {
486        self.mark_offset = offset;
487        self
488    }
489
490    /// First marker char for the splitter.
491    pub fn mark_0(mut self, mark: &'a str) -> Self {
492        self.mark_0_char = Some(mark);
493        self
494    }
495
496    /// Second marker char for the splitter.
497    pub fn mark_1(mut self, mark: &'a str) -> Self {
498        self.mark_1_char = Some(mark);
499        self
500    }
501
502    /// Constructs the widget for rendering.
503    ///
504    /// Returns the SplitWidget that actually renders the split decorations.
505    ///
506    /// Use [SplitState::widget_areas] to render your contents, and
507    /// then render the SplitWidget. This allows it to render some
508    /// decorations on top of your widgets.
509    #[deprecated(since = "2.4.0", note = "use into_widgets() instead")]
510    pub fn into_widget(self, area: Rect, state: &mut SplitState) -> SplitWidget<'a> {
511        layout_split(&self, area, state);
512        SplitWidget {
513            split: Rc::new(self),
514        }
515    }
516
517    /// Constructs the widgets for rendering.
518    ///
519    /// Returns the SplitWidget that actually renders the split.
520    /// Returns a `Vec<Rect>` with the regions for each split.
521    ///
522    /// Render your content first, using the layout information.
523    /// And the SplitWidget as last to allow rendering over
524    /// the content widgets.
525    #[deprecated(since = "2.4.0", note = "use into_widgets() instead")]
526    pub fn into_widget_layout(
527        self,
528        area: Rect,
529        state: &mut SplitState,
530    ) -> (SplitWidget<'a>, Vec<Rect>) {
531        layout_split(&self, area, state);
532        (
533            SplitWidget {
534                split: Rc::new(self),
535            },
536            state.widget_areas.clone(),
537        )
538    }
539
540    /// Constructs the widgets for rendering.
541    ///
542    /// Returns the LayoutWidget that must run first. It
543    /// doesn't actually render anything, it just calculates
544    /// the layout for the split regions.
545    ///
546    /// Use the layout in [SplitState::widget_areas] to render
547    /// your widgets.
548    ///
549    /// The SplitWidget actually renders the split itself.
550    /// Render it after you finished with the content.
551    pub fn into_widgets(self) -> (LayoutWidget<'a>, SplitWidget<'a>) {
552        let split = Rc::new(self);
553        (
554            LayoutWidget {
555                split: split.clone(), //
556            },
557            SplitWidget {
558                split, //
559            },
560        )
561    }
562}
563
564impl<'a> StatefulWidget for &Split<'a> {
565    type State = SplitState;
566
567    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
568        layout_split(self, area, state);
569        render_split(self, buf, state);
570        state.relocate_split = false;
571    }
572}
573
574impl<'a> StatefulWidget for Split<'a> {
575    type State = SplitState;
576
577    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
578        layout_split(&self, area, state);
579        render_split(&self, buf, state);
580        state.relocate_split = false;
581    }
582}
583
584impl<'a> StatefulWidget for &LayoutWidget<'a> {
585    type State = SplitState;
586
587    fn render(self, area: Rect, _buf: &mut Buffer, state: &mut Self::State) {
588        // just run the layout here. SplitWidget renders the rest.
589        layout_split(&self.split, area, state);
590    }
591}
592
593impl<'a> StatefulWidget for LayoutWidget<'a> {
594    type State = SplitState;
595
596    fn render(self, area: Rect, _buf: &mut Buffer, state: &mut Self::State) {
597        // just run the layout here. SplitWidget renders the rest.
598        layout_split(&self.split, area, state);
599    }
600}
601
602impl<'a> StatefulWidget for &SplitWidget<'a> {
603    type State = SplitState;
604
605    fn render(self, _area: Rect, buf: &mut Buffer, state: &mut Self::State) {
606        render_split(&self.split, buf, state);
607    }
608}
609
610impl StatefulWidget for SplitWidget<'_> {
611    type State = SplitState;
612
613    fn render(self, _area: Rect, buf: &mut Buffer, state: &mut Self::State) {
614        render_split(&self.split, buf, state);
615    }
616}
617
618fn render_split(split: &Split<'_>, buf: &mut Buffer, state: &mut SplitState) {
619    let area = state.area;
620    if state.is_focused() {
621        if state.focus_marker.is_none() {
622            state.focus_marker = Some(0);
623        }
624    } else {
625        state.focus_marker = None;
626    }
627
628    split.block.clone().render(area, buf);
629
630    for (n, split_area) in state.splitline_areas.iter().enumerate() {
631        // skip 0 width/height
632        if split.direction == Direction::Horizontal {
633            if split_area.width == 0 {
634                continue;
635            }
636        } else {
637            if split_area.height == 0 {
638                continue;
639            }
640        }
641
642        let (style, arrow_style) = if Some(n) == state.mouse.drag.get()
643            || Some(n) == state.focus_marker
644            || Some(n) == state.mouse.hover.get()
645        {
646            if let Some(drag) = split.drag_style {
647                (drag, drag)
648            } else {
649                (revert_style(split.style), revert_style(split.style))
650            }
651        } else {
652            if let Some(arrow) = split.arrow_style {
653                (split.style, arrow)
654            } else {
655                (split.style, split.style)
656            }
657        };
658
659        if let Some(fill) = get_fill_char(split) {
660            fill_buf_area(buf, *split_area, fill, style);
661        }
662
663        let mark = state.splitline_mark_position[n];
664        if split.direction == Direction::Horizontal {
665            if buf.area.contains((mark.x, mark.y).into()) {
666                if let Some(cell) = buf.cell_mut((mark.x, mark.y)) {
667                    cell.set_style(arrow_style);
668                    cell.set_symbol(get_mark_0(split));
669                }
670            }
671            if buf.area.contains((mark.x, mark.y + 1).into()) {
672                if let Some(cell) = buf.cell_mut((mark.x, mark.y + 1)) {
673                    cell.set_style(arrow_style);
674                    cell.set_symbol(get_mark_1(split));
675                }
676            }
677        } else {
678            if let Some(cell) = buf.cell_mut((mark.x, mark.y)) {
679                cell.set_style(arrow_style);
680                cell.set_symbol(get_mark_0(split));
681            }
682            if let Some(cell) = buf.cell_mut((mark.x + 1, mark.y)) {
683                cell.set_style(arrow_style);
684                cell.set_symbol(get_mark_1(split));
685            }
686        }
687
688        if let Some((pos_0, c_0)) = get_join_0(split, *split_area, state) {
689            if let Some(cell) = buf.cell_mut((pos_0.x, pos_0.y)) {
690                cell.set_symbol(c_0);
691            }
692        }
693        if let Some((pos_1, c_1)) = get_join_1(split, *split_area, state) {
694            if let Some(cell) = buf.cell_mut((pos_1.x, pos_1.y)) {
695                cell.set_symbol(c_1);
696            }
697        }
698    }
699}
700
701impl Default for SplitState {
702    fn default() -> Self {
703        Self {
704            area: Default::default(),
705            inner: Default::default(),
706            widget_areas: Default::default(),
707            splitline_areas: Default::default(),
708            splitline_mark_position: Default::default(),
709            mark_offset: Default::default(),
710            direction: Default::default(),
711            split_type: Default::default(),
712            resize: Default::default(),
713            area_length: Default::default(),
714            area_constraint: Default::default(),
715            hidden_length: Default::default(),
716            focus: Default::default(),
717            focus_marker: Default::default(),
718            mouse: Default::default(),
719            relocate_split: Default::default(),
720            non_exhaustive: NonExhaustive,
721        }
722    }
723}
724
725impl Clone for SplitState {
726    fn clone(&self) -> Self {
727        Self {
728            area: self.area,
729            inner: self.inner,
730            widget_areas: self.widget_areas.clone(),
731            splitline_areas: self.splitline_areas.clone(),
732            splitline_mark_position: self.splitline_mark_position.clone(),
733            mark_offset: self.mark_offset,
734            direction: self.direction,
735            split_type: self.split_type,
736            resize: self.resize,
737            area_length: self.area_length.clone(),
738            area_constraint: self.area_constraint.clone(),
739            hidden_length: self.hidden_length.clone(),
740            focus: self.focus.new_instance(),
741            focus_marker: self.focus_marker,
742            mouse: Default::default(),
743            relocate_split: self.relocate_split,
744            non_exhaustive: NonExhaustive,
745        }
746    }
747}
748
749impl HasFocus for SplitState {
750    fn build(&self, builder: &mut FocusBuilder) {
751        builder.leaf_widget(self);
752    }
753
754    fn focus(&self) -> FocusFlag {
755        self.focus.clone()
756    }
757
758    fn area(&self) -> Rect {
759        self.inner
760    }
761
762    fn navigable(&self) -> Navigation {
763        if self.is_focused() {
764            Navigation::Leave
765        } else {
766            Navigation::None
767        }
768    }
769}
770
771impl RelocatableState for SplitState {
772    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
773        // relocate after the popup/split itself is rendered.
774        if !self.relocate_split {
775            self.area = relocate_area(self.area, shift, clip);
776            self.inner = relocate_area(self.inner, shift, clip);
777            relocate_areas(self.widget_areas.as_mut_slice(), shift, clip);
778            relocate_areas(self.splitline_areas.as_mut_slice(), shift, clip);
779            relocate_positions(self.splitline_mark_position.as_mut_slice(), shift, clip);
780        }
781    }
782
783    fn relocate_popup(&mut self, shift: (i16, i16), clip: Rect) {
784        if self.relocate_split {
785            self.relocate_split = false;
786            self.area = relocate_area(self.area, shift, clip);
787            self.inner = relocate_area(self.inner, shift, clip);
788            relocate_areas(self.widget_areas.as_mut_slice(), shift, clip);
789            relocate_areas(self.splitline_areas.as_mut_slice(), shift, clip);
790            relocate_positions(self.splitline_mark_position.as_mut_slice(), shift, clip);
791        }
792    }
793}
794
795#[allow(clippy::len_without_is_empty)]
796impl SplitState {
797    /// New state.
798    pub fn new() -> Self {
799        Self::default()
800    }
801
802    /// New state with a focus-name.
803    pub fn named(name: &str) -> Self {
804        let mut z = Self::default();
805        z.focus = z.focus.with_name(name);
806        z
807    }
808
809    /// Set the position for the nth splitter.
810    ///
811    /// The position is limited the combined area of the two adjacent areas.
812    /// The position is further limited to leave space for rendering the
813    /// splitter.
814    ///
815    pub fn set_screen_split_pos(&mut self, n: usize, pos: (u16, u16)) -> bool {
816        if self.is_hidden(n) {
817            return false;
818        }
819        if self.direction == Direction::Horizontal {
820            let pos = if pos.0 < self.inner.left() {
821                0
822            } else if pos.0 < self.inner.right() {
823                pos.0 - self.inner.x
824            } else {
825                self.inner.width
826            };
827
828            let split_pos = self.split_pos(n);
829            self.set_split_pos(n, pos);
830
831            split_pos != self.split_pos(n)
832        } else {
833            let pos = if pos.1 < self.inner.top() {
834                0
835            } else if pos.1 < self.inner.bottom() {
836                pos.1 - self.inner.y
837            } else {
838                self.inner.height
839            };
840
841            let split_pos = self.split_pos(n);
842            self.set_split_pos(n, pos);
843
844            split_pos != self.split_pos(n)
845        }
846    }
847
848    /// Move the nth split position.
849    /// If delta is greater than the area length it sets the
850    /// length to 0.
851    pub fn move_split_left(&mut self, n: usize, delta: u16) -> bool {
852        let split_pos = self.split_pos(n);
853        self.set_split_pos(n, split_pos - delta);
854
855        split_pos != self.split_pos(n)
856    }
857
858    /// Move the nth split position.
859    /// Does nothing if the change is bigger than the length of the split.
860    pub fn move_split_right(&mut self, n: usize, delta: u16) -> bool {
861        let split_pos = self.split_pos(n);
862        self.set_split_pos(n, split_pos + delta);
863
864        split_pos != self.split_pos(n)
865    }
866
867    /// Move the nth split position.
868    /// Does nothing if the change is bigger than the length of the split.
869    pub fn move_split_up(&mut self, n: usize, delta: u16) -> bool {
870        self.move_split_left(n, delta)
871    }
872
873    /// Move the nth split position.
874    /// Does nothing if the change is bigger than the length of the split.
875    pub fn move_split_down(&mut self, n: usize, delta: u16) -> bool {
876        self.move_split_right(n, delta)
877    }
878
879    /// Select the next splitter for manual adjustment.
880    pub fn select_next_split(&mut self) -> bool {
881        if self.is_focused() {
882            let n = self.focus_marker.unwrap_or_default();
883            if n + 1 >= self.area_length.len().saturating_sub(1) {
884                self.focus_marker = Some(0);
885            } else {
886                self.focus_marker = Some(n + 1);
887            }
888            true
889        } else {
890            false
891        }
892    }
893
894    /// Select the previous splitter for manual adjustment.
895    pub fn select_prev_split(&mut self) -> bool {
896        if self.is_focused() {
897            let n = self.focus_marker.unwrap_or_default();
898            if n == 0 {
899                self.focus_marker =
900                    Some(self.area_length.len().saturating_sub(1).saturating_sub(1));
901            } else {
902                self.focus_marker = Some(n - 1);
903            }
904            true
905        } else {
906            false
907        }
908    }
909
910    /// Number of split-areas
911    pub fn len(&self) -> usize {
912        self.area_length.len()
913    }
914
915    /// Get all area lengths.
916    pub fn area_lengths(&self) -> &[u16] {
917        &self.area_length
918    }
919
920    /// Set all area lengths.
921    ///
922    /// This will adjust the list of the hidden splits too.
923    ///
924    /// __Caution__
925    /// If the sum of the lengths doesn't match the display-width
926    /// this will trigger a layout and will use the given lenghts
927    /// as Constraint::Fill().
928    ///
929    /// __Caution__
930    ///
931    /// If a length is 0 it will not display the split at all.
932    pub fn set_area_lengths(&mut self, lengths: Vec<u16>) {
933        self.area_length = lengths;
934        self.area_constraint.clear();
935        while self.hidden_length.len() < self.area_length.len() {
936            self.hidden_length.push(0);
937        }
938        while self.hidden_length.len() > self.area_length.len() {
939            self.hidden_length.pop();
940        }
941    }
942
943    /// Get the value of the hidden lengths.
944    pub fn hidden_lengths(&self) -> &[u16] {
945        &self.hidden_length
946    }
947
948    /// Set the value of the hidden lengths.
949    ///
950    /// This will take at most area_length.len() items of this Vec.
951    /// And it will fill missing items as 0.
952    pub fn set_hidden_lengths(&mut self, hidden: Vec<u16>) {
953        for i in 0..self.hidden_length.len() {
954            if let Some(v) = hidden.get(i) {
955                self.hidden_length[i] = *v;
956            } else {
957                self.hidden_length[i] = 0;
958            }
959        }
960    }
961
962    /// Length of the nth split.
963    ///
964    /// __Caution__
965    ///
966    /// This length **includes** the width of the split itself.
967    /// Which may or may not take some space. Except for the last.
968    /// So it will be better to use `widget_areas` for anything
969    /// rendering related.
970    ///
971    pub fn area_len(&self, n: usize) -> u16 {
972        if n >= self.area_length.len() {
973            return 0;
974        }
975        self.area_length[n]
976    }
977
978    /// Sum of all area lengths.
979    pub fn total_area_len(&self) -> u16 {
980        self.area_length.iter().sum()
981    }
982
983    /// Set the length of the nth split.
984    ///
985    /// This resets any hidden state of the nth split.
986    ///
987    /// __Caution__
988    /// The sum of all lengths must be equal with the width/height of
989    /// the splitter. If it is not this operation doesn't set the
990    /// absolute width of the nth split. Instead, it triggers a layout
991    /// of the widget, takes all the lengths as Constraint::Fill()
992    /// values and redistributes the size.
993    ///
994    /// You can either ensure to change some other len to accommodate
995    /// for your changes. Or use [set_split_pos](Self::set_split_pos) or
996    /// [set_screen_split_pos](Self::set_screen_split_pos)
997    ///
998    /// __Caution__
999    ///
1000    /// This length **includes** the width of the split itself.
1001    /// Which may or may not take some space. Except for the last area.
1002    /// Which doesn't have a split.
1003    ///
1004    /// So:
1005    /// - If you set the length to 0 the area will be hidden completely
1006    ///   and no split will be shown.
1007    /// - A value of 1 is fine.
1008    /// - The last area can have a length 0 and that's fine too.
1009    ///
1010    /// __Caution__
1011    ///
1012    /// Before the first render this will do nothing.
1013    /// Use [set_area_lengths](SplitState::set_area_lengths) to initialize the areas.
1014    ///
1015    pub fn set_area_len(&mut self, n: usize, len: u16) {
1016        if n >= self.area_length.len() {
1017            return;
1018        }
1019        self.area_length[n] = len;
1020        self.area_constraint.clear();
1021        self.hidden_length[n] = 0;
1022    }
1023
1024    /// Returns the position of the nth split.
1025    ///
1026    /// __Caution__
1027    ///
1028    /// The numbering for the splitters goes from `0` to `len-1` __exclusive__.
1029    /// Split `n` marks the gap between area `n` and `n+1`.
1030    ///
1031    /// __Caution__
1032    ///
1033    /// This returns the position of the gap between two adjacent
1034    /// split-areas. Use `splitline_areas` for anything rendering related.
1035    pub fn split_pos(&self, n: usize) -> u16 {
1036        if n + 1 >= self.area_length.len() {
1037            return self.area_length.iter().sum();
1038        }
1039        self.area_length[..n + 1].iter().sum()
1040    }
1041
1042    /// Sets the position of the nth split.
1043    ///
1044    /// Depending on the resize strategy this can limit the allowed positions
1045    /// for the split.
1046    ///
1047    /// __Caution__
1048    ///
1049    /// The numbering for the splitters goes from `0` to `len-1` __exclusive__.
1050    /// Split `n` marks the gap between area `n` and `n+1`.
1051    ///
1052    /// __Caution__
1053    ///
1054    /// This marks the position of the gap between two adjacent
1055    /// split-areas. If you start from screen-coordinates it might
1056    /// be easier to use [set_screen_split_pos](Self::set_screen_split_pos)
1057    pub fn set_split_pos(&mut self, n: usize, pos: u16) {
1058        if n + 1 >= self.area_length.len() {
1059            return;
1060        }
1061
1062        match self.resize {
1063            SplitResize::Neighbours => {
1064                self.set_split_pos_neighbour(n, pos);
1065            }
1066            SplitResize::Full => {
1067                self.set_split_pos_full(n, pos);
1068            }
1069        }
1070    }
1071
1072    /// Limits the possible position of the split to the
1073    /// width of the two direct neighbours of the split.
1074    fn set_split_pos_neighbour(&mut self, n: usize, pos: u16) {
1075        assert!(n + 1 < self.area_length.len());
1076
1077        // create dual
1078        let mut pos_vec = Vec::new();
1079        let mut pp = 0;
1080        for len in &self.area_length {
1081            pp += *len;
1082            pos_vec.push(pp);
1083        }
1084        // last is not a split
1085        let pos_count = pos_vec.len();
1086
1087        let (min_pos, max_pos) = if n == 0 {
1088            if n + 2 >= pos_count {
1089                (SPLIT_WIDTH, pos_vec[n + 1])
1090            } else {
1091                (SPLIT_WIDTH, pos_vec[n + 1] - SPLIT_WIDTH)
1092            }
1093        } else if n + 2 < pos_count {
1094            (pos_vec[n - 1] + 1, pos_vec[n + 1] - SPLIT_WIDTH)
1095        } else {
1096            (pos_vec[n - 1] + 1, pos_vec[n + 1])
1097        };
1098
1099        pos_vec[n] = min(max(min_pos, pos), max_pos);
1100
1101        // revert dual
1102        for i in 0..pos_vec.len() {
1103            if i > 0 {
1104                self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1105            } else {
1106                self.area_length[i] = pos_vec[i];
1107            }
1108        }
1109        self.area_constraint.clear();
1110    }
1111
1112    /// Allows the full range for the split-pos.
1113    /// Minus the space needed to render the split itself.
1114    #[allow(clippy::needless_range_loop)]
1115    #[allow(clippy::comparison_chain)]
1116    fn set_split_pos_full(&mut self, n: usize, pos: u16) {
1117        assert!(n + 1 < self.area_length.len());
1118
1119        let total_len = self.total_area_len();
1120
1121        // create dual
1122        let mut pos_vec = Vec::new();
1123        let mut pp = 0;
1124        for len in &self.area_length {
1125            pp += *len;
1126            pos_vec.push(pp);
1127        }
1128        // last is not a split
1129        pos_vec.pop();
1130        let pos_count = pos_vec.len();
1131
1132        let mut min_pos = SPLIT_WIDTH;
1133        for i in 0..pos_vec.len() {
1134            if i < n {
1135                if self.area_length[i] == 0 {
1136                    pos_vec[i] = min_pos;
1137                } else if self.hidden_length[i] != 0 {
1138                    pos_vec[i] = min_pos;
1139                    min_pos += SPLIT_WIDTH;
1140                } else {
1141                    if pos_vec[i] >= pos {
1142                        // how many split between here and there
1143                        let rest_area_count = n - (i + 1);
1144                        let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1145                        // min
1146                        pos_vec[i] = max(
1147                            min_pos,
1148                            pos.saturating_sub(SPLIT_WIDTH)
1149                                .saturating_sub(rest_area_width),
1150                        );
1151                        min_pos += SPLIT_WIDTH;
1152                    } else {
1153                        // unchanged
1154                    }
1155                }
1156            } else if i == n {
1157                // remaining area count with a split
1158                let rest_area_count = pos_count - (i + 1);
1159                let rest_area_width = rest_area_count as u16 * SPLIT_WIDTH;
1160                let rest_len = total_len - (min_pos + 1);
1161                // min for remaining areas
1162                let rest_len = rest_len - rest_area_width;
1163                // last can be 0
1164                let rest_len = rest_len + SPLIT_WIDTH;
1165
1166                let max_pos = min_pos + rest_len;
1167
1168                pos_vec[i] = min(max(min_pos, pos), max_pos);
1169
1170                min_pos = pos_vec[i] + SPLIT_WIDTH;
1171            } else {
1172                if self.area_length[i] == 0 {
1173                    pos_vec[i] = min_pos;
1174                } else if self.hidden_length[i] != 0 {
1175                    pos_vec[i] = min_pos;
1176                    min_pos += SPLIT_WIDTH;
1177                } else {
1178                    if pos_vec[i] <= pos {
1179                        pos_vec[i] = min_pos;
1180                        min_pos += SPLIT_WIDTH;
1181                    } else {
1182                        // unchanged
1183                    }
1184                }
1185            }
1186        }
1187
1188        // revert dual
1189        for i in 0..pos_vec.len() {
1190            if i > 0 {
1191                self.area_length[i] = pos_vec[i] - pos_vec[i - 1];
1192            } else {
1193                self.area_length[i] = pos_vec[i];
1194            }
1195        }
1196        self.area_length[pos_count] = total_len - pos_vec[pos_count - 1];
1197        self.area_constraint.clear();
1198    }
1199
1200    /// Is the split hidden?
1201    pub fn is_hidden(&self, n: usize) -> bool {
1202        self.hidden_length[n] > 0
1203    }
1204
1205    /// Hide the split and adds its area to the following split.
1206    /// If there is no following split it will go left/up.
1207    /// Leaves enough space to render the splitter.
1208    pub fn hide_split(&mut self, n: usize) -> bool {
1209        if self.hidden_length[n] == 0 {
1210            self.area_constraint.clear();
1211
1212            let mut hide = if n + 1 == self.area_length.len() {
1213                self.area_length[n]
1214            } else {
1215                self.area_length[n].saturating_sub(SPLIT_WIDTH)
1216            };
1217            for idx in n + 1..self.area_length.len() {
1218                if self.hidden_length[idx] == 0 {
1219                    self.area_length[idx] += hide;
1220                    hide = 0;
1221                    break;
1222                }
1223            }
1224            if hide > 0 {
1225                for idx in (0..n).rev() {
1226                    if self.hidden_length[idx] == 0 {
1227                        self.area_length[idx] += hide;
1228                        hide = 0;
1229                        break;
1230                    }
1231                }
1232            }
1233
1234            if hide > 0 {
1235                // don't hide last split.
1236                self.hidden_length[n] = 0;
1237                false
1238            } else {
1239                if n + 1 == self.area_length.len() {
1240                    self.hidden_length[n] = self.area_length[n];
1241                    self.area_length[n] = 0;
1242                } else {
1243                    self.hidden_length[n] = self.area_length[n].saturating_sub(SPLIT_WIDTH);
1244                    self.area_length[n] = 1;
1245                };
1246                true
1247            }
1248        } else {
1249            false
1250        }
1251    }
1252
1253    /// Show a hidden split.
1254    /// It will first try to reduce the areas to the right,
1255    /// and then the areas to the left to make space.
1256    pub fn show_split(&mut self, n: usize) -> bool {
1257        let mut show = self.hidden_length[n];
1258        if show > 0 {
1259            for idx in n + 1..self.area_length.len() {
1260                if self.hidden_length[idx] == 0 {
1261                    // steal as much as we can
1262                    if self.area_length[idx] > show + SPLIT_WIDTH {
1263                        self.area_length[idx] -= show;
1264                        show = 0;
1265                    } else if self.area_length[idx] > SPLIT_WIDTH {
1266                        show -= self.area_length[idx] - SPLIT_WIDTH;
1267                        self.area_length[idx] = SPLIT_WIDTH;
1268                    }
1269                    if show == 0 {
1270                        break;
1271                    }
1272                }
1273            }
1274            if show > 0 {
1275                for idx in (0..n).rev() {
1276                    if self.hidden_length[idx] == 0 {
1277                        if self.area_length[idx] > show + SPLIT_WIDTH {
1278                            self.area_length[idx] -= show;
1279                            show = 0;
1280                        } else if self.area_length[idx] > SPLIT_WIDTH {
1281                            show -= self.area_length[idx] - SPLIT_WIDTH;
1282                            self.area_length[idx] = SPLIT_WIDTH;
1283                        }
1284                        if show == 0 {
1285                            break;
1286                        }
1287                    }
1288                }
1289            }
1290
1291            self.area_length[n] += self.hidden_length[n] - show;
1292            self.hidden_length[n] = 0;
1293            self.area_constraint.clear();
1294            true
1295        } else {
1296            false
1297        }
1298    }
1299}
1300
1301impl HandleEvent<Event, Regular, Outcome> for SplitState {
1302    fn handle(&mut self, event: &Event, _qualifier: Regular) -> Outcome {
1303        event_flow!(
1304            return if self.is_focused() {
1305                if let Some(n) = self.focus_marker {
1306                    match event {
1307                        ct_event!(keycode press Left) => self.select_prev_split().into(),
1308                        ct_event!(keycode press Right) => self.select_next_split().into(),
1309                        ct_event!(keycode press Up) => self.select_prev_split().into(),
1310                        ct_event!(keycode press Down) => self.select_next_split().into(),
1311
1312                        ct_event!(keycode press CONTROL-Left) => self.move_split_left(n, 1).into(),
1313                        ct_event!(keycode press CONTROL-Right) => {
1314                            self.move_split_right(n, 1).into()
1315                        }
1316                        ct_event!(keycode press CONTROL-Up) => self.move_split_up(n, 1).into(),
1317                        ct_event!(keycode press CONTROL-Down) => self.move_split_down(n, 1).into(),
1318                        _ => Outcome::Continue,
1319                    }
1320                } else {
1321                    Outcome::Continue
1322                }
1323            } else {
1324                Outcome::Continue
1325            }
1326        );
1327
1328        self.handle(event, MouseOnly)
1329    }
1330}
1331
1332impl HandleEvent<Event, MouseOnly, Outcome> for SplitState {
1333    fn handle(&mut self, event: &Event, _qualifier: MouseOnly) -> Outcome {
1334        if self.is_obscured() {
1335            return Outcome::Continue;
1336        }
1337
1338        match event {
1339            ct_event!(mouse any for m) if self.mouse.hover(&self.splitline_areas, m) => {
1340                Outcome::Changed
1341            }
1342            ct_event!(mouse any for m) => {
1343                let was_drag = self.mouse.drag.get();
1344                if self.mouse.drag(&self.splitline_areas, m) {
1345                    if let Some(n) = self.mouse.drag.get() {
1346                        self.set_screen_split_pos(n, self.mouse.pos_of(m)).into()
1347                    } else {
1348                        Outcome::Continue
1349                    }
1350                } else {
1351                    // repaint after drag is finished. resets the displayed style.
1352                    if was_drag.is_some() {
1353                        Outcome::Changed
1354                    } else {
1355                        Outcome::Continue
1356                    }
1357                }
1358            }
1359            _ => Outcome::Continue,
1360        }
1361    }
1362}
1363
1364/// Handle all events.
1365/// Text events are only processed if focus is true.
1366/// Mouse events are processed if they are in range.
1367pub fn handle_events(state: &mut SplitState, focus: bool, event: &Event) -> Outcome {
1368    state.focus.set(focus);
1369    HandleEvent::handle(state, event, Regular)
1370}
1371
1372/// Handle only mouse-events.
1373pub fn handle_mouse_events(state: &mut SplitState, event: &Event) -> Outcome {
1374    HandleEvent::handle(state, event, MouseOnly)
1375}