Skip to main content

smudge_core/
widget.rs

1//! The trait every widget implements, and the widgets this crate provides.
2//!
3//! The tree stores widgets as `Box<dyn Widget>`, so the trait is object safe.
4//! A widget never holds a reference to the tree: bindings push updates through
5//! a queue, and the frame applies them.
6
7use std::any::Any;
8
9use crate::element::Element;
10use crate::geometry::{Corners, Rect, Size};
11use crate::paint::PaintOp;
12use crate::semantics::Semantics;
13use crate::style::{Edges, Style};
14use crate::theme::Theme;
15
16/// The space a measure call offers on one axis, or the intrinsic size it asks
17/// for instead.
18#[derive(Clone, Copy, Debug, PartialEq)]
19pub enum AvailableSpace {
20    /// Space offered in logical pixels.
21    Definite(f32),
22    /// Asks for the smallest size the content fits in, such as the widest
23    /// word of a text.
24    MinContent,
25    /// Asks for the size the content takes without wrapping.
26    MaxContent,
27}
28
29/// What one measure call offers a widget.
30#[derive(Clone, Copy, Debug)]
31pub struct MeasureInput {
32    /// The width of the content box the layout engine treats as fixed for
33    /// this call, inside the node's padding and border.
34    ///
35    /// A fixed size comes from the node's style or from a container that
36    /// stretches the node across this axis. It can be absent even when the
37    /// node's style fixes a width: the engine's final layout pass passes no
38    /// fixed size at all.
39    pub known_width: Option<f32>,
40    /// The height of the content box the layout engine treats as fixed for
41    /// this call, inside the node's padding and border.
42    ///
43    /// A fixed size comes from the node's style or from a container that
44    /// stretches the node across this axis. It can be absent even when the
45    /// node's style fixes a height: the engine's final layout pass passes no
46    /// fixed size at all.
47    pub known_height: Option<f32>,
48    /// The space offered on the x axis, inside the node's padding and border.
49    pub available_width: AvailableSpace,
50    /// The space offered on the y axis, inside the node's padding and border.
51    pub available_height: AvailableSpace,
52    /// The number of physical pixels per logical pixel the window runs at,
53    /// so a widget that sizes itself from pixel data, such as an image,
54    /// can divide it out.
55    pub scale: f32,
56}
57
58/// A piece of user interface owned by one tree node.
59///
60/// A widget holds its own state and is the only place that state lives. The
61/// tree owns the widget and the [`Style`] that positions and paints it.
62///
63/// Implementing this trait requires the type to be `'static`, because the tree
64/// downcasts to the concrete type when a binding updates a widget property.
65/// The `TypeName` bound is met by every type without any code, and supplies
66/// the name of the widget's type that diagnostics print.
67pub trait Widget: Any + type_name::TypeName {
68    /// Returns the size of this widget's content, or `None` to let the layout
69    /// engine size the node from its style.
70    ///
71    /// `input.known_width` and `input.known_height` hold the sizes the engine
72    /// treats as fixed for this call, and either can be absent even when the
73    /// node's style fixes a size. `input.available_width` and
74    /// `input.available_height` are the space left on each axis inside the
75    /// node's padding and border. An axis can also ask for an intrinsic size
76    /// instead of offering space: [`AvailableSpace::MinContent`] asks for the
77    /// smallest size the content fits in, such as the widest word of a text,
78    /// and [`AvailableSpace::MaxContent`] asks for the size the content takes
79    /// without wrapping. The returned size is the content size, so the engine
80    /// adds padding and border around it.
81    ///
82    /// The engine may call this several times in one layout pass.
83    ///
84    /// The default returns `None`, which suits a widget that only paints. A
85    /// widget that returns a size must return the same size for the same
86    /// inputs, because the layout engine caches it until the node is marked
87    /// for layout again.
88    fn measure(&mut self, _input: MeasureInput) -> Option<Size> {
89        None
90    }
91
92    /// Appends this widget's paint operations, in paint coordinates.
93    ///
94    /// Paint coordinates are logical window coordinates with the scroll
95    /// offsets of the enclosing scroll views left out, so outside a scrolled
96    /// subtree they are the window's own. A scroll then moves the operations
97    /// without asking the widget to paint again.
98    ///
99    /// `cx` carries the node's rectangle after layout, in paint coordinates,
100    /// and its resolved style. Paint this node only: its children paint
101    /// themselves.
102    fn paint(&self, cx: &PaintCx<'_>, out: &mut Vec<PaintOp>);
103
104    /// Returns the rectangle the input method should anchor to while this
105    /// widget holds the keyboard focus, in the paint coordinates of
106    /// [`Widget::paint`].
107    ///
108    /// The app reports the rectangle in window coordinates: it moves the
109    /// rectangle by minus the scroll offsets that move the widget at the time
110    /// it asks.
111    ///
112    /// The app asks only the focused widget and reports [`None`] when nothing
113    /// is focused or the widget has no area to report. The default reports
114    /// [`None`], which suits a widget that takes no text.
115    fn ime_area(&self) -> Option<Rect> {
116        None
117    }
118
119    /// Returns the accessibility description that places this widget in the
120    /// projection, or [`None`] to leave it out.
121    ///
122    /// The default reports [`None`], which suits a widget that paints for
123    /// sighted users alone. A widget that returns a description is projected
124    /// with the role it reports, and an element can still override the role
125    /// with [`Element::role`](crate::element::Element::role).
126    fn semantics(&self) -> Option<Semantics> {
127        None
128    }
129}
130
131/// The name of a widget's concrete type, which diagnostics print.
132///
133/// The trait is implemented for every type and cannot be named outside this
134/// crate, so a widget can neither implement nor override it.
135pub(crate) mod type_name {
136    /// Reports the name of the implementing type.
137    pub trait TypeName {
138        /// Returns the full name of the implementing type, such as
139        /// `smudge_core::widget::Panel`.
140        fn type_name(&self) -> &'static str;
141    }
142
143    impl<T> TypeName for T {
144        fn type_name(&self) -> &'static str {
145            std::any::type_name::<T>()
146        }
147    }
148}
149
150/// What a widget paints from.
151pub struct PaintCx<'a> {
152    /// The node's rectangle in logical pixels, in the paint coordinates of
153    /// [`Widget::paint`], as the last layout pass left it. A scroll does not
154    /// change it.
155    pub rect: Rect,
156    /// The node's content box in logical pixels, in paint coordinates: its
157    /// rectangle inside the border and the padding, as the last layout pass
158    /// left it.
159    pub content: Rect,
160    /// The node's resolved style.
161    pub style: &'a Style,
162    /// The theme that style values which name a token resolve against.
163    pub theme: &'a Theme,
164}
165
166/// A container that paints a background and a border and does nothing else.
167///
168/// A border whose four sides differ in width is painted at the widest side,
169/// because a single stroke carries a single width.
170pub struct Panel;
171
172/// Creates a [`Panel`] with the default style.
173pub fn panel() -> Element<Panel> {
174    Element::new(Panel)
175}
176
177impl Widget for Panel {
178    fn paint(&self, cx: &PaintCx<'_>, out: &mut Vec<PaintOp>) {
179        let paint = &cx.style.paint;
180        // The radius is resolved only for an operation that uses it, so a node
181        // that paints nothing never consults the theme and a theme switch
182        // leaves it alone.
183        if let Some(background) = &paint.background {
184            out.push(PaintOp::FillRect {
185                rect: cx.rect,
186                radius: Corners::all(paint.corner_radius.resolve(cx.theme)),
187                color: background.resolve(cx.theme),
188            });
189        }
190        if let Some(border) = &paint.border_color {
191            let width = widest_side(cx.style.layout.border_width);
192            if width > 0.0 {
193                out.push(PaintOp::StrokeRect {
194                    rect: cx.rect,
195                    radius: Corners::all(paint.corner_radius.resolve(cx.theme)),
196                    width,
197                    color: border.resolve(cx.theme),
198                });
199            }
200        }
201    }
202}
203
204/// Returns the widest of the four border sides, because a stroke has one width.
205fn widest_side(border: Edges<f32>) -> f32 {
206    border
207        .top
208        .max(border.right)
209        .max(border.bottom)
210        .max(border.left)
211}
212
213#[cfg(test)]
214#[path = "widget_tests.rs"]
215mod tests;