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, FocusRingPlacement};
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 /// Draw the stock focus ring just inside this node's layout rect.
85 ///
86 /// The default focus ring is outside the rect so it does not reduce
87 /// usable control area. Inside rings are for dense, flush stacks such as
88 /// menu rows, where adding gaps would change the intended visual recipe.
89 pub fn focus_ring_inside(mut self) -> Self {
90 self.focus_ring_placement = FocusRingPlacement::Inside;
91 self
92 }
93
94 /// Draw the stock focus ring outside this node's layout rect.
95 pub fn focus_ring_outside(mut self) -> Self {
96 self.focus_ring_placement = FocusRingPlacement::Outside;
97 self
98 }
99
100 /// Attach a hover tooltip to this element. The runtime synthesizes
101 /// a floating tooltip layer when the pointer rests on the node for
102 /// the configured delay.
103 ///
104 /// **The node must also have a [`key`](Self::key).** Tooltips fire
105 /// through the hit-test pipeline, and `crate::hit_test` only
106 /// returns keyed nodes — an unkeyed leaf with `.tooltip()` is
107 /// silently dead, because hover skips past it to the nearest
108 /// keyed ancestor (which has a different `computed_id` and a
109 /// different tooltip). The bundle lint flags this case as
110 /// [`crate::bundle::lint::FindingKind::DeadTooltip`].
111 ///
112 /// For info-only chrome inside list rows (sha cells, timestamps,
113 /// chips, identicon avatars) the usual key is a synthetic one
114 /// like `"row:{idx}.<part>"` — its only purpose is to make the
115 /// tooltip's hover land. The tooltip text is snapshotted onto the
116 /// hit target at hit-test time, so tooltips fire correctly even
117 /// on `virtual_list_dyn` rows whose children are realized only
118 /// during layout.
119 pub fn tooltip(mut self, text: impl Into<String>) -> Self {
120 self.tooltip = Some(text.into());
121 self
122 }
123
124 /// Declare the pointer cursor when the pointer is over this
125 /// element.
126 pub fn cursor(mut self, cursor: crate::cursor::Cursor) -> Self {
127 self.cursor = Some(cursor);
128 self
129 }
130
131 /// Declare the cursor shown only while a press is captured at this
132 /// exact node.
133 pub fn cursor_pressed(mut self, cursor: crate::cursor::Cursor) -> Self {
134 self.cursor_pressed = Some(cursor);
135 self
136 }
137
138 // ---- Paint-time transforms (animatable via `.animate()`) ----
139 /// Multiply this element's paint alpha by `v` (clamped to `[0, 1]`).
140 pub fn opacity(mut self, v: f32) -> Self {
141 self.opacity = v.clamp(0.0, 1.0);
142 self
143 }
144
145 /// Offset this element's paint and its descendants by `(x, y)` in
146 /// logical pixels.
147 pub fn translate(mut self, x: f32, y: f32) -> Self {
148 self.translate = (x, y);
149 self
150 }
151
152 /// Uniformly scale this element's paint around its rect centre.
153 pub fn scale(mut self, v: f32) -> Self {
154 self.scale = v.max(0.0);
155 self
156 }
157
158 /// Opt this element into app-driven prop interpolation.
159 pub fn animate(mut self, timing: Timing) -> Self {
160 self.animate = Some(timing);
161 self
162 }
163
164 /// Bind a shader for the surface paint, replacing the implicit
165 /// `stock::rounded_rect`.
166 pub fn shader(mut self, binding: ShaderBinding) -> Self {
167 self.shader_override = Some(binding);
168 self
169 }
170
171 // ---- Internal: style profile ----
172 pub fn style_profile(mut self, p: StyleProfile) -> Self {
173 self.style_profile = p;
174 self
175 }
176
177 pub(crate) fn default_radius(mut self, r: impl Into<Corners>) -> Self {
178 self.radius = r.into();
179 self.explicit_radius = false;
180 self
181 }
182}