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