Skip to main content

kas_core/core/
widget.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//! Widget and Events traits
7
8#[allow(unused)] use super::{Events, Layout};
9use super::{Node, Tile};
10use crate::Id;
11#[allow(unused)] use crate::event::EventState;
12use crate::event::{ConfigCx, Event, EventCx, IsUsed};
13use crate::geom::{Coord, Offset, Rect, Size};
14use crate::theme::{DrawCx, SizeCx};
15#[allow(unused)] use kas_macros as macros;
16use kas_macros::autoimpl;
17
18/// The Widget trait
19///
20/// The primary widget trait covers event handling over super trait [`Tile`]
21/// which governs layout, drawing, child enumeration and identification.
22/// Most methods of `Widget` are hidden and only for use within the Kas library.
23///
24/// `Widget` is dyn-safe given a type parameter, e.g. `dyn Widget<Data = ()>`.
25/// [`Tile`] is dyn-safe without a type parameter. [`Node`] is a dyn-safe
26/// abstraction over a `&dyn Widget<Data = T>` plus a `&T` data parameter.
27///
28/// # Widget lifecycle
29///
30/// Widget methods have a specified call order:
31///
32/// 1.  The widget is configured (see [`Events#configuration`])
33/// 2.  The widget is updated ([`Events#update`])
34/// 3.  The widget is sized (see [`Layout#sizing`])
35/// 4.  The widget is ready for other methods to be called
36///
37/// Configuration, update and sizing may be repeated at any time (see above
38/// linked documentation).
39///
40/// Widgets are responsible for ensuring that their children may observe this
41/// lifecycle. Usually this simply involves inclusion of the child in layout
42/// operations. Steps of the lifecycle may be postponed until a widget becomes
43/// visible.
44///
45/// # Implementing Widget
46///
47/// To implement a widget, use the [`#widget`] macro within an
48/// [`impl_self`](macros::impl_self), [`impl_scope!`](macros::impl_scope) or
49/// [`impl_anon!`](macros::impl_anon) macro.
50/// **This is the only supported method of implementing `Widget`.**
51///
52/// Explicit (partial) implementations of [`Widget`], [`Layout`], [`Tile`] and [`Events`]
53/// are optional. The [`#widget`] macro completes implementations.
54///
55/// Synopsis:
56/// ```ignore
57/// #[impl_self]
58/// mod MyWidget {
59///     #[widget {
60///         // macro properties (all optional)
61///         Data = T;
62///     }]
63///     #[layout(self.foo)]
64///     struct MyWidget {
65///         core: widget_core!(),
66///         #[widget] foo: impl Widget<Data = T> = make_foo(),
67///         // ...
68///     }
69///
70///     // Optional implementations:
71///     impl Layout for Self { /* ... */ }
72///     impl Events for Self { /* ... */ }
73///     impl Self { /* ... */ }
74/// }
75/// ```
76///
77/// Details may be categorised as follows:
78///
79/// -   **Data**: the type [`Widget::Data`] must be specified exactly once, but
80///     this type may be given in any of three locations: as a property of the
81///     [`#widget`] macro or as [`Widget::Data`].
82/// -   **Core** methods of [`Tile`] are *always* implemented via the [`#widget`]
83///     macro, whether or not an `impl Tile { ... }` item is present.
84/// -   **Introspection** methods [`Tile::child_indices`], [`Tile::get_child`]
85///     and [`Widget::child_node`] are implemented by the [`#widget`] macro
86///     in most cases: child widgets embedded within a layout descriptor or
87///     included as fields marked with `#[widget]` are enumerated.
88/// -   **Introspection** methods [`Tile::find_child_index`] and
89///     [`Events::make_child_id`] have default implementations which *usually*
90///     suffice.
91/// -   **Layout** is specified either via [`layout`](macro@crate::layout) macro
92///     or via implementation of at least [`Layout`].
93///-    **Event handling** is optional, implemented through [`Events`].
94///
95/// For examples, check the source code of widgets in the widgets library
96/// or [examples apps](https://github.com/kas-gui/kas/tree/master/examples).
97/// (Check that the code uses the same Kas version since the widget traits are
98/// not yet stable.)
99///
100/// [`#widget`]: macros::widget
101#[autoimpl(for<T: trait + ?Sized> &'_ mut T, Box<T>)]
102pub trait Widget: Tile {
103    /// Input data type
104    ///
105    /// Widget expects data of this type to be provided by reference when
106    /// calling any event-handling operation on this widget.
107    ///
108    /// Type `Data` should be specified either here (`impl Widget { ... }`) or
109    /// in `impl Events { ... }`. Alternatively, if the widget has no children
110    /// and no explicit `impl Events` or `impl Widget`, then `Data = ()` is
111    /// assumed; or, if the prior conditions are met and `#[collection]` is used
112    /// on some field, then `Data = <#field_ty as ::kas::Collection>::Data` is
113    /// assumed.
114    ///
115    /// [`#widget`]: macros::widget
116    //
117    // SAFETY: the unsafe_node feature requires Data: Sized.
118    type Data: Sized;
119
120    /// Erase type
121    ///
122    /// This method is implemented by the `#[widget]` macro.
123    fn as_node<'a>(&'a mut self, data: &'a Self::Data) -> Node<'a> {
124        let _ = data;
125        unimplemented!() // make rustdoc show that this is a provided method
126    }
127
128    /// Access a child as a [`Node`], if available
129    ///
130    /// This method is the `mut` version of [`Tile::get_child`] but which also
131    /// pairs the returned widget with its input `data`. It is expected to
132    /// succeed where [`Tile::get_child`] succeeds.
133    ///
134    /// Valid `index` values may be discovered by calling
135    /// [`Tile::child_indices`], [`Tile::find_child_index`] or
136    /// [`Tile::nav_next`]. The `index`-to-child mapping is not
137    /// required to remain fixed; use an [`Id`] to track a widget over time.
138    ///
139    /// This method must be implemented explicitly when [`Tile::get_child`] is.
140    /// It might also need to be implemented explicitly to map `data`, though
141    /// usually the `#[widget]` attribute on children specifies this mapping.
142    fn child_node<'n>(&'n mut self, data: &'n Self::Data, index: usize) -> Option<Node<'n>> {
143        let _ = (data, index);
144        unimplemented!() // make rustdoc show that this is a provided method
145    }
146
147    /// Internal method: configure recursively
148    ///
149    /// Requires status: none.
150    ///
151    /// Do not implement this method directly!
152    #[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
153    #[cfg_attr(docsrs, doc(cfg(internal_doc)))]
154    fn _configure(&mut self, cx: &mut ConfigCx, data: &Self::Data, id: Id);
155
156    /// Internal method: update recursively
157    ///
158    /// Requires status: configured.
159    ///
160    /// Do not implement this method directly!
161    #[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
162    #[cfg_attr(docsrs, doc(cfg(internal_doc)))]
163    fn _update(&mut self, cx: &mut ConfigCx, data: &Self::Data);
164
165    /// Internal method: send recursively
166    ///
167    /// Requires status: configured and sized.
168    ///
169    /// Do not implement this method directly!
170    #[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
171    #[cfg_attr(docsrs, doc(cfg(internal_doc)))]
172    fn _send(&mut self, cx: &mut EventCx, data: &Self::Data, id: Id, event: Event) -> IsUsed;
173
174    /// Internal method: replay recursively
175    ///
176    /// Traverses the widget tree to `id`, then unwinds with standard handling
177    /// of event state.
178    ///
179    /// If a message is being sent, it must already be on the stack. If the
180    /// target is not found, unsent messages are dropped.
181    ///
182    /// Requires status: configured.
183    ///
184    /// Do not implement this method directly!
185    #[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
186    #[cfg_attr(docsrs, doc(cfg(internal_doc)))]
187    fn _replay(&mut self, cx: &mut EventCx, data: &Self::Data, id: Id);
188}
189
190/// Layout routines for scrollable content
191///
192/// A `Viewport` supports content larger than its assigned `rect` (the `rect`
193/// passed to [`Layout::set_rect`]). This `rect` is considered the viewport
194/// through which content may be viewed (approximately: see
195/// [`Self::draw_with_offset`]).
196///
197/// If the parent widget supports scrolling over contents implementing
198/// `Viewport`, it should call [`Viewport::draw_with_offset`] instead of
199/// [`Layout::draw`].
200///
201/// It is intended that the widget implementing this trait is the child of some
202/// parent widget which supports scrolling through event handling and provision
203/// of a scroll offset, and that this parent uses the methods of this trait
204/// where applicable (see below). In case the parent does not support scrolling,
205/// the widget should remain usable (but with only a subset of content being
206/// accessible).
207pub trait Viewport: Widget {
208    /// Get content size
209    ///
210    /// When the content size is larger than the viewport, content becomes
211    /// scrollable with a maximum offset of `content_size - viewport_size`.
212    ///
213    /// # Calling
214    ///
215    /// This method is called during sizing.
216    fn content_size(&self) -> Size;
217
218    /// Set the scroll offset
219    ///
220    /// The `viewport` and `offset` parameters are the same as those of
221    /// [`Viewport::draw_with_offset`].
222    ///
223    /// # Calling
224    ///
225    /// This method should be called immediately after [`Layout::set_rect`]
226    /// (unless it's known that the implementation doesn't use this method).
227    ///
228    /// # Implementation
229    ///
230    /// This method only needs to do anything in cases where only a subset of
231    /// content is prepared.
232    fn set_offset(&mut self, cx: &mut SizeCx, viewport: Rect, offset: Offset) {
233        let _ = (cx, viewport, offset);
234    }
235
236    /// Update the scroll offset
237    ///
238    /// The `viewport` and `offset` parameters are the same as those of
239    /// [`Viewport::draw_with_offset`].
240    ///
241    /// # Calling
242    ///
243    /// This method should be called whenever the scroll offset changes to allow
244    /// preparation of content (unless it's known that the implementation
245    /// doesn't use this method). It must be called before drawing and event
246    /// handling operations using this new `offset`.
247    ///
248    /// # Implementation
249    ///
250    /// This method only needs to do anything in cases where only a subset of
251    /// content is prepared.
252    fn update_offset(
253        &mut self,
254        cx: &mut ConfigCx,
255        data: &Self::Data,
256        viewport: Rect,
257        offset: Offset,
258    ) {
259        let _ = (cx, data, viewport, offset);
260    }
261
262    /// Draw with a scroll offset
263    ///
264    /// Drawing should be clamped to the given `viewport`. This `viewport` may
265    /// be the same as [`Layout::rect`] but is allowed to be slightly different;
266    /// for example `EditBox` passes a larger [`Rect`] to allow drawing in the
267    /// margin allocated between its frame and content.
268    ///
269    /// The `offset` should be the same as that used by the parent widget
270    /// in [`Tile::translation`].
271    ///
272    /// Effectively, content is drawn at position `self.rect().pos - offset`
273    /// but clamped to `viewport`.
274    ///
275    /// # Calling
276    ///
277    /// This method should be called instead of [`Layout::draw`] by compatible
278    /// parent widgets.
279    ///
280    /// # Implementation
281    ///
282    /// ## Method modification
283    ///
284    /// The `#[widget]` macro injects a call to [`DrawCx::set_id`] into this
285    /// method where possible, allowing correct detection of disabled and
286    /// highlight states.
287    ///
288    /// This method modification should never cause issues (besides the implied
289    /// limitation that widgets cannot easily detect a parent's state while
290    /// being drawn).
291    fn draw_with_offset(&self, draw: DrawCx, viewport: Rect, offset: Offset);
292
293    /// Probe a coordinate for a widget's [`Id`]
294    ///
295    /// # Calling
296    ///
297    /// This method should be called instead of [`Tile::try_probe`].
298    ///
299    /// # Implementation
300    ///
301    /// The default implementation will normally suffice. It calls
302    /// [`Events::probe`] with `coord + offset` when
303    /// `self.rect().contains(coord)`.
304    fn try_probe_with_offset(&self, coord: Coord, offset: Offset) -> Option<Id> {
305        let _ = (coord, offset);
306        unimplemented!() // make rustdoc show that this is a provided method
307    }
308}