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