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;