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(crate) mod hit_testing;
91pub use hit_testing::{
92    HitRegionSnapshot, dispatch_keyboard_action, hit_region_contains, hit_region_local_rect,
93    hit_region_pointer_coordinates, hit_region_to_local, hit_region_transformed_origin,
94    hit_region_world_rect, hit_test_enabled_frame, hit_test_frame, hit_test_frame_path,
95    hit_test_frame_regions,
96};
97pub mod layout;
98pub use layout::IntrinsicSizeMode;
99pub mod lazy;
100pub mod selection;
101pub mod subcompose;
102pub use lazy::{
103    LazyColumn, LazyHorizontalGrid, LazyRow, LazyVerticalGrid, LazyVerticalStaggeredGrid,
104    SimpleList,
105};
106pub mod lazy_states;
107pub use lazy_states::{
108    ItemHeight, LazyColumnConfig, LazyColumnState, LazyGridConfig, LazyGridState, LazyRowConfig,
109    LazyRowState, LazyVerticalStaggeredGridConfig, LazyVerticalStaggeredGridState,
110};
111pub use subcompose::{
112    BoxWithConstraints, SubcomposeLayout, box_with_constraints_with_key, subcompose_hash_key,
113    subcompose_layout_with_slots, subcompose_with_key, subcompose_with_key_slots,
114};
115pub mod overlay;
116pub mod pager;
117pub mod scroll;
118pub mod window_v2;
119pub mod windowing;
120
121use std::cell::RefCell;
122use std::collections::{HashMap, HashSet};
123use std::rc::Rc;
124use std::sync::atomic::{AtomicU64, Ordering};
125
126use repose_core::*;
127
128pub(crate) mod text_selection;
129pub mod textfield;
130use repose_core::locals;
131pub use selection::{SelectableText, SelectableTextExt};
132pub use textfield::{
133    BasicSecureTextField, BasicTextField, KeyboardOptions, TextFieldConfig, TextFieldState,
134};
135
136thread_local! {
137    static LAYOUT_ENGINE: RefCell<layout::LayoutEngine> =
138        RefCell::new(layout::LayoutEngine::new());
139}
140
141#[derive(Default)]
142pub struct Interactions {
143    pub hover: Option<u64>,
144    pub hover_ancestors: std::collections::HashSet<u64>,
145    pub pressed: HashSet<u64>,
146}
147
148pub fn Box(modifier: Modifier) -> View {
149    View::new(0, ViewKind::Box).modifier(modifier)
150}
151
152pub fn Row(modifier: Modifier) -> View {
153    View::new(0, ViewKind::Row).modifier(modifier)
154}
155
156/// Scope receiver for building `Row` children with row-only capabilities,
157/// mirroring Compose's `RowScope`.
158///
159/// Obtain one only inside [`row_scope`], whose closure signature keeps the
160/// reference from escaping, then attach the returned children to a `Row`:
161/// ```ignore
162/// Row(Modifier::new()).children(row_scope(|s| vec![
163///     s.align_by_baseline(Text("Ag").size(32.0)),
164///     Text("Ag").size(16.0),
165/// ]))
166/// ```
167/// Children built outside a scope (plain `View`s) mix freely with scoped
168/// ones; only `align_by_*` children participate in baseline alignment.
169pub struct RowScope {
170    _private: (),
171}
172
173impl RowScope {
174    /// Align this child by its first text baseline within the `Row`
175    /// (Compose `RowScope.alignByBaseline`). Siblings without a baseline
176    /// request keep their normal cross-axis alignment.
177    pub fn align_by_baseline(&self, view: View) -> View {
178        self.align_by(view, BaselineAlign::FirstBaseline)
179    }
180
181    /// Align this child by its last text baseline within the `Row`
182    /// (Compose `RowScope.alignBy(LastBaseline)`).
183    pub fn align_by_last_baseline(&self, view: View) -> View {
184        self.align_by(view, BaselineAlign::LastBaseline)
185    }
186
187    /// Align this child by the given baseline (Compose `RowScope.alignBy`).
188    pub fn align_by(&self, mut view: View, line: BaselineAlign) -> View {
189        view.modifier.baseline_align = Some(line);
190        view
191    }
192}
193
194/// Run `content` with a [`RowScope`] receiver and collect the children.
195/// The scope reference cannot escape the closure; use the result as (part
196/// of) a `Row`'s children.
197pub fn row_scope<R>(content: impl FnOnce(&RowScope) -> R) -> R {
198    content(&RowScope { _private: () })
199}
200
201pub fn Column(modifier: Modifier) -> View {
202    View::new(0, ViewKind::Column).modifier(modifier)
203}
204
205/// Configuration for [`FlowRow`]. M3-style config: construct with
206/// `..Default::default()` so future options don't break callers.
207#[derive(Clone, Copy, Debug, PartialEq)]
208pub struct FlowRowConfig {
209    /// Pack lines to balance their lengths instead of filling each line
210    /// greedily (taffy 0.14 `FlexWrap::Balance`). Best for chip/tag clouds.
211    pub balanced: bool,
212    /// Minimum number of lines; balanced items spread into at least this
213    /// many lines. `1` (default) leaves the count to layout.
214    pub min_lines: u16,
215}
216
217impl Default for FlowRowConfig {
218    fn default() -> Self {
219        Self {
220            balanced: false,
221            min_lines: 1,
222        }
223    }
224}
225
226/// Configuration for [`FlowColumn`]. See [`FlowRowConfig`].
227#[derive(Clone, Copy, Debug, PartialEq)]
228pub struct FlowColumnConfig {
229    /// Pack lines to balance their lengths (taffy 0.14 `FlexWrap::Balance`).
230    pub balanced: bool,
231    /// Minimum number of lines. `1` (default) leaves the count to layout.
232    pub min_lines: u16,
233}
234
235impl Default for FlowColumnConfig {
236    fn default() -> Self {
237        Self {
238            balanced: false,
239            min_lines: 1,
240        }
241    }
242}
243
244/// A horizontally-oriented flow layout that wraps children to new rows when
245/// they exceed the available width. Equivalent to `Row` with `flex_wrap(Wrap)`.
246/// Tune via [`FlowRowConfig`] (e.g. balanced packing).
247pub fn FlowRow(modifier: Modifier, config: FlowRowConfig) -> View {
248    let mut modifier = modifier.flex_wrap(if config.balanced {
249        FlexWrap::Balance
250    } else {
251        FlexWrap::Wrap
252    });
253    modifier.flex_line_count = Some(config.min_lines.max(1));
254    Row(modifier)
255}
256
257/// Flipped container (identical to `Column`).
258/// Deprecated: use `Column` directly.
259#[deprecated = "Use Column instead (identical behavior)"]
260pub fn Stack(modifier: Modifier) -> View {
261    Column(modifier)
262}
263
264/// A vertically-oriented flow layout that wraps children to new columns when
265/// they exceed the available height. Equivalent to `Column` with `flex_wrap(Wrap)`.
266/// Tune via [`FlowColumnConfig`] (e.g. balanced packing).
267pub fn FlowColumn(modifier: Modifier, config: FlowColumnConfig) -> View {
268    let mut modifier = modifier.flex_wrap(if config.balanced {
269        FlexWrap::Balance
270    } else {
271        FlexWrap::Wrap
272    });
273    modifier.flex_line_count = Some(config.min_lines.max(1));
274    Column(modifier)
275}
276
277/// Centers children both axes inside this Box.
278/// (Compose `Box(contentAlignment = Alignment.Center)`.)
279/// Uses *safe* centering so overflowing content stays reachable inside
280/// scroll containers instead of spilling past both edges.
281pub fn Center(modifier: Modifier) -> View {
282    Box(modifier.content_alignment_safe(Alignment::Center))
283}
284
285pub fn ZStack(modifier: Modifier) -> View {
286    View::new(0, ViewKind::ZStack).modifier(modifier)
287}
288
289pub fn OverlayHost(modifier: Modifier) -> View {
290    View::new(0, ViewKind::OverlayHost).modifier(modifier)
291}
292
293#[deprecated = "Use Modifier::vertical_scroll instead"]
294pub fn Scroll(modifier: Modifier) -> View {
295    View::new(0, ViewKind::Box).modifier(modifier.vertical_scroll(ScrollAxisBinding {
296        show_scrollbar: true,
297        ..Default::default()
298    }))
299}
300
301pub fn Text(text: impl Into<String>) -> View {
302    View::new(
303        0,
304        ViewKind::Text {
305            text: text.into(),
306            color: locals::content_color(),
307            // Sp (converted to px in layout/paint, including TextScale).
308            font_size: locals::text_size().unwrap_or(Sp(16.0)),
309            soft_wrap: true,
310            max_lines: None,
311            overflow: TextOverflow::Clip,
312            font_family: Some("sans-serif"),
313            annotations: None,
314            text_align: TextAlign::Start,
315            font_weight: FontWeight::NORMAL,
316            font_style: FontStyle::Normal,
317            text_decoration: TextDecoration::default(),
318            letter_spacing: Sp::ZERO,
319            line_height: Sp::ZERO,
320            url: None,
321            font_variation_settings: None,
322            draw_style: DrawStyle::Fill,
323        },
324    )
325}
326
327/// Create a text view with rich text spans (AnnotatedString).
328///
329/// Each span can override color and font_size for a range of text.
330pub fn AnnotatedText(annotated: AnnotatedString) -> View {
331    let annotations: Option<std::sync::Arc<[TextSpan]>> = if annotated.spans.is_empty() {
332        None
333    } else {
334        Some(annotated.spans.clone())
335    };
336    View::new(
337        0,
338        ViewKind::Text {
339            text: annotated.text,
340            color: locals::content_color(),
341            font_size: locals::text_size().unwrap_or(Sp(16.0)),
342            soft_wrap: true,
343            max_lines: None,
344            overflow: TextOverflow::Clip,
345            font_family: Some("sans-serif"),
346            annotations,
347            text_align: TextAlign::Start,
348            font_weight: FontWeight::NORMAL,
349            font_style: FontStyle::Normal,
350            text_decoration: TextDecoration::default(),
351            letter_spacing: Sp::ZERO,
352            line_height: Sp::ZERO,
353            url: None,
354            font_variation_settings: None,
355            draw_style: DrawStyle::Fill,
356        },
357    )
358}
359
360pub fn Spacer() -> View {
361    Box(Modifier::new().flex_grow(1.0))
362}
363
364pub fn Space(modifier: Modifier) -> View {
365    Box(modifier)
366}
367
368pub fn Grid(
369    columns: usize,
370    modifier: Modifier,
371    children: Vec<View>,
372    row_gap: Dp,
373    column_gap: Dp,
374) -> View {
375    Column(modifier.grid(columns, row_gap, column_gap)).with_children(children)
376}
377
378static DRAGVALUE_COUNTER: AtomicU64 = AtomicU64::new(0);
379
380/// A drag-to-change numeric value field (like egui's `DragValue`).
381///
382/// Click and drag left/right to change the value. Displays the current value
383/// as centered text in a bordered box.
384pub fn DragValue(
385    value: f32,
386    range: (f32, f32),
387    speed: f32,
388    on_change: impl Fn(f32) + 'static,
389) -> View {
390    let id = DRAGVALUE_COUNTER.fetch_add(1, Ordering::Relaxed);
391    let drag_start_x = remember_mutable_with_key(format!("dv_dsx_{}", id), || 0.0f32);
392    let drag_start_val = remember_mutable_with_key(format!("dv_dsv_{}", id), || 0.0f32);
393    let is_dragging = remember_mutable_with_key(format!("dv_drg_{}", id), || false);
394
395    let oc = Rc::new(on_change);
396    let min = range.0;
397    let max = range.1;
398    let cur = value;
399
400    let th = locals::theme();
401
402    Box(Modifier::new()
403        .min_width(Dp(48.0))
404        .height(Dp(28.0))
405        .background(th.surface_container)
406        .border(Dp(1.0), th.outline, Dp(4.0))
407        .clip_rounded(Dp(4.0))
408        .padding_values(PaddingValues {
409            left: Dp(4.0),
410            right: Dp(4.0),
411            top: Dp::ZERO,
412            bottom: Dp::ZERO,
413        })
414        .on_pointer_down({
415            let dsx = drag_start_x.clone();
416            let dsv = drag_start_val.clone();
417            let drg = is_dragging.clone();
418            move |pe: PointerEvent| {
419                drg.set(true);
420                dsx.set(pe.position_in_window().x);
421                dsv.set(cur);
422            }
423        })
424        .on_pointer_move({
425            let dsx = drag_start_x.clone();
426            let dsv = drag_start_val.clone();
427            let drg = is_dragging.clone();
428            let oc = oc.clone();
429            move |pe: PointerEvent| {
430                if !drg.with(|v| *v) {
431                    return;
432                }
433                let start_x = dsx.with(|v| *v);
434                let start_val = dsv.with(|v| *v);
435                let new_val =
436                    (start_val + (pe.position_in_window().x - start_x) * speed).clamp(min, max);
437                (oc)(new_val);
438            }
439        })
440        .on_pointer_up({
441            let drg = is_dragging.clone();
442            move |_pe: PointerEvent| {
443                drg.set(false);
444            }
445        })
446        .on_pointer_cancel({
447            let drg = is_dragging.clone();
448            move |_pe: PointerEvent| {
449                drg.set(false);
450            }
451        })
452        .cursor(CursorIcon::EwResize))
453    .child(
454        Text(format_value(value))
455            .size(Sp(13.0))
456            .color(th.on_surface)
457            .single_line()
458            .overflow_ellipsize(),
459    )
460}
461
462fn format_value(v: f32) -> String {
463    if (v - v.round()).abs() < 1e-6 {
464        format!("{}", v.round() as i64)
465    } else if (v * 10.0 - (v * 10.0).round()).abs() < 1e-6 {
466        format!("{:.1}", v)
467    } else {
468        format!("{:.2}", v)
469    }
470}
471
472pub fn Image(modifier: Modifier, handle: ImageHandle) -> View {
473    View::new(
474        0,
475        ViewKind::Image {
476            handle,
477            tint: Color::WHITE,
478            fit: ImageFit::Contain,
479            filter: ImageFilter::Linear,
480            source_rect: None,
481            alignment: ImageAlignment::Center,
482        },
483    )
484    .modifier(modifier)
485}
486
487/// Embedded GPU callback (like `egui::PaintCallback`).
488pub fn Embedded(modifier: Modifier, payload: PaintCallbackPayload) -> View {
489    let mut m = modifier.paint_callback(payload);
490    let has_size = m.size.is_some()
491        || m.width.is_some()
492        || m.height.is_some()
493        || m.fill_max.is_some()
494        || m.fill_max_w.is_some()
495        || m.fill_max_h.is_some();
496    if !has_size {
497        m = m.size(Dp(100.0), Dp(100.0));
498    }
499    Box(m)
500}
501
502pub trait ImageExt {
503    fn image_tint(self, c: Color) -> View;
504    fn image_fit(self, fit: ImageFit) -> View;
505    fn image_filter(self, filter: ImageFilter) -> View;
506    fn image_source_rect(self, source_rect: ImageSourceRect) -> View;
507    fn image_alignment(self, alignment: ImageAlignment) -> View;
508}
509impl ImageExt for View {
510    fn image_tint(mut self, c: Color) -> View {
511        if let ViewKind::Image { tint, .. } = &mut self.kind {
512            *tint = c;
513        }
514        self
515    }
516    fn image_fit(mut self, fit: ImageFit) -> View {
517        if let ViewKind::Image { fit: f, .. } = &mut self.kind {
518            *f = fit;
519        }
520        self
521    }
522    fn image_filter(mut self, filter: ImageFilter) -> View {
523        if let ViewKind::Image { filter: f, .. } = &mut self.kind {
524            *f = filter;
525        }
526        self
527    }
528    fn image_source_rect(mut self, source: ImageSourceRect) -> View {
529        if let ViewKind::Image { source_rect, .. } = &mut self.kind {
530            *source_rect = Some(source);
531        }
532        self
533    }
534    fn image_alignment(mut self, alignment: ImageAlignment) -> View {
535        if let ViewKind::Image {
536            alignment: current, ..
537        } = &mut self.kind
538        {
539            *current = alignment;
540        }
541        self
542    }
543}
544
545/// Extension trait for child building
546pub trait ViewExt: Sized {
547    fn child(self, children: impl IntoChildren) -> Self;
548}
549
550impl ViewExt for View {
551    fn child(mut self, children: impl IntoChildren) -> Self {
552        self.children.extend(children.into_children());
553        self
554    }
555}
556
557pub trait IntoChildren {
558    fn into_children(self) -> Vec<View>;
559}
560
561impl IntoChildren for View {
562    fn into_children(self) -> Vec<View> {
563        vec![self]
564    }
565}
566
567impl IntoChildren for Vec<View> {
568    fn into_children(self) -> Vec<View> {
569        self
570    }
571}
572
573impl<const N: usize> IntoChildren for [View; N] {
574    fn into_children(self) -> Vec<View> {
575        self.into()
576    }
577}
578
579// Tuple implementations
580macro_rules! impl_into_children_tuple {
581    ($($idx:tt $t:ident),+) => {
582        impl<$($t: IntoChildren),+> IntoChildren for ($($t,)+) {
583            fn into_children(self) -> Vec<View> {
584                let mut v = Vec::new();
585                $(v.extend(self.$idx.into_children());)+
586                v
587            }
588        }
589    };
590}
591
592impl_into_children_tuple!(0 A);
593impl_into_children_tuple!(0 A, 1 B);
594impl_into_children_tuple!(0 A, 1 B, 2 C);
595impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D);
596impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E);
597impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);
598impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);
599impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);
600
601/// Reconcile `root` into the thread-local `LayoutEngine` and run incremental
602/// layout + paint for this frame.
603pub fn layout_and_paint(
604    root: &View,
605    size_px_u32: (u32, u32),
606    textfield_states: &HashMap<u64, Rc<RefCell<TextFieldState>>>,
607    interactions: &Interactions,
608    focused: Option<u64>,
609) -> (Scene, Vec<HitRegion>, Vec<SemNode>) {
610    let result = LAYOUT_ENGINE.with(|engine| {
611        engine
612            .borrow_mut()
613            .layout_frame(root, size_px_u32, textfield_states, interactions, focused)
614    });
615    textfield::prune_textfield_registries();
616    result
617}
618
619/// Return the [`LayoutStats`] from the most recent `layout_and_paint` call on
620/// this thread. Used by the inspector / HUD to report real layout+paint timing
621/// and cache counters instead of a hardcoded estimate.
622pub fn last_layout_stats() -> layout::LayoutStats {
623    LAYOUT_ENGINE.with(|engine| engine.borrow().stats.clone())
624}
625
626pub use layout::LayoutStats;
627
628/// Method styling (text sizes are [`Sp`]).
629pub trait TextStyle {
630    fn color(self, c: Color) -> View;
631    fn size(self, size: Sp) -> View;
632    fn max_lines(self, n: usize) -> View;
633    fn single_line(self) -> View;
634    fn overflow_ellipsize(self) -> View;
635    fn overflow_clip(self) -> View;
636    fn overflow_visible(self) -> View;
637    fn font_family(self, family: &'static str) -> View;
638    fn text_align(self, align: TextAlign) -> View;
639    fn font_weight(self, weight: FontWeight) -> View;
640    fn font_style(self, style: FontStyle) -> View;
641    fn text_decoration(self, decoration: TextDecoration) -> View;
642    fn letter_spacing(self, spacing: Sp) -> View;
643    fn line_height(self, height: Sp) -> View;
644    fn url(self, url: impl Into<std::sync::Arc<str>>) -> View;
645    fn font_variation_settings(self, settings: &str) -> View;
646    fn draw_style(self, style: DrawStyle) -> View;
647    /// Faux-bold: fill plus a same-color outline (`width` in em-units,
648    /// 0.04 is a good start). For fonts without a bold face.
649    fn fill_and_stroke(self, width: f32) -> View;
650    /// Godot `font_outline_size` / `font_outline_color`: the fill with a
651    /// contrasting outline drawn underneath, in one layout pass. `width` is
652    /// em-units and the stroke is centred on the glyph outline, so pass
653    /// `2 * outline_px / font_px` to grow `outline_px` outward.
654    fn outlined(self, width: f32, color: Color) -> View;
655}
656impl TextStyle for View {
657    fn color(mut self, c: Color) -> View {
658        if let ViewKind::Text {
659            color: text_color, ..
660        } = &mut self.kind
661        {
662            *text_color = c;
663        }
664        self
665    }
666    fn size(mut self, size: Sp) -> View {
667        if let ViewKind::Text {
668            font_size: text_size_sp,
669            ..
670        } = &mut self.kind
671        {
672            *text_size_sp = size;
673        }
674        self
675    }
676    fn max_lines(mut self, n: usize) -> View {
677        if let ViewKind::Text {
678            max_lines,
679            soft_wrap,
680            ..
681        } = &mut self.kind
682        {
683            *max_lines = Some(n);
684            *soft_wrap = true;
685        }
686        self
687    }
688    fn single_line(mut self) -> View {
689        if let ViewKind::Text {
690            soft_wrap,
691            max_lines,
692            ..
693        } = &mut self.kind
694        {
695            *soft_wrap = false;
696            *max_lines = Some(1);
697        }
698        self
699    }
700    fn overflow_ellipsize(mut self) -> View {
701        if let ViewKind::Text { overflow, .. } = &mut self.kind {
702            *overflow = TextOverflow::Ellipsis;
703        }
704        self
705    }
706    fn overflow_clip(mut self) -> View {
707        if let ViewKind::Text { overflow, .. } = &mut self.kind {
708            *overflow = TextOverflow::Clip;
709        }
710        self
711    }
712    fn overflow_visible(mut self) -> View {
713        if let ViewKind::Text { overflow, .. } = &mut self.kind {
714            *overflow = TextOverflow::Visible;
715        }
716        self
717    }
718    fn font_family(mut self, family: &'static str) -> View {
719        if let ViewKind::Text {
720            font_family: ff, ..
721        } = &mut self.kind
722        {
723            *ff = Some(family);
724        }
725        self
726    }
727    fn text_align(mut self, align: TextAlign) -> View {
728        if let ViewKind::Text { text_align, .. } = &mut self.kind {
729            *text_align = align;
730        }
731        self
732    }
733    fn font_weight(mut self, weight: FontWeight) -> View {
734        if let ViewKind::Text { font_weight, .. } = &mut self.kind {
735            *font_weight = weight;
736        }
737        self
738    }
739    fn font_style(mut self, style: FontStyle) -> View {
740        if let ViewKind::Text { font_style, .. } = &mut self.kind {
741            *font_style = style;
742        }
743        self
744    }
745    fn text_decoration(mut self, decoration: TextDecoration) -> View {
746        if let ViewKind::Text {
747            text_decoration, ..
748        } = &mut self.kind
749        {
750            *text_decoration = decoration;
751        }
752        self
753    }
754    fn letter_spacing(mut self, spacing: Sp) -> View {
755        if let ViewKind::Text { letter_spacing, .. } = &mut self.kind {
756            *letter_spacing = spacing;
757        }
758        self
759    }
760    fn line_height(mut self, height: Sp) -> View {
761        if let ViewKind::Text { line_height, .. } = &mut self.kind {
762            *line_height = height;
763        }
764        self
765    }
766    fn url(mut self, url: impl Into<std::sync::Arc<str>>) -> View {
767        if let ViewKind::Text {
768            url: u,
769            text_decoration,
770            ..
771        } = &mut self.kind
772        {
773            *u = Some(url.into());
774            if !text_decoration.underline && !text_decoration.strikethrough {
775                *text_decoration = TextDecoration::UNDERLINE;
776            }
777        }
778        self
779    }
780    fn font_variation_settings(mut self, settings: &str) -> View {
781        if let ViewKind::Text {
782            font_variation_settings,
783            ..
784        } = &mut self.kind
785        {
786            *font_variation_settings = Some(settings.into());
787        }
788        self
789    }
790    fn draw_style(mut self, style: DrawStyle) -> View {
791        if let ViewKind::Text { draw_style, .. } = &mut self.kind {
792            *draw_style = style;
793        }
794        self
795    }
796    fn fill_and_stroke(mut self, width: f32) -> View {
797        if let ViewKind::Text { draw_style, .. } = &mut self.kind {
798            *draw_style = DrawStyle::fill_and_stroke(width);
799        }
800        self
801    }
802    fn outlined(mut self, width: f32, color: Color) -> View {
803        if let ViewKind::Text { draw_style, .. } = &mut self.kind {
804            *draw_style = DrawStyle::fill_and_stroke_with_outline(width, color);
805        }
806        self
807    }
808}