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