Skip to main content

repose_ui/
lib.rs

1#![allow(non_snake_case)]
2//! # Views, Modifiers, and Layout
3//!
4//! Repose UI is built around three core ideas:
5//!
6//! - `View`: an immutable description of a UI node (cheap to rebuild every frame).
7//! - `Modifier`: layout, styling, and interaction hints attached to a `View`.
8//! - Incremental layout + paint via a persistent engine:
9//!   composition produces a new `View` tree each frame; `LayoutEngine`
10//!   reconciles it into a persistent `ViewTree` (`repose-tree`) and runs
11//!   incremental Taffy layout + paint (with scopes, dirty sets, and paint caches).
12//!
13//! ## Views
14//!
15//! A `View` is a lightweight value that describes *what* to show, not *how* it is
16//! rendered. It is cheap to create; you rebuild the description each frame
17//! (Compose-style). Identity and layout state live in the persistent tree, not
18//! in the `View` values themselves.
19//!
20//! ```rust,ignore
21//! use repose_core::*;
22//! use repose_ui::*;
23//!
24//! fn Counter(count: i32, on_inc: impl Fn() + 'static) -> View {
25//!     Column(Modifier::new().padding(16.0.dp())).child((
26//!         Text(format!("Count = {count}")),
27//!         Button("Increment".into_children(), on_inc),
28//!     ))
29//! }
30//! ```
31//!
32//! Internally, a `View` has:
33//!
34//! - `id: ViewId` - assigned during composition / layout.
35//! - `kind: ViewKind` - which widget it is (Text, Button, etc.).
36//! - `modifier: Modifier` - layout/styling/interaction metadata.
37//! - `children: Vec<View>` - owned child views.
38//!
39//! Views are *pure data*: they do not hold state or platform handles.
40//! State lives in signals / `remember_*`; platform integration is in
41//! `repose-platform` / `repose-app`.
42//!
43//! ## Modifiers
44//!
45//! `Modifier` describes *how* a view participates in layout and hit-testing:
46//!
47//! - Size: `size`, `width`, `height`, `min_*`, `max_*`, `fill_max_*`
48//! - Box model: `padding`, `padding_values`, margins
49//! - Visuals: `background`, `border`, `clip_rounded`, `alpha`, `transform`, layers
50//! - Flex / grid: `flex_*`, `align_*`, `justify_*`, `grid`, `grid_span`
51//! - Positioning: `absolute()`, `offset(..)`
52//! - Scroll: `vertical_scroll` / `horizontal_scroll` / `scrollable`, `nested_scroll_connection`
53//! - Interaction: `clickable()`, pointer callbacks, `semantics`
54//! - Custom paint: `painter` (used by `repose-canvas`)
55//! - Incremental helpers: `key`, `repaint_boundary`, `scope!` (core)
56//!
57//! Modifiers are mapped to Taffy `Style` inside `LayoutEngine`. Values are in
58//! density-independent pixels (dp) and converted to physical px via `Density`.
59//!
60//! ## Layout + paint
61//!
62//! Public entry:
63//!
64//! ```rust,ignore
65//! pub fn layout_and_paint(
66//!     root: &View,
67//!     size_px: (u32, u32),
68//!     textfield_states: &HashMap<u64, Rc<RefCell<TextFieldState>>>,
69//!     interactions: &Interactions,
70//!     focused: Option<u64>,
71//! ) -> (Scene, Vec<HitRegion>, Vec<SemNode>);
72//! ```
73//!
74//! This is a thin thread-local wrapper around `LayoutEngine::layout_frame`, which:
75//!
76//! 1. Reconciles `root` into the persistent `ViewTree` (stable `NodeId`s, content +
77//!    subtree hashes, dirty set, generation GC).
78//! 2. Syncs dual Taffy trees (root + per-`scope!` `ScopeLayoutTree`s).
79//! 3. Computes layout (measure callbacks, constraint equality skip for scopes).
80//! 4. Walks the tree to emit `SceneNode`s, `HitRegion`s, and `SemNode`s, with
81//!    paint-cache hits on `repaint_boundary` / scopes, culling, nested scroll, etc.
82//!
83//! Prefer `scope!`, stable keys, and `repaint_boundary` on expensive subtrees so
84//! the incremental engine can skip work.
85
86pub mod adaptive;
87pub mod anim;
88pub mod anim_ext;
89pub mod color_picker;
90pub mod gestures;
91pub mod layout;
92pub use layout::IntrinsicSizeMode;
93pub mod lazy;
94pub mod selection;
95pub mod subcompose;
96pub use lazy::{
97    LazyColumn, LazyHorizontalGrid, LazyRow, LazyVerticalGrid, LazyVerticalStaggeredGrid,
98    SimpleList,
99};
100pub mod lazy_states;
101pub use lazy_states::{
102    ItemHeight, LazyColumnConfig, LazyColumnState, LazyGridConfig, LazyGridState, LazyRowConfig,
103    LazyRowState, LazyVerticalStaggeredGridConfig, LazyVerticalStaggeredGridState,
104};
105pub use subcompose::{
106    BoxWithConstraints, SubcomposeLayout, box_with_constraints_with_key, subcompose_hash_key,
107    subcompose_layout_with_slots, subcompose_with_key, subcompose_with_key_slots,
108};
109pub mod overlay;
110pub mod pager;
111pub mod scroll;
112pub mod window_v2;
113pub mod windowing;
114
115use std::cell::RefCell;
116use std::collections::{HashMap, HashSet};
117use std::rc::Rc;
118use std::sync::atomic::{AtomicU64, Ordering};
119
120use repose_core::*;
121
122pub mod textfield;
123use repose_core::locals;
124pub use selection::{SelectableText, SelectableTextExt};
125pub use textfield::{
126    BasicSecureTextField, BasicTextField, KeyboardOptions, TextFieldConfig, TextFieldState,
127};
128
129thread_local! {
130    static LAYOUT_ENGINE: RefCell<layout::LayoutEngine> =
131        RefCell::new(layout::LayoutEngine::new());
132}
133
134#[derive(Default)]
135pub struct Interactions {
136    pub hover: Option<u64>,
137    pub hover_ancestors: std::collections::HashSet<u64>,
138    pub pressed: HashSet<u64>,
139}
140
141pub fn Box(modifier: Modifier) -> View {
142    View::new(0, ViewKind::Box).modifier(modifier)
143}
144
145pub fn Row(modifier: Modifier) -> View {
146    View::new(0, ViewKind::Row).modifier(modifier)
147}
148
149/// Scope receiver for building `Row` children with row-only capabilities,
150/// mirroring Compose's `RowScope`.
151///
152/// Obtain one only inside [`row_scope`], whose closure signature keeps the
153/// reference from escaping, then attach the returned children to a `Row`:
154/// ```ignore
155/// Row(Modifier::new()).children(row_scope(|s| vec![
156///     s.align_by_baseline(Text("Ag").size(32.0)),
157///     Text("Ag").size(16.0),
158/// ]))
159/// ```
160/// Children built outside a scope (plain `View`s) mix freely with scoped
161/// ones; only `align_by_*` children participate in baseline alignment.
162pub struct RowScope {
163    _private: (),
164}
165
166impl RowScope {
167    /// Align this child by its first text baseline within the `Row`
168    /// (Compose `RowScope.alignByBaseline`). Siblings without a baseline
169    /// request keep their normal cross-axis alignment.
170    pub fn align_by_baseline(&self, view: View) -> View {
171        self.align_by(view, BaselineAlign::FirstBaseline)
172    }
173
174    /// Align this child by its last text baseline within the `Row`
175    /// (Compose `RowScope.alignBy(LastBaseline)`).
176    pub fn align_by_last_baseline(&self, view: View) -> View {
177        self.align_by(view, BaselineAlign::LastBaseline)
178    }
179
180    /// Align this child by the given baseline (Compose `RowScope.alignBy`).
181    pub fn align_by(&self, mut view: View, line: BaselineAlign) -> View {
182        view.modifier.baseline_align = Some(line);
183        view
184    }
185}
186
187/// Run `content` with a [`RowScope`] receiver and collect the children.
188/// The scope reference cannot escape the closure; use the result as (part
189/// of) a `Row`'s children.
190pub fn row_scope<R>(content: impl FnOnce(&RowScope) -> R) -> R {
191    content(&RowScope { _private: () })
192}
193
194pub fn Column(modifier: Modifier) -> View {
195    View::new(0, ViewKind::Column).modifier(modifier)
196}
197
198/// Configuration for [`FlowRow`]. M3-style config: construct with
199/// `..Default::default()` so future options don't break callers.
200#[derive(Clone, Copy, Debug, PartialEq)]
201pub struct FlowRowConfig {
202    /// Pack lines to balance their lengths instead of filling each line
203    /// greedily (taffy 0.14 `FlexWrap::Balance`). Best for chip/tag clouds.
204    pub balanced: bool,
205    /// Minimum number of lines; balanced items spread into at least this
206    /// many lines. `1` (default) leaves the count to layout.
207    pub min_lines: u16,
208}
209
210impl Default for FlowRowConfig {
211    fn default() -> Self {
212        Self {
213            balanced: false,
214            min_lines: 1,
215        }
216    }
217}
218
219/// Configuration for [`FlowColumn`]. See [`FlowRowConfig`].
220#[derive(Clone, Copy, Debug, PartialEq)]
221pub struct FlowColumnConfig {
222    /// Pack lines to balance their lengths (taffy 0.14 `FlexWrap::Balance`).
223    pub balanced: bool,
224    /// Minimum number of lines. `1` (default) leaves the count to layout.
225    pub min_lines: u16,
226}
227
228impl Default for FlowColumnConfig {
229    fn default() -> Self {
230        Self {
231            balanced: false,
232            min_lines: 1,
233        }
234    }
235}
236
237/// A horizontally-oriented flow layout that wraps children to new rows when
238/// they exceed the available width. Equivalent to `Row` with `flex_wrap(Wrap)`.
239/// Tune via [`FlowRowConfig`] (e.g. balanced packing).
240pub fn FlowRow(modifier: Modifier, config: FlowRowConfig) -> View {
241    let mut modifier = modifier.flex_wrap(if config.balanced {
242        FlexWrap::Balance
243    } else {
244        FlexWrap::Wrap
245    });
246    modifier.flex_line_count = Some(config.min_lines.max(1));
247    Row(modifier)
248}
249
250/// Flipped container (identical to `Column`).
251/// Deprecated: use `Column` directly.
252#[deprecated = "Use Column instead (identical behavior)"]
253pub fn Stack(modifier: Modifier) -> View {
254    Column(modifier)
255}
256
257/// A vertically-oriented flow layout that wraps children to new columns when
258/// they exceed the available height. Equivalent to `Column` with `flex_wrap(Wrap)`.
259/// Tune via [`FlowColumnConfig`] (e.g. balanced packing).
260pub fn FlowColumn(modifier: Modifier, config: FlowColumnConfig) -> View {
261    let mut modifier = modifier.flex_wrap(if config.balanced {
262        FlexWrap::Balance
263    } else {
264        FlexWrap::Wrap
265    });
266    modifier.flex_line_count = Some(config.min_lines.max(1));
267    Column(modifier)
268}
269
270/// Centers children both axes inside this Box.
271/// (Compose `Box(contentAlignment = Alignment.Center)`.)
272/// Uses *safe* centering so overflowing content stays reachable inside
273/// scroll containers instead of spilling past both edges.
274pub fn Center(modifier: Modifier) -> View {
275    Box(modifier.content_alignment_safe(Alignment::Center))
276}
277
278pub fn ZStack(modifier: Modifier) -> View {
279    View::new(0, ViewKind::ZStack).modifier(modifier)
280}
281
282pub fn OverlayHost(modifier: Modifier) -> View {
283    View::new(0, ViewKind::OverlayHost).modifier(modifier)
284}
285
286#[deprecated = "Use Modifier::vertical_scroll instead"]
287pub fn Scroll(modifier: Modifier) -> View {
288    View::new(0, ViewKind::Box).modifier(modifier.vertical_scroll(ScrollAxisBinding {
289        show_scrollbar: true,
290        ..Default::default()
291    }))
292}
293
294pub fn Text(text: impl Into<String>) -> View {
295    View::new(
296        0,
297        ViewKind::Text {
298            text: text.into(),
299            color: locals::content_color(),
300            // Sp (converted to px in layout/paint, including TextScale).
301            font_size: locals::text_size().unwrap_or(Sp(16.0)),
302            soft_wrap: true,
303            max_lines: None,
304            overflow: TextOverflow::Clip,
305            font_family: Some("sans-serif"),
306            annotations: None,
307            text_align: TextAlign::Start,
308            font_weight: FontWeight::NORMAL,
309            font_style: FontStyle::Normal,
310            text_decoration: TextDecoration::default(),
311            letter_spacing: Sp::ZERO,
312            line_height: Sp::ZERO,
313            url: None,
314            font_variation_settings: None,
315            draw_style: DrawStyle::Fill,
316        },
317    )
318}
319
320/// Create a text view with rich text spans (AnnotatedString).
321///
322/// Each span can override color and font_size for a range of text.
323pub fn AnnotatedText(annotated: AnnotatedString) -> View {
324    let annotations: Option<std::sync::Arc<[TextSpan]>> = if annotated.spans.is_empty() {
325        None
326    } else {
327        Some(annotated.spans.clone())
328    };
329    View::new(
330        0,
331        ViewKind::Text {
332            text: annotated.text,
333            color: locals::content_color(),
334            font_size: locals::text_size().unwrap_or(Sp(16.0)),
335            soft_wrap: true,
336            max_lines: None,
337            overflow: TextOverflow::Clip,
338            font_family: Some("sans-serif"),
339            annotations,
340            text_align: TextAlign::Start,
341            font_weight: FontWeight::NORMAL,
342            font_style: FontStyle::Normal,
343            text_decoration: TextDecoration::default(),
344            letter_spacing: Sp::ZERO,
345            line_height: Sp::ZERO,
346            url: None,
347            font_variation_settings: None,
348            draw_style: DrawStyle::Fill,
349        },
350    )
351}
352
353pub fn Spacer() -> View {
354    Box(Modifier::new().flex_grow(1.0))
355}
356
357pub fn Space(modifier: Modifier) -> View {
358    Box(modifier)
359}
360
361pub fn Grid(
362    columns: usize,
363    modifier: Modifier,
364    children: Vec<View>,
365    row_gap: Dp,
366    column_gap: Dp,
367) -> View {
368    Column(modifier.grid(columns, row_gap, column_gap)).with_children(children)
369}
370
371static DRAGVALUE_COUNTER: AtomicU64 = AtomicU64::new(0);
372
373/// A drag-to-change numeric value field (like egui's `DragValue`).
374///
375/// Click and drag left/right to change the value. Displays the current value
376/// as centered text in a bordered box.
377pub fn DragValue(
378    value: f32,
379    range: (f32, f32),
380    speed: f32,
381    on_change: impl Fn(f32) + 'static,
382) -> View {
383    let id = DRAGVALUE_COUNTER.fetch_add(1, Ordering::Relaxed);
384    let drag_start_x = remember_mutable_with_key(format!("dv_dsx_{}", id), || 0.0f32);
385    let drag_start_val = remember_mutable_with_key(format!("dv_dsv_{}", id), || 0.0f32);
386    let is_dragging = remember_mutable_with_key(format!("dv_drg_{}", id), || false);
387
388    let oc = Rc::new(on_change);
389    let min = range.0;
390    let max = range.1;
391    let cur = value;
392
393    let th = locals::theme();
394
395    Box(Modifier::new()
396        .min_width(Dp(48.0))
397        .height(Dp(28.0))
398        .background(th.surface_container)
399        .border(Dp(1.0), th.outline, Dp(4.0))
400        .clip_rounded(Dp(4.0))
401        .padding_values(PaddingValues {
402            left: Dp(4.0),
403            right: Dp(4.0),
404            top: Dp::ZERO,
405            bottom: Dp::ZERO,
406        })
407        .on_pointer_down({
408            let dsx = drag_start_x.clone();
409            let dsv = drag_start_val.clone();
410            let drg = is_dragging.clone();
411            move |pe: PointerEvent| {
412                drg.set(true);
413                dsx.set(pe.position_in_window().x);
414                dsv.set(cur);
415            }
416        })
417        .on_pointer_move({
418            let dsx = drag_start_x.clone();
419            let dsv = drag_start_val.clone();
420            let drg = is_dragging.clone();
421            let oc = oc.clone();
422            move |pe: PointerEvent| {
423                if !drg.with(|v| *v) {
424                    return;
425                }
426                let start_x = dsx.with(|v| *v);
427                let start_val = dsv.with(|v| *v);
428                let new_val =
429                    (start_val + (pe.position_in_window().x - start_x) * speed).clamp(min, max);
430                (oc)(new_val);
431            }
432        })
433        .on_pointer_up({
434            let drg = is_dragging.clone();
435            move |_pe: PointerEvent| {
436                drg.set(false);
437            }
438        })
439        .cursor(CursorIcon::EwResize))
440    .child(
441        Text(format_value(value))
442            .size(Sp(13.0))
443            .color(th.on_surface)
444            .single_line()
445            .overflow_ellipsize(),
446    )
447}
448
449fn format_value(v: f32) -> String {
450    if (v - v.round()).abs() < 1e-6 {
451        format!("{}", v.round() as i64)
452    } else if (v * 10.0 - (v * 10.0).round()).abs() < 1e-6 {
453        format!("{:.1}", v)
454    } else {
455        format!("{:.2}", v)
456    }
457}
458
459pub fn Image(modifier: Modifier, handle: ImageHandle) -> View {
460    View::new(
461        0,
462        ViewKind::Image {
463            handle,
464            tint: Color::WHITE,
465            fit: ImageFit::Contain,
466        },
467    )
468    .modifier(modifier)
469}
470
471/// Embedded GPU callback (like `egui::PaintCallback`).
472pub fn Embedded(modifier: Modifier, payload: PaintCallbackPayload) -> View {
473    let mut m = modifier.paint_callback(payload);
474    let has_size = m.size.is_some()
475        || m.width.is_some()
476        || m.height.is_some()
477        || m.fill_max.is_some()
478        || m.fill_max_w.is_some()
479        || m.fill_max_h.is_some();
480    if !has_size {
481        m = m.size(Dp(100.0), Dp(100.0));
482    }
483    Box(m)
484}
485
486pub trait ImageExt {
487    fn image_tint(self, c: Color) -> View;
488    fn image_fit(self, fit: ImageFit) -> View;
489}
490impl ImageExt for View {
491    fn image_tint(mut self, c: Color) -> View {
492        if let ViewKind::Image { tint, .. } = &mut self.kind {
493            *tint = c;
494        }
495        self
496    }
497    fn image_fit(mut self, fit: ImageFit) -> View {
498        if let ViewKind::Image { fit: f, .. } = &mut self.kind {
499            *f = fit;
500        }
501        self
502    }
503}
504
505/// Extension trait for child building
506pub trait ViewExt: Sized {
507    fn child(self, children: impl IntoChildren) -> Self;
508}
509
510impl ViewExt for View {
511    fn child(mut self, children: impl IntoChildren) -> Self {
512        self.children.extend(children.into_children());
513        self
514    }
515}
516
517pub trait IntoChildren {
518    fn into_children(self) -> Vec<View>;
519}
520
521impl IntoChildren for View {
522    fn into_children(self) -> Vec<View> {
523        vec![self]
524    }
525}
526
527impl IntoChildren for Vec<View> {
528    fn into_children(self) -> Vec<View> {
529        self
530    }
531}
532
533impl<const N: usize> IntoChildren for [View; N] {
534    fn into_children(self) -> Vec<View> {
535        self.into()
536    }
537}
538
539// Tuple implementations
540macro_rules! impl_into_children_tuple {
541    ($($idx:tt $t:ident),+) => {
542        impl<$($t: IntoChildren),+> IntoChildren for ($($t,)+) {
543            fn into_children(self) -> Vec<View> {
544                let mut v = Vec::new();
545                $(v.extend(self.$idx.into_children());)+
546                v
547            }
548        }
549    };
550}
551
552impl_into_children_tuple!(0 A);
553impl_into_children_tuple!(0 A, 1 B);
554impl_into_children_tuple!(0 A, 1 B, 2 C);
555impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D);
556impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E);
557impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);
558impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);
559impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);
560
561/// Reconcile `root` into the thread-local `LayoutEngine` and run incremental
562/// layout + paint for this frame.
563pub fn layout_and_paint(
564    root: &View,
565    size_px_u32: (u32, u32),
566    textfield_states: &HashMap<u64, Rc<RefCell<TextFieldState>>>,
567    interactions: &Interactions,
568    focused: Option<u64>,
569) -> (Scene, Vec<HitRegion>, Vec<SemNode>) {
570    LAYOUT_ENGINE.with(|engine| {
571        engine
572            .borrow_mut()
573            .layout_frame(root, size_px_u32, textfield_states, interactions, focused)
574    })
575}
576
577/// Return the [`LayoutStats`] from the most recent `layout_and_paint` call on
578/// this thread. Used by the inspector / HUD to report real layout+paint timing
579/// and cache counters instead of a hardcoded estimate.
580pub fn last_layout_stats() -> layout::LayoutStats {
581    LAYOUT_ENGINE.with(|engine| engine.borrow().stats.clone())
582}
583
584pub use layout::LayoutStats;
585
586/// Method styling (text sizes are [`Sp`]).
587pub trait TextStyle {
588    fn color(self, c: Color) -> View;
589    fn size(self, size: Sp) -> View;
590    fn max_lines(self, n: usize) -> View;
591    fn single_line(self) -> View;
592    fn overflow_ellipsize(self) -> View;
593    fn overflow_clip(self) -> View;
594    fn overflow_visible(self) -> View;
595    fn font_family(self, family: &'static str) -> View;
596    fn text_align(self, align: TextAlign) -> View;
597    fn font_weight(self, weight: FontWeight) -> View;
598    fn font_style(self, style: FontStyle) -> View;
599    fn text_decoration(self, decoration: TextDecoration) -> View;
600    fn letter_spacing(self, spacing: Sp) -> View;
601    fn line_height(self, height: Sp) -> View;
602    fn url(self, url: impl Into<std::sync::Arc<str>>) -> View;
603    fn font_variation_settings(self, settings: &str) -> View;
604    fn draw_style(self, style: DrawStyle) -> View;
605    /// Faux-bold: fill plus a same-color outline (`width` in em-units,
606    /// 0.04 is a good start). For fonts without a bold face.
607    fn fill_and_stroke(self, width: f32) -> View;
608}
609impl TextStyle for View {
610    fn color(mut self, c: Color) -> View {
611        if let ViewKind::Text {
612            color: text_color, ..
613        } = &mut self.kind
614        {
615            *text_color = c;
616        }
617        self
618    }
619    fn size(mut self, size: Sp) -> View {
620        if let ViewKind::Text {
621            font_size: text_size_sp,
622            ..
623        } = &mut self.kind
624        {
625            *text_size_sp = size;
626        }
627        self
628    }
629    fn max_lines(mut self, n: usize) -> View {
630        if let ViewKind::Text {
631            max_lines,
632            soft_wrap,
633            ..
634        } = &mut self.kind
635        {
636            *max_lines = Some(n);
637            *soft_wrap = true;
638        }
639        self
640    }
641    fn single_line(mut self) -> View {
642        if let ViewKind::Text {
643            soft_wrap,
644            max_lines,
645            ..
646        } = &mut self.kind
647        {
648            *soft_wrap = false;
649            *max_lines = Some(1);
650        }
651        self
652    }
653    fn overflow_ellipsize(mut self) -> View {
654        if let ViewKind::Text { overflow, .. } = &mut self.kind {
655            *overflow = TextOverflow::Ellipsis;
656        }
657        self
658    }
659    fn overflow_clip(mut self) -> View {
660        if let ViewKind::Text { overflow, .. } = &mut self.kind {
661            *overflow = TextOverflow::Clip;
662        }
663        self
664    }
665    fn overflow_visible(mut self) -> View {
666        if let ViewKind::Text { overflow, .. } = &mut self.kind {
667            *overflow = TextOverflow::Visible;
668        }
669        self
670    }
671    fn font_family(mut self, family: &'static str) -> View {
672        if let ViewKind::Text {
673            font_family: ff, ..
674        } = &mut self.kind
675        {
676            *ff = Some(family);
677        }
678        self
679    }
680    fn text_align(mut self, align: TextAlign) -> View {
681        if let ViewKind::Text { text_align, .. } = &mut self.kind {
682            *text_align = align;
683        }
684        self
685    }
686    fn font_weight(mut self, weight: FontWeight) -> View {
687        if let ViewKind::Text { font_weight, .. } = &mut self.kind {
688            *font_weight = weight;
689        }
690        self
691    }
692    fn font_style(mut self, style: FontStyle) -> View {
693        if let ViewKind::Text { font_style, .. } = &mut self.kind {
694            *font_style = style;
695        }
696        self
697    }
698    fn text_decoration(mut self, decoration: TextDecoration) -> View {
699        if let ViewKind::Text {
700            text_decoration, ..
701        } = &mut self.kind
702        {
703            *text_decoration = decoration;
704        }
705        self
706    }
707    fn letter_spacing(mut self, spacing: Sp) -> View {
708        if let ViewKind::Text { letter_spacing, .. } = &mut self.kind {
709            *letter_spacing = spacing;
710        }
711        self
712    }
713    fn line_height(mut self, height: Sp) -> View {
714        if let ViewKind::Text { line_height, .. } = &mut self.kind {
715            *line_height = height;
716        }
717        self
718    }
719    fn url(mut self, url: impl Into<std::sync::Arc<str>>) -> View {
720        if let ViewKind::Text {
721            url: u,
722            text_decoration,
723            ..
724        } = &mut self.kind
725        {
726            *u = Some(url.into());
727            if !text_decoration.underline && !text_decoration.strikethrough {
728                *text_decoration = TextDecoration::UNDERLINE;
729            }
730        }
731        self
732    }
733    fn font_variation_settings(mut self, settings: &str) -> View {
734        if let ViewKind::Text {
735            font_variation_settings,
736            ..
737        } = &mut self.kind
738        {
739            *font_variation_settings = Some(settings.into());
740        }
741        self
742    }
743    fn draw_style(mut self, style: DrawStyle) -> View {
744        if let ViewKind::Text { draw_style, .. } = &mut self.kind {
745            *draw_style = style;
746        }
747        self
748    }
749    fn fill_and_stroke(mut self, width: f32) -> View {
750        if let ViewKind::Text { draw_style, .. } = &mut self.kind {
751            *draw_style = DrawStyle::fill_and_stroke(width);
752        }
753        self
754    }
755}