rat_widget/
splitter.rs

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