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(¤t_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(¤t_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}