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