floem/
view.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
46use floem_reactive::{ReadSignal, RwSignal, SignalGet};
47use peniko::kurbo::{Circle, Insets, Line, Point, Rect, RoundedRect, Size};
48use std::any::Any;
49use taffy::tree::NodeId;
50
51use crate::{
52    app_state::AppState,
53    context::{ComputeLayoutCx, EventCx, LayoutCx, PaintCx, StyleCx, UpdateCx},
54    event::{Event, EventPropagation},
55    id::ViewId,
56    style::{LayoutProps, Style, StyleClassRef},
57    view_state::ViewStyleProps,
58    views::{dyn_view, DynamicView},
59    Renderer,
60};
61
62/// type erased [`View`]
63///
64/// Views in Floem are strongly typed. [`AnyView`] allows you to escape the strong typing by converting any type implementing [View] into the [AnyView] type.
65///
66/// ## Bad Example
67///```compile_fail
68/// use floem::views::*;
69/// use floem::widgets::*;
70/// use floem::reactive::{RwSignal, SignalGet};
71///
72/// let check = true;
73///
74/// container(if check == true {
75///     checkbox(|| true)
76/// } else {
77///     label(|| "no check".to_string())
78/// });
79/// ```
80/// The above example will fail to compile because `container` is expecting a single type implementing `View` so the if and
81/// 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
82/// to escape the strongly typed requirement.
83///
84/// ```
85/// use floem::reactive::{RwSignal, SignalGet};
86/// use floem::views::*;
87/// use floem::{IntoView, View};
88///
89/// let check = true;
90///
91/// container(if check == true {
92///     checkbox(|| true).into_any()
93/// } else {
94///     label(|| "no check".to_string()).into_any()
95/// });
96/// ```
97pub type AnyView = Box<dyn View>;
98
99/// Converts a value into a [`View`].
100///
101/// This trait can be implemented on types which can be built into another type that implements the `View` trait.
102///
103/// For example, `&str` implements `IntoView` by building a `text` view and can therefore be used directly in a View tuple.
104/// ```rust
105/// # use floem::reactive::*;
106/// # use floem::views::*;
107/// # use floem::IntoView;
108/// fn app_view() -> impl IntoView {
109///     v_stack(("Item One", "Item Two"))
110/// }
111/// ```
112/// Check out the [other types](#foreign-impls) that `IntoView` is implemented for.
113pub trait IntoView: Sized {
114    type V: View + 'static;
115
116    /// Converts the value into a [`View`].
117    fn into_view(self) -> Self::V;
118
119    /// Converts the value into a [`AnyView`].
120    fn into_any(self) -> AnyView {
121        Box::new(self.into_view())
122    }
123}
124
125impl<IV: IntoView + 'static> IntoView for Box<dyn Fn() -> IV> {
126    type V = DynamicView;
127
128    fn into_view(self) -> Self::V {
129        dyn_view(self)
130    }
131}
132
133impl<T: IntoView + Clone + 'static> IntoView for RwSignal<T> {
134    type V = DynamicView;
135
136    fn into_view(self) -> Self::V {
137        dyn_view(move || self.get())
138    }
139}
140
141impl<T: IntoView + Clone + 'static> IntoView for ReadSignal<T> {
142    type V = DynamicView;
143
144    fn into_view(self) -> Self::V {
145        dyn_view(move || self.get())
146    }
147}
148
149impl<VW: View + 'static> IntoView for VW {
150    type V = VW;
151
152    fn into_view(self) -> Self::V {
153        self
154    }
155}
156
157impl IntoView for i32 {
158    type V = crate::views::Label;
159
160    fn into_view(self) -> Self::V {
161        crate::views::text(self)
162    }
163}
164
165impl IntoView for usize {
166    type V = crate::views::Label;
167
168    fn into_view(self) -> Self::V {
169        crate::views::text(self)
170    }
171}
172
173impl IntoView for &str {
174    type V = crate::views::Label;
175
176    fn into_view(self) -> Self::V {
177        crate::views::text(self)
178    }
179}
180
181impl IntoView for String {
182    type V = crate::views::Label;
183
184    fn into_view(self) -> Self::V {
185        crate::views::text(self)
186    }
187}
188
189impl<IV: IntoView + 'static> IntoView for Vec<IV> {
190    type V = crate::views::Stack;
191
192    fn into_view(self) -> Self::V {
193        crate::views::stack_from_iter(self)
194    }
195}
196
197/// Default implementation of `View::layout()` which can be used by
198/// view implementations that need the default behavior and also need
199/// to implement that method to do additional work.
200pub fn recursively_layout_view(id: ViewId, cx: &mut LayoutCx) -> NodeId {
201    cx.layout_node(id, true, |cx| {
202        let mut nodes = Vec::new();
203        for child in id.children() {
204            let view = child.view();
205            let mut view = view.borrow_mut();
206            nodes.push(view.layout(cx));
207        }
208        nodes
209    })
210}
211
212/// The View trait contains the methods for implementing updates, styling, layout, events, and painting.
213///
214/// The [id](View::id) method must be implemented.
215/// The other methods may be implemented as necessary to implement the functionality of the View.
216/// ## State Management in a Custom View
217///
218/// 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.
219/// 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.
220///
221/// For example a minimal slider might look like the following. First, we define the struct that contains the [ViewId](crate::ViewId).
222/// 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.
223/// 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.
224/// ```rust
225/// # use floem::{*, views::*, reactive::*};
226///
227/// struct Slider {
228///     id: ViewId,
229///     percent: f32,
230/// }
231/// pub fn slider(percent: impl Fn() -> f32 + 'static) -> Slider {
232///     let id = ViewId::new();
233///
234///     // If the following effect is not created, and `percent` is accessed directly,
235///     // `percent` will only be accessed a single time and will not be reactive.
236///     // Therefore the following `create_effect` is necessary for reactivity.
237///     create_effect(move |_| {
238///         let percent = percent();
239///         id.update_state(percent);
240///     });
241///     Slider { id, percent: 0.0 }
242/// }
243/// impl View for Slider {
244///     fn id(&self) -> ViewId {
245///         self.id
246///     }
247///
248///     fn update(&mut self, cx: &mut floem::context::UpdateCx, state: Box<dyn std::any::Any>) {
249///         if let Ok(percent) = state.downcast::<f32>() {
250///             self.percent = *percent;
251///             self.id.request_layout();
252///         }
253///     }
254/// }
255/// ```
256pub trait View {
257    fn id(&self) -> ViewId;
258
259    fn view_style(&self) -> Option<Style> {
260        None
261    }
262
263    fn view_class(&self) -> Option<StyleClassRef> {
264        None
265    }
266
267    fn debug_name(&self) -> std::borrow::Cow<'static, str> {
268        core::any::type_name::<Self>().into()
269    }
270
271    /// Use this method to react to changes in view-related state.
272    /// You will usually send state to this hook manually using the `View`'s `Id` handle
273    ///
274    /// ```ignore
275    /// self.id.update_state(SomeState)
276    /// ```
277    ///
278    /// You are in charge of downcasting the state to the expected type.
279    ///
280    /// If the update needs other passes to run you're expected to call
281    /// `_cx.app_state_mut().request_changes`.
282    fn update(&mut self, cx: &mut UpdateCx, state: Box<dyn Any>) {
283        // these are here to just ignore these arguments in the default case
284        let _ = cx;
285        let _ = state;
286    }
287
288    /// Use this method to style the view's children.
289    ///
290    /// If the style changes needs other passes to run you're expected to call
291    /// `cx.app_state_mut().request_changes`.
292    fn style_pass(&mut self, cx: &mut StyleCx<'_>) {
293        for child in self.id().children() {
294            cx.style_view(child);
295        }
296    }
297
298    /// Use this method to layout the view's children.
299    /// Usually you'll do this by calling `LayoutCx::layout_node`.
300    ///
301    /// If the layout changes needs other passes to run you're expected to call
302    /// `cx.app_state_mut().request_changes`.
303    fn layout(&mut self, cx: &mut LayoutCx) -> NodeId {
304        recursively_layout_view(self.id(), cx)
305    }
306
307    /// Responsible for computing the layout of the view's children.
308    ///
309    /// If the layout changes needs other passes to run you're expected to call
310    /// `cx.app_state_mut().request_changes`.
311    fn compute_layout(&mut self, cx: &mut ComputeLayoutCx) -> Option<Rect> {
312        default_compute_layout(self.id(), cx)
313    }
314
315    fn event_before_children(&mut self, cx: &mut EventCx, event: &Event) -> EventPropagation {
316        // these are here to just ignore these arguments in the default case
317        let _ = cx;
318        let _ = event;
319
320        EventPropagation::Continue
321    }
322
323    fn event_after_children(&mut self, cx: &mut EventCx, event: &Event) -> EventPropagation {
324        // these are here to just ignore these arguments in the default case
325        let _ = cx;
326        let _ = event;
327
328        EventPropagation::Continue
329    }
330
331    /// `View`-specific implementation. Will be called in [`PaintCx::paint_view`](crate::context::PaintCx::paint_view).
332    /// Usually you'll call `paint_view` for every child view. But you might also draw text, adjust the offset, clip
333    /// or draw text.
334    fn paint(&mut self, cx: &mut PaintCx) {
335        cx.paint_children(self.id());
336    }
337
338    /// Scrolls the view and all direct and indirect children to bring the `target` view to be
339    /// visible. Returns true if this view contains or is the target.
340    fn scroll_to(&mut self, cx: &mut AppState, target: ViewId, rect: Option<Rect>) -> bool {
341        if self.id() == target {
342            return true;
343        }
344        let mut found = false;
345
346        for child in self.id().children() {
347            found |= child.view().borrow_mut().scroll_to(cx, target, rect);
348        }
349        found
350    }
351}
352
353impl View for Box<dyn View> {
354    fn id(&self) -> ViewId {
355        (**self).id()
356    }
357
358    fn view_style(&self) -> Option<Style> {
359        (**self).view_style()
360    }
361
362    fn view_class(&self) -> Option<StyleClassRef> {
363        (**self).view_class()
364    }
365
366    fn debug_name(&self) -> std::borrow::Cow<'static, str> {
367        (**self).debug_name()
368    }
369
370    fn update(&mut self, cx: &mut UpdateCx, state: Box<dyn Any>) {
371        (**self).update(cx, state)
372    }
373
374    fn style_pass(&mut self, cx: &mut StyleCx) {
375        (**self).style_pass(cx)
376    }
377
378    fn layout(&mut self, cx: &mut LayoutCx) -> NodeId {
379        (**self).layout(cx)
380    }
381
382    fn event_before_children(&mut self, cx: &mut EventCx, event: &Event) -> EventPropagation {
383        (**self).event_before_children(cx, event)
384    }
385
386    fn event_after_children(&mut self, cx: &mut EventCx, event: &Event) -> EventPropagation {
387        (**self).event_after_children(cx, event)
388    }
389
390    fn compute_layout(&mut self, cx: &mut ComputeLayoutCx) -> Option<Rect> {
391        (**self).compute_layout(cx)
392    }
393
394    fn paint(&mut self, cx: &mut PaintCx) {
395        (**self).paint(cx)
396    }
397
398    fn scroll_to(&mut self, cx: &mut AppState, target: ViewId, rect: Option<Rect>) -> bool {
399        (**self).scroll_to(cx, target, rect)
400    }
401}
402
403/// Computes the layout of the view's children, if any.
404pub fn default_compute_layout(id: ViewId, cx: &mut ComputeLayoutCx) -> Option<Rect> {
405    let mut layout_rect: Option<Rect> = None;
406    for child in id.children() {
407        let child_layout = cx.compute_view_layout(child);
408        if let Some(child_layout) = child_layout {
409            if let Some(rect) = layout_rect {
410                layout_rect = Some(rect.union(child_layout));
411            } else {
412                layout_rect = Some(child_layout);
413            }
414        }
415    }
416    layout_rect
417}
418
419pub(crate) fn paint_bg(cx: &mut PaintCx, style: &ViewStyleProps, size: Size) {
420    let radius = match style.border_radius() {
421        crate::unit::PxPct::Px(px) => px,
422        crate::unit::PxPct::Pct(pct) => size.min_side() * (pct / 100.),
423    };
424    if radius > 0.0 {
425        let rect = size.to_rect();
426        let width = rect.width();
427        let height = rect.height();
428        if width > 0.0 && height > 0.0 && radius > width.max(height) / 2.0 {
429            let radius = width.max(height) / 2.0;
430            let circle = Circle::new(rect.center(), radius);
431            let bg = match style.background() {
432                Some(color) => color,
433                None => return,
434            };
435            cx.fill(&circle, &bg, 0.0);
436        } else {
437            paint_box_shadow(cx, style, rect, Some(radius));
438            let bg = match style.background() {
439                Some(color) => color,
440                None => return,
441            };
442            let rounded_rect = rect.to_rounded_rect(radius);
443            cx.fill(&rounded_rect, &bg, 0.0);
444        }
445    } else {
446        paint_box_shadow(cx, style, size.to_rect(), None);
447        let bg = match style.background() {
448            Some(color) => color,
449            None => return,
450        };
451        cx.fill(&size.to_rect(), &bg, 0.0);
452    }
453}
454
455fn paint_box_shadow(
456    cx: &mut PaintCx,
457    style: &ViewStyleProps,
458    rect: Rect,
459    rect_radius: Option<f64>,
460) {
461    if let Some(shadow) = &style.shadow() {
462        let min = rect.size().min_side();
463        let h_offset = match shadow.h_offset {
464            crate::unit::PxPct::Px(px) => px,
465            crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
466        };
467        let v_offset = match shadow.v_offset {
468            crate::unit::PxPct::Px(px) => px,
469            crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
470        };
471        let spread = match shadow.spread {
472            crate::unit::PxPct::Px(px) => px,
473            crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
474        };
475        let blur_radius = match shadow.blur_radius {
476            crate::unit::PxPct::Px(px) => px,
477            crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
478        };
479        let inset = Insets::new(
480            -h_offset / 2.0,
481            -v_offset / 2.0,
482            h_offset / 2.0,
483            v_offset / 2.0,
484        );
485        let rect = rect.inflate(spread, spread).inset(inset);
486        if let Some(radii) = rect_radius {
487            let rounded_rect = RoundedRect::from_rect(rect, radii + spread);
488            cx.fill(&rounded_rect, shadow.color, blur_radius);
489        } else {
490            cx.fill(&rect, shadow.color, blur_radius);
491        }
492    }
493}
494
495pub(crate) fn paint_outline(cx: &mut PaintCx, style: &ViewStyleProps, size: Size) {
496    let outline = &style.outline().0;
497    if outline.width == 0. {
498        // TODO: we should warn! when outline is < 0
499        return;
500    }
501    let half = outline.width / 2.0;
502    let rect = size.to_rect().inflate(half, half);
503    let border_radius = match style.border_radius() {
504        crate::unit::PxPct::Px(px) => px,
505        crate::unit::PxPct::Pct(pct) => size.min_side() * (pct / 100.),
506    };
507    cx.stroke(
508        &rect.to_rounded_rect(border_radius + half),
509        &style.outline_color(),
510        outline,
511    );
512}
513
514pub(crate) fn paint_border(
515    cx: &mut PaintCx,
516    layout_style: &LayoutProps,
517    style: &ViewStyleProps,
518    size: Size,
519) {
520    let left = layout_style.border_left().0;
521    let top = layout_style.border_top().0;
522    let right = layout_style.border_right().0;
523    let bottom = layout_style.border_bottom().0;
524
525    let border_color = style.border_color();
526    if left.width == top.width
527        && top.width == right.width
528        && right.width == bottom.width
529        && bottom.width == left.width
530        && left.width > 0.0
531    {
532        let half = left.width / 2.0;
533        let rect = size.to_rect().inflate(-half, -half);
534        let radius = match style.border_radius() {
535            crate::unit::PxPct::Px(px) => px,
536            crate::unit::PxPct::Pct(pct) => size.min_side() * (pct / 100.),
537        };
538        if radius > 0.0 {
539            let radius = (radius - half).max(0.0);
540            cx.stroke(&rect.to_rounded_rect(radius), &border_color, &left);
541        } else {
542            cx.stroke(&rect, &border_color, &left);
543        }
544    } else {
545        // TODO: now with vello should we do this left.width > 0. check?
546        if left.width > 0.0 {
547            let half = left.width / 2.0;
548            cx.stroke(
549                &Line::new(Point::new(half, 0.0), Point::new(half, size.height)),
550                &border_color,
551                &left,
552            );
553        }
554        if right.width > 0.0 {
555            let half = right.width / 2.0;
556            cx.stroke(
557                &Line::new(
558                    Point::new(size.width - half, 0.0),
559                    Point::new(size.width - half, size.height),
560                ),
561                &border_color,
562                &right,
563            );
564        }
565        if top.width > 0.0 {
566            let half = top.width / 2.0;
567            cx.stroke(
568                &Line::new(Point::new(0.0, half), Point::new(size.width, half)),
569                &border_color,
570                &top,
571            );
572        }
573        if bottom.width > 0.0 {
574            let half = bottom.width / 2.0;
575            cx.stroke(
576                &Line::new(
577                    Point::new(0.0, size.height - half),
578                    Point::new(size.width, size.height - half),
579                ),
580                &border_color,
581                &bottom,
582            );
583        }
584    }
585}
586
587/// Tab navigation finds the next or previous view with the `keyboard_navigatable` status in the tree.
588#[allow(dead_code)]
589pub(crate) fn view_tab_navigation(root_view: ViewId, app_state: &mut AppState, backwards: bool) {
590    let start = app_state
591        .focus
592        .unwrap_or(app_state.prev_focus.unwrap_or(root_view));
593
594    let tree_iter = |id: ViewId| {
595        if backwards {
596            view_tree_previous(root_view, id).unwrap_or_else(|| view_nested_last_child(root_view))
597        } else {
598            view_tree_next(id).unwrap_or(root_view)
599        }
600    };
601
602    let mut new_focus = tree_iter(start);
603    while new_focus != start && !app_state.can_focus(new_focus) {
604        new_focus = tree_iter(new_focus);
605    }
606
607    app_state.clear_focus();
608    app_state.update_focus(new_focus, true);
609}
610
611/// Get the next item in the tree, either the first child or the next sibling of this view or of the first parent view
612fn view_tree_next(id: ViewId) -> Option<ViewId> {
613    if let Some(child) = id.children().into_iter().next() {
614        return Some(child);
615    }
616
617    let mut ancestor = id;
618    loop {
619        if let Some(next_sibling) = view_next_sibling(ancestor) {
620            return Some(next_sibling);
621        }
622        ancestor = ancestor.parent()?;
623    }
624}
625
626/// Get the id of the view after this one (but with the same parent and level of nesting)
627fn view_next_sibling(id: ViewId) -> Option<ViewId> {
628    let parent = id.parent();
629
630    let Some(parent) = parent else {
631        // We're the root, which has no sibling
632        return None;
633    };
634
635    let children = parent.children();
636    //TODO: Log a warning if the child isn't found. This shouldn't happen (error in floem if it does), but this shouldn't panic if that does happen
637    let pos = children.iter().position(|v| v == &id)?;
638
639    if pos + 1 < children.len() {
640        Some(children[pos + 1])
641    } else {
642        None
643    }
644}
645
646/// Get the next item in the tree, the deepest last child of the previous sibling of this view or the parent
647fn view_tree_previous(root_view: ViewId, id: ViewId) -> Option<ViewId> {
648    view_previous_sibling(id)
649        .map(view_nested_last_child)
650        .or_else(|| {
651            (root_view != id).then_some(
652                id.parent()
653                    .unwrap_or_else(|| view_nested_last_child(root_view)),
654            )
655        })
656}
657
658/// Get the id of the view before this one (but with the same parent and level of nesting)
659fn view_previous_sibling(id: ViewId) -> Option<ViewId> {
660    let parent = id.parent();
661
662    let Some(parent) = parent else {
663        // We're the root, which has no sibling
664        return None;
665    };
666
667    let children = parent.children();
668    let pos = children.iter().position(|v| v == &id).unwrap();
669
670    if pos > 0 {
671        Some(children[pos - 1])
672    } else {
673        None
674    }
675}
676
677fn view_nested_last_child(view: ViewId) -> ViewId {
678    let mut last_child = view;
679    while let Some(new_last_child) = last_child.children().pop() {
680        last_child = new_last_child;
681    }
682    last_child
683}
684
685/// Produces an ascii art debug display of all of the views.
686#[allow(dead_code)]
687pub(crate) fn view_debug_tree(root_view: ViewId) {
688    let mut views = vec![(root_view, Vec::new())];
689    while let Some((current_view, active_lines)) = views.pop() {
690        // Ascii art for the tree view
691        if let Some((leaf, root)) = active_lines.split_last() {
692            for line in root {
693                print!("{}", if *line { "│   " } else { "    " });
694            }
695            print!("{}", if *leaf { "├── " } else { "└── " });
696        }
697        println!(
698            "{:?} {}",
699            current_view,
700            current_view.view().borrow().debug_name()
701        );
702
703        let mut children = current_view.children();
704        if let Some(last_child) = children.pop() {
705            views.push((last_child, [active_lines.as_slice(), &[false]].concat()));
706        }
707
708        views.extend(
709            children
710                .into_iter()
711                .rev()
712                .map(|child| (child, [active_lines.as_slice(), &[true]].concat())),
713        );
714    }
715}