Skip to main content

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_widths(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_widths(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    /// Solve ranges, with priority space allocation
198    ///
199    /// Excess space is allocated to the first or `last` item with the highest
200    /// stretch priority (above `Stretch::None` only).
201    pub fn solve_range(&mut self, storage: &mut S, range: Range<usize>, width: i32, last: bool) {
202        assert!(range.end <= self.offsets.as_mut().len());
203
204        let (widths, rules) = storage.widths_and_rules();
205        SizeRules::solve_widths_with_priority(
206            &mut widths[range.clone()],
207            &rules[range],
208            width,
209            last,
210        );
211    }
212
213    /// Calculates the maximal rect of a given child
214    ///
215    /// This assumes that all other entries have minimum size.
216    pub fn maximal_rect_of(&mut self, storage: &mut S, index: usize) -> Rect {
217        let pre_rules = SizeRules::min_sum(&storage.rules()[0..index]);
218        let m = storage.rules()[index].margins();
219        let len = storage.widths().len();
220        let post_rules = SizeRules::min_sum(&storage.rules()[(index + 1)..len]);
221
222        let size1 = pre_rules.min_size() + i32::from(pre_rules.margins().1.max(m.0));
223        let size2 = size1 + post_rules.min_size() + i32::from(post_rules.margins().0.max(m.1));
224
225        let mut rect = self.rect;
226        if self.direction.is_horizontal() {
227            rect.pos.0 = self.rect.pos.0 + size1;
228            rect.size.0 = (self.rect.size.0 - size2).max(0);
229        } else {
230            rect.pos.1 = self.rect.pos.1 + size1;
231            rect.size.1 = (self.rect.size.1 - size2).max(0);
232        }
233        rect
234    }
235}
236
237impl<D: Directional, T: RowTemp, S: RowStorage> RulesSetter for RowSetter<D, T, S> {
238    type Storage = S;
239    type ChildInfo = usize;
240
241    fn child_rect(&mut self, storage: &mut Self::Storage, index: Self::ChildInfo) -> Rect {
242        let mut rect = self.rect;
243        if self.direction.is_horizontal() {
244            rect.pos.0 = self.offsets.as_mut()[index];
245            rect.size.0 = storage.widths()[index];
246        } else {
247            rect.pos.1 = self.offsets.as_mut()[index];
248            rect.size.1 = storage.widths()[index];
249        }
250        rect
251    }
252}
253
254/// Allows efficient implementations of `draw` / event handlers based on the
255/// layout representation.
256///
257/// This is only applicable where child widgets are contained in a [`Collection`].
258#[derive(Clone, Copy, Debug)]
259pub struct RowPositionSolver<D: Directional> {
260    direction: D,
261}
262
263impl<D: Directional> RowPositionSolver<D> {
264    /// Construct with given directionality
265    pub fn new(direction: D) -> Self {
266        RowPositionSolver { direction }
267    }
268
269    fn binary_search<C: Collection + ?Sized>(
270        self,
271        widgets: &C,
272        coord: Coord,
273    ) -> Option<Result<usize, usize>> {
274        match self.direction.as_direction() {
275            Direction::Right => widgets.binary_search_by(|w| w.rect().pos.0.cmp(&coord.0)),
276            Direction::Down => widgets.binary_search_by(|w| w.rect().pos.1.cmp(&coord.1)),
277            Direction::Left => widgets.binary_search_by(|w| w.rect().pos.0.cmp(&coord.0).reverse()),
278            Direction::Up => widgets.binary_search_by(|w| w.rect().pos.1.cmp(&coord.1).reverse()),
279        }
280    }
281
282    /// Find the child containing the given coordinates
283    ///
284    /// Returns `None` when the coordinates lie within the margin area or
285    /// outside of the parent widget.
286    /// Also returns `None` if [`Collection::get_tile`] returns `None` for
287    /// some index less than `len` (a theoretical but unexpected error case).
288    pub fn find_child_index<C: Collection + ?Sized>(
289        self,
290        widgets: &C,
291        coord: Coord,
292    ) -> Option<usize> {
293        match self.binary_search(widgets, coord)? {
294            Ok(i) => Some(i),
295            Err(i) if self.direction.is_reversed() => {
296                if i == widgets.len() || !widgets.get_tile(i)?.rect().contains(coord) {
297                    None
298                } else {
299                    Some(i)
300                }
301            }
302            Err(i) => {
303                if i == 0 || !widgets.get_tile(i - 1)?.rect().contains(coord) {
304                    None
305                } else {
306                    Some(i - 1)
307                }
308            }
309        }
310    }
311
312    /// Find the child containing the given coordinates
313    ///
314    /// Returns `None` when the coordinates lie within the margin area or
315    /// outside of the parent widget.
316    /// Also returns `None` if [`Collection::get_tile`] returns `None` for
317    /// some index less than `len` (a theoretical but unexpected error case).
318    #[inline]
319    pub fn find_child<C: Collection + ?Sized>(
320        self,
321        widgets: &C,
322        coord: Coord,
323    ) -> Option<&dyn Tile> {
324        self.find_child_index(widgets, coord)
325            .and_then(|i| widgets.get_tile(i))
326    }
327
328    /// Find the child containing the given coordinates
329    ///
330    /// Returns `None` when the coordinates lie within the margin area or
331    /// outside of the parent widget.
332    /// Also returns `None` if [`Collection::get_tile`] returns `None` for
333    /// some index less than `len` (a theoretical but unexpected error case).
334    #[inline]
335    pub fn find_child_mut<C: Collection + ?Sized>(
336        self,
337        widgets: &mut C,
338        coord: Coord,
339    ) -> Option<&mut dyn Tile> {
340        self.find_child_index(widgets, coord)
341            .and_then(|i| widgets.get_mut_tile(i))
342    }
343
344    /// Call `f` on each child intersecting the given `rect`
345    pub fn for_children<C: Collection + ?Sized, F: FnMut(&dyn Tile)>(
346        self,
347        widgets: &C,
348        rect: Rect,
349        mut f: F,
350    ) {
351        let (pos, end) = match self.direction.is_reversed() {
352            false => (rect.pos, rect.pos2()),
353            true => (rect.pos2(), rect.pos),
354        };
355        let start = match self.binary_search(widgets, pos) {
356            Some(Ok(i)) => i,
357            Some(Err(i)) if i > 0 => {
358                let mut j = i - 1;
359                if let Some(rect) = widgets.get_tile(j).map(|l| l.rect()) {
360                    let cond = match self.direction.as_direction() {
361                        Direction::Right => pos.0 < rect.pos2().0,
362                        Direction::Down => pos.1 < rect.pos2().1,
363                        Direction::Left => rect.pos.0 <= pos.0,
364                        Direction::Up => rect.pos.1 <= pos.1,
365                    };
366                    if !cond {
367                        j = i;
368                    }
369                }
370                j
371            }
372            _ => 0,
373        };
374
375        for i in start..widgets.len() {
376            if let Some(child) = widgets.get_tile(i) {
377                let do_break = match self.direction.as_direction() {
378                    Direction::Right => child.rect().pos.0 >= end.0,
379                    Direction::Down => child.rect().pos.1 >= end.1,
380                    Direction::Left => child.rect().pos2().0 < end.0,
381                    Direction::Up => child.rect().pos2().1 < end.1,
382                };
383                if do_break {
384                    break;
385                }
386                f(child);
387            }
388        }
389    }
390}