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::event::ConfigCx;
11use crate::geom::{Rect, Size};
12use crate::layout::AlignHints;
13use crate::theme::SizeCx;
14use crate::util::WidgetHierarchy;
15use crate::{Layout, Node};
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    /// Calculates the maximal rect of a given child
58    ///
59    /// This assumes that all other entries have minimum size.
60    fn maximal_rect_of(&mut self, storage: &mut Self::Storage, index: Self::ChildInfo) -> Rect;
61}
62
63/// Solve size rules for a widget
64///
65/// Automatic layout solving requires that a widget's `size_rules` method is
66/// called for each axis before `set_rect`. This method simply calls
67/// `size_rules` on each axis.
68///
69/// If `size_rules` is not called, internal layout may be poor (depending on the
70/// widget). If widget content changes, it is recommended to call
71/// `solve_size_rules` and `set_rect` again.
72///
73/// Parameters `x_size` and `y_size` should be passed where this dimension is
74/// fixed and are used e.g. for text wrapping.
75pub fn solve_size_rules<W: Layout + ?Sized>(
76    widget: &mut W,
77    sizer: SizeCx,
78    x_size: Option<i32>,
79    y_size: Option<i32>,
80) {
81    widget.size_rules(sizer.re(), AxisInfo::new(false, y_size));
82    widget.size_rules(sizer.re(), AxisInfo::new(true, x_size));
83}
84
85/// Size solver
86///
87/// This struct is used to solve widget layout, read size constraints and
88/// cache the results until the next solver run.
89///
90/// [`SolveCache::find_constraints`] constructs an instance of this struct,
91/// solving for size constraints.
92///
93/// [`SolveCache::apply_rect`] accepts a [`Rect`], updates constraints as
94/// necessary and sets widget positions within this `rect`.
95pub struct SolveCache {
96    // Technically we don't need to store min and ideal here, but it simplifies
97    // the API for very little real cost.
98    min: Size,
99    ideal: Size,
100    margins: Margins,
101    refresh_rules: bool,
102    last_width: i32,
103}
104
105impl SolveCache {
106    /// Get the minimum size
107    ///
108    /// If `inner_margin` is true, margins are included in the result.
109    pub fn min(&self, inner_margin: bool) -> Size {
110        if inner_margin {
111            self.margins.pad(self.min)
112        } else {
113            self.min
114        }
115    }
116
117    /// Get the ideal size
118    ///
119    /// If `inner_margin` is true, margins are included in the result.
120    pub fn ideal(&self, inner_margin: bool) -> Size {
121        if inner_margin {
122            self.margins.pad(self.ideal)
123        } else {
124            self.ideal
125        }
126    }
127
128    /// Get the margins
129    pub fn margins(&self) -> Margins {
130        self.margins
131    }
132
133    /// Calculate required size of widget
134    ///
135    /// Assumes no explicit alignment.
136    pub fn find_constraints(mut widget: Node<'_>, sizer: SizeCx) -> Self {
137        let start = std::time::Instant::now();
138
139        let w = widget.size_rules(sizer.re(), AxisInfo::new(false, None));
140        let h = widget.size_rules(sizer.re(), AxisInfo::new(true, Some(w.ideal_size())));
141
142        let min = Size(w.min_size(), h.min_size());
143        let ideal = Size(w.ideal_size(), h.ideal_size());
144        let margins = Margins::hv(w.margins(), h.margins());
145
146        log::trace!(
147            target: "kas_perf::layout", "find_constraints: {}μs",
148            start.elapsed().as_micros(),
149        );
150        log::debug!("find_constraints: min={min:?}, ideal={ideal:?}, margins={margins:?}");
151        let refresh_rules = false;
152        let last_width = ideal.0;
153        SolveCache {
154            min,
155            ideal,
156            margins,
157            refresh_rules,
158            last_width,
159        }
160    }
161
162    /// Force updating of size rules
163    ///
164    /// This should be called whenever widget size rules have been changed. It
165    /// forces [`SolveCache::apply_rect`] to recompute these rules when next
166    /// called.
167    pub fn invalidate_rule_cache(&mut self) {
168        self.refresh_rules = true;
169    }
170
171    /// Apply layout solution to a widget
172    ///
173    /// The widget's layout is solved for the given `rect` and assigned.
174    /// If `inner_margin` is true, margins are internal to this `rect`; if not,
175    /// the caller is responsible for handling margins.
176    ///
177    /// If [`SolveCache::invalidate_rule_cache`] was called since rules were
178    /// last calculated then this method will recalculate all rules; otherwise
179    /// it will only do so if necessary (when dimensions do not match those
180    /// last used).
181    pub fn apply_rect(
182        &mut self,
183        mut widget: Node<'_>,
184        cx: &mut ConfigCx,
185        mut rect: Rect,
186        inner_margin: bool,
187    ) {
188        let start = std::time::Instant::now();
189
190        let mut width = rect.size.0;
191        if inner_margin {
192            width -= self.margins.sum_horiz();
193        }
194
195        // We call size_rules not because we want the result, but to allow
196        // internal layout solving.
197        if self.refresh_rules || width != self.last_width {
198            if self.refresh_rules {
199                let w = widget.size_rules(cx.size_cx(), AxisInfo::new(false, None));
200                self.min.0 = w.min_size();
201                self.ideal.0 = w.ideal_size();
202                self.margins.horiz = w.margins();
203                width = rect.size.0 - self.margins.sum_horiz();
204            }
205
206            let h = widget.size_rules(cx.size_cx(), AxisInfo::new(true, Some(width)));
207            self.min.1 = h.min_size();
208            self.ideal.1 = h.ideal_size();
209            self.margins.vert = h.margins();
210            self.last_width = width;
211        }
212
213        if inner_margin {
214            rect.pos += Size::conv((self.margins.horiz.0, self.margins.vert.0));
215            rect.size.0 = width;
216            rect.size.1 -= self.margins.sum_vert();
217        }
218        widget.set_rect(cx, rect, AlignHints::NONE);
219
220        log::trace!(target: "kas_perf::layout", "apply_rect: {}μs", start.elapsed().as_micros());
221        self.refresh_rules = false;
222    }
223
224    /// Print widget heirarchy in the trace log
225    ///
226    /// This is sometimes called after [`Self::apply_rect`].
227    pub fn print_widget_heirarchy(&mut self, widget: &dyn Layout) {
228        let rect = widget.rect();
229        let hier = WidgetHierarchy::new(widget, None);
230        log::trace!(
231            target: "kas_core::layout::hierarchy",
232            "apply_rect: rect={rect:?}:{hier}",
233        );
234    }
235}