Skip to main content

kas_core/layout/
flow_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//! Flow solver
7
8use super::{AxisInfo, SizeRules};
9use super::{RulesSetter, RulesSolver};
10use crate::dir::{Direction, Directional};
11use crate::geom::{Coord, Rect, Size};
12
13/// Storage required by [`FlowSolver`] and [`FlowSetter`]
14#[derive(Clone, Debug, Default)]
15pub struct FlowStorage {
16    rules: Vec<SizeRules>,
17    /// The length should be `self.breaks.len() + 1`.
18    height_rules: Vec<SizeRules>,
19    widths: Vec<i32>,
20    /// These are the indices at which a new line starts. It may be assumed that
21    /// a new line starts at index zero, hence 0 is not present in this list.
22    breaks: Vec<usize>,
23}
24
25/// A [`RulesSolver`] for flows
26///
27/// The flow direction is currently restricted to lines which flow to the right,
28/// wrapping down (as in English text).
29///
30/// Margins of the "flow" as a whole are the maximum of all item margins since
31/// it is not known in advance which items will be on the first/last line or at
32/// the start/end of each line.
33pub struct FlowSolver {
34    axis: AxisInfo,
35    direction: Direction,
36    secondary_is_reversed: bool,
37    opt_rules: Option<SizeRules>,
38    rules: SizeRules,
39    min_cols: i32,
40    ideal_cols: i32,
41}
42
43impl FlowSolver {
44    /// Construct a solver
45    ///
46    /// Parameters:
47    ///
48    /// - `axis`: `AxisInfo` instance passed into `size_rules`
49    /// - `direction`: primary direction of flow (lines)
50    /// - `secondary_is_reversed`: true if the direction in which lines wrap is
51    ///   left or up (this corresponds to [`Directional::is_reversed`])
52    /// - `len`:  and total number of items
53    /// - `storage`: reference to persistent storage
54    pub fn new(
55        axis: AxisInfo,
56        direction: Direction,
57        secondary_is_reversed: bool,
58        len: usize,
59        storage: &mut FlowStorage,
60    ) -> Self {
61        storage.rules.resize(len, SizeRules::EMPTY);
62        storage.widths.resize(len, 0);
63
64        if direction.is_horizontal() || axis.is_horizontal() {
65            storage.breaks.clear();
66            storage.height_rules.clear();
67        }
68
69        // If the flow consists of rows, then we solve widths on the vertical
70        // axis. For columns we can't do anything useful here.
71        if axis.has_fixed && direction.is_horizontal() && len != 0 {
72            debug_assert!(axis.is_vertical());
73            // Assume we already have rules for the other axis; solve for the given width
74            let width = axis.other_axis;
75
76            let mut total = storage.rules[0];
77            let mut start = 0;
78            for i in 1..storage.rules.len() {
79                let sum = total.appended(storage.rules[i]);
80                if sum.min_size() <= width {
81                    total = sum;
82                    continue;
83                }
84
85                // Line break. Solve widths for the previous line:
86                SizeRules::solve_widths_with_total(
87                    &mut storage.widths[start..i],
88                    &storage.rules[start..i],
89                    total,
90                    width,
91                );
92                storage.breaks.push(i);
93                start = i;
94                total = storage.rules[i];
95            }
96
97            // Final line:
98            SizeRules::solve_widths_with_total(
99                &mut storage.widths[start..],
100                &storage.rules[start..],
101                total,
102                width,
103            );
104        }
105
106        storage.height_rules.reserve_exact(storage.breaks.len() + 1);
107
108        FlowSolver {
109            axis,
110            direction,
111            secondary_is_reversed,
112            opt_rules: None,
113            rules: SizeRules::EMPTY,
114            min_cols: 1,
115            ideal_cols: 3,
116        }
117    }
118
119    /// Set the (minimum, ideal) numbers of columns
120    ///
121    /// This affects the final [`SizeRules`] for the horizontal axis.
122    ///
123    /// By default, the values `1, 3` are used.
124    #[inline]
125    pub fn set_num_columns(&mut self, min: i32, ideal: i32) {
126        self.min_cols = min;
127        self.ideal_cols = ideal;
128    }
129
130    /// Set column width
131    ///
132    /// When the primary direction is vertical, the column width cannot be
133    /// inferred. Set it with this method. (In other cases this does nothing.)
134    ///
135    /// This does not directly affect the returned [`SizeRules`], but it *does*
136    /// affect the width supplied to children when inferring their height
137    /// (see [`AxisInfo::other`]), which could be useful if e.g. child widgtes
138    /// contain text which wraps at this width.
139    ///
140    /// Further note: the result of [`Self::finish`] for the horizontal axis
141    /// will be just the maximum [`SizeRules`] of all children. You may wish to
142    /// call [`SizeRules::multiply_with_margin`] for the horizontal axis to
143    /// reserve enough room for multiple columns.
144    pub fn set_column_properties(&mut self, width: i32) {
145        if self.direction.is_vertical() && self.axis.is_vertical() {
146            self.axis.map_other(|w| w.min(width));
147        }
148    }
149}
150
151impl RulesSolver for FlowSolver {
152    type Storage = FlowStorage;
153    type ChildInfo = usize;
154
155    fn for_child<CR: FnOnce(AxisInfo) -> SizeRules>(
156        &mut self,
157        storage: &mut Self::Storage,
158        index: Self::ChildInfo,
159        child_rules: CR,
160    ) {
161        if self.direction.is_horizontal() && self.axis.has_fixed {
162            // For rows, use per-item widths (solved in Self::new)
163            self.axis.other_axis = storage.widths[index];
164        }
165        let child_rules = child_rules(self.axis);
166        if self.direction.is_horizontal() == self.axis.is_horizontal() {
167            storage.rules[index] = child_rules;
168        }
169
170        if self.direction.is_horizontal() == self.axis.is_horizontal() {
171            // We calculate the ideal size by appending all items into one line:
172            self.opt_rules = Some(if let Some(rules) = self.opt_rules {
173                if self.direction.is_reversed() {
174                    child_rules.appended(rules)
175                } else {
176                    rules.appended(child_rules)
177                }
178            } else {
179                child_rules
180            });
181        } else {
182            if storage.breaks.contains(&index) {
183                storage.height_rules.push(self.rules);
184
185                self.opt_rules = Some(if let Some(rules) = self.opt_rules {
186                    if self.secondary_is_reversed {
187                        rules.appended(self.rules)
188                    } else {
189                        self.rules.appended(rules)
190                    }
191                } else {
192                    self.rules
193                });
194                self.rules = SizeRules::EMPTY;
195            }
196        }
197
198        self.rules = self.rules.max(child_rules);
199    }
200
201    fn finish(self, storage: &mut Self::Storage) -> SizeRules {
202        if self.direction.is_horizontal() == self.axis.is_horizontal() {
203            let mut col_limited_rules = self.rules;
204            col_limited_rules.multiply_with_margin(self.min_cols, self.ideal_cols);
205            let unwrapped_width = self.opt_rules.unwrap_or(SizeRules::EMPTY).ideal_size();
206            let min = col_limited_rules.min_size();
207            let ideal = unwrapped_width.min(col_limited_rules.ideal_size());
208            let stretch = self.rules.stretch();
209            SizeRules::new(min, ideal, stretch).with_margins(self.rules.margins())
210        } else {
211            let rules = if let Some(rules) = self.opt_rules {
212                if self.secondary_is_reversed {
213                    rules.appended(self.rules)
214                } else {
215                    self.rules.appended(rules)
216                }
217            } else {
218                self.rules
219            };
220
221            storage.height_rules.push(rules);
222            debug_assert_eq!(storage.breaks.len() + 1, storage.height_rules.len());
223            rules
224        }
225    }
226}
227
228/// A [`RulesSetter`] for flows
229pub struct FlowSetter {
230    pos: Coord,
231    offsets: Vec<i32>,
232    direction: Direction,
233    secondary_is_reversed: bool,
234    heights: Vec<i32>, // by row
235    row_offsets: Vec<i32>,
236}
237
238impl FlowSetter {
239    /// Construct a setter
240    ///
241    /// Parameters:
242    ///
243    /// -   `rect`: the [`Rect`] within which to position children
244    /// - `direction`: primary direction of flow (lines)
245    /// - `secondary_is_reversed`: true if the direction in which lines wrap is
246    ///   left or up (this corresponds to [`Directional::is_reversed`])
247    /// - `len`:  and total number of items
248    /// - `storage`: reference to persistent storage
249    pub fn new(
250        rect: Rect,
251        direction: Direction,
252        secondary_is_reversed: bool,
253        len: usize,
254        storage: &mut FlowStorage,
255    ) -> Self {
256        let offsets = vec![0; len];
257        assert_eq!(storage.rules.len(), len);
258        let mut heights = vec![];
259
260        if direction.is_vertical() {
261            // TODO: solve storage.breaks (wrap points) here, then solve storage.widths (which are
262            // heights in this case) and finally column widths (which requires per-item width rules
263            // or just fixed column widths).
264            todo!()
265        }
266
267        if len != 0 {
268            let height = rect.size.extract(direction.flipped());
269            heights = vec![0; storage.height_rules.len()];
270            SizeRules::solve_widths(&mut heights, &storage.height_rules, height);
271        }
272
273        let mut row = FlowSetter {
274            pos: rect.pos,
275            offsets,
276            direction,
277            secondary_is_reversed,
278            heights,
279            row_offsets: vec![],
280        };
281        row.update_offsets(storage);
282        row
283    }
284
285    fn update_offsets(&mut self, storage: &mut FlowStorage) {
286        fn set_offsets(
287            pos: i32,
288            rules: &[SizeRules],
289            sizes: &[i32],
290            offsets: &mut [i32],
291            iter: impl ExactSizeIterator<Item = usize>,
292            is_break: impl Fn(usize) -> bool,
293        ) {
294            debug_assert_eq!(rules.len(), sizes.len());
295            debug_assert_eq!(rules.len(), iter.len());
296
297            let mut caret = pos;
298            let mut margin = 0;
299            for i in iter {
300                let margins = rules[i].margins_i32();
301                if is_break(i) {
302                    caret = pos;
303                } else {
304                    caret += margin.max(margins.0);
305                }
306                margin = margins.1;
307
308                offsets[i] = caret;
309                caret += sizes[i];
310            }
311        }
312
313        let len = self.offsets.len();
314        if len == 0 {
315            return;
316        }
317
318        let pos = self.pos.extract(self.direction);
319        if !self.direction.is_reversed() {
320            set_offsets(
321                pos,
322                &storage.rules,
323                &storage.widths,
324                &mut self.offsets,
325                0..len,
326                |i| i == 0 || storage.breaks.contains(&i),
327            );
328        } else {
329            set_offsets(
330                pos,
331                &storage.rules,
332                &storage.widths,
333                &mut self.offsets,
334                (0..len).rev(),
335                |i| i == len - 1 || storage.breaks.contains(&(i + 1)),
336            );
337        }
338
339        let pos = self.pos.extract(self.direction.flipped());
340        let len = storage.height_rules.len();
341        self.row_offsets.resize(len, 0);
342        let offsets = &mut self.row_offsets;
343        if !self.secondary_is_reversed {
344            set_offsets(
345                pos,
346                &storage.height_rules,
347                &self.heights,
348                offsets,
349                0..len,
350                |_| false,
351            );
352        } else {
353            set_offsets(
354                pos,
355                &storage.height_rules,
356                &self.heights,
357                offsets,
358                (0..len).rev(),
359                |_| false,
360            );
361        }
362    }
363}
364
365impl RulesSetter for FlowSetter {
366    type Storage = FlowStorage;
367    type ChildInfo = usize;
368
369    fn child_rect(&mut self, storage: &mut Self::Storage, index: Self::ChildInfo) -> Rect {
370        let row = match storage.breaks.binary_search(&index) {
371            Ok(i) => i + 1,
372            Err(i) => i,
373        };
374        let w = storage.widths[index];
375        let h = self.heights[row];
376        let x = self.offsets[index];
377        let y = self.row_offsets[row];
378
379        if self.direction.is_horizontal() {
380            Rect::new(Coord(x, y), Size(w, h))
381        } else {
382            Rect::new(Coord(y, x), Size(h, w))
383        }
384    }
385}