Skip to main content

damascene_core/tree/
content.rs

1//! Content-related [`El`] modifiers: text runs, icon source, and raster image source.
2
3use crate::image::{DynamicRangeLimit, Image, ImageFit};
4
5use super::layout_types::Size;
6use super::node::El;
7use super::semantics::Kind;
8use super::text_types::{FontFamily, FontWeight, TextAlign, TextOverflow, TextRole, TextWrap};
9use crate::color::Color;
10
11impl El {
12    // ---- Text-bearing ----
13    pub fn text(mut self, t: impl Into<String>) -> Self {
14        self.text = Some(t.into());
15        self
16    }
17
18    pub fn text_color(mut self, c: Color) -> Self {
19        self.text_color = Some(c);
20        self
21    }
22
23    pub fn text_align(mut self, align: TextAlign) -> Self {
24        self.text_align = align;
25        self
26    }
27
28    pub fn center_text(self) -> Self {
29        self.text_align(TextAlign::Center)
30    }
31
32    pub fn end_text(self) -> Self {
33        self.text_align(TextAlign::End)
34    }
35
36    pub fn text_wrap(mut self, wrap: TextWrap) -> Self {
37        self.text_wrap = wrap;
38        self
39    }
40
41    pub fn wrap_text(self) -> Self {
42        self.text_wrap(TextWrap::Wrap)
43    }
44
45    pub fn nowrap_text(self) -> Self {
46        self.text_wrap(TextWrap::NoWrap)
47    }
48
49    pub fn text_overflow(mut self, overflow: TextOverflow) -> Self {
50        self.text_overflow = overflow;
51        self
52    }
53
54    pub fn ellipsis(self) -> Self {
55        self.text_overflow(TextOverflow::Ellipsis)
56    }
57
58    pub fn max_lines(mut self, lines: usize) -> Self {
59        self.text_max_lines = Some(lines.max(1));
60        self
61    }
62
63    pub fn font_size(mut self, s: f32) -> Self {
64        self.font_size = s;
65        self.line_height = crate::tokens::line_height_for_size(s);
66        self
67    }
68
69    pub fn line_height(mut self, h: f32) -> Self {
70        self.line_height = h.max(1.0);
71        self
72    }
73
74    pub fn font_weight(mut self, w: FontWeight) -> Self {
75        self.font_weight = w;
76        self
77    }
78
79    pub fn font_family(mut self, family: FontFamily) -> Self {
80        self.font_family = family;
81        self.explicit_font_family = true;
82        self
83    }
84
85    pub fn inter(self) -> Self {
86        self.font_family(FontFamily::Inter)
87    }
88
89    pub fn roboto(self) -> Self {
90        self.font_family(FontFamily::Roboto)
91    }
92
93    /// Override the monospace face used when this node renders as code
94    /// (`font_mono = true`, `TextRole::Code`, or any descendant that
95    /// inherits the value through theme propagation). Setting this
96    /// pins the node — theme `with_mono_font_family(...)` no longer
97    /// stamps over it.
98    pub fn mono_font_family(mut self, family: FontFamily) -> Self {
99        self.mono_font_family = family;
100        self.explicit_mono_font_family = true;
101        self
102    }
103
104    /// Pin this node's monospace face to JetBrains Mono. Convenience
105    /// shorthand for `.mono_font_family(FontFamily::JetBrainsMono)`.
106    pub fn jetbrains_mono(self) -> Self {
107        self.mono_font_family(FontFamily::JetBrainsMono)
108    }
109
110    /// Set the icon for this element to either a built-in [`crate::IconName`],
111    /// an app-supplied [`crate::SvgIcon`], or a string-typed name from
112    /// the built-in vocabulary.
113    pub fn icon_source(mut self, source: impl crate::icons::svg::IntoIconSource) -> Self {
114        self.icon = Some(source.into_icon_source());
115        self
116    }
117
118    /// Convenience alias for [`Self::icon_source`] preserved for call
119    /// sites that want the historical name.
120    pub fn icon_name(self, source: impl crate::icons::svg::IntoIconSource) -> Self {
121        self.icon_source(source)
122    }
123
124    pub fn icon_stroke_width(mut self, width: f32) -> Self {
125        self.icon_stroke_width = width.max(0.25);
126        self
127    }
128
129    /// Set the icon glyph metric (`font_size` + `line_height`) for this
130    /// element, and — when this element *is* an icon — its layout box.
131    ///
132    /// Kind-aware: width/height are only assigned when `self.kind` is
133    /// `Kind::Custom("icon")`. On container kinds like
134    /// [`crate::button_with_icon`] or `Kind::Custom("icon_button")`,
135    /// only `font_size` / `line_height` are set — chaining
136    /// `.icon_size(...)` no longer collapses the outer rect.
137    ///
138    /// Propagates to direct children whose `kind` is `Kind::Custom("icon")`
139    /// so a caller's `.icon_size(...)` on a container widget resizes the
140    /// inner icon child too. Containers that hold the icon deeper than
141    /// one level (or behind a layout wrapper) need to set the icon size
142    /// at the icon site directly.
143    pub fn icon_size(mut self, size: f32) -> Self {
144        let size = size.max(1.0);
145        self.font_size = size;
146        self.line_height = size;
147        if matches!(&self.kind, Kind::Custom(name) if *name == "icon") {
148            self.width = Size::Fixed(size);
149            self.height = Size::Fixed(size);
150            self.explicit_width = true;
151            self.explicit_height = true;
152        }
153        for child in self.children.iter_mut() {
154            if matches!(&child.kind, Kind::Custom(name) if *name == "icon") {
155                child.font_size = size;
156                child.line_height = size;
157                child.width = Size::Fixed(size);
158                child.height = Size::Fixed(size);
159                child.explicit_width = true;
160                child.explicit_height = true;
161            }
162        }
163        self
164    }
165
166    /// Attach a raster image. Usually you'll want the [`crate::image`]
167    /// free builder instead, which sets [`crate::Kind::Image`] for you; this
168    /// method exists for cases where you've already constructed an El
169    /// (e.g. through a stock widget) and want to swap in pixel art.
170    pub fn image(mut self, image: impl Into<Image>) -> Self {
171        self.image = Some(image.into());
172        self
173    }
174
175    pub fn image_fit(mut self, fit: ImageFit) -> Self {
176        self.image_fit = fit;
177        self
178    }
179
180    /// How much of the output's HDR headroom this image may use
181    /// (mirrors CSS `dynamic-range-limit`). Defaults to
182    /// [`DynamicRangeLimit::NoLimit`] — the image uses the panel's full
183    /// headroom, remastered (hue-preserving BT.2390 roll-off) when its
184    /// content peaks brighter than the panel can show. `ConstrainedHigh`
185    /// bounds HDR brights for grids/feeds; `Standard` tonemaps to SDR.
186    pub fn dynamic_range_limit(mut self, limit: DynamicRangeLimit) -> Self {
187        self.image_range_limit = limit;
188        self
189    }
190
191    pub fn image_tint(mut self, c: Color) -> Self {
192        self.image_tint = Some(c);
193        self
194    }
195
196    /// Attach an app-owned GPU texture source. Typically set via the
197    /// [`crate::tree::surface`] builder (which also sets
198    /// [`crate::Kind::Surface`]); reach for this method on a stock
199    /// widget El whose Kind you want to keep.
200    pub fn surface_source(mut self, source: crate::surface::SurfaceSource) -> Self {
201        self.surface_source = Some(source);
202        self
203    }
204
205    /// Attach a 3D scene specification. Typically set via the
206    /// [`crate::tree::chart3d`] builder (which also sets
207    /// [`crate::Kind::Scene3D`]).
208    pub fn scene_source(mut self, scene: crate::scene::SceneSpec) -> Self {
209        self.scene_source = Some(Box::new(scene));
210        self
211    }
212
213    /// How a [`crate::Kind::Surface`] El composes with widgets below
214    /// it. Default is [`crate::surface::SurfaceAlpha::Premultiplied`].
215    pub fn surface_alpha(mut self, alpha: crate::surface::SurfaceAlpha) -> Self {
216        self.surface_alpha = alpha;
217        self
218    }
219
220    /// How a [`crate::Kind::Surface`] El's texture projects into its
221    /// resolved rect. Defaults to [`crate::image::ImageFit::Fill`] —
222    /// stretch to the rect — for parity with the pre-`surface_fit`
223    /// behaviour. `Contain` / `Cover` / `None` mirror the modes on
224    /// [`crate::El::image_fit`].
225    pub fn surface_fit(mut self, fit: crate::image::ImageFit) -> Self {
226        self.surface_fit = fit;
227        self
228    }
229
230    /// Affine applied to the texture quad in destination space, around
231    /// the centre of the post-[`Self::surface_fit`] rect. Defaults to
232    /// identity. Use this for rotation, mirroring, source-dimension-
233    /// independent zoom/pan, or any combination thereof. The El's
234    /// auto-clip scissor still clamps the rendered content to the
235    /// resolved rect.
236    pub fn surface_transform(mut self, transform: crate::affine::Affine2) -> Self {
237        self.surface_transform = transform;
238        self
239    }
240
241    /// Attach a vector asset source. Typically set via the
242    /// [`crate::tree::vector`] builder (which also sets
243    /// [`crate::Kind::Vector`]); reach for this method on a stock
244    /// widget El whose Kind you want to keep.
245    pub fn vector_source(
246        mut self,
247        asset: impl Into<std::sync::Arc<crate::vector::VectorAsset>>,
248    ) -> Self {
249        self.vector_source = Some(asset.into());
250        self
251    }
252
253    /// Select how a vector asset should render. The default is
254    /// [`crate::vector::VectorRenderMode::Painted`], which preserves
255    /// authored fills/strokes/gradients. Use [`Self::vector_mask`] when
256    /// the asset is intended as one-colour coverage geometry.
257    pub fn vector_render_mode(mut self, mode: crate::vector::VectorRenderMode) -> Self {
258        self.vector_render_mode = mode;
259        self
260    }
261
262    /// Treat this vector as coverage geometry and paint it with one
263    /// colour. Backends can render this through their MSDF path.
264    pub fn vector_mask(self, color: Color) -> Self {
265        self.vector_render_mode(crate::vector::VectorRenderMode::Mask { color })
266    }
267
268    /// Preserve authored vector paint. This is the default for
269    /// [`crate::tree::vector`].
270    pub fn vector_painted(self) -> Self {
271        self.vector_render_mode(crate::vector::VectorRenderMode::Painted)
272    }
273
274    /// Inside-out redraw deadline. While this El is visible (rect
275    /// intersects the viewport), Damascene asks the host to drive the next
276    /// frame within `deadline`. Aggregated across the tree via `min`,
277    /// so the host gets a single signal regardless of how many widgets
278    /// are asking. Use `Duration::ZERO` for "next frame ASAP";
279    /// non-zero values pace the redraw loop below the display rate.
280    ///
281    /// Apps that pause / resume animation (e.g. GIF playback) just
282    /// stop calling this method on the relevant El — Damascene re-runs
283    /// the aggregation each frame, so the redraw scheduler quiets
284    /// automatically when no visible widget is asking.
285    pub fn redraw_within(mut self, deadline: std::time::Duration) -> Self {
286        self.redraw_within = Some(deadline);
287        self
288    }
289
290    /// Opt this node into the monospace face. Setting this flag also
291    /// sets [`El::explicit_mono`] so a subsequent role modifier
292    /// (`.caption()` / `.label()` / `.body()` / `.title()` /
293    /// `.heading()` / `.display()`) won't silently reset `font_mono`
294    /// when the role's default is non-mono. The natural reading order
295    /// `text(s).mono().caption()` therefore renders in mono.
296    pub fn mono(mut self) -> Self {
297        self.font_mono = true;
298        self.explicit_mono = true;
299        self
300    }
301
302    /// Italic styling for a text run. Honoured by the
303    /// [`crate::Kind::Inlines`] layout pass and (best-effort) on
304    /// standalone text Els.
305    pub fn italic(mut self) -> Self {
306        self.text_italic = true;
307        self
308    }
309
310    /// Inline-run background. Honoured when this El is a styled text
311    /// leaf inside an [`crate::Kind::Inlines`] parent: the shaped span
312    /// paints a solid quad behind its glyphs (per-line if the span
313    /// wraps). Mirrors HTML's `<mark>` / inline `background`; the rect
314    /// tracks the glyph extent rather than the El's layout box, so a
315    /// wrapped highlight follows the prose. No effect on standalone
316    /// text Els.
317    pub fn background(mut self, color: Color) -> Self {
318        self.text_bg = Some(color);
319        self
320    }
321
322    /// Underline styling for a text run.
323    pub fn underline(mut self) -> Self {
324        self.text_underline = true;
325        self
326    }
327
328    /// Strikethrough styling for a text run.
329    pub fn strikethrough(mut self) -> Self {
330        self.text_strikethrough = true;
331        self
332    }
333
334    /// Markdown-flavoured inline-code styling. Currently `mono`-styled;
335    /// a tinted background per the theme is a future addition. Authors
336    /// who want raw mono without code chrome should use [`Self::mono`]
337    /// instead.
338    pub fn code(self) -> Self {
339        self.text_role(TextRole::Code)
340    }
341
342    /// Mark this run as a link to `url`. Inside an
343    /// [`crate::Kind::Inlines`] parent the run paints with a
344    /// link-themed color; runs sharing the same URL group together for
345    /// hit-test.
346    pub fn link(mut self, url: impl Into<String>) -> Self {
347        self.text_link = Some(url.into());
348        self
349    }
350
351    pub fn math_expr(mut self, expr: impl Into<std::sync::Arc<crate::math::MathExpr>>) -> Self {
352        self.math = Some(expr.into());
353        self
354    }
355
356    pub fn math_display(mut self, display: crate::math::MathDisplay) -> Self {
357        self.math_display = display;
358        self
359    }
360}