kas_core/core/layout.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 and LayoutExt traits
7
8use crate::event::ConfigCx;
9use crate::geom::{Coord, Offset, Rect};
10use crate::layout::{AlignHints, AxisInfo, SizeRules};
11use crate::theme::{DrawCx, SizeCx};
12use crate::util::IdentifyWidget;
13use crate::{HasId, Id};
14use kas_macros::autoimpl;
15
16#[allow(unused)] use super::{Events, Widget};
17#[allow(unused)] use crate::layout::{self, AlignPair};
18#[allow(unused)] use kas_macros as macros;
19
20/// Positioning and drawing routines for [`Widget`]s
21///
22/// `Layout` is a super-trait of [`Widget`] which:
23///
24/// - Has no [`Data`](Widget::Data) parameter
25/// - Supports read-only tree reflection: [`Self::get_child`]
26/// - Provides some basic operations: [`Self::id_ref`], [`Self::rect`]
27/// - Covers sizing and drawing operations ("layout")
28///
29/// # Implementing Layout
30///
31/// See [`Widget`] documentation and the [`#widget`] macro.
32/// `Layout` may not be implemented independently.
33///
34/// # Widget lifecycle
35///
36/// 1. The widget is configured ([`Events::configure`]) and immediately updated
37/// ([`Events::update`]).
38/// 2. The widget has its size-requirements checked by calling [`Self::size_rules`]
39/// for each axis (usually via recursion, sometimes via [`layout::solve_size_rules`]
40/// or [`layout::SolveCache`]).
41/// 3. [`Self::set_rect`] is called to position elements. This may use data
42/// cached by `size_rules`.
43/// 4. The widget is updated again after any data change (see [`ConfigCx::update`]).
44/// 5. The widget is ready for event-handling and drawing ([`Events`],
45/// [`Self::find_id`], [`Self::draw`]).
46///
47/// Widgets are responsible for ensuring that their children may observe this
48/// lifecycle. Usually this simply involves inclusion of the child in layout
49/// operations. Steps of the lifecycle may be postponed until a widget becomes
50/// visible.
51///
52/// # Tree reflection
53///
54/// `Layout` offers a reflection API over the widget tree via
55/// [`Layout::get_child`]. This is limited to read-only functions, and thus
56/// cannot directly violate the widget lifecycle, however note that the
57/// [`id_ref`](Self::id_ref) could be invalid or could be valid but refer to a
58/// node which has not yet been sized and positioned (and thus which it is not
59/// valid to send events to).
60///
61/// [`#widget`]: macros::widget
62#[autoimpl(for<T: trait + ?Sized> &'_ mut T, Box<T>)]
63pub trait Layout {
64 /// Get as a `dyn Layout`
65 ///
66 /// This method is implemented by the `#[widget]` macro.
67 fn as_layout(&self) -> &dyn Layout {
68 unimplemented!() // make rustdoc show that this is a provided method
69 }
70
71 /// Get the widget's identifier
72 ///
73 /// Note that the default-constructed [`Id`] is *invalid*: any
74 /// operations on this value will cause a panic. A valid identifier is
75 /// assigned when the widget is configured (immediately before calling
76 /// [`Events::configure`]).
77 ///
78 /// This method is implemented by the `#[widget]` macro.
79 fn id_ref(&self) -> &Id {
80 unimplemented!() // make rustdoc show that this is a provided method
81 }
82
83 /// Get the widget's region, relative to its parent.
84 ///
85 /// This method is implemented by the `#[widget]` macro.
86 fn rect(&self) -> Rect {
87 unimplemented!() // make rustdoc show that this is a provided method
88 }
89
90 /// Get the name of the widget struct
91 ///
92 /// This method is implemented by the `#[widget]` macro.
93 fn widget_name(&self) -> &'static str {
94 unimplemented!() // make rustdoc show that this is a provided method
95 }
96
97 /// Get the number of child widgets
98 ///
99 /// Every value in the range `0..self.num_children()` is a valid child
100 /// index.
101 ///
102 /// This method is usually implemented automatically by the `#[widget]`
103 /// macro. It should be implemented directly if and only if
104 /// [`Layout::get_child`] and [`Widget::for_child_node`] are
105 /// implemented directly.
106 fn num_children(&self) -> usize {
107 unimplemented!() // make rustdoc show that this is a provided method
108 }
109
110 /// Access a child as a `dyn Layout`
111 ///
112 /// This method is usually implemented automatically by the `#[widget]`
113 /// macro.
114 fn get_child(&self, index: usize) -> Option<&dyn Layout> {
115 let _ = index;
116 unimplemented!() // make rustdoc show that this is a provided method
117 }
118
119 /// Find the child which is an ancestor of this `id`, if any
120 ///
121 /// If `Some(index)` is returned, this is *probably* but not guaranteed
122 /// to be a valid child index.
123 ///
124 /// The default implementation simply uses [`Id::next_key_after`].
125 /// Widgets may choose to assign children custom keys by overriding this
126 /// method and [`Events::make_child_id`].
127 #[inline]
128 fn find_child_index(&self, id: &Id) -> Option<usize> {
129 id.next_key_after(self.id_ref())
130 }
131
132 /// Get size rules for the given axis
133 ///
134 /// Typically, this method is called twice: first for the horizontal axis,
135 /// second for the vertical axis (with resolved width available through
136 /// the `axis` parameter allowing content wrapping).
137 /// For a description of the widget size model, see [`SizeRules`].
138 ///
139 /// This method is expected to cache any size requirements calculated from
140 /// children which would be required for space allocations in
141 /// [`Self::set_rect`]. As an example, the horizontal [`SizeRules`] for a
142 /// row layout is the sum of the rules for each column (plus margins);
143 /// these per-column [`SizeRules`] are also needed to calculate column
144 /// widths in [`Self::size_rules`] once the available size is known.
145 ///
146 /// For row/column/grid layouts, a [`crate::layout::RulesSolver`] engine
147 /// may be useful.
148 ///
149 /// Required: `self` is configured ([`ConfigCx::configure`]) before this
150 /// method is called, and that `size_rules` is called for the
151 /// horizontal axis before it is called for the vertical axis.
152 /// Further, [`Self::set_rect`] must be called after this method before
153 /// drawing or event handling.
154 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules;
155
156 /// Set size and position
157 ///
158 /// This method is called after [`Self::size_rules`] and may use values
159 /// cached by `size_rules` (in the case `size_rules` is not called first,
160 /// the widget may exhibit incorrect layout but should not panic). This
161 /// method should not write over values cached by `size_rules` since
162 /// `set_rect` may be called multiple times consecutively.
163 /// After `set_rect` is called, the widget must be ready for drawing and event handling.
164 ///
165 /// The size of the assigned `rect` is normally at least the minimum size
166 /// requested by [`Self::size_rules`], but this is not guaranteed. In case
167 /// this minimum is not met, it is permissible for the widget to draw
168 /// outside of its assigned `rect` and to not function as normal.
169 ///
170 /// The assigned `rect` may be larger than the widget's size requirements,
171 /// regardless of the [`Stretch`] policy used: containers divide up space
172 /// based on children's [`SizeRules`] but do not attempt to align content
173 /// when excess space is available. Instead, content is responsible for
174 /// aligning itself using the provided `hints` and/or local information.
175 ///
176 /// Required: [`Self::size_rules`] is called for both axes before this
177 /// method is called, and that this method has been called *after* the last
178 /// call to [`Self::size_rules`] *before* any of the following methods:
179 /// [`Layout::find_id`], [`Layout::draw`], [`Events::handle_event`].
180 ///
181 /// Default implementation when not using the `layout` property: set `rect`
182 /// field of `widget_core!()` to the input `rect`.
183 ///
184 /// [`Stretch`]: crate::layout::Stretch
185 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
186 let _ = (cx, rect, hints);
187 unimplemented!() // make rustdoc show that this is a provided method
188 }
189
190 /// Navigation in spatial order
191 ///
192 /// Controls <kbd>Tab</kbd> navigation order of children.
193 /// This method should:
194 ///
195 /// - Return `None` if there is no next child
196 /// - Determine the next child after `from` (if provided) or the whole
197 /// range, optionally in `reverse` order
198 /// - Ensure that the selected widget is addressable through
199 /// [`Layout::get_child`]
200 ///
201 /// Both `from` and the return value use the widget index, as used by
202 /// [`Layout::get_child`].
203 ///
204 /// Default implementation:
205 ///
206 /// - Generated from `#[widget]`'s layout property, if used (not always possible!)
207 /// - Otherwise, iterate through children in order of definition
208 fn nav_next(&self, reverse: bool, from: Option<usize>) -> Option<usize> {
209 let _ = (reverse, from);
210 unimplemented!() // make rustdoc show that this is a provided method
211 }
212
213 /// Get translation of children relative to this widget
214 ///
215 /// Usually this is zero; only widgets with scrollable or offset content
216 /// *and* child widgets need to implement this.
217 /// Such widgets must also implement [`Events::handle_scroll`].
218 ///
219 /// Affects event handling via [`Layout::find_id`] and affects the positioning
220 /// of pop-up menus. [`Layout::draw`] must be implemented directly using
221 /// [`DrawCx::with_clip_region`] to offset contents.
222 ///
223 /// Default implementation: return [`Offset::ZERO`]
224 #[inline]
225 fn translation(&self) -> Offset {
226 Offset::ZERO
227 }
228
229 /// Translate a coordinate to an [`Id`]
230 ///
231 /// This method is used to determine which widget reacts to the mouse cursor
232 /// or a touch event. The result affects mouse-hover highlighting, event
233 /// handling by the target, and potentially also event handling by other
234 /// widgets (e.g. a `Label` widget will not handle touch events, but if it
235 /// is contained by a `ScrollRegion`, that widget may capture these via
236 /// [`Events::handle_event`] to implement touch scrolling).
237 ///
238 /// The result is usually the widget which draws at the given `coord`, but
239 /// does not have to be. For example, a `Button` widget will return its own
240 /// `id` for coordinates drawn by internal content, while the `CheckButton`
241 /// widget uses an internal component for event handling and thus reports
242 /// this component's `id` even over its own area.
243 ///
244 /// It is expected that [`Layout::set_rect`] is called before this method,
245 /// but failure to do so should not cause a fatal error.
246 ///
247 /// The default implementation suffices for widgets without children as well
248 /// as widgets using the `layout` property of [`#[widget]`](crate::widget).
249 /// Custom implementations may be required if:
250 ///
251 /// - A custom [`Layout`] implementation is used
252 /// - Event stealing or donation is desired (but note that
253 /// `layout = button: ..;` does this already)
254 ///
255 /// When writing a custom implementation:
256 ///
257 /// - Widgets should test `self.rect().contains(coord)`, returning `None`
258 /// if this test is `false`; otherwise, they should always return *some*
259 /// [`Id`], either a childs or their own.
260 /// - If the Widget uses a translated coordinate space (i.e.
261 /// `self.translation() != Offset::ZERO`) then pass
262 /// `coord + self.translation()` to children.
263 ///
264 /// The default implementation is non-trivial:
265 /// ```ignore
266 /// if !self.rect().contains(coord) {
267 /// return None;
268 /// }
269 /// let coord = coord + self.translation();
270 /// for child in ITER_OVER_CHILDREN {
271 /// if let Some(id) = child.find_id(coord) {
272 /// return Some(id);
273 /// }
274 /// }
275 /// Some(self.id())
276 /// ```
277 fn find_id(&mut self, coord: Coord) -> Option<Id> {
278 let _ = coord;
279 unimplemented!() // make rustdoc show that this is a provided method
280 }
281
282 /// Draw a widget and its children
283 ///
284 /// This method is invoked each frame to draw visible widgets. It should
285 /// draw itself and recurse into all visible children.
286 ///
287 /// It is expected that [`Self::set_rect`] is called before this method,
288 /// but failure to do so should not cause a fatal error.
289 ///
290 /// The `draw` parameter is pre-parameterized with this widget's
291 /// [`Id`], allowing drawn components to react to input state. This
292 /// implies that when calling `draw` on children, the child's `id` must be
293 /// supplied via [`DrawCx::re_id`] or [`DrawCx::recurse`].
294 fn draw(&mut self, draw: DrawCx);
295}
296
297impl<W: Layout + ?Sized> HasId for &W {
298 #[inline]
299 fn has_id(self) -> Id {
300 self.id_ref().clone()
301 }
302}
303
304impl<W: Layout + ?Sized> HasId for &mut W {
305 #[inline]
306 fn has_id(self) -> Id {
307 self.id_ref().clone()
308 }
309}
310
311/// Extension trait over widgets
312pub trait LayoutExt: Layout {
313 /// Get the widget's identifier
314 ///
315 /// Note that the default-constructed [`Id`] is *invalid*: any
316 /// operations on this value will cause a panic. Valid identifiers are
317 /// assigned during configure.
318 #[inline]
319 fn id(&self) -> Id {
320 self.id_ref().clone()
321 }
322
323 /// Test widget identifier for equality
324 ///
325 /// This method may be used to test against `Id`, `Option<Id>`
326 /// and `Option<&Id>`.
327 #[inline]
328 fn eq_id<T>(&self, rhs: T) -> bool
329 where
330 Id: PartialEq<T>,
331 {
332 *self.id_ref() == rhs
333 }
334
335 /// Display as "StructName#Id"
336 #[inline]
337 fn identify(&self) -> IdentifyWidget {
338 IdentifyWidget(self.widget_name(), self.id_ref())
339 }
340
341 /// Check whether `id` is self or a descendant
342 ///
343 /// This function assumes that `id` is a valid widget.
344 #[inline]
345 fn is_ancestor_of(&self, id: &Id) -> bool {
346 self.id_ref().is_ancestor_of(id)
347 }
348
349 /// Check whether `id` is not self and is a descendant
350 ///
351 /// This function assumes that `id` is a valid widget.
352 #[inline]
353 fn is_strict_ancestor_of(&self, id: &Id) -> bool {
354 !self.eq_id(id) && self.id_ref().is_ancestor_of(id)
355 }
356
357 /// Run a closure on all children
358 fn for_children(&self, mut f: impl FnMut(&dyn Layout)) {
359 for index in 0..self.num_children() {
360 if let Some(child) = self.get_child(index) {
361 f(child);
362 }
363 }
364 }
365
366 /// Run a fallible closure on all children
367 ///
368 /// Returns early in case of error.
369 fn for_children_try<E>(
370 &self,
371 mut f: impl FnMut(&dyn Layout) -> Result<(), E>,
372 ) -> Result<(), E> {
373 let mut result = Ok(());
374 for index in 0..self.num_children() {
375 if let Some(child) = self.get_child(index) {
376 result = f(child);
377 }
378 if result.is_err() {
379 break;
380 }
381 }
382 result
383 }
384
385 /// Find the descendant with this `id`, if any
386 ///
387 /// Since `id` represents a path, this operation is normally `O(d)` where
388 /// `d` is the depth of the path (depending on widget implementations).
389 fn find_widget(&self, id: &Id) -> Option<&dyn Layout> {
390 if let Some(child) = self.find_child_index(id).and_then(|i| self.get_child(i)) {
391 child.find_widget(id)
392 } else if self.eq_id(id) {
393 Some(self.as_layout())
394 } else {
395 None
396 }
397 }
398}
399impl<W: Layout + ?Sized> LayoutExt for W {}