kas_core/core/
tile.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, Tile and TileExt traits
7
8use crate::geom::{Coord, Offset, Rect};
9use crate::util::IdentifyWidget;
10use crate::{ChildIndices, HasId, Id, Layout, Role, RoleCx};
11use kas_macros::autoimpl;
12
13#[allow(unused)] use super::{Events, RoleCxExt, Widget};
14#[allow(unused)] use crate::layout::{self, AlignPair};
15#[allow(unused)] use crate::theme::DrawCx;
16#[allow(unused)] use kas_macros as macros;
17
18/// A sizable, drawable, identifiable, introspectible 2D tree object
19///
20/// `Tile` is a super-trait of [`Widget`] which:
21///
22/// -   Is a sub-trait of [`Layout`]: is sizable and drawable
23/// -   Has no [`Data`](Widget::Data) parameter or event-handling methods
24/// -   Has an [identifier](Self::identify) and [`Self::role`]
25/// -   Supports read-only tree reflection: [`Self::child_indices`], [`Self::get_child`]
26///
27/// `Tile` may not be implemented directly; it will be implemented by the
28/// [`#widget`] macro.
29///
30/// # Tree reflection
31///
32/// `Tile` offers a reflection API over the widget tree via
33/// [`Tile::get_child`]. This is limited to read-only functions, and thus
34/// cannot directly violate the widget lifecycle, however note that the
35/// [`id_ref`](Self::id_ref) could be invalid or could be valid but refer to a
36/// node which has not yet been sized and positioned (and thus which it is not
37/// valid to send events to).
38///
39/// [`#widget`]: macros::widget
40#[autoimpl(for<T: trait + ?Sized> &'_ mut T, Box<T>)]
41pub trait Tile: Layout {
42    /// Get as a `dyn Tile`
43    ///
44    /// This method is implemented by the `#[widget]` macro.
45    fn as_tile(&self) -> &dyn Tile {
46        unimplemented!() // make rustdoc show that this is a provided method
47    }
48
49    /// Get a reference to the widget's identifier
50    ///
51    /// The widget identifier is assigned when the widget is configured (see
52    /// [`Events::configure`] and [`Events::configure_recurse`]). In case the
53    /// [`Id`] is accessed before this, it will be [invalid](Id#invalid-state).
54    /// The identifier *may* change when widgets which are descendants of some
55    /// dynamic layout are reconfigured.
56    ///
57    /// This method is implemented by the `#[widget]` macro.
58    fn id_ref(&self) -> &Id {
59        unimplemented!() // make rustdoc show that this is a provided method
60    }
61
62    /// Get the widget's identifier
63    ///
64    /// This method returns a [`Clone`] of [`Self::id_ref`]. Since cloning an
65    /// `Id` is [very cheap](Id#representation), this can mostly be ignored.
66    ///
67    /// The widget identifier is assigned when the widget is configured (see
68    /// [`Events::configure`] and [`Events::configure_recurse`]). In case the
69    /// [`Id`] is accessed before this, it will be [invalid](Id#invalid-state).
70    /// The identifier *may* change when widgets which are descendants of some
71    /// dynamic layout are reconfigured.
72    #[inline]
73    fn id(&self) -> Id {
74        self.id_ref().clone()
75    }
76
77    /// Return a [`Display`]-able widget identifier
78    ///
79    /// This method is implemented by the `#[widget]` macro.
80    ///
81    /// [`Display`]: std::fmt::Display
82    fn identify(&self) -> IdentifyWidget<'_> {
83        unimplemented!() // make rustdoc show that this is a provided method
84    }
85
86    /// Whether this widget supports navigation focus
87    ///
88    /// By default this is false.
89    fn navigable(&self) -> bool {
90        false
91    }
92
93    /// Tooltip
94    ///
95    /// This is shown on mouse hover and may or may not use the same text as the
96    /// label defined by the role.
97    ///
98    /// By default this is `None`.
99    fn tooltip(&self) -> Option<&str> {
100        None
101    }
102
103    /// Describe the widget's role
104    ///
105    /// This descriptor supports accessibility tooling and UI introspection.
106    ///
107    /// ## Default implementation
108    ///
109    /// If the widget has children with macro-defined layout, the `#[widget]`
110    /// macro will implement this method to return [`Role::None`]; otherwise,
111    /// the default implementation simply returns [`Role::Unknown`].
112    fn role(&self, cx: &mut dyn RoleCx) -> Role<'_> {
113        let _ = cx;
114        Role::Unknown
115    }
116
117    /// Specify additional role properties for child `index`
118    ///
119    /// Role properties (for example [`RoleCxExt::set_label`]) may be specified
120    /// by a widget for itself in [`Self::role`] or by a parent in this method.
121    ///
122    /// The default implementation does nothing.
123    #[inline]
124    fn role_child_properties(&self, cx: &mut dyn RoleCx, index: usize) {
125        let _ = (cx, index);
126    }
127
128    /// Get child indices available to recursion
129    ///
130    /// This method returns a range of child indices for "visible" children.
131    /// These children are expected to be accessible through [`Self::get_child`]
132    /// and to be configured and sized (assuming that `self` is). They may
133    /// or may not be visible on the screen.
134    ///
135    /// Hidden children might be excluded from this list, for example a
136    /// collapsed element, closed menu or inactive stack page. (Such children
137    /// might also not be configured or sized.)
138    ///
139    /// This method is generated by default unless [`Self::get_child`] has an
140    /// explicit implementation. A non-default implementation may be needed when
141    /// a child should be hidden. In this case it might be necessary to write
142    /// an explicit implementation of [`Events::configure_recurse`].
143    fn child_indices(&self) -> ChildIndices {
144        unimplemented!() // make rustdoc show that this is a provided method
145    }
146
147    /// Access a child as a `dyn Tile`, if available
148    ///
149    /// Valid `index` values may be discovered by calling
150    /// [`Tile::child_indices`], [`Tile::find_child_index`] or
151    /// [`Tile::nav_next`]. The `index`-to-child mapping is not
152    /// required to remain fixed; use an [`Id`] to track a widget over time.
153    ///
154    /// This method is usually implemented automatically by the `#[widget]`
155    /// macro, but must be implemented explicitly in some cases (e.g. if
156    /// children are not marked with `#[widget]` or `#[collection]`).
157    fn get_child(&self, index: usize) -> Option<&dyn Tile> {
158        let _ = index;
159        unimplemented!() // make rustdoc show that this is a provided method
160    }
161
162    /// Find the child which is an ancestor of this `id`, if any
163    ///
164    /// As with [`Self::child_indices`], this method should only return
165    /// `Some(index)` when [`Self::get_child`] succeeds on the given `index`,
166    /// returning access to a child which is configured and sized (assuming that
167    /// `self` is). `find_child_index` should return `None` if the above
168    /// conditions would not be met, even if `id` identifies a known child.
169    ///
170    /// The default implementation simply uses [`Id::next_key_after`].
171    /// Widgets may choose to assign children custom keys by overriding this
172    /// method and [`Events::make_child_id`].
173    #[inline]
174    fn find_child_index(&self, id: &Id) -> Option<usize> {
175        id.next_key_after(self.id_ref())
176    }
177
178    /// Navigation in spatial order
179    ///
180    /// Controls <kbd>Tab</kbd> navigation order of children.
181    /// This method should:
182    ///
183    /// -   Return `None` if there is no (next) navigable child
184    /// -   In the case there are navigable children and `from == None`, return
185    ///     the index of the first (or last if `reverse`) navigable child
186    /// -   In the case there are navigable children and `from == Some(index)`,
187    ///     it may be expected that `from` is the output of a previous call to
188    ///     this method; the method should return the next (or previous if
189    ///     `reverse`) navigable child (if any)
190    ///
191    /// The return value is expected to be `None` or `Some(index)` where
192    /// [`Self::get_child`] returns access to a child at the given `index` and
193    /// that child is configured and sized (assuming that `self` is).
194    ///
195    /// Default (macro generated) implementation:
196    ///
197    /// -   Generated from `#[widget]`'s layout property, if used (not always possible!)
198    /// -   Otherwise, iterate through children in order of definition
199    fn nav_next(&self, reverse: bool, from: Option<usize>) -> Option<usize> {
200        let _ = (reverse, from);
201        unimplemented!() // make rustdoc show that this is a provided method
202    }
203
204    /// Get translation of child `index` relative to this widget
205    ///
206    /// Usually this is zero; only widgets with scrollable or offset content
207    /// *and* child widgets need to implement this.
208    /// Such widgets must also implement [`Tile::probe`] and (if they scroll)
209    /// [`Events::handle_scroll`].
210    ///
211    /// Affects event handling via [`Tile::probe`] and affects the positioning
212    /// of pop-up menus. [`Layout::draw`] must be implemented directly using
213    /// [`DrawCx::with_clip_region`] to offset contents.
214    ///
215    /// Default implementation: return [`Offset::ZERO`]
216    #[inline]
217    fn translation(&self, index: usize) -> Offset {
218        let _ = index;
219        Offset::ZERO
220    }
221
222    /// Probe a coordinate for a widget's [`Id`]
223    ///
224    /// Returns the [`Id`] of the widget expected to handle clicks and touch
225    /// events at the given `coord`. Typically this is the lowest descendant in
226    /// the widget tree at the given `coord`, but it is not required to be; e.g.
227    /// a `Button` may use an inner widget as a label but return its own [`Id`]
228    /// to indicate that the button (not the inner label) handles clicks.
229    ///
230    /// # Calling
231    ///
232    /// **Prefer to call [`Layout::try_probe`] instead**.
233    ///
234    /// ## Call order
235    ///
236    /// It is expected that [`Layout::set_rect`] is called before this method,
237    /// but failure to do so should not cause a fatal error.
238    ///
239    /// # Implementation
240    ///
241    /// The callee may usually assume that it occupies `coord` and may thus
242    /// return its own [`Id`] when no child occupies the input `coord`.
243    ///
244    /// If the [`Self::translation`] is non-zero for any child, then the
245    /// coordinate passed to that child must be translated:
246    /// `coord + translation`.
247    ///
248    /// ## Default implementation
249    ///
250    /// The `#[widget]` macro may implement this method as:
251    /// ```ignore
252    /// MacroDefinedLayout::try_probe(self, coord).unwrap_or_else(|| self.id())
253    /// ```
254    fn probe(&self, coord: Coord) -> Id
255    where
256        Self: Sized,
257    {
258        let _ = coord;
259        unimplemented!() // make rustdoc show that this is a provided method
260    }
261}
262
263impl<W: Tile + ?Sized> HasId for &W {
264    #[inline]
265    fn has_id(self) -> Id {
266        self.id_ref().clone()
267    }
268}
269
270impl<W: Tile + ?Sized> HasId for &mut W {
271    #[inline]
272    fn has_id(self) -> Id {
273        self.id_ref().clone()
274    }
275}
276
277/// Extension trait over widgets
278pub trait TileExt: Tile {
279    /// Test widget identifier for equality
280    ///
281    /// This method may be used to test against `Id`, `Option<Id>`
282    /// and `Option<&Id>`.
283    #[inline]
284    fn eq_id<T>(&self, rhs: T) -> bool
285    where
286        Id: PartialEq<T>,
287    {
288        *self.id_ref() == rhs
289    }
290
291    /// Check whether `id` is self or a descendant
292    ///
293    /// This function assumes that `id` is a valid widget.
294    #[inline]
295    fn is_ancestor_of(&self, id: &Id) -> bool {
296        self.id_ref().is_ancestor_of(id)
297    }
298
299    /// Check whether `id` is not self and is a descendant
300    ///
301    /// This function assumes that `id` is a valid widget.
302    #[inline]
303    fn is_strict_ancestor_of(&self, id: &Id) -> bool {
304        !self.eq_id(id) && self.id_ref().is_ancestor_of(id)
305    }
306
307    /// Return an iterator over visible children
308    ///
309    /// This method may exclude hidden children: see [`Tile::child_indices`].
310    fn children(&self) -> impl Iterator<Item = &dyn Tile> {
311        self.child_indices()
312            .into_iter()
313            .flat_map(|i| self.get_child(i))
314    }
315
316    /// Find the descendant with this `id`, if any
317    ///
318    /// Since `id` represents a path, this operation is normally `O(d)` where
319    /// `d` is the depth of the path (depending on widget implementations).
320    fn find_tile(&self, id: &Id) -> Option<&dyn Tile> {
321        if let Some(child) = self.find_child_index(id).and_then(|i| self.get_child(i)) {
322            child.find_tile(id)
323        } else if self.eq_id(id) {
324            Some(self.as_tile())
325        } else {
326            None
327        }
328    }
329
330    /// Find the [`Rect`] of the descendant with this `id`, if any
331    ///
332    /// The [`Rect`] is returned in the widgets own coordinate space where this
333    /// space is translated by the [`Offset`] returned. The result is thus
334    /// `rect + translation` in the caller's coordinate space.
335    fn find_tile_rect(&self, id: &Id) -> Option<(Rect, Offset)> {
336        let mut widget = self.as_tile();
337        let mut translation = Offset::ZERO;
338        loop {
339            if widget.eq_id(id) {
340                let rect = widget.rect();
341                return Some((rect, translation));
342            }
343
344            let index = widget.find_child_index(id)?;
345            translation += widget.translation(index);
346            widget = widget.get_child(index)?;
347        }
348    }
349}
350impl<W: Tile + ?Sized> TileExt for W {}