kas_core/layout/
row_solver.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//! Row / column solver
7
8use std::marker::PhantomData;
9use std::ops::Range;
10
11use super::{AxisInfo, SizeRules};
12use super::{RowStorage, RowTemp, RulesSetter, RulesSolver};
13use crate::dir::{Direction, Directional};
14use crate::geom::{Coord, Rect};
15use crate::{Collection, Tile};
16
17/// A [`RulesSolver`] for rows (and, without loss of generality, for columns).
18///
19/// This is parameterised over:
20///
21/// -   `S:` [`RowStorage`] — persistent storage type
22pub struct RowSolver<S: RowStorage> {
23    // Generalisation implies that axis.vert() is incorrect
24    axis: AxisInfo,
25    axis_is_vertical: bool,
26    axis_is_reversed: bool,
27    rules: Option<SizeRules>,
28    _s: PhantomData<S>,
29}
30
31impl<S: RowStorage> RowSolver<S> {
32    /// Construct.
33    ///
34    /// Argument order is consistent with other [`RulesSolver`]s.
35    ///
36    /// - `axis`: `AxisInfo` instance passed into `size_rules`
37    /// - `(dir, len)`: direction and number of items
38    /// - `storage`: reference to persistent storage
39    pub fn new<D: Directional>(axis: AxisInfo, (dir, len): (D, usize), storage: &mut S) -> Self {
40        storage.set_dim(len);
41
42        let axis_is_vertical = axis.is_vertical() ^ dir.is_vertical();
43
44        if axis.has_fixed && axis_is_vertical {
45            // Assume we already have rules for the other axis; solve for the given width
46            let (widths, rules) = storage.widths_and_rules();
47            SizeRules::solve_seq(widths, rules, axis.other_axis);
48        }
49
50        RowSolver {
51            axis,
52            axis_is_vertical,
53            axis_is_reversed: dir.is_reversed(),
54            rules: None,
55            _s: Default::default(),
56        }
57    }
58}
59
60impl<S: RowStorage> RulesSolver for RowSolver<S> {
61    type Storage = S;
62    type ChildInfo = usize;
63
64    fn for_child<CR: FnOnce(AxisInfo) -> SizeRules>(
65        &mut self,
66        storage: &mut Self::Storage,
67        index: Self::ChildInfo,
68        child_rules: CR,
69    ) {
70        if self.axis.has_fixed && self.axis_is_vertical {
71            self.axis.other_axis = storage.widths()[index];
72        }
73        let child_rules = child_rules(self.axis);
74
75        if !self.axis_is_vertical {
76            storage.rules()[index] = child_rules;
77            if let Some(rules) = self.rules {
78                if self.axis_is_reversed {
79                    self.rules = Some(child_rules.appended(rules));
80                } else {
81                    self.rules = Some(rules.appended(child_rules));
82                }
83            } else {
84                self.rules = Some(child_rules);
85            }
86        } else {
87            self.rules = Some(
88                self.rules
89                    .map(|rules| rules.max(child_rules))
90                    .unwrap_or(child_rules),
91            );
92        }
93    }
94
95    fn finish(self, _: &mut Self::Storage) -> SizeRules {
96        self.rules.unwrap_or(SizeRules::EMPTY)
97    }
98}
99
100/// A [`RulesSetter`] for rows (and, without loss of generality, for columns).
101///
102/// This is parameterised over:
103///
104/// -   `D:` [`Directional`] — whether this represents a row or a column
105/// -   `T:` [`RowTemp`] — temporary storage type
106/// -   `S:` [`RowStorage`] — persistent storage type
107pub struct RowSetter<D, T: RowTemp, S: RowStorage> {
108    rect: Rect,
109    offsets: T,
110    direction: D,
111    _s: PhantomData<S>,
112}
113
114impl<D: Directional, T: RowTemp, S: RowStorage> RowSetter<D, T, S> {
115    /// Construct
116    ///
117    /// Argument order is consistent with other [`RulesSetter`]s.
118    ///
119    /// -   `rect`: the [`Rect`] within which to position children
120    /// - `(direction, len)`: direction and number of items
121    /// -   `storage`: access to the solver's storage
122    pub fn new(rect: Rect, (direction, len): (D, usize), storage: &mut S) -> Self {
123        let mut offsets = T::default();
124        offsets.set_len(len);
125        storage.set_dim(len);
126
127        if len > 0 {
128            let is_horiz = direction.is_horizontal();
129            let width = if is_horiz { rect.size.0 } else { rect.size.1 };
130            let (widths, rules) = storage.widths_and_rules();
131            SizeRules::solve_seq(widths, rules, width);
132        }
133
134        let _s = Default::default();
135        let mut row = RowSetter {
136            rect,
137            offsets,
138            direction,
139            _s,
140        };
141        row.update_offsets(storage);
142        row
143    }
144
145    /// Construct without solving
146    ///
147    /// In this case, it is assumed that the storage was already solved by a
148    /// previous `RowSetter`. The user should optionally call `solve_range` on
149    /// any ranges needing updating and finally call `update_offsets` before
150    /// using this `RowSetter` to calculate child positions.
151    pub fn new_unsolved(rect: Rect, (direction, len): (D, usize), storage: &mut S) -> Self {
152        let mut offsets = T::default();
153        offsets.set_len(len);
154        storage.set_dim(len);
155
156        let _s = Default::default();
157        RowSetter {
158            rect,
159            offsets,
160            direction,
161            _s,
162        }
163    }
164
165    pub fn update_offsets(&mut self, storage: &mut S) {
166        let offsets = self.offsets.as_mut();
167        let len = offsets.len();
168        if len == 0 {
169            return;
170        }
171
172        let pos = if self.direction.is_horizontal() {
173            self.rect.pos.0
174        } else {
175            self.rect.pos.1
176        };
177
178        if self.direction.is_reversed() {
179            offsets[len - 1] = pos;
180            for i in (0..(len - 1)).rev() {
181                let i1 = i + 1;
182                let m1 = storage.rules()[i1].margins_i32().1;
183                let m0 = storage.rules()[i].margins_i32().0;
184                offsets[i] = offsets[i1] + storage.widths()[i1] + m1.max(m0);
185            }
186        } else {
187            offsets[0] = pos;
188            for i in 1..len {
189                let i1 = i - 1;
190                let m1 = storage.rules()[i1].margins_i32().1;
191                let m0 = storage.rules()[i].margins_i32().0;
192                offsets[i] = offsets[i1] + storage.widths()[i1] + m1.max(m0);
193            }
194        }
195    }
196
197    pub fn solve_range(&mut self, storage: &mut S, range: Range<usize>, width: i32) {
198        assert!(range.end <= self.offsets.as_mut().len());
199
200        let (widths, rules) = storage.widths_and_rules();
201        SizeRules::solve_seq(&mut widths[range.clone()], &rules[range], width);
202    }
203}
204
205impl<D: Directional, T: RowTemp, S: RowStorage> RulesSetter for RowSetter<D, T, S> {
206    type Storage = S;
207    type ChildInfo = usize;
208
209    fn child_rect(&mut self, storage: &mut Self::Storage, index: Self::ChildInfo) -> Rect {
210        let mut rect = self.rect;
211        if self.direction.is_horizontal() {
212            rect.pos.0 = self.offsets.as_mut()[index];
213            rect.size.0 = storage.widths()[index];
214        } else {
215            rect.pos.1 = self.offsets.as_mut()[index];
216            rect.size.1 = storage.widths()[index];
217        }
218        rect
219    }
220
221    fn maximal_rect_of(&mut self, storage: &mut Self::Storage, index: Self::ChildInfo) -> Rect {
222        let pre_rules = SizeRules::min_sum(&storage.rules()[0..index]);
223        let m = storage.rules()[index].margins();
224        let len = storage.widths().len();
225        let post_rules = SizeRules::min_sum(&storage.rules()[(index + 1)..len]);
226
227        let size1 = pre_rules.min_size() + i32::from(pre_rules.margins().1.max(m.0));
228        let size2 = size1 + post_rules.min_size() + i32::from(post_rules.margins().0.max(m.1));
229
230        let mut rect = self.rect;
231        if self.direction.is_horizontal() {
232            rect.pos.0 = self.rect.pos.0 + size1;
233            rect.size.0 = (self.rect.size.0 - size2).max(0);
234        } else {
235            rect.pos.1 = self.rect.pos.1 + size1;
236            rect.size.1 = (self.rect.size.1 - size2).max(0);
237        }
238        rect
239    }
240}
241
242/// Allows efficient implementations of `draw` / event handlers based on the
243/// layout representation.
244///
245/// This is only applicable where child widgets are contained in a [`Collection`].
246#[derive(Clone, Copy, Debug)]
247pub struct RowPositionSolver<D: Directional> {
248    direction: D,
249}
250
251impl<D: Directional> RowPositionSolver<D> {
252    /// Construct with given directionality
253    pub fn new(direction: D) -> Self {
254        RowPositionSolver { direction }
255    }
256
257    fn binary_search<C: Collection + ?Sized>(
258        self,
259        widgets: &C,
260        coord: Coord,
261    ) -> Option<Result<usize, usize>> {
262        match self.direction.as_direction() {
263            Direction::Right => widgets.binary_search_by(|w| w.rect().pos.0.cmp(&coord.0)),
264            Direction::Down => widgets.binary_search_by(|w| w.rect().pos.1.cmp(&coord.1)),
265            Direction::Left => widgets.binary_search_by(|w| w.rect().pos.0.cmp(&coord.0).reverse()),
266            Direction::Up => widgets.binary_search_by(|w| w.rect().pos.1.cmp(&coord.1).reverse()),
267        }
268    }
269
270    /// Find the child containing the given coordinates
271    ///
272    /// Returns `None` when the coordinates lie within the margin area or
273    /// outside of the parent widget.
274    /// Also returns `None` if [`Collection::get_tile`] returns `None` for
275    /// some index less than `len` (a theoretical but unexpected error case).
276    pub fn find_child_index<C: Collection + ?Sized>(
277        self,
278        widgets: &C,
279        coord: Coord,
280    ) -> Option<usize> {
281        match self.binary_search(widgets, coord)? {
282            Ok(i) => Some(i),
283            Err(i) if self.direction.is_reversed() => {
284                if i == widgets.len() || !widgets.get_tile(i)?.rect().contains(coord) {
285                    None
286                } else {
287                    Some(i)
288                }
289            }
290            Err(i) => {
291                if i == 0 || !widgets.get_tile(i - 1)?.rect().contains(coord) {
292                    None
293                } else {
294                    Some(i - 1)
295                }
296            }
297        }
298    }
299
300    /// Find the child containing the given coordinates
301    ///
302    /// Returns `None` when the coordinates lie within the margin area or
303    /// outside of the parent widget.
304    /// Also returns `None` if [`Collection::get_tile`] returns `None` for
305    /// some index less than `len` (a theoretical but unexpected error case).
306    #[inline]
307    pub fn find_child<C: Collection + ?Sized>(
308        self,
309        widgets: &C,
310        coord: Coord,
311    ) -> Option<&dyn Tile> {
312        self.find_child_index(widgets, coord)
313            .and_then(|i| widgets.get_tile(i))
314    }
315
316    /// Find the child containing the given coordinates
317    ///
318    /// Returns `None` when the coordinates lie within the margin area or
319    /// outside of the parent widget.
320    /// Also returns `None` if [`Collection::get_tile`] returns `None` for
321    /// some index less than `len` (a theoretical but unexpected error case).
322    #[inline]
323    pub fn find_child_mut<C: Collection + ?Sized>(
324        self,
325        widgets: &mut C,
326        coord: Coord,
327    ) -> Option<&mut dyn Tile> {
328        self.find_child_index(widgets, coord)
329            .and_then(|i| widgets.get_mut_tile(i))
330    }
331
332    /// Call `f` on each child intersecting the given `rect`
333    pub fn for_children<C: Collection + ?Sized, F: FnMut(&dyn Tile)>(
334        self,
335        widgets: &C,
336        rect: Rect,
337        mut f: F,
338    ) {
339        let (pos, end) = match self.direction.is_reversed() {
340            false => (rect.pos, rect.pos2()),
341            true => (rect.pos2(), rect.pos),
342        };
343        let start = match self.binary_search(widgets, pos) {
344            Some(Ok(i)) => i,
345            Some(Err(i)) if i > 0 => {
346                let mut j = i - 1;
347                if let Some(rect) = widgets.get_tile(j).map(|l| l.rect()) {
348                    let cond = match self.direction.as_direction() {
349                        Direction::Right => pos.0 < rect.pos2().0,
350                        Direction::Down => pos.1 < rect.pos2().1,
351                        Direction::Left => rect.pos.0 <= pos.0,
352                        Direction::Up => rect.pos.1 <= pos.1,
353                    };
354                    if !cond {
355                        j = i;
356                    }
357                }
358                j
359            }
360            _ => 0,
361        };
362
363        for i in start..widgets.len() {
364            if let Some(child) = widgets.get_tile(i) {
365                let do_break = match self.direction.as_direction() {
366                    Direction::Right => child.rect().pos.0 >= end.0,
367                    Direction::Down => child.rect().pos.1 >= end.1,
368                    Direction::Left => child.rect().pos2().0 < end.0,
369                    Direction::Up => child.rect().pos2().1 < end.1,
370                };
371                if do_break {
372                    break;
373                }
374                f(child);
375            }
376        }
377    }
378}