aetna_core/tree/visual_modifiers.rs
1//! Visual, cursor, and paint-transform modifiers for [`El`].
2
3use crate::anim::Timing;
4use crate::shader::ShaderBinding;
5use crate::style::StyleProfile;
6
7use super::color::Color;
8use super::geometry::{Corners, Sides};
9use super::node::El;
10use super::semantics::SurfaceRole;
11
12impl El {
13 // ---- Visual ----
14 pub fn fill(mut self, c: Color) -> Self {
15 self.fill = Some(c);
16 self
17 }
18
19 /// Fill applied when the nearest focusable ancestor isn't focused;
20 /// the painter lerps from `dim_fill` toward `fill` as the focus
21 /// envelope rises from 0 to 1. See [`Self::dim_fill`] field doc.
22 pub fn dim_fill(mut self, c: Color) -> Self {
23 self.dim_fill = Some(c);
24 self
25 }
26
27 pub fn stroke(mut self, c: Color) -> Self {
28 self.stroke = Some(c);
29 if self.stroke_width == 0.0 {
30 self.stroke_width = 1.0;
31 }
32 self
33 }
34
35 pub fn stroke_width(mut self, w: f32) -> Self {
36 self.stroke_width = w;
37 self
38 }
39
40 /// Set the element's corner radii. A scalar (e.g.
41 /// `.radius(tokens::RADIUS_MD)`) sets all four corners uniformly
42 /// via [`Corners::from`]; pass [`Corners::top`] / [`Corners::bottom`]
43 /// / [`Corners::left`] / [`Corners::right`], or a directly-built
44 /// [`Corners`], to round only a subset of corners.
45 pub fn radius(mut self, r: impl Into<Corners>) -> Self {
46 self.radius = r.into();
47 self.explicit_radius = true;
48 self
49 }
50
51 pub fn shadow(mut self, s: f32) -> Self {
52 self.shadow = s;
53 self
54 }
55
56 /// Tag this node with a semantic [`SurfaceRole`] so the theme can
57 /// route it through the appropriate paint recipe. Most app code
58 /// should not call this directly: the catalog widgets (`card()`,
59 /// `sidebar()`, `dialog()`, `popover()`, `tabs_list()`, etc.) set
60 /// the right role *and* the matching fill / stroke / radius /
61 /// shadow together, while the `.selected()` and `.current()`
62 /// chainables wrap the corresponding state recipes.
63 ///
64 /// Reach for the raw chainable when authoring a new widget or when
65 /// composing a custom container that the catalog doesn't cover —
66 /// and remember that decorative roles (`Panel`, `Raised`, `Popover`,
67 /// `Danger`) require you to supply a fill yourself; see the
68 /// [`SurfaceRole`] doc for the per-variant contract. The bundle
69 /// lint pass flags `Panel` without a fill as
70 /// [`crate::bundle::lint::FindingKind::MissingSurfaceFill`].
71 pub fn surface_role(mut self, role: SurfaceRole) -> Self {
72 self.surface_role = role;
73 self
74 }
75
76 /// Permit paint to extend beyond this element's layout bounds by
77 /// `outset` on each side. Layout-neutral; siblings don't move and
78 /// hit-testing still uses the layout rect.
79 pub fn paint_overflow(mut self, outset: impl Into<Sides>) -> Self {
80 self.paint_overflow = outset.into();
81 self
82 }
83
84 /// Attach a hover tooltip to this element. The runtime synthesizes
85 /// a floating tooltip layer when the pointer rests on the node for
86 /// the configured delay.
87 ///
88 /// **The node must also have a [`key`](Self::key).** Tooltips fire
89 /// through the hit-test pipeline, and `crate::hit_test` only
90 /// returns keyed nodes — an unkeyed leaf with `.tooltip()` is
91 /// silently dead, because hover skips past it to the nearest
92 /// keyed ancestor (which has a different `computed_id` and a
93 /// different tooltip). The bundle lint flags this case as
94 /// [`crate::bundle::lint::FindingKind::DeadTooltip`].
95 ///
96 /// For info-only chrome inside list rows (sha cells, timestamps,
97 /// chips, identicon avatars) the usual key is a synthetic one
98 /// like `"row:{idx}.<part>"` — its only purpose is to make the
99 /// tooltip's hover land. The tooltip text is snapshotted onto the
100 /// hit target at hit-test time, so tooltips fire correctly even
101 /// on `virtual_list_dyn` rows whose children are realized only
102 /// during layout.
103 pub fn tooltip(mut self, text: impl Into<String>) -> Self {
104 self.tooltip = Some(text.into());
105 self
106 }
107
108 /// Declare the pointer cursor when the pointer is over this
109 /// element.
110 pub fn cursor(mut self, cursor: crate::cursor::Cursor) -> Self {
111 self.cursor = Some(cursor);
112 self
113 }
114
115 /// Declare the cursor shown only while a press is captured at this
116 /// exact node.
117 pub fn cursor_pressed(mut self, cursor: crate::cursor::Cursor) -> Self {
118 self.cursor_pressed = Some(cursor);
119 self
120 }
121
122 // ---- Paint-time transforms (animatable via `.animate()`) ----
123 /// Multiply this element's paint alpha by `v` (clamped to `[0, 1]`).
124 pub fn opacity(mut self, v: f32) -> Self {
125 self.opacity = v.clamp(0.0, 1.0);
126 self
127 }
128
129 /// Offset this element's paint and its descendants by `(x, y)` in
130 /// logical pixels.
131 pub fn translate(mut self, x: f32, y: f32) -> Self {
132 self.translate = (x, y);
133 self
134 }
135
136 /// Uniformly scale this element's paint around its rect centre.
137 pub fn scale(mut self, v: f32) -> Self {
138 self.scale = v.max(0.0);
139 self
140 }
141
142 /// Opt this element into app-driven prop interpolation.
143 pub fn animate(mut self, timing: Timing) -> Self {
144 self.animate = Some(timing);
145 self
146 }
147
148 /// Bind a shader for the surface paint, replacing the implicit
149 /// `stock::rounded_rect`.
150 pub fn shader(mut self, binding: ShaderBinding) -> Self {
151 self.shader_override = Some(binding);
152 self
153 }
154
155 // ---- Internal: style profile ----
156 pub fn style_profile(mut self, p: StyleProfile) -> Self {
157 self.style_profile = p;
158 self
159 }
160
161 pub(crate) fn default_radius(mut self, r: impl Into<Corners>) -> Self {
162 self.radius = r.into();
163 self.explicit_radius = false;
164 self
165 }
166}