Skip to main content

kas_core/layout/
sizer.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//! Layout solver
7
8use super::{AxisInfo, Margins, SizeRules};
9use crate::cast::Conv;
10use crate::geom::{Rect, Size};
11use crate::layout::AlignHints;
12use crate::theme::SizeCx;
13use crate::util::WidgetHierarchy;
14use crate::{Node, Tile};
15use log::trace;
16
17/// A [`SizeRules`] solver for layouts
18///
19/// Typically, a solver is invoked twice, once for each axis, before the
20/// corresponding [`RulesSetter`] is invoked. This is managed by [`SolveCache`].
21///
22/// Implementations require access to storage able to persist between multiple
23/// solver runs and a subsequent setter run. This storage is of type
24/// [`RulesSolver::Storage`] and is passed via reference to the constructor.
25pub trait RulesSolver {
26    /// Type of storage
27    type Storage: Clone;
28
29    /// Type required by [`RulesSolver::for_child`] (see implementation documentation)
30    type ChildInfo;
31
32    /// Called once for each child. For most layouts the order is important.
33    fn for_child<CR: FnOnce(AxisInfo) -> SizeRules>(
34        &mut self,
35        storage: &mut Self::Storage,
36        child_info: Self::ChildInfo,
37        child_rules: CR,
38    );
39
40    /// Called at the end to output [`SizeRules`].
41    ///
42    /// Note that this does not include margins!
43    fn finish(self, storage: &mut Self::Storage) -> SizeRules;
44}
45
46/// Resolves a [`RulesSolver`] solution for each child
47pub trait RulesSetter {
48    /// Type of storage
49    type Storage: Clone;
50
51    /// Type required by [`RulesSolver::for_child`] (see implementation documentation)
52    type ChildInfo;
53
54    /// Called once for each child. The order is unimportant.
55    fn child_rect(&mut self, storage: &mut Self::Storage, child_info: Self::ChildInfo) -> Rect;
56}
57
58/// Solve size rules for a widget
59///
60/// Automatic layout solving requires that a widget's `size_rules` method is
61/// called for each axis before `set_rect`. This method simply calls
62/// `size_rules` on each axis.
63///
64/// If `size_rules` is not called, internal layout may be poor (depending on the
65/// widget). If widget content changes, it is recommended to call
66/// `solve_size_rules` and `set_rect` again.
67///
68/// Parameters `x_size` and `y_size` should be passed where this dimension is
69/// fixed and are used e.g. for text wrapping.
70pub fn solve_size_rules<W: Tile + ?Sized>(
71    widget: &mut W,
72    cx: &mut SizeCx,
73    x_size: Option<i32>,
74    y_size: Option<i32>,
75) {
76    trace!(
77        "solve_size_rules({}, _, {:?}, {:?})",
78        widget.identify(),
79        x_size,
80        y_size
81    );
82    widget.size_rules(cx, AxisInfo::new(false, y_size));
83    widget.size_rules(cx, AxisInfo::new(true, x_size));
84}
85
86/// Size solver
87///
88/// This struct is used to solve widget layout, read size constraints and
89/// cache the results until the next solver run.
90///
91/// [`SolveCache::find_constraints`] constructs an instance of this struct,
92/// solving for size constraints.
93///
94/// [`SolveCache::apply_rect`] accepts a [`Rect`], updates constraints as
95/// necessary and sets widget positions within this `rect`.
96#[derive(Default)]
97pub struct SolveCache {
98    // Technically we don't need to store min and ideal here, but it simplifies
99    // the API for very little real cost.
100    min: Size,
101    ideal: Size,
102    margins: Margins,
103    last_width: i32,
104}
105
106impl SolveCache {
107    /// Get the minimum size
108    ///
109    /// If `inner_margin` is true, margins are included in the result.
110    pub fn min(&self, inner_margin: bool) -> Size {
111        if inner_margin {
112            self.margins.pad(self.min)
113        } else {
114            self.min
115        }
116    }
117
118    /// Get the ideal size
119    ///
120    /// If `inner_margin` is true, margins are included in the result.
121    pub fn ideal(&self, inner_margin: bool) -> Size {
122        if inner_margin {
123            self.margins.pad(self.ideal)
124        } else {
125            self.ideal
126        }
127    }
128
129    /// Get the margins
130    pub fn margins(&self) -> Margins {
131        self.margins
132    }
133
134    /// Calculate required size of widget
135    ///
136    /// Assumes no explicit alignment.
137    pub fn find_constraints(&mut self, mut widget: Node<'_>, cx: &mut SizeCx) {
138        let start = std::time::Instant::now();
139
140        let w = widget.size_rules(cx, AxisInfo::new(false, None));
141        let h = widget.size_rules(cx, AxisInfo::new(true, Some(w.ideal_size())));
142
143        self.min = Size(w.min_size(), h.min_size());
144        self.ideal = Size(w.ideal_size(), h.ideal_size());
145        self.margins = Margins::hv(w.margins(), h.margins());
146
147        log::trace!(
148            target: "kas_perf::layout", "find_constraints: {}μs",
149            start.elapsed().as_micros(),
150        );
151        log::debug!(
152            "find_constraints: min={:?}, ideal={:?}, margins={:?}",
153            &self.min,
154            &self.ideal,
155            &self.margins
156        );
157        self.last_width = self.ideal.0;
158    }
159
160    /// Apply layout solution to a widget
161    ///
162    /// The widget's layout is solved for the given `rect` and assigned.
163    /// If `inner_margin` is true, margins are internal to this `rect`; if not,
164    /// the caller is responsible for handling margins.
165    ///
166    /// This method will only recalculate rules as necessary (namely height
167    /// rules when the width has changed).
168    pub fn apply_rect(
169        &mut self,
170        mut widget: Node<'_>,
171        cx: &mut SizeCx,
172        mut rect: Rect,
173        inner_margin: bool,
174    ) {
175        let start = std::time::Instant::now();
176
177        let mut width = rect.size.0;
178        if inner_margin {
179            width -= self.margins.sum_horiz();
180        }
181
182        // We call size_rules not because we want the result, but to allow
183        // internal layout solving.
184        if width != self.last_width {
185            let h = widget.size_rules(cx, AxisInfo::new(true, Some(width)));
186            self.min.1 = h.min_size();
187            self.ideal.1 = h.ideal_size();
188            self.margins.vert = h.margins();
189            self.last_width = width;
190        }
191
192        if inner_margin {
193            rect.pos += Size::conv((self.margins.horiz.0, self.margins.vert.0));
194            rect.size.0 = width;
195            rect.size.1 -= self.margins.sum_vert();
196        }
197        widget.set_rect(cx, rect, AlignHints::NONE);
198
199        log::trace!(target: "kas_perf::layout", "apply_rect: {}μs", start.elapsed().as_micros());
200    }
201
202    /// Print widget heirarchy in the trace log
203    ///
204    /// This is sometimes called after [`Self::apply_rect`].
205    pub fn print_widget_heirarchy(&mut self, widget: &dyn Tile) {
206        let rect = widget.rect();
207        let hier = WidgetHierarchy::new(widget, None);
208        log::trace!(
209            target: "kas_core::layout::hierarchy",
210            "apply_rect: rect={rect:?}:{hier}",
211        );
212    }
213}