kas_widgets/
splitter.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! A row or column with sizes adjustable via dividing grips
7
8use std::collections::hash_map::{Entry, HashMap};
9use std::ops::{Index, IndexMut};
10
11use super::{GripMsg, GripPart};
12use kas::Collection;
13use kas::layout::{self, RulesSetter, RulesSolver};
14use kas::prelude::*;
15use kas::theme::Feature;
16
17#[impl_self]
18mod Splitter {
19    /// A resizable row/column widget
20    ///
21    /// Similar to [`crate::List`] but with draggable grips between items.
22    // TODO: better doc
23    #[derive(Clone, Default, Debug)]
24    #[widget]
25    pub struct Splitter<C: Collection, D: Directional = Direction> {
26        core: widget_core!(),
27        align_hints: AlignHints,
28        widgets: C,
29        grips: Vec<GripPart>,
30        data: layout::DynRowStorage,
31        direction: D,
32        size_solved: bool,
33        next: usize,
34        id_map: HashMap<usize, usize>, // map key of Id to index
35    }
36
37    impl Self
38    where
39        D: Default,
40    {
41        /// Construct a new instance with default-constructed direction
42        #[inline]
43        pub fn new(widgets: C) -> Self {
44            Self::new_dir(widgets, Default::default())
45        }
46    }
47    impl<C: Collection> Splitter<C, kas::dir::Left> {
48        /// Construct a new instance with fixed direction
49        #[inline]
50        pub fn left(widgets: C) -> Self {
51            Self::new(widgets)
52        }
53    }
54    impl<C: Collection> Splitter<C, kas::dir::Right> {
55        /// Construct a new instance with fixed direction
56        #[inline]
57        pub fn right(widgets: C) -> Self {
58            Self::new(widgets)
59        }
60    }
61    impl<C: Collection> Splitter<C, kas::dir::Up> {
62        /// Construct a new instance with fixed direction
63        #[inline]
64        pub fn up(widgets: C) -> Self {
65            Self::new(widgets)
66        }
67    }
68    impl<C: Collection> Splitter<C, kas::dir::Down> {
69        /// Construct a new instance with fixed direction
70        #[inline]
71        pub fn down(widgets: C) -> Self {
72            Self::new(widgets)
73        }
74    }
75
76    impl<C: Collection> Splitter<C, Direction> {
77        /// Set the direction of contents
78        pub fn set_direction(&mut self, cx: &mut EventState, direction: Direction) {
79            if direction == self.direction {
80                return;
81            }
82
83            self.direction = direction;
84            // Note: most of the time Action::SET_RECT would be enough, but margins can be different
85            cx.resize(self);
86        }
87    }
88
89    impl Self {
90        /// Construct a new instance with explicit direction
91        #[inline]
92        pub fn new_dir(widgets: C, direction: D) -> Self {
93            let mut grips = Vec::new();
94            grips.resize_with(widgets.len().saturating_sub(1), GripPart::new);
95            Splitter {
96                core: Default::default(),
97                align_hints: AlignHints::NONE,
98                widgets,
99                grips,
100                data: Default::default(),
101                direction,
102                size_solved: false,
103                next: 0,
104                id_map: Default::default(),
105            }
106        }
107
108        // Assumption: index is a valid entry of self.widgets
109        fn make_next_id(&mut self, is_grip: bool, index: usize) -> Id {
110            let child_index = (2 * index) + (is_grip as usize);
111            if !is_grip {
112                if let Some(child) = self.widgets.get_tile(index) {
113                    // Use the widget's existing identifier, if valid
114                    if child.id_ref().is_valid() && self.id_ref().is_ancestor_of(child.id_ref()) {
115                        if let Some(key) = child.id_ref().next_key_after(self.id_ref()) {
116                            if let Entry::Vacant(entry) = self.id_map.entry(key) {
117                                entry.insert(child_index);
118                                return child.id();
119                            }
120                        }
121                    }
122                }
123            } else {
124                if let Some(child) = self.grips.get_tile(index) {
125                    // Use the widget's existing identifier, if valid
126                    if child.id_ref().is_valid() && self.id_ref().is_ancestor_of(child.id_ref()) {
127                        if let Some(key) = child.id_ref().next_key_after(self.id_ref()) {
128                            if let Entry::Vacant(entry) = self.id_map.entry(key) {
129                                entry.insert(child_index);
130                                return child.id();
131                            }
132                        }
133                    }
134                }
135            }
136
137            loop {
138                let key = self.next;
139                self.next += 1;
140                if let Entry::Vacant(entry) = self.id_map.entry(key) {
141                    entry.insert(child_index);
142                    return self.id_ref().make_child(key);
143                }
144            }
145        }
146
147        #[inline]
148        fn dim(&self) -> (D, usize) {
149            (self.direction, self.widgets.len() + self.grips.len())
150        }
151    }
152
153    impl Layout for Self {
154        fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
155            if self.widgets.is_empty() {
156                return SizeRules::EMPTY;
157            }
158            assert_eq!(self.grips.len() + 1, self.widgets.len());
159
160            let grip_rules = sizer.feature(Feature::Separator, axis);
161
162            let mut solver = layout::RowSolver::new(axis, self.dim(), &mut self.data);
163
164            let mut n = 0;
165            loop {
166                assert!(n < self.widgets.len());
167                let widgets = &mut self.widgets;
168                if let Some(w) = widgets.get_mut_tile(n) {
169                    solver.for_child(&mut self.data, n << 1, |axis| {
170                        w.size_rules(sizer.re(), axis)
171                    });
172                }
173
174                if n >= self.grips.len() {
175                    break;
176                }
177                let grips = &mut self.grips;
178                solver.for_child(&mut self.data, (n << 1) + 1, |axis| {
179                    grips[n].size_rules(sizer.re(), axis);
180                    grip_rules
181                });
182                n += 1;
183            }
184            solver.finish(&mut self.data)
185        }
186
187        fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
188            widget_set_rect!(rect);
189            self.align_hints = hints;
190            self.size_solved = true;
191            if self.widgets.is_empty() {
192                return;
193            }
194            assert!(self.grips.len() + 1 == self.widgets.len());
195
196            let mut setter =
197                layout::RowSetter::<D, Vec<i32>, _>::new(rect, self.dim(), &mut self.data);
198
199            let mut n = 0;
200            loop {
201                assert!(n < self.widgets.len());
202                if let Some(w) = self.widgets.get_mut_tile(n) {
203                    w.set_rect(cx, setter.child_rect(&mut self.data, n << 1), hints);
204                }
205
206                if n >= self.grips.len() {
207                    break;
208                }
209
210                // TODO(opt): calculate all maximal sizes simultaneously
211                let index = (n << 1) + 1;
212                let track = setter.maximal_rect_of(&mut self.data, index);
213                self.grips[n].set_track(track);
214                let rect = setter.child_rect(&mut self.data, index);
215                self.grips[n].set_rect(cx, rect, AlignHints::NONE);
216
217                n += 1;
218            }
219        }
220
221        fn draw(&self, mut draw: DrawCx) {
222            if !self.size_solved {
223                debug_assert!(false);
224                return;
225            }
226            // find_child should gracefully handle the case that a coord is between
227            // widgets, so there's no harm (and only a small performance loss) in
228            // calling it twice.
229
230            let solver = layout::RowPositionSolver::new(self.direction);
231            solver.for_children(&self.widgets, draw.get_clip_rect(), |w| {
232                w.draw(draw.re());
233            });
234
235            let solver = layout::RowPositionSolver::new(self.direction);
236            solver.for_children(&self.grips, draw.get_clip_rect(), |w| {
237                draw.separator(w.rect())
238            });
239        }
240    }
241
242    impl Tile for Self {
243        fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
244            Role::Splitter
245        }
246
247        #[inline]
248        fn child_indices(&self) -> ChildIndices {
249            (0..self.widgets.len() + self.grips.len()).into()
250        }
251        fn get_child(&self, index: usize) -> Option<&dyn Tile> {
252            if (index & 1) != 0 {
253                self.grips.get(index >> 1).map(|w| w.as_tile())
254            } else {
255                self.widgets.get_tile(index >> 1)
256            }
257        }
258
259        fn find_child_index(&self, id: &Id) -> Option<usize> {
260            id.next_key_after(self.id_ref())
261                .and_then(|k| self.id_map.get(&k).cloned())
262        }
263
264        fn probe(&self, coord: Coord) -> Id {
265            if !self.size_solved {
266                debug_assert!(false);
267                return self.id();
268            }
269
270            // find_child should gracefully handle the case that a coord is between
271            // widgets, so there's no harm (and only a small performance loss) in
272            // calling it twice.
273
274            let solver = layout::RowPositionSolver::new(self.direction);
275            if let Some(child) = solver.find_child(&self.widgets, coord) {
276                return child.try_probe(coord).unwrap_or_else(|| self.id());
277            }
278
279            let solver = layout::RowPositionSolver::new(self.direction);
280            if let Some(child) = solver.find_child(&self.grips, coord) {
281                return child.try_probe(coord).unwrap_or_else(|| self.id());
282            }
283
284            self.id()
285        }
286    }
287
288    impl Events for Self {
289        fn make_child_id(&mut self, child_index: usize) -> Id {
290            let is_grip = (child_index & 1) != 0;
291            self.make_next_id(is_grip, child_index / 2)
292        }
293
294        fn configure(&mut self, _: &mut ConfigCx) {
295            // All children will be re-configured which will rebuild id_map
296            self.id_map.clear();
297        }
298
299        fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
300            if let Some(index) = cx.last_child() {
301                if (index & 1) == 1 {
302                    if let Some(GripMsg::PressMove(mut offset)) = cx.try_pop() {
303                        let n = index >> 1;
304                        assert!(n < self.grips.len());
305                        if let Some(grip) = self.grips.get_mut(n) {
306                            if self.direction.is_reversed() {
307                                offset = Offset::conv(grip.track().size) - offset;
308                            }
309                            grip.set_offset(cx, offset);
310                        }
311                        self.adjust_size(&mut cx.config_cx(), n);
312                    }
313                }
314            }
315        }
316    }
317
318    impl Widget for Self {
319        type Data = C::Data;
320
321        fn child_node<'n>(&'n mut self, data: &'n C::Data, index: usize) -> Option<Node<'n>> {
322            if (index & 1) != 0 {
323                self.grips.get_mut(index >> 1).map(|w| w.as_node(&()))
324            } else {
325                self.widgets.child_node(data, index >> 1)
326            }
327        }
328    }
329}
330
331impl<C: Collection, D: Directional> Splitter<C, D> {
332    fn adjust_size(&mut self, cx: &mut ConfigCx, n: usize) {
333        assert!(n < self.grips.len());
334        assert_eq!(self.widgets.len(), self.grips.len() + 1);
335        let index = 2 * n + 1;
336
337        let hrect = self.grips[n].rect();
338        let width1 = (hrect.pos - self.rect().pos).extract(self.direction);
339        let width2 = (self.rect().size - hrect.size).extract(self.direction) - width1;
340
341        let dim = self.dim();
342        let mut setter =
343            layout::RowSetter::<D, Vec<i32>, _>::new_unsolved(self.rect(), dim, &mut self.data);
344        setter.solve_range(&mut self.data, 0..index, width1);
345        setter.solve_range(&mut self.data, (index + 1)..dim.1, width2);
346        setter.update_offsets(&mut self.data);
347
348        let mut n = 0;
349        loop {
350            assert!(n < self.widgets.len());
351            if let Some(w) = self.widgets.get_mut_tile(n) {
352                let rect = setter.child_rect(&mut self.data, n << 1);
353                w.set_rect(cx, rect, self.align_hints);
354            }
355
356            if n >= self.grips.len() {
357                break;
358            }
359
360            let index = (n << 1) + 1;
361            let track = self.grips[n].track();
362            self.grips[n].set_track(track);
363            let rect = setter.child_rect(&mut self.data, index);
364            self.grips[n].set_rect(cx, rect, AlignHints::NONE);
365
366            n += 1;
367        }
368    }
369
370    /// True if there are no child widgets
371    pub fn is_empty(&self) -> bool {
372        self.widgets.is_empty()
373    }
374
375    /// Returns the number of child widgets (excluding grips)
376    pub fn len(&self) -> usize {
377        self.widgets.len()
378    }
379}
380
381impl<W: Widget, D: Directional> Index<usize> for Splitter<Vec<W>, D> {
382    type Output = W;
383
384    fn index(&self, index: usize) -> &Self::Output {
385        &self.widgets[index]
386    }
387}
388
389impl<W: Widget, D: Directional> IndexMut<usize> for Splitter<Vec<W>, D> {
390    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
391        &mut self.widgets[index]
392    }
393}
394
395impl<W: Widget, D: Directional> Splitter<Vec<W>, D> {
396    /// Returns a reference to the child, if any
397    pub fn get(&self, index: usize) -> Option<&W> {
398        self.widgets.get(index)
399    }
400
401    /// Returns a mutable reference to the child, if any
402    pub fn get_mut(&mut self, index: usize) -> Option<&mut W> {
403        self.widgets.get_mut(index)
404    }
405
406    /// Remove all child widgets
407    pub fn clear(&mut self) {
408        self.widgets.clear();
409        self.grips.clear();
410        self.size_solved = false;
411    }
412
413    /// Append a child widget
414    ///
415    /// The new child is configured immediately. [`Action::RESIZE`] is
416    /// triggered.
417    ///
418    /// Returns the new element's index.
419    pub fn push(&mut self, cx: &mut ConfigCx, data: &W::Data, mut widget: W) -> usize {
420        let index = self.widgets.len();
421        if index > 0 {
422            let len = self.grips.len();
423            let id = self.make_next_id(true, len);
424            let mut w = GripPart::new();
425            cx.configure(w.as_node(&()), id);
426            self.grips.push(w);
427        }
428
429        let id = self.make_next_id(false, index);
430        cx.configure(widget.as_node(data), id);
431        self.widgets.push(widget);
432
433        self.size_solved = false;
434        cx.resize(self);
435        index
436    }
437
438    /// Remove the last child widget (if any) and return
439    ///
440    /// Triggers [`Action::RESIZE`].
441    pub fn pop(&mut self, cx: &mut EventState) -> Option<W> {
442        let result = self.widgets.pop();
443        if let Some(w) = result.as_ref() {
444            cx.resize(&self);
445
446            if w.id_ref().is_valid() {
447                if let Some(key) = w.id_ref().next_key_after(self.id_ref()) {
448                    self.id_map.remove(&key);
449                }
450            }
451
452            if let Some(w) = self.grips.pop() {
453                if w.id_ref().is_valid() {
454                    if let Some(key) = w.id_ref().next_key_after(self.id_ref()) {
455                        self.id_map.remove(&key);
456                    }
457                }
458            }
459        }
460        result
461    }
462
463    /// Inserts a child widget position `index`
464    ///
465    /// Panics if `index > len`.
466    ///
467    /// The new child is configured immediately. Triggers [`Action::RESIZE`].
468    pub fn insert(&mut self, cx: &mut ConfigCx, data: &W::Data, index: usize, mut widget: W) {
469        for v in self.id_map.values_mut() {
470            if *v >= index {
471                *v += 2;
472            }
473        }
474
475        if !self.widgets.is_empty() {
476            let index = index.min(self.grips.len());
477            let id = self.make_next_id(true, index);
478            let mut w = GripPart::new();
479            cx.configure(w.as_node(&()), id);
480            self.grips.insert(index, w);
481        }
482
483        let id = self.make_next_id(false, index);
484        cx.configure(widget.as_node(data), id);
485        self.widgets.insert(index, widget);
486
487        self.size_solved = false;
488        cx.resize(self);
489    }
490
491    /// Removes the child widget at position `index`
492    ///
493    /// Panics if `index` is out of bounds.
494    ///
495    /// Triggers [`Action::RESIZE`].
496    pub fn remove(&mut self, cx: &mut EventState, index: usize) -> W {
497        if !self.grips.is_empty() {
498            let index = index.min(self.grips.len());
499            let w = self.grips.remove(index);
500            if let Some(key) = w.id_ref().next_key_after(self.id_ref()) {
501                self.id_map.remove(&key);
502            }
503        }
504
505        let w = self.widgets.remove(index);
506        if w.id_ref().is_valid() {
507            if let Some(key) = w.id_ref().next_key_after(self.id_ref()) {
508                self.id_map.remove(&key);
509            }
510        }
511
512        cx.resize(&self);
513
514        for v in self.id_map.values_mut() {
515            if *v > index {
516                *v -= 2;
517            }
518        }
519        w
520    }
521
522    /// Replace the child at `index`
523    ///
524    /// Panics if `index` is out of bounds.
525    ///
526    /// The new child is configured immediately. Triggers [`Action::RESIZE`].
527    pub fn replace(&mut self, cx: &mut ConfigCx, data: &W::Data, index: usize, mut w: W) -> W {
528        let id = self.make_next_id(false, index);
529        cx.configure(w.as_node(data), id);
530        std::mem::swap(&mut w, &mut self.widgets[index]);
531
532        if w.id_ref().is_valid() {
533            if let Some(key) = w.id_ref().next_key_after(self.id_ref()) {
534                self.id_map.remove(&key);
535            }
536        }
537
538        self.size_solved = false;
539        cx.resize(self);
540
541        w
542    }
543
544    /// Append child widgets from an iterator
545    ///
546    /// New children are configured immediately. Triggers [`Action::RESIZE`].
547    pub fn extend<T: IntoIterator<Item = W>>(
548        &mut self,
549        data: &W::Data,
550        cx: &mut ConfigCx,
551        iter: T,
552    ) {
553        let iter = iter.into_iter();
554        if let Some(ub) = iter.size_hint().1 {
555            self.grips.reserve(ub);
556            self.widgets.reserve(ub);
557        }
558
559        for mut widget in iter {
560            let index = self.widgets.len();
561            if index > 0 {
562                let id = self.make_next_id(true, self.grips.len());
563                let mut w = GripPart::new();
564                cx.configure(w.as_node(&()), id);
565                self.grips.push(w);
566            }
567
568            let id = self.make_next_id(false, index);
569            cx.configure(widget.as_node(data), id);
570            self.widgets.push(widget);
571        }
572
573        self.size_solved = false;
574        cx.resize(self);
575    }
576
577    /// Resize, using the given closure to construct new widgets
578    ///
579    /// New children are configured immediately. Triggers [`Action::RESIZE`].
580    pub fn resize_with<F: Fn(usize) -> W>(
581        &mut self,
582        data: &W::Data,
583        cx: &mut ConfigCx,
584        len: usize,
585        f: F,
586    ) {
587        let old_len = self.widgets.len();
588
589        if len < old_len {
590            cx.resize(&self);
591            loop {
592                let result = self.widgets.pop();
593                if let Some(w) = result.as_ref() {
594                    if w.id_ref().is_valid() {
595                        if let Some(key) = w.id_ref().next_key_after(self.id_ref()) {
596                            self.id_map.remove(&key);
597                        }
598                    }
599
600                    if let Some(w) = self.grips.pop() {
601                        if w.id_ref().is_valid() {
602                            if let Some(key) = w.id_ref().next_key_after(self.id_ref()) {
603                                self.id_map.remove(&key);
604                            }
605                        }
606                    }
607                }
608
609                if len == self.widgets.len() {
610                    return;
611                }
612            }
613        }
614
615        if len > old_len {
616            self.widgets.reserve(len - old_len);
617            for index in old_len..len {
618                if index > 0 {
619                    let id = self.make_next_id(true, self.grips.len());
620                    let mut w = GripPart::new();
621                    cx.configure(w.as_node(&()), id);
622                    self.grips.push(w);
623                }
624
625                let id = self.make_next_id(false, index);
626                let mut widget = f(index);
627                cx.configure(widget.as_node(data), id);
628                self.widgets.push(widget);
629            }
630
631            self.size_solved = false;
632            cx.resize(self);
633        }
634    }
635}