rat_widget/
splitter.rs

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