yakui_core/
widget.rs

1//! Defines traits for building widgets.
2
3use std::any::{type_name, Any, TypeId};
4use std::fmt;
5
6use glam::Vec2;
7
8use crate::dom::Dom;
9use crate::event::EventResponse;
10use crate::event::{EventInterest, WidgetEvent};
11use crate::geometry::{Constraints, FlexFit};
12use crate::input::{InputState, NavDirection};
13use crate::layout::LayoutDom;
14use crate::paint::PaintDom;
15use crate::{Flow, WidgetId};
16
17/// Trait that's automatically implemented for all widget props.
18///
19/// This trait is used by yakui to enforce that props implement `Debug`.
20pub trait Props: fmt::Debug {}
21impl<T> Props for T where T: fmt::Debug {}
22
23/// Information available to a widget during the layout phase.
24#[non_exhaustive]
25#[allow(missing_docs)]
26pub struct LayoutContext<'dom> {
27    pub dom: &'dom Dom,
28    pub input: &'dom InputState,
29    pub layout: &'dom mut LayoutDom,
30}
31
32impl<'dom> LayoutContext<'dom> {
33    /// Calculate the layout for the given widget with the given constraints.
34    ///
35    /// This method currently must only be called once per widget per layout
36    /// phase.
37    pub fn calculate_layout(&mut self, widget: WidgetId, constraints: Constraints) -> Vec2 {
38        self.layout
39            .calculate(self.dom, self.input, widget, constraints)
40    }
41}
42
43/// Information available to a widget during the paint phase.
44#[non_exhaustive]
45#[allow(missing_docs)]
46pub struct PaintContext<'dom> {
47    pub dom: &'dom Dom,
48    pub layout: &'dom LayoutDom,
49    pub paint: &'dom mut PaintDom,
50}
51
52impl<'dom> PaintContext<'dom> {
53    /// Paint the given widget.
54    pub fn paint(&mut self, widget: WidgetId) {
55        self.paint.paint(self.dom, self.layout, widget);
56    }
57}
58
59/// Information available to a widget when it has received an event.
60#[non_exhaustive]
61#[allow(missing_docs)]
62pub struct EventContext<'dom> {
63    pub dom: &'dom Dom,
64    pub layout: &'dom LayoutDom,
65    pub input: &'dom InputState,
66}
67
68/// Information available to a widget when it is being queried for navigation.
69#[non_exhaustive]
70#[allow(missing_docs)]
71pub struct NavigateContext<'dom> {
72    pub dom: &'dom Dom,
73    pub layout: &'dom LayoutDom,
74    pub input: &'dom InputState,
75}
76
77/// A yakui widget. Implement this trait to create a custom widget if composing
78/// existing widgets does not solve your use case.
79pub trait Widget: 'static + fmt::Debug {
80    /// The props that this widget needs to be created or updated. Props define
81    /// all of the values that a widget's user can specify every render.
82    type Props<'a>: Props;
83
84    /// The type that the widget will return to the user when it is created or
85    /// updated. This type should contain information like whether the widget
86    /// was clicked, had keyboard input, or other info that might be useful.
87    type Response;
88
89    /// Create the widget.
90    fn new() -> Self;
91
92    /// Update the widget with new props.
93    fn update(&mut self, props: Self::Props<'_>) -> Self::Response;
94
95    /// Returns whether this widget should grow to fill a flexible layout, and
96    /// if so, what weight should be applied to it if other widgets also want to
97    /// grow.
98    ///
99    /// A value of `0` indicates that this widget should not grow, while `1`
100    /// means that it should stretch to take the available space.
101    fn flex(&self) -> (u32, FlexFit) {
102        (0, FlexFit::Loose)
103    }
104
105    /// Returns the behavior that this widget should have when part of a layout.
106    ///
107    /// By default, widgets participate in layout using [`Flow::Inline`].
108    fn flow(&self) -> Flow {
109        Flow::Inline
110    }
111
112    /// Calculate this widget's layout with the given constraints and return its
113    /// size. The returned size must fit within the given constraints, which can
114    /// be done using `constraints.constrain(size)`.
115    ///
116    /// The default implementation will lay out all of this widget's children on
117    /// top of each other, and fit the widget tightly around them.
118    fn layout(&self, ctx: LayoutContext<'_>, constraints: Constraints) -> Vec2 {
119        self.default_layout(ctx, constraints)
120    }
121
122    /// A convenience method that always performs the default layout strategy
123    /// for a widget. This method is intended to be called from custom widget's
124    /// `layout` methods.
125    #[inline]
126    fn default_layout(&self, mut ctx: LayoutContext<'_>, constraints: Constraints) -> Vec2 {
127        let node = ctx.dom.get_current();
128        let mut size = Vec2::ZERO;
129        for &child in &node.children {
130            let child_size = ctx.calculate_layout(child, constraints);
131            size = size.max(child_size);
132        }
133
134        constraints.constrain_min(size)
135    }
136
137    /// Paint the widget based on its current state.
138    ///
139    /// The default implementation will paint all of the widget's children.
140    fn paint(&self, ctx: PaintContext<'_>) {
141        self.default_paint(ctx);
142    }
143
144    /// A convenience method that always performs the default painting operation
145    /// for a widget. This method is intended to be called from custom widget's
146    /// `paint` methods.
147    #[inline]
148    fn default_paint(&self, mut ctx: PaintContext<'_>) {
149        let node = ctx.dom.get_current();
150        for &child in &node.children {
151            ctx.paint(child);
152        }
153    }
154
155    /// Tells which events the widget is interested in receiving.
156    ///
157    /// The default implementation will register interest in no events.
158    fn event_interest(&self) -> EventInterest {
159        EventInterest::empty()
160    }
161
162    /// Handle the given event and update the widget's state.
163    ///
164    /// The default implementation will bubble all events.
165    #[allow(unused)]
166    fn event(&mut self, ctx: EventContext<'_>, event: &WidgetEvent) -> EventResponse {
167        EventResponse::Bubble
168    }
169
170    /// Tell which widget should be navigated to if the user navigates in a
171    /// given direction.
172    #[allow(unused)]
173    fn navigate(&self, ctx: NavigateContext<'_>, dir: NavDirection) -> Option<WidgetId> {
174        None
175    }
176}
177
178/// A type-erased version of [`Widget`].
179pub trait ErasedWidget: Any + fmt::Debug {
180    /// See [`Widget::layout`].
181    fn layout(&self, ctx: LayoutContext<'_>, constraints: Constraints) -> Vec2;
182
183    /// See [`Widget::flex`].
184    fn flex(&self) -> (u32, FlexFit);
185
186    /// See [`Widget::flow`].
187    fn flow(&self) -> Flow;
188
189    /// See [`Widget::paint`].
190    fn paint(&self, ctx: PaintContext<'_>);
191
192    /// See [`Widget::event_interest`].
193    fn event_interest(&self) -> EventInterest;
194
195    /// See [`Widget::event`].
196    fn event(&mut self, ctx: EventContext<'_>, event: &WidgetEvent) -> EventResponse;
197
198    /// Returns the type name of the widget, usable only for debugging.
199    fn type_name(&self) -> &'static str;
200}
201
202impl<T> ErasedWidget for T
203where
204    T: Widget,
205{
206    fn layout(&self, ctx: LayoutContext<'_>, constraints: Constraints) -> Vec2 {
207        <T as Widget>::layout(self, ctx, constraints)
208    }
209
210    fn flex(&self) -> (u32, FlexFit) {
211        <T as Widget>::flex(self)
212    }
213
214    fn flow(&self) -> Flow {
215        <T as Widget>::flow(self)
216    }
217
218    fn paint(&self, ctx: PaintContext<'_>) {
219        <T as Widget>::paint(self, ctx)
220    }
221
222    fn event_interest(&self) -> EventInterest {
223        <T as Widget>::event_interest(self)
224    }
225
226    fn event(&mut self, ctx: EventContext<'_>, event: &WidgetEvent) -> EventResponse {
227        log::debug!("Event on {}: {event:?}", type_name::<T>());
228
229        <T as Widget>::event(self, ctx, event)
230    }
231
232    fn type_name(&self) -> &'static str {
233        type_name::<T>()
234    }
235}
236
237mopmopafy!(ErasedWidget);