waterui_core/components/
anyview.rs

1//! This module provides type-erased view implementations to enable
2//! heterogeneous collections of views and dynamic dispatch.
3//!
4//! The main type provided by this module is [`AnyView`], which wraps
5//! any type implementing the [`View`] trait and erases its concrete type
6//! while preserving its behavior.
7use core::{
8    any::{Any, TypeId, type_name},
9    fmt::Debug,
10};
11
12use alloc::boxed::Box;
13
14use crate::{Environment, View, layout::StretchAxis};
15
16trait AnyViewImpl: 'static {
17    fn body(self: Box<Self>, env: Environment) -> AnyView;
18    fn type_id(&self) -> TypeId {
19        TypeId::of::<Self>()
20    }
21    fn name(&self) -> &'static str {
22        type_name::<Self>()
23    }
24    fn stretch_axis(&self) -> StretchAxis;
25}
26
27impl<T: View> AnyViewImpl for T {
28    fn body(self: Box<Self>, env: Environment) -> AnyView {
29        AnyView::new(View::body(*self, &env))
30    }
31    fn stretch_axis(&self) -> StretchAxis {
32        View::stretch_axis(self)
33    }
34}
35
36/// A type-erased wrapper for a `View`.
37///
38/// This allows storing and passing around different view types uniformly.
39#[must_use]
40pub struct AnyView(Box<dyn AnyViewImpl>);
41
42impl Debug for AnyView {
43    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
44        f.write_fmt(format_args!("AnyView({})", self.name()))
45    }
46}
47
48impl Default for AnyView {
49    fn default() -> Self {
50        Self::new(())
51    }
52}
53
54impl AnyView {
55    /// Creates a new `AnyView` from any type that implements `View`.
56    ///
57    /// If the provided view is already an `AnyView`, it will be unwrapped
58    /// to avoid unnecessary nesting.
59    pub fn new<V: View>(view: V) -> Self {
60        #[allow(clippy::missing_panics_doc)]
61        if TypeId::of::<V>() == TypeId::of::<Self>() {
62            let any = &mut Some(view) as &mut dyn Any;
63            return any
64                .downcast_mut::<Option<Self>>()
65                .expect("downcast to option should succeed")
66                .take()
67                .expect("option should contain a value"); // TODO: use downcast_mut_unchecked when it's stable
68        }
69
70        Self(Box::new(view))
71    }
72
73    /// Checks if the contained view is of type `T`.
74    #[must_use]
75    pub fn is<T: 'static>(&self) -> bool {
76        self.type_id() == TypeId::of::<T>()
77    }
78
79    /// Returns the `TypeId` of the contained view.
80    #[must_use]
81    pub fn type_id(&self) -> TypeId {
82        AnyViewImpl::type_id(&*self.0)
83    }
84
85    /// Returns the type name of the contained view.
86    #[must_use]
87    pub fn name(&self) -> &'static str {
88        AnyViewImpl::name(&*self.0)
89    }
90
91    /// Returns the stretch axis of the contained view.
92    ///
93    /// This delegates to the `View::stretch_axis()` method of the wrapped view,
94    /// which for native views returns their layout stretch behavior.
95    #[must_use]
96    pub fn stretch_axis(&self) -> StretchAxis {
97        AnyViewImpl::stretch_axis(&*self.0)
98    }
99
100    /// Downcasts `AnyView` to a concrete view type without any runtime checks.
101    ///
102    /// # Safety
103    /// Calling this method with the incorrect type is undefined behavior.
104    #[must_use]
105    pub unsafe fn downcast_unchecked<T: 'static>(self) -> Box<T> {
106        unsafe { Box::from_raw(Box::into_raw(self.0).cast::<T>()) }
107    }
108
109    /// Returns a reference to the contained view without any runtime checks.
110    ///
111    /// # Safety
112    /// Calling this method with the incorrect type is undefined behavior.
113    #[must_use]
114    pub const unsafe fn downcast_ref_unchecked<T: 'static>(&self) -> &T {
115        unsafe { &*(&raw const *self.0).cast::<T>() }
116    }
117
118    /// Returns a mutable reference to the contained view without any runtime checks.
119    ///
120    /// # Safety
121    /// Calling this method with the incorrect type is undefined behavior.
122    pub const unsafe fn downcast_mut_unchecked<T: 'static>(&mut self) -> &mut T {
123        unsafe { &mut *(&raw mut *self.0).cast::<T>() }
124    }
125
126    /// Attempts to downcast `AnyView` to a concrete view type.
127    ///
128    /// Returns `Ok` with the boxed value if the types match, or
129    /// `Err` with the original `AnyView` if the types don't match.
130    ///
131    /// # Errors
132    ///
133    /// Returns `Err(Self)` if the contained type does not match `T`.
134    pub fn downcast<T: 'static>(self) -> Result<Box<T>, Self> {
135        if self.is::<T>() {
136            unsafe { Ok(self.downcast_unchecked()) }
137        } else {
138            Err(self)
139        }
140    }
141
142    /// Attempts to get a reference to the contained view of a specific type.
143    ///
144    /// Returns `Some` if the types match, or `None` if they don't.
145    #[must_use]
146    pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
147        unsafe { self.is::<T>().then(|| self.downcast_ref_unchecked()) }
148    }
149
150    /// Attempts to get a mutable reference to the contained view of a specific type.
151    ///
152    /// Returns `Some` if the types match, or `None` if they don't.
153    pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
154        unsafe { self.is::<T>().then(move || self.downcast_mut_unchecked()) }
155    }
156}
157
158impl View for AnyView {
159    fn body(self, env: &Environment) -> impl View {
160        self.0.body(env.clone())
161    }
162}
163
164#[cfg(test)]
165mod test {
166    use core::any::TypeId;
167
168    use super::AnyView;
169
170    #[test]
171    pub fn get_type_id() {
172        assert_eq!(AnyView::new(()).type_id(), TypeId::of::<()>());
173    }
174}