Skip to main content

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}