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::Length,
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(
1137    radius: crate::unit::Length,
1138    size: f64,
1139    resolve_cx: &crate::unit::FontSizeCx,
1140) -> f64 {
1141    radius.resolve(size, resolve_cx)
1142}
1143
1144fn border_to_radii_view(
1145    style: &ViewStyleProps,
1146    size: Size,
1147    resolve_cx: &crate::unit::FontSizeCx,
1148) -> RoundedRectRadii {
1149    let border_radii = style.border_radius();
1150    RoundedRectRadii {
1151        top_left: border_radius(
1152            border_radii.top_left.unwrap_or(Length::Pt(0.0)),
1153            size.min_side(),
1154            resolve_cx,
1155        ),
1156        top_right: border_radius(
1157            border_radii.top_right.unwrap_or(Length::Pt(0.0)),
1158            size.min_side(),
1159            resolve_cx,
1160        ),
1161        bottom_left: border_radius(
1162            border_radii.bottom_left.unwrap_or(Length::Pt(0.0)),
1163            size.min_side(),
1164            resolve_cx,
1165        ),
1166        bottom_right: border_radius(
1167            border_radii.bottom_right.unwrap_or(Length::Pt(0.0)),
1168            size.min_side(),
1169            resolve_cx,
1170        ),
1171    }
1172}
1173
1174pub(crate) fn paint_bg(cx: &mut PaintCx, style: &ViewStyleProps, rect: Rect) {
1175    let radii = border_to_radii_view(style, rect.size(), &cx.font_size_cx);
1176    if radii_max(radii) > 0.0 {
1177        paint_box_shadow(cx, style, rect, Some(radii));
1178        let bg = match style.background() {
1179            Some(color) => color,
1180            None => return,
1181        };
1182        let rounded_rect = rect.to_rounded_rect(radii);
1183        cx.fill(&rounded_rect, &bg, 0.0);
1184    } else {
1185        paint_box_shadow(cx, style, rect, None);
1186        let bg = match style.background() {
1187            Some(color) => color,
1188            None => return,
1189        };
1190        cx.fill(&rect, &bg, 0.0);
1191    }
1192}
1193
1194fn paint_box_shadow(
1195    cx: &mut PaintCx,
1196    style: &ViewStyleProps,
1197    rect: Rect,
1198    rect_radius: Option<RoundedRectRadii>,
1199) {
1200    for shadow in &style.shadow() {
1201        let min = rect.size().min_side();
1202        let left_offset = shadow.left_offset.resolve(min, &cx.font_size_cx);
1203        let right_offset = shadow.right_offset.resolve(min, &cx.font_size_cx);
1204        let top_offset = shadow.top_offset.resolve(min, &cx.font_size_cx);
1205        let bottom_offset = shadow.bottom_offset.resolve(min, &cx.font_size_cx);
1206        let spread = shadow.spread.resolve(min, &cx.font_size_cx);
1207        let blur_radius = shadow.blur_radius.resolve(min, &cx.font_size_cx);
1208        let inset = Insets::new(
1209            left_offset / 2.0,
1210            top_offset / 2.0,
1211            right_offset / 2.0,
1212            bottom_offset / 2.0,
1213        );
1214        let rect = rect.inflate(spread, spread).inset(inset);
1215        if let Some(radii) = rect_radius {
1216            let rounded_rect = RoundedRect::from_rect(rect, radii_add(radii, spread));
1217            cx.fill(&rounded_rect, shadow.color, blur_radius);
1218        } else {
1219            cx.fill(&rect, shadow.color, blur_radius);
1220        }
1221    }
1222}
1223pub(crate) fn paint_outline(cx: &mut PaintCx, style: &ViewStyleProps, rect: Rect) {
1224    if cx.is_vger() {
1225        let outline = &style.outline();
1226        if outline.width == 0. {
1227            return;
1228        }
1229        let half = outline.width / 2.0;
1230        let rect = rect.inflate(half, half);
1231        let border_radii = border_to_radii_view(style, rect.size(), &cx.font_size_cx);
1232        cx.stroke(
1233            &rect.to_rounded_rect(radii_add(border_radii, half)),
1234            &style.outline_color(),
1235            outline,
1236        );
1237        return;
1238    }
1239
1240    use crate::{
1241        paint::{BorderPath, BorderPathEvent},
1242        unit::Pct,
1243    };
1244
1245    let outlines = [
1246        (style.outline(), style.outline_color()),
1247        (style.outline(), style.outline_color()),
1248        (style.outline(), style.outline_color()),
1249        (style.outline(), style.outline_color()),
1250    ];
1251
1252    // Early return if no outlines
1253    if outlines.iter().any(|o| o.0.width == 0.0) {
1254        return;
1255    }
1256
1257    let outline_color = style.outline_color();
1258    let Pct(outline_progress) = style.outline_progress();
1259
1260    let half_width = outlines[0].0.width / 2.0;
1261    let rect = rect.inflate(half_width, half_width);
1262
1263    let radii = radii_map(
1264        border_to_radii_view(style, rect.size(), &cx.font_size_cx),
1265        |r| (r + half_width).max(0.0),
1266    );
1267
1268    let mut outline_path = BorderPath::new(rect, radii);
1269
1270    // Only create subsegment if needed
1271    if outline_progress < 100. {
1272        outline_path.subsegment(0.0..(outline_progress.clamp(0.0, 100.) / 100.));
1273    }
1274
1275    let mut current_path = Vec::new();
1276    for event in outline_path.path_elements(&outlines, 0.1) {
1277        match event {
1278            BorderPathEvent::PathElement(el) => current_path.push(el),
1279            BorderPathEvent::NewStroke(stroke) => {
1280                // Render current path with previous stroke if any
1281                if !current_path.is_empty() {
1282                    cx.stroke(&current_path.as_slice(), &outline_color, &stroke.0);
1283                    current_path.clear();
1284                }
1285            }
1286        }
1287    }
1288    assert!(current_path.is_empty());
1289}
1290
1291pub(crate) fn paint_border(
1292    cx: &mut PaintCx,
1293    layout_style: &LayoutProps,
1294    style: &ViewStyleProps,
1295    rect: Rect,
1296) {
1297    if cx.is_vger() {
1298        let border = layout_style.border();
1299
1300        let left = border.left.unwrap_or(Stroke::new(0.));
1301        let top = border.top.unwrap_or(Stroke::new(0.));
1302        let right = border.right.unwrap_or(Stroke::new(0.));
1303        let bottom = border.bottom.unwrap_or(Stroke::new(0.));
1304
1305        if left.width == top.width
1306            && top.width == right.width
1307            && right.width == bottom.width
1308            && bottom.width == left.width
1309            && left.width > 0.0
1310            && style.border_color().left.is_some()
1311            && style.border_color().top.is_some()
1312            && style.border_color().right.is_some()
1313            && style.border_color().bottom.is_some()
1314            && style.border_color().left == style.border_color().top
1315            && style.border_color().top == style.border_color().right
1316            && style.border_color().right == style.border_color().bottom
1317        {
1318            let half = left.width / 2.0;
1319            let rect = rect.inflate(-half, -half);
1320            let radii = border_to_radii_view(style, rect.size(), &cx.font_size_cx);
1321            if let Some(color) = style.border_color().left {
1322                if radii_max(radii) > 0.0 {
1323                    let radii = radii_map(radii, |r| (r - half).max(0.0));
1324                    cx.stroke(&rect.to_rounded_rect(radii), &color, &left);
1325                } else {
1326                    cx.stroke(&rect, &color, &left);
1327                }
1328            }
1329        } else {
1330            if left.width > 0.0
1331                && let Some(color) = style.border_color().left
1332            {
1333                let half = left.width / 2.0;
1334                cx.stroke(
1335                    &Line::new(Point::new(half, 0.0), Point::new(half, rect.height())),
1336                    &color,
1337                    &left,
1338                );
1339            }
1340            if right.width > 0.0
1341                && let Some(color) = style.border_color().right
1342            {
1343                let half = right.width / 2.0;
1344                cx.stroke(
1345                    &Line::new(
1346                        Point::new(rect.width() - half, 0.0),
1347                        Point::new(rect.width() - half, rect.height()),
1348                    ),
1349                    &color,
1350                    &right,
1351                );
1352            }
1353            if top.width > 0.0
1354                && let Some(color) = style.border_color().top
1355            {
1356                let half = top.width / 2.0;
1357                cx.stroke(
1358                    &Line::new(Point::new(0.0, half), Point::new(rect.width(), half)),
1359                    &color,
1360                    &top,
1361                );
1362            }
1363            if bottom.width > 0.0
1364                && let Some(color) = style.border_color().bottom
1365            {
1366                let half = bottom.width / 2.0;
1367                cx.stroke(
1368                    &Line::new(
1369                        Point::new(0.0, rect.height() - half),
1370                        Point::new(rect.width(), rect.height() - half),
1371                    ),
1372                    &color,
1373                    &bottom,
1374                );
1375            }
1376        }
1377        return;
1378    }
1379
1380    use crate::{
1381        paint::{BorderPath, BorderPathEvent},
1382        unit::Pct,
1383    };
1384
1385    let border = layout_style.border();
1386    let borders = [
1387        (
1388            border.top.unwrap_or(Stroke::new(0.)),
1389            style.border_color().top.unwrap_or_default(),
1390        ),
1391        (
1392            border.right.unwrap_or(Stroke::new(0.)),
1393            style.border_color().right.unwrap_or_default(),
1394        ),
1395        (
1396            border.bottom.unwrap_or(Stroke::new(0.)),
1397            style.border_color().bottom.unwrap_or_default(),
1398        ),
1399        (
1400            border.left.unwrap_or(Stroke::new(0.)),
1401            style.border_color().left.unwrap_or_default(),
1402        ),
1403    ];
1404
1405    // Early return if no borders
1406    if borders.iter().all(|b| b.0.width == 0.0) {
1407        return;
1408    }
1409
1410    let Pct(border_progress) = style.border_progress();
1411
1412    let half_width = borders[0].0.width / 2.0;
1413    let rect = rect.inflate(-half_width, -half_width);
1414
1415    let radii = radii_map(
1416        border_to_radii_view(style, rect.size(), &cx.font_size_cx),
1417        |r| (r - half_width).max(0.0),
1418    );
1419
1420    let mut border_path = BorderPath::new(rect, radii);
1421
1422    // Only create subsegment if needed
1423    if border_progress < 100. {
1424        border_path.subsegment(0.0..(border_progress.clamp(0.0, 100.) / 100.));
1425    }
1426
1427    // optimize for maximum which is 12 paths and a single move to
1428    let mut current_path = smallvec::SmallVec::<[_; 13]>::new();
1429    for event in border_path.path_elements(&borders, 0.1) {
1430        match event {
1431            BorderPathEvent::PathElement(el) => {
1432                if !current_path.is_empty() && matches!(el, PathEl::MoveTo(_)) {
1433                    // extra move to's will mess up dashed patterns
1434                    continue;
1435                }
1436                current_path.push(el)
1437            }
1438            BorderPathEvent::NewStroke(stroke) => {
1439                // Render current path with previous stroke if any
1440                if !current_path.is_empty() && stroke.0.width > 0. {
1441                    cx.stroke(&current_path.as_slice(), &stroke.1, &stroke.0);
1442                    current_path.clear();
1443                } else if stroke.0.width == 0. {
1444                    current_path.clear();
1445                }
1446            }
1447        }
1448    }
1449    assert!(current_path.is_empty());
1450}
1451
1452// Helper functions for futzing with RoundedRectRadii. These should probably be in kurbo.
1453
1454fn radii_map(radii: RoundedRectRadii, f: impl Fn(f64) -> f64) -> RoundedRectRadii {
1455    RoundedRectRadii {
1456        top_left: f(radii.top_left),
1457        top_right: f(radii.top_right),
1458        bottom_left: f(radii.bottom_left),
1459        bottom_right: f(radii.bottom_right),
1460    }
1461}
1462
1463pub(crate) const fn radii_max(radii: RoundedRectRadii) -> f64 {
1464    radii
1465        .top_left
1466        .max(radii.top_right)
1467        .max(radii.bottom_left)
1468        .max(radii.bottom_right)
1469}
1470
1471fn radii_add(radii: RoundedRectRadii, offset: f64) -> RoundedRectRadii {
1472    radii_map(radii, |r| r + offset)
1473}