Skip to main content

floem/view/
mod.rs

1//! # View and Widget Traits
2//! Views are self-contained components that can be composed together to create complex UIs.
3//! Views are the main building blocks of Floem.
4//!
5//! Views are structs that implement the [`View`] and [`Widget`] traits. Many of these structs will also contain a child field that also implements [`View`]. In this way, views can be composed together easily to create complex UIs. This is the most common way to build UIs in Floem. For more information on how to compose views check out the [views](crate::views) module.
6//!
7//! Creating a struct and manually implementing the [`View`] and [`Widget`] traits is typically only needed for building new widgets and for special cases. The rest of this module documentation is for help when manually implementing [`View`] and [`Widget`] on your own types.
8//!
9//!
10//! ## The View and Widget Traits
11//! The [`View`] trait is the trait that Floem uses to build  and display elements, and it builds on the [`Widget`] trait. The [`Widget`] trait contains the methods for implementing updates, styling, layout, events, and painting.
12//! Eventually, the goal is for Floem to integrate the [`Widget`] trait with other rust UI libraries so that the widget layer can be shared among all compatible UI libraries.
13//!
14//! ## State management
15//!
16//! For all reactive state that your type contains, either in the form of signals or derived signals, you need to process the changes within an effect.
17//! The most common pattern is to [`get`](floem_reactive::ReadSignal::get) the data in an effect and pass it in to `id.update_state()` and then handle that data in the `update` method of the View trait.
18//!
19//! For example a minimal slider might look like the following. First, we define the struct with the [`ViewData`] that contains the [`Id`].
20//! Then, we use a function to construct the slider. As part of this function we create an effect that will be re-run every time the signals in the  `percent` closure change.
21//! In the effect we send the change to the associated [`Id`]. This change can then be handled in the [`Widget::update`] method.
22//! ```rust
23//! use floem::ViewId;
24//! use floem::reactive::*;
25//!
26//! struct Slider {
27//!     id: ViewId,
28//! }
29//! pub fn slider(percent: impl Fn() -> f32 + 'static) -> Slider {
30//!    let id = ViewId::new();
31//!
32//!    // If the following effect is not created, and `percent` is accessed directly,
33//!    // `percent` will only be accessed a single time and will not be reactive.
34//!    // Therefore the following `create_effect` is necessary for reactivity.
35//!    create_effect(move |_| {
36//!        let percent = percent();
37//!        id.update_state(percent);
38//!    });
39//!    Slider {
40//!        id,
41//!    }
42//! }
43//! ```
44//!
45
46mod id;
47mod into_iter;
48pub(crate) mod stacking;
49pub(crate) mod state;
50mod storage;
51pub mod tuple;
52
53pub use id::{ViewId, process_pending_scope_reparents};
54pub use into_iter::*;
55pub use state::*;
56pub(crate) use storage::*;
57pub use tuple::*;
58
59use floem_reactive::{Effect, ReadSignal, RwSignal, Scope, SignalGet, UpdaterEffect};
60use peniko::kurbo::*;
61use smallvec::SmallVec;
62use std::any::Any;
63use std::cell::Cell;
64use std::hash::Hash;
65use std::rc::Rc;
66
67use crate::{
68    Renderer,
69    context::{EventCx, PaintCx, StyleCx, UpdateCx},
70    event::EventPropagation,
71    style::{LayoutProps, Style, StyleClassRef},
72    unit::PxPct,
73    views::{
74        DynamicView,
75        dyn_stack::{FxIndexSet, HashRun, diff},
76        dyn_view,
77    },
78};
79use state::ViewStyleProps;
80
81/// type erased [`View`]
82///
83/// Views in Floem are strongly typed. [`AnyView`] allows you to escape the strong typing by converting any type implementing [`View`] into the [`AnyView`] type.
84///
85/// ## Bad Example
86///```compile_fail
87/// use floem::views::*;
88/// use floem::widgets::*;
89/// use floem::reactive::{RwSignal, SignalGet};
90///
91/// let check = true;
92///
93/// container(if check == true {
94///     checkbox(|| true)
95/// } else {
96///     label(|| "no check".to_string())
97/// });
98/// ```
99/// The above example will fail to compile because `container` is expecting a single type implementing `View` so the if and
100/// the else must return the same type. However the branches return different types. The solution to this is to use the [`IntoView::into_any`] method
101/// to escape the strongly typed requirement.
102///
103/// ```
104/// use floem::reactive::{RwSignal, SignalGet};
105/// use floem::views::*;
106/// use floem::{IntoView, View};
107///
108/// let check = true;
109///
110/// container(if check == true {
111///     checkbox(|| true).into_any()
112/// } else {
113///     label(|| "no check".to_string()).into_any()
114/// });
115/// ```
116pub type AnyView = Box<dyn View>;
117
118/// Converts a value into a [`View`].
119///
120/// This trait can be implemented on types which can be built into another type that implements the `View` trait.
121///
122/// For example, `&str` implements `IntoView` by building a `text` view and can therefore be used directly in a View tuple.
123/// ```rust
124/// # use floem::reactive::*;
125/// # use floem::views::*;
126/// # use floem::IntoView;
127/// fn app_view() -> impl IntoView {
128///     v_stack(("Item One", "Item Two"))
129/// }
130/// ```
131/// Check out the [other types](#foreign-impls) that `IntoView` is implemented for.
132pub trait IntoView: Sized {
133    /// The final View type this converts to.
134    type V: View + 'static;
135
136    /// Intermediate type that has a [`ViewId`] before full view construction.
137    ///
138    /// For [`View`] types, this is `Self` (already has ViewId).
139    /// For primitives, this is [`LazyView<Self>`] (creates ViewId, defers view construction).
140    /// For tuples/vecs, this is the converted view type (eager conversion).
141    type Intermediate: HasViewId + IntoView<V = Self::V>;
142
143    /// Converts to the intermediate form which has a [`ViewId`].
144    ///
145    /// This is used by [`Decorators`](crate::views::Decorators) to get a [`ViewId`]
146    /// for applying styles before the final view is constructed.
147    fn into_intermediate(self) -> Self::Intermediate;
148
149    /// Converts the value into a [`View`].
150    fn into_view(self) -> Self::V {
151        self.into_intermediate().into_view()
152    }
153
154    /// Converts the value into a [`AnyView`].
155    fn into_any(self) -> AnyView {
156        Box::new(self.into_view())
157    }
158}
159
160/// A trait for types that have an associated [`ViewId`].
161///
162/// This is automatically implemented for all types that implement [`View`],
163/// and can be manually implemented for intermediate types like [`Pending`].
164pub trait HasViewId {
165    /// Returns the [`ViewId`] associated with this value.
166    fn view_id(&self) -> ViewId;
167}
168
169/// Blanket implementation of [`HasViewId`] for all [`View`] types.
170impl<V: View> HasViewId for V {
171    fn view_id(&self) -> ViewId {
172        self.id()
173    }
174}
175
176/// A trait for views that can accept children.
177///
178/// This provides a builder-pattern API for adding children to views,
179/// similar to GPUI's `ParentElement` trait. Both methods append to
180/// existing children rather than replacing them.
181///
182/// Views opt-in to this trait by implementing it. Not all views should
183/// have children (e.g., `Label`, `TextInput`), so there is no blanket
184/// implementation.
185///
186/// ## Example
187/// ```rust,ignore
188/// Stack::empty()
189///     .child(text("Header"))
190///     .children((0..5).map(|i| text(format!("Item {i}"))))
191///     .child(text("Footer"))
192/// ```
193pub trait ParentView: HasViewId + Sized {
194    /// Returns the scope associated with this view, if any.
195    ///
196    /// Views that need to provide context to children should:
197    /// 1. Create a scope in their constructor: `Scope::new()` or `Scope::current().create_child()`
198    /// 2. Provide context in that scope: `scope.provide_context(...)`
199    /// 3. Override this method to return that scope
200    ///
201    /// When this returns `Some(scope)`, the scope is stored on the view (via `set_scope()`)
202    /// so that descendants can find it via `ViewId::find_scope()`.
203    fn scope(&self) -> Option<Scope> {
204        None
205    }
206
207    /// Adds a single child to this view.
208    ///
209    /// The child is constructed lazily during the update cycle. The scope
210    /// is resolved at build time by walking up the view hierarchy to find
211    /// the nearest ancestor with a scope (via `ViewId::find_scope()`).
212    fn child(self, child: impl IntoView + 'static) -> Self {
213        // If this view provides a scope, store it for descendant lookup
214        if let Some(scope) = self.scope() {
215            self.view_id().set_scope(scope);
216        }
217        self.view_id().add_child_deferred(move || child.into_any());
218        self
219    }
220
221    /// Adds multiple children to this view.
222    ///
223    /// Accepts arrays, tuples, vectors, and iterators of views.
224    /// The children are constructed lazily during the update cycle. The scope
225    /// is resolved at build time by walking up the view hierarchy to find
226    /// the nearest ancestor with a scope (via `ViewId::find_scope()`).
227    fn children(self, children: impl IntoViewIter + 'static) -> Self {
228        // If this view provides a scope, store it for descendant lookup
229        if let Some(scope) = self.scope() {
230            self.view_id().set_scope(scope);
231        }
232        self.view_id()
233            .add_children_deferred(move || children.into_view_iter().collect());
234        self
235    }
236
237    /// Adds reactive children that update when signals change.
238    ///
239    /// The children function is called initially and re-called whenever
240    /// its reactive dependencies change, replacing all children.
241    ///
242    /// The scope is resolved at build time by walking up the view hierarchy
243    /// to find the nearest ancestor with a scope (via `ViewId::find_scope()`).
244    ///
245    /// ## Example
246    /// ```rust,ignore
247    /// use floem::prelude::*;
248    /// use floem::views::Stem;
249    ///
250    /// let items = RwSignal::new(vec!["a", "b", "c"]);
251    ///
252    /// Stem::new().derived_children(move || {
253    ///     items.get().into_iter().map(|item| text(item))
254    /// });
255    /// ```
256    fn derived_children<CF, C>(self, children_fn: CF) -> Self
257    where
258        CF: Fn() -> C + 'static,
259        C: IntoViewIter + 'static,
260    {
261        let id = self.view_id();
262
263        // If this view provides a scope, store it for descendant lookup
264        if let Some(scope) = self.scope() {
265            id.set_scope(scope);
266        }
267
268        // Defer the entire setup to message processing
269        // The scope is resolved via find_scope() when the message is processed
270        id.setup_reactive_children_deferred(move || {
271            let children_fn = Box::new(
272                Scope::current()
273                    .enter_child(move |_| children_fn().into_view_iter().collect::<Vec<_>>()),
274            );
275
276            let (initial_children, initial_scope) = UpdaterEffect::new(
277                move || children_fn(()),
278                move |(new_children, new_scope): (Vec<AnyView>, Scope)| {
279                    // Dispose old scope and remove old children
280                    if let Some(old_scope) = id.take_children_scope() {
281                        old_scope.dispose();
282                    }
283                    // Set new children and store new scope
284                    id.set_children_vec(new_children);
285                    id.set_children_scope(new_scope);
286                    id.request_all();
287                },
288            );
289
290            // Set initial children and scope
291            id.set_children_vec(initial_children);
292            id.set_children_scope(initial_scope);
293        });
294        self
295    }
296
297    /// Adds a single reactive child with explicit state tracking.
298    ///
299    /// Takes two closures: one that tracks reactive dependencies and returns state,
300    /// and another that builds a view from that state. When the state changes,
301    /// only the managed child is replaced while preserving any other siblings.
302    /// This allows mixing static children with a reactive child.
303    ///
304    /// For a simpler API where a single closure both tracks deps and returns a view,
305    /// use `derived_child` instead.
306    ///
307    /// The scope is resolved at build time by walking up the view hierarchy
308    /// to find the nearest ancestor with a scope (via `ViewId::find_scope()`).
309    ///
310    /// ## Example
311    /// ```rust,ignore
312    /// use floem::prelude::*;
313    /// use floem::views::Stem;
314    ///
315    /// #[derive(Clone)]
316    /// enum ViewType { One, Two }
317    ///
318    /// let view_type = RwSignal::new(ViewType::One);
319    ///
320    /// Stem::new().stateful_child(
321    ///     move || view_type.get(),
322    ///     |value| match value {
323    ///         ViewType::One => text("One"),
324    ///         ViewType::Two => text("Two"),
325    ///     }
326    /// );
327    /// ```
328    fn stateful_child<S, SF, CF, V>(self, state_fn: SF, child_fn: CF) -> Self
329    where
330        SF: Fn() -> S + 'static,
331        CF: Fn(S) -> V + 'static,
332        V: IntoView + 'static,
333        S: 'static,
334    {
335        let id = self.view_id();
336
337        // If this view provides a scope, store it for descendant lookup
338        if let Some(scope) = self.scope() {
339            id.set_scope(scope);
340        }
341
342        // Track the managed child's ViewId and Scope (append/track mode)
343        let managed_child: Rc<Cell<Option<(ViewId, Scope)>>> = Rc::new(Cell::new(None));
344        let managed_child_clone = managed_child.clone();
345
346        // Defer the entire setup to message processing
347        id.setup_reactive_children_deferred(move || {
348            let child_fn: Rc<dyn Fn(S) -> (AnyView, Scope)> =
349                Rc::new(Scope::current().enter_child(move |state: S| child_fn(state).into_any()));
350
351            let child_fn_for_effect = child_fn.clone();
352            let managed_child_for_effect = managed_child_clone.clone();
353
354            let initial_state = UpdaterEffect::new(state_fn, move |new_state: S| {
355                // Get and dispose old managed child
356                if let Some((old_child_id, old_scope)) = managed_child_for_effect.take() {
357                    old_scope.dispose();
358
359                    // Remove old child from children list
360                    let mut current_children = id.children();
361                    let pos = current_children
362                        .iter()
363                        .position(|&c| c == old_child_id)
364                        .unwrap_or(current_children.len());
365
366                    if pos < current_children.len() {
367                        current_children.remove(pos);
368                        id.request_remove_views(vec![old_child_id]);
369                    }
370
371                    // Create and insert new child
372                    let (new_child, new_scope) = child_fn_for_effect(new_state);
373                    let new_child_id = new_child.id();
374                    new_child_id.set_parent(id);
375                    new_child_id.set_view(new_child);
376
377                    let insert_pos = pos.min(current_children.len());
378                    current_children.insert(insert_pos, new_child_id);
379
380                    id.set_children_ids(current_children);
381                    managed_child_for_effect.set(Some((new_child_id, new_scope)));
382                }
383                id.request_all();
384            });
385
386            // Create initial child and append to existing children
387            let (initial_child, initial_scope) = child_fn(initial_state);
388            let child_id = initial_child.id();
389            child_id.set_parent(id);
390            child_id.set_view(initial_child);
391
392            let mut current_children = id.children();
393            current_children.push(child_id);
394            id.set_children_ids(current_children);
395
396            managed_child_clone.set(Some((child_id, initial_scope)));
397        });
398        self
399    }
400
401    /// Adds keyed reactive children that efficiently update when signals change.
402    ///
403    /// Unlike `derived_children` which recreates all children on every update,
404    /// `keyed_children` uses keys to identify items and only creates/removes
405    /// children that actually changed. Views for unchanged items are reused.
406    ///
407    /// The scope is resolved at build time by walking up the view hierarchy
408    /// to find the nearest ancestor with a scope (via `ViewId::find_scope()`).
409    ///
410    /// ## Arguments
411    /// - `items_fn`: A function that returns an iterator of items
412    /// - `key_fn`: A function that extracts a unique key from each item
413    /// - `view_fn`: A function that creates a view from an item
414    ///
415    /// ## Example
416    /// ```rust,ignore
417    /// use floem::prelude::*;
418    /// use floem::views::Stem;
419    ///
420    /// let items = RwSignal::new(vec!["a", "b", "c"]);
421    ///
422    /// Stem::new().keyed_children(
423    ///     move || items.get(),
424    ///     |item| *item,  // key by the item itself
425    ///     |item| text(item),
426    /// );
427    /// ```
428    fn keyed_children<IF, I, T, K, KF, VF, V>(self, items_fn: IF, key_fn: KF, view_fn: VF) -> Self
429    where
430        IF: Fn() -> I + 'static,
431        I: IntoIterator<Item = T>,
432        KF: Fn(&T) -> K + 'static,
433        K: Eq + Hash + 'static,
434        VF: Fn(T) -> V + 'static,
435        V: IntoView + 'static,
436        T: 'static,
437    {
438        let id = self.view_id();
439
440        // If this view provides a scope, store it for descendant lookup
441        if let Some(scope) = self.scope() {
442            id.set_scope(scope);
443        }
444
445        // Defer the entire setup to message processing
446        // The scope is resolved via find_scope() when the message is processed
447        id.setup_reactive_children_deferred(move || {
448            // Wrap view_fn to create scoped views - each child gets its own scope
449            let view_fn = Scope::current().enter_child(move |item: T| view_fn(item).into_any());
450
451            // Initialize keyed children state
452            id.set_keyed_children(Vec::new());
453
454            Effect::new(move |prev_hash_run: Option<HashRun<FxIndexSet<K>>>| {
455                let items: SmallVec<[T; 128]> = items_fn().into_iter().collect();
456                let new_keys: FxIndexSet<K> = items.iter().map(&key_fn).collect();
457
458                // Take current children state
459                let mut children: Vec<Option<(ViewId, Scope)>> = id
460                    .take_keyed_children()
461                    .unwrap_or_default()
462                    .into_iter()
463                    .map(Some)
464                    .collect();
465
466                if let Some(HashRun(prev_keys)) = prev_hash_run {
467                    // Compute diff between old and new keys
468                    let mut diff_result = diff::<K, T>(&prev_keys, &new_keys);
469
470                    // Prepare items for added entries
471                    let mut items: SmallVec<[Option<T>; 128]> =
472                        items.into_iter().map(Some).collect();
473                    for added in &mut diff_result.added {
474                        added.view = items[added.at].take();
475                    }
476
477                    // Apply diff operations (returns views to remove)
478                    let views_to_remove =
479                        apply_keyed_diff(id, &mut children, diff_result, &view_fn);
480
481                    // Request removal via message (processed during update phase)
482                    id.request_remove_views(views_to_remove);
483                } else {
484                    // First run - create all children
485                    for item in items {
486                        let (view, scope) = view_fn(item);
487                        let child_id = view.id();
488                        child_id.set_parent(id);
489                        child_id.set_view(view);
490                        children.push(Some((child_id, scope)));
491                    }
492                }
493
494                // Update the actual children list
495                let children_ids: Vec<ViewId> = children
496                    .iter()
497                    .filter_map(|c| Some(c.as_ref()?.0))
498                    .collect();
499                id.set_children_ids(children_ids);
500
501                // Store updated children state (convert back from Option)
502                let children_vec: Vec<(ViewId, Scope)> = children.into_iter().flatten().collect();
503                id.set_keyed_children(children_vec);
504
505                id.request_all();
506                HashRun(new_keys)
507            });
508        });
509
510        self
511    }
512
513    /// Adds a single reactive child that updates when signals change.
514    ///
515    /// The closure both tracks reactive dependencies and returns a view.
516    /// When any tracked dependency changes, only the managed child is replaced
517    /// while preserving any other siblings. This allows mixing static children
518    /// with a reactive child.
519    ///
520    /// This is consistent with `derived_children` which also takes a single
521    /// closure. For explicit state tracking with separate closures, use
522    /// `stateful_child` instead.
523    ///
524    /// The scope is resolved at build time by walking up the view hierarchy
525    /// to find the nearest ancestor with a scope (via `ViewId::find_scope()`).
526    ///
527    /// ## Example
528    /// ```rust,ignore
529    /// use floem::prelude::*;
530    /// use floem::views::Stem;
531    ///
532    /// #[derive(Clone)]
533    /// enum ViewType { One, Two }
534    ///
535    /// let view_type = RwSignal::new(ViewType::One);
536    ///
537    /// Stem::new().derived_child(move || {
538    ///     match view_type.get() {  // tracking happens here
539    ///         ViewType::One => text("One").into_any(),
540    ///         ViewType::Two => text("Two").into_any(),
541    ///     }
542    /// });
543    /// ```
544    fn derived_child<CF, V>(self, child_fn: CF) -> Self
545    where
546        CF: Fn() -> V + 'static,
547        V: IntoView + 'static,
548    {
549        let id = self.view_id();
550
551        // If this view provides a scope, store it for descendant lookup
552        if let Some(scope) = self.scope() {
553            id.set_scope(scope);
554        }
555
556        // Track the managed child's ViewId and Scope (append/track mode)
557        let managed_child: Rc<Cell<Option<(ViewId, Scope)>>> = Rc::new(Cell::new(None));
558        let managed_child_clone = managed_child.clone();
559
560        // Defer the entire setup to message processing
561        id.setup_reactive_children_deferred(move || {
562            let child_fn =
563                Box::new(Scope::current().enter_child(move |_: ()| child_fn().into_any()));
564
565            let managed_child_for_effect = managed_child_clone.clone();
566
567            let (initial_child, initial_scope) = UpdaterEffect::new(
568                move || child_fn(()),
569                move |(new_child, new_scope): (AnyView, Scope)| {
570                    // Get and dispose old managed child
571                    if let Some((old_child_id, old_scope)) = managed_child_for_effect.take() {
572                        old_scope.dispose();
573
574                        // Remove old child from children list
575                        let mut current_children = id.children();
576                        let pos = current_children
577                            .iter()
578                            .position(|&c| c == old_child_id)
579                            .unwrap_or(current_children.len());
580
581                        if pos < current_children.len() {
582                            current_children.remove(pos);
583                            id.request_remove_views(vec![old_child_id]);
584                        }
585
586                        // Add new child at the same position (or end if not found)
587                        let new_child_id = new_child.id();
588                        new_child_id.set_parent(id);
589                        new_child_id.set_view(new_child);
590
591                        // Insert at the old position or append
592                        let insert_pos = pos.min(current_children.len());
593                        current_children.insert(insert_pos, new_child_id);
594
595                        id.set_children_ids(current_children);
596                        managed_child_for_effect.set(Some((new_child_id, new_scope)));
597                    }
598                    id.request_all();
599                },
600            );
601
602            // Append initial child to existing children
603            let child_id = initial_child.id();
604            child_id.set_parent(id);
605            child_id.set_view(initial_child);
606
607            let mut current_children = id.children();
608            current_children.push(child_id);
609            id.set_children_ids(current_children);
610
611            managed_child_clone.set(Some((child_id, initial_scope)));
612        });
613        self
614    }
615}
616
617/// Apply keyed diff operations to children.
618///
619/// Returns a list of ViewIds to remove (removal is deferred via message).
620fn apply_keyed_diff<T, VF>(
621    parent_id: ViewId,
622    children: &mut Vec<Option<(ViewId, Scope)>>,
623    diff: crate::views::dyn_stack::Diff<T>,
624    view_fn: &VF,
625) -> Vec<ViewId>
626where
627    VF: Fn(T) -> (AnyView, Scope),
628{
629    use crate::views::dyn_stack::{DiffOpAdd, DiffOpMove, DiffOpRemove};
630
631    let mut views_to_remove = Vec::new();
632
633    // Resize children if needed
634    if diff.added.len() > diff.removed.len() {
635        let target_size = children.len() + diff.added.len() - diff.removed.len();
636        children.resize_with(target_size, || None);
637    }
638
639    // Items to move (deferred to avoid overwriting)
640    let mut items_to_move = Vec::with_capacity(diff.moved.len());
641
642    // 1. Clear all if requested
643    if diff.clear {
644        for child in &mut *children {
645            if let Some((view_id, scope)) = child.take() {
646                views_to_remove.push(view_id);
647                scope.dispose();
648            }
649        }
650    }
651
652    // 2. Remove items (collect for deferred removal)
653    for DiffOpRemove { at } in diff.removed {
654        if let Some((view_id, scope)) = children[at].take() {
655            views_to_remove.push(view_id);
656            scope.dispose();
657        }
658    }
659
660    // 3. Collect items to move
661    for DiffOpMove { from, to } in diff.moved {
662        if let Some(item) = children[from].take() {
663            items_to_move.push((to, item));
664        }
665    }
666
667    // 4. Add new items
668    for DiffOpAdd { at, view } in diff.added {
669        if let Some(item) = view {
670            let (view, scope) = view_fn(item);
671            let child_id = view.id();
672            child_id.set_parent(parent_id);
673            child_id.set_view(view);
674            children[at] = Some((child_id, scope));
675        }
676    }
677
678    // 5. Apply moves
679    for (to, item) in items_to_move {
680        children[to] = Some(item);
681    }
682
683    // 6. Remove holes
684    children.retain(|c| c.is_some());
685
686    views_to_remove
687}
688
689/// A wrapper type for lazy view construction.
690///
691/// `LazyView<T>` wraps a value that will eventually be converted into a [`View`],
692/// but creates its [`ViewId`] eagerly. This allows decorators to be applied
693/// before the actual view is constructed.
694///
695/// ## Example
696/// ```rust
697/// use floem::views::*;
698/// use floem::{IntoView, LazyView};
699///
700/// // The ViewId is created when LazyView is constructed,
701/// // but the Label is only created when into_view() is called
702/// let lazy = LazyView::new("Hello");
703/// let view = lazy.style(|s| s.padding(10.0)).into_view();
704/// ```
705pub struct LazyView<T> {
706    /// The ViewId created eagerly for this lazy view.
707    pub id: ViewId,
708    /// The content that will be converted into a view.
709    pub content: T,
710}
711
712impl<T> LazyView<T> {
713    /// Creates a new `LazyView` wrapper with an eagerly-created [`ViewId`].
714    pub fn new(content: T) -> Self {
715        Self {
716            id: ViewId::new(),
717            content,
718        }
719    }
720
721    /// Creates a new `LazyView` wrapper with an existing [`ViewId`].
722    pub fn with_id(id: ViewId, content: T) -> Self {
723        Self { id, content }
724    }
725}
726
727impl<T> HasViewId for LazyView<T> {
728    fn view_id(&self) -> ViewId {
729        self.id
730    }
731}
732
733impl<IV: IntoView + 'static> IntoView for Box<dyn Fn() -> IV> {
734    type V = DynamicView;
735    type Intermediate = DynamicView;
736
737    fn into_intermediate(self) -> Self::Intermediate {
738        dyn_view(self)
739    }
740}
741
742impl<T: IntoView + Clone + 'static> IntoView for RwSignal<T> {
743    type V = DynamicView;
744    type Intermediate = DynamicView;
745
746    fn into_intermediate(self) -> Self::Intermediate {
747        dyn_view(move || self.get())
748    }
749}
750
751impl<T: IntoView + Clone + 'static> IntoView for ReadSignal<T> {
752    type V = DynamicView;
753    type Intermediate = DynamicView;
754
755    fn into_intermediate(self) -> Self::Intermediate {
756        dyn_view(move || self.get())
757    }
758}
759
760impl<VW: View + 'static> IntoView for VW {
761    type V = VW;
762    type Intermediate = Self;
763
764    fn into_intermediate(self) -> Self::Intermediate {
765        self
766    }
767
768    fn into_view(self) -> Self::V {
769        self
770    }
771}
772
773impl IntoView for i32 {
774    type V = crate::views::Label;
775    type Intermediate = LazyView<i32>;
776
777    fn into_intermediate(self) -> Self::Intermediate {
778        LazyView::new(self)
779    }
780}
781
782impl IntoView for usize {
783    type V = crate::views::Label;
784    type Intermediate = LazyView<usize>;
785
786    fn into_intermediate(self) -> Self::Intermediate {
787        LazyView::new(self)
788    }
789}
790
791impl IntoView for &str {
792    type V = crate::views::Label;
793    type Intermediate = LazyView<String>;
794
795    fn into_intermediate(self) -> Self::Intermediate {
796        LazyView::new(self.to_string())
797    }
798}
799
800impl IntoView for String {
801    type V = crate::views::Label;
802    type Intermediate = LazyView<String>;
803
804    fn into_intermediate(self) -> Self::Intermediate {
805        LazyView::new(self)
806    }
807}
808
809impl IntoView for () {
810    type V = crate::views::Empty;
811    type Intermediate = LazyView<()>;
812
813    fn into_intermediate(self) -> Self::Intermediate {
814        LazyView::new(self)
815    }
816}
817
818impl<IV: IntoView + 'static> IntoView for Vec<IV> {
819    type V = crate::views::Stack;
820    type Intermediate = LazyView<Vec<IV>>;
821
822    fn into_intermediate(self) -> Self::Intermediate {
823        LazyView::new(self)
824    }
825}
826
827impl<IV: IntoView + 'static> IntoView for LazyView<Vec<IV>> {
828    type V = crate::views::Stack;
829    type Intermediate = Self;
830
831    fn into_intermediate(self) -> Self::Intermediate {
832        self
833    }
834
835    fn into_view(self) -> Self::V {
836        crate::views::from_iter_with_id(self.id, self.content, None)
837    }
838}
839
840// IntoView implementations for LazyView<T> types
841// These use the pre-created ViewId from LazyView
842// LazyView is its own Intermediate since it already has a ViewId
843
844impl IntoView for LazyView<i32> {
845    type V = crate::views::Label;
846    type Intermediate = Self;
847
848    fn into_intermediate(self) -> Self::Intermediate {
849        self
850    }
851
852    fn into_view(self) -> Self::V {
853        crate::views::Label::with_id(self.id, self.content)
854    }
855}
856
857impl IntoView for LazyView<usize> {
858    type V = crate::views::Label;
859    type Intermediate = Self;
860
861    fn into_intermediate(self) -> Self::Intermediate {
862        self
863    }
864
865    fn into_view(self) -> Self::V {
866        crate::views::Label::with_id(self.id, self.content)
867    }
868}
869
870impl IntoView for LazyView<&str> {
871    type V = crate::views::Label;
872    type Intermediate = Self;
873
874    fn into_intermediate(self) -> Self::Intermediate {
875        self
876    }
877
878    fn into_view(self) -> Self::V {
879        crate::views::Label::with_id(self.id, self.content)
880    }
881}
882
883impl IntoView for LazyView<String> {
884    type V = crate::views::Label;
885    type Intermediate = Self;
886
887    fn into_intermediate(self) -> Self::Intermediate {
888        self
889    }
890
891    fn into_view(self) -> Self::V {
892        crate::views::Label::with_id(self.id, self.content)
893    }
894}
895
896impl IntoView for LazyView<()> {
897    type V = crate::views::Empty;
898    type Intermediate = Self;
899
900    fn into_intermediate(self) -> Self::Intermediate {
901        self
902    }
903
904    fn into_view(self) -> Self::V {
905        crate::views::Empty::with_id(self.id)
906    }
907}
908
909/// The View trait contains the methods for implementing updates, styling, layout, events, and painting.
910///
911/// The [`id`](View::id) method must be implemented.
912/// The other methods may be implemented as necessary to implement the functionality of the View.
913/// ## State Management in a Custom View
914///
915/// For all reactive state that your type contains, either in the form of signals or derived signals, you need to process the changes within an effect.
916/// The most common pattern is to [`get`](floem_reactive::SignalGet::get) the data in an effect and pass it in to `id.update_state()` and then handle that data in the `update` method of the `View` trait.
917///
918/// For example a minimal slider might look like the following. First, we define the struct that contains the [`ViewId`](crate::ViewId).
919/// Then, we use a function to construct the slider. As part of this function we create an effect that will be re-run every time the signals in the  `percent` closure change.
920/// In the effect we send the change to the associated [`ViewId`](crate::ViewId). This change can then be handled in the [`View::update`](crate::View::update) method.
921/// ```rust
922/// # use floem::{*, views::*, reactive::*};
923///
924/// struct Slider {
925///     id: ViewId,
926///     percent: f32,
927/// }
928/// pub fn slider(percent: impl Fn() -> f32 + 'static) -> Slider {
929///     let id = ViewId::new();
930///
931///     // If the following effect is not created, and `percent` is accessed directly,
932///     // `percent` will only be accessed a single time and will not be reactive.
933///     // Therefore the following `create_effect` is necessary for reactivity.
934///     create_effect(move |_| {
935///         let percent = percent();
936///         id.update_state(percent);
937///     });
938///     Slider { id, percent: 0.0 }
939/// }
940/// impl View for Slider {
941///     fn id(&self) -> ViewId {
942///         self.id
943///     }
944///
945///     fn update(&mut self, cx: &mut floem::context::UpdateCx, state: Box<dyn std::any::Any>) {
946///         if let Ok(percent) = state.downcast::<f32>() {
947///             self.percent = *percent;
948///             self.id.request_layout();
949///         }
950///     }
951/// }
952/// ```
953pub trait View {
954    fn id(&self) -> ViewId;
955
956    fn view_style(&self) -> Option<Style> {
957        None
958    }
959
960    fn view_class(&self) -> Option<StyleClassRef> {
961        None
962    }
963
964    fn debug_name(&self) -> std::borrow::Cow<'static, str> {
965        core::any::type_name::<Self>().into()
966    }
967
968    /// Use this method to react to changes in view-related state.
969    /// You will usually send state to this hook manually using the `View`'s `Id` handle
970    ///
971    /// ```ignore
972    /// self.id.update_state(SomeState)
973    /// ```
974    ///
975    /// You are in charge of downcasting the state to the expected type.
976    ///
977    /// If the update needs other passes to run you're expected to call
978    /// `_cx.window_state_mut().request_changes`.
979    fn update(&mut self, cx: &mut UpdateCx, state: Box<dyn Any>) {
980        // these are here to just ignore these arguments in the default case
981        let _ = cx;
982        let _ = state;
983    }
984
985    /// Use this method to style the view's children.
986    ///
987    /// If the style changes needs other passes to run you're expected to call
988    /// `cx.window_state.style_dirty.insert(view_id)`.
989    fn style_pass(&mut self, cx: &mut StyleCx<'_>) {
990        let _ = cx;
991    }
992
993    /// Handles an event during the **capture phase** of event propagation.
994    ///
995    /// The capture phase runs **from the root of the dispatch path down toward
996    /// the target element**, before the target itself receives the event. This
997    /// method is invoked for each element encountered during that downward
998    /// traversal.
999    ///
1000    /// In contrast, [`Self::event`] runs during the **target and bubble phases**,
1001    /// after the event reaches the target and begins propagating back up the
1002    /// ancestor chain.
1003    ///
1004    /// Capture handlers are useful when an element must observe or influence an
1005    /// event **before the target processes it**. This can be necessary when:
1006    ///
1007    /// - the ancestor needs an opportunity to intercept or block the event before
1008    ///   it reaches the target
1009    /// - the ancestor must coordinate state across a subtree before the target
1010    ///   reacts
1011    /// - the event must be transformed or redirected before normal handling occurs
1012    ///
1013    /// Returning [`EventPropagation::Stop`] prevents the event from propagating to
1014    /// additional elements further along the dispatch path. The current element’s
1015    /// handlers will still complete.
1016    ///
1017    /// This differs from [`EventCx::stop_immediate_propagation`], which halts
1018    /// dispatch **immediately**, preventing any remaining handlers on the current
1019    /// element from running.
1020    ///
1021    /// Calling [`EventCx::prevent_default`] marks the event as having its default
1022    /// behavior suppressed. This flag is shared across all phases of dispatch,
1023    /// allowing capture handlers to prevent the default action before the event
1024    /// reaches the target.
1025    ///
1026    /// # See also
1027    ///
1028    /// - [`Self::event`] — handles the same event during the target and bubble phases.
1029    ///
1030    /// # Default behavior
1031    ///
1032    /// The default implementation ignores the event and allows propagation to
1033    /// continue.
1034    fn event_capture(&mut self, _cx: &mut EventCx) -> EventPropagation {
1035        EventPropagation::Continue
1036    }
1037
1038    /// Handles an event during the **target and bubble phases** of event propagation.
1039    ///
1040    /// After the capture phase completes, the event is delivered to the **target
1041    /// element** and then propagates upward through its ancestors. This method is
1042    /// invoked for the target and for each ancestor encountered during that upward
1043    /// traversal.
1044    ///
1045    /// In contrast, [`Self::event_capture`] runs earlier during the **capture phase**
1046    /// while the event travels downward toward the target.
1047    ///
1048    /// Returning [`EventPropagation::Stop`] prevents the event from propagating to
1049    /// additional elements further along the dispatch path. The current element’s
1050    /// handlers will still complete.
1051    ///
1052    /// This differs from [`EventCx::stop_immediate_propagation`], which stops
1053    /// dispatch immediately and prevents any remaining handlers on the current
1054    /// element from executing.
1055    ///
1056    /// Calling [`EventCx::prevent_default`] suppresses the event’s default behavior.
1057    /// This flag is shared across all phases of dispatch and can be set in either
1058    /// [`Self::event_capture`] or [`Self::event`].
1059    ///
1060    /// # Propagation behavior
1061    ///
1062    /// Event dispatch proceeds in this order:
1063    ///
1064    /// 1. [`Self::event_capture`] on ancestors from root → target
1065    /// 2. [`Self::event`] on the target
1066    /// 3. [`Self::event`] on ancestors from target → root
1067    ///
1068    /// Propagation may be stopped at any point by returning
1069    /// [`EventPropagation::Stop`] or by calling
1070    /// [`EventCx::stop_immediate_propagation`].
1071    ///
1072    /// # Default behavior
1073    ///
1074    /// The default implementation ignores the event and allows propagation to
1075    /// continue.
1076    fn event(&mut self, _cx: &mut EventCx) -> EventPropagation {
1077        EventPropagation::Continue
1078    }
1079
1080    /// `View`-specific implementation. Called during paint traversal for this view.
1081    /// Children are painted automatically by Floem.
1082    /// Views should only paint their own content (backgrounds, borders, custom drawing).
1083    fn paint(&mut self, cx: &mut PaintCx) {
1084        let _ = cx;
1085        // Default implementation: no custom painting
1086        // Children are painted automatically by traversal
1087    }
1088
1089    fn post_paint(&mut self, cx: &mut PaintCx) {
1090        let _ = cx;
1091    }
1092}
1093
1094impl View for Box<dyn View> {
1095    fn id(&self) -> ViewId {
1096        (**self).id()
1097    }
1098
1099    fn view_style(&self) -> Option<Style> {
1100        (**self).view_style()
1101    }
1102
1103    fn view_class(&self) -> Option<StyleClassRef> {
1104        (**self).view_class()
1105    }
1106
1107    fn debug_name(&self) -> std::borrow::Cow<'static, str> {
1108        (**self).debug_name()
1109    }
1110
1111    fn update(&mut self, cx: &mut UpdateCx, state: Box<dyn Any>) {
1112        (**self).update(cx, state)
1113    }
1114
1115    fn style_pass(&mut self, cx: &mut StyleCx) {
1116        (**self).style_pass(cx)
1117    }
1118
1119    fn event_capture(&mut self, cx: &mut EventCx) -> EventPropagation {
1120        (**self).event_capture(cx)
1121    }
1122
1123    fn event(&mut self, cx: &mut EventCx) -> EventPropagation {
1124        (**self).event(cx)
1125    }
1126
1127    fn paint(&mut self, cx: &mut PaintCx) {
1128        (**self).paint(cx)
1129    }
1130
1131    fn post_paint(&mut self, cx: &mut PaintCx) {
1132        (**self).paint(cx)
1133    }
1134}
1135
1136pub(crate) fn border_radius(radius: crate::unit::PxPct, size: f64) -> f64 {
1137    match radius {
1138        crate::unit::PxPct::Px(px) => px,
1139        crate::unit::PxPct::Pct(pct) => size * (pct / 100.),
1140    }
1141}
1142
1143fn border_to_radii_view(style: &ViewStyleProps, size: Size) -> RoundedRectRadii {
1144    let border_radii = style.border_radius();
1145    RoundedRectRadii {
1146        top_left: border_radius(
1147            border_radii.top_left.unwrap_or(PxPct::Px(0.0)),
1148            size.min_side(),
1149        ),
1150        top_right: border_radius(
1151            border_radii.top_right.unwrap_or(PxPct::Px(0.0)),
1152            size.min_side(),
1153        ),
1154        bottom_left: border_radius(
1155            border_radii.bottom_left.unwrap_or(PxPct::Px(0.0)),
1156            size.min_side(),
1157        ),
1158        bottom_right: border_radius(
1159            border_radii.bottom_right.unwrap_or(PxPct::Px(0.0)),
1160            size.min_side(),
1161        ),
1162    }
1163}
1164
1165pub(crate) fn paint_bg(cx: &mut PaintCx, style: &ViewStyleProps, rect: Rect) {
1166    let radii = border_to_radii_view(style, rect.size());
1167    if radii_max(radii) > 0.0 {
1168        paint_box_shadow(cx, style, rect, Some(radii));
1169        let bg = match style.background() {
1170            Some(color) => color,
1171            None => return,
1172        };
1173        let rounded_rect = rect.to_rounded_rect(radii);
1174        cx.fill(&rounded_rect, &bg, 0.0);
1175    } else {
1176        paint_box_shadow(cx, style, rect, None);
1177        let bg = match style.background() {
1178            Some(color) => color,
1179            None => return,
1180        };
1181        cx.fill(&rect, &bg, 0.0);
1182    }
1183}
1184
1185fn paint_box_shadow(
1186    cx: &mut PaintCx,
1187    style: &ViewStyleProps,
1188    rect: Rect,
1189    rect_radius: Option<RoundedRectRadii>,
1190) {
1191    for shadow in &style.shadow() {
1192        let min = rect.size().min_side();
1193        let left_offset = match shadow.left_offset {
1194            crate::unit::PxPct::Px(px) => px,
1195            crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
1196        };
1197        let right_offset = match shadow.right_offset {
1198            crate::unit::PxPct::Px(px) => px,
1199            crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
1200        };
1201        let top_offset = match shadow.top_offset {
1202            crate::unit::PxPct::Px(px) => px,
1203            crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
1204        };
1205        let bottom_offset = match shadow.bottom_offset {
1206            crate::unit::PxPct::Px(px) => px,
1207            crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
1208        };
1209        let spread = match shadow.spread {
1210            crate::unit::PxPct::Px(px) => px,
1211            crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
1212        };
1213        let blur_radius = match shadow.blur_radius {
1214            crate::unit::PxPct::Px(px) => px,
1215            crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
1216        };
1217        let inset = Insets::new(
1218            left_offset / 2.0,
1219            top_offset / 2.0,
1220            right_offset / 2.0,
1221            bottom_offset / 2.0,
1222        );
1223        let rect = rect.inflate(spread, spread).inset(inset);
1224        if let Some(radii) = rect_radius {
1225            let rounded_rect = RoundedRect::from_rect(rect, radii_add(radii, spread));
1226            cx.fill(&rounded_rect, shadow.color, blur_radius);
1227        } else {
1228            cx.fill(&rect, shadow.color, blur_radius);
1229        }
1230    }
1231}
1232pub(crate) fn paint_outline(cx: &mut PaintCx, style: &ViewStyleProps, rect: Rect) {
1233    if cx.is_vger() {
1234        let outline = &style.outline();
1235        if outline.width == 0. {
1236            return;
1237        }
1238        let half = outline.width / 2.0;
1239        let rect = rect.inflate(half, half);
1240        let border_radii = border_to_radii_view(style, rect.size());
1241        cx.stroke(
1242            &rect.to_rounded_rect(radii_add(border_radii, half)),
1243            &style.outline_color(),
1244            outline,
1245        );
1246        return;
1247    }
1248
1249    use crate::{
1250        paint::{BorderPath, BorderPathEvent},
1251        unit::Pct,
1252    };
1253
1254    let outlines = [
1255        (style.outline(), style.outline_color()),
1256        (style.outline(), style.outline_color()),
1257        (style.outline(), style.outline_color()),
1258        (style.outline(), style.outline_color()),
1259    ];
1260
1261    // Early return if no outlines
1262    if outlines.iter().any(|o| o.0.width == 0.0) {
1263        return;
1264    }
1265
1266    let outline_color = style.outline_color();
1267    let Pct(outline_progress) = style.outline_progress();
1268
1269    let half_width = outlines[0].0.width / 2.0;
1270    let rect = rect.inflate(half_width, half_width);
1271
1272    let radii = radii_map(border_to_radii_view(style, rect.size()), |r| {
1273        (r + half_width).max(0.0)
1274    });
1275
1276    let mut outline_path = BorderPath::new(rect, radii);
1277
1278    // Only create subsegment if needed
1279    if outline_progress < 100. {
1280        outline_path.subsegment(0.0..(outline_progress.clamp(0.0, 100.) / 100.));
1281    }
1282
1283    let mut current_path = Vec::new();
1284    for event in outline_path.path_elements(&outlines, 0.1) {
1285        match event {
1286            BorderPathEvent::PathElement(el) => current_path.push(el),
1287            BorderPathEvent::NewStroke(stroke) => {
1288                // Render current path with previous stroke if any
1289                if !current_path.is_empty() {
1290                    cx.stroke(&current_path.as_slice(), &outline_color, &stroke.0);
1291                    current_path.clear();
1292                }
1293            }
1294        }
1295    }
1296    assert!(current_path.is_empty());
1297}
1298
1299pub(crate) fn paint_border(
1300    cx: &mut PaintCx,
1301    layout_style: &LayoutProps,
1302    style: &ViewStyleProps,
1303    rect: Rect,
1304) {
1305    if cx.is_vger() {
1306        let border = layout_style.border();
1307
1308        let left = border.left.unwrap_or(Stroke::new(0.));
1309        let top = border.top.unwrap_or(Stroke::new(0.));
1310        let right = border.right.unwrap_or(Stroke::new(0.));
1311        let bottom = border.bottom.unwrap_or(Stroke::new(0.));
1312
1313        if left.width == top.width
1314            && top.width == right.width
1315            && right.width == bottom.width
1316            && bottom.width == left.width
1317            && left.width > 0.0
1318            && style.border_color().left.is_some()
1319            && style.border_color().top.is_some()
1320            && style.border_color().right.is_some()
1321            && style.border_color().bottom.is_some()
1322            && style.border_color().left == style.border_color().top
1323            && style.border_color().top == style.border_color().right
1324            && style.border_color().right == style.border_color().bottom
1325        {
1326            let half = left.width / 2.0;
1327            let rect = rect.inflate(-half, -half);
1328            let radii = border_to_radii_view(style, rect.size());
1329            if let Some(color) = style.border_color().left {
1330                if radii_max(radii) > 0.0 {
1331                    let radii = radii_map(radii, |r| (r - half).max(0.0));
1332                    cx.stroke(&rect.to_rounded_rect(radii), &color, &left);
1333                } else {
1334                    cx.stroke(&rect, &color, &left);
1335                }
1336            }
1337        } else {
1338            if left.width > 0.0
1339                && let Some(color) = style.border_color().left
1340            {
1341                let half = left.width / 2.0;
1342                cx.stroke(
1343                    &Line::new(Point::new(half, 0.0), Point::new(half, rect.height())),
1344                    &color,
1345                    &left,
1346                );
1347            }
1348            if right.width > 0.0
1349                && let Some(color) = style.border_color().right
1350            {
1351                let half = right.width / 2.0;
1352                cx.stroke(
1353                    &Line::new(
1354                        Point::new(rect.width() - half, 0.0),
1355                        Point::new(rect.width() - half, rect.height()),
1356                    ),
1357                    &color,
1358                    &right,
1359                );
1360            }
1361            if top.width > 0.0
1362                && let Some(color) = style.border_color().top
1363            {
1364                let half = top.width / 2.0;
1365                cx.stroke(
1366                    &Line::new(Point::new(0.0, half), Point::new(rect.width(), half)),
1367                    &color,
1368                    &top,
1369                );
1370            }
1371            if bottom.width > 0.0
1372                && let Some(color) = style.border_color().bottom
1373            {
1374                let half = bottom.width / 2.0;
1375                cx.stroke(
1376                    &Line::new(
1377                        Point::new(0.0, rect.height() - half),
1378                        Point::new(rect.width(), rect.height() - half),
1379                    ),
1380                    &color,
1381                    &bottom,
1382                );
1383            }
1384        }
1385        return;
1386    }
1387
1388    use crate::{
1389        paint::{BorderPath, BorderPathEvent},
1390        unit::Pct,
1391    };
1392
1393    let border = layout_style.border();
1394    let borders = [
1395        (
1396            border.top.unwrap_or(Stroke::new(0.)),
1397            style.border_color().top.unwrap_or_default(),
1398        ),
1399        (
1400            border.right.unwrap_or(Stroke::new(0.)),
1401            style.border_color().right.unwrap_or_default(),
1402        ),
1403        (
1404            border.bottom.unwrap_or(Stroke::new(0.)),
1405            style.border_color().bottom.unwrap_or_default(),
1406        ),
1407        (
1408            border.left.unwrap_or(Stroke::new(0.)),
1409            style.border_color().left.unwrap_or_default(),
1410        ),
1411    ];
1412
1413    // Early return if no borders
1414    if borders.iter().all(|b| b.0.width == 0.0) {
1415        return;
1416    }
1417
1418    let Pct(border_progress) = style.border_progress();
1419
1420    let half_width = borders[0].0.width / 2.0;
1421    let rect = rect.inflate(-half_width, -half_width);
1422
1423    let radii = radii_map(border_to_radii_view(style, rect.size()), |r| {
1424        (r - half_width).max(0.0)
1425    });
1426
1427    let mut border_path = BorderPath::new(rect, radii);
1428
1429    // Only create subsegment if needed
1430    if border_progress < 100. {
1431        border_path.subsegment(0.0..(border_progress.clamp(0.0, 100.) / 100.));
1432    }
1433
1434    // optimize for maximum which is 12 paths and a single move to
1435    let mut current_path = smallvec::SmallVec::<[_; 13]>::new();
1436    for event in border_path.path_elements(&borders, 0.1) {
1437        match event {
1438            BorderPathEvent::PathElement(el) => {
1439                if !current_path.is_empty() && matches!(el, PathEl::MoveTo(_)) {
1440                    // extra move to's will mess up dashed patterns
1441                    continue;
1442                }
1443                current_path.push(el)
1444            }
1445            BorderPathEvent::NewStroke(stroke) => {
1446                // Render current path with previous stroke if any
1447                if !current_path.is_empty() && stroke.0.width > 0. {
1448                    cx.stroke(&current_path.as_slice(), &stroke.1, &stroke.0);
1449                    current_path.clear();
1450                } else if stroke.0.width == 0. {
1451                    current_path.clear();
1452                }
1453            }
1454        }
1455    }
1456    assert!(current_path.is_empty());
1457}
1458
1459// Helper functions for futzing with RoundedRectRadii. These should probably be in kurbo.
1460
1461fn radii_map(radii: RoundedRectRadii, f: impl Fn(f64) -> f64) -> RoundedRectRadii {
1462    RoundedRectRadii {
1463        top_left: f(radii.top_left),
1464        top_right: f(radii.top_right),
1465        bottom_left: f(radii.bottom_left),
1466        bottom_right: f(radii.bottom_right),
1467    }
1468}
1469
1470pub(crate) const fn radii_max(radii: RoundedRectRadii) -> f64 {
1471    radii
1472        .top_left
1473        .max(radii.top_right)
1474        .max(radii.bottom_left)
1475        .max(radii.bottom_right)
1476}
1477
1478fn radii_add(radii: RoundedRectRadii, offset: f64) -> RoundedRectRadii {
1479    radii_map(radii, |r| r + offset)
1480}