Skip to main content

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