Skip to main content

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