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 {}