Skip to main content

iocraft/
component.rs

1use crate::{
2    context::ContextStack,
3    element::{ElementKey, ElementType},
4    hook::{AnyHook, Hook, Hooks},
5    multimap::RemoveOnlyMultimap,
6    props::{AnyProps, Props},
7    render::{ComponentDrawer, ComponentUpdater, UpdateContext},
8};
9use core::{
10    any::{Any, TypeId},
11    marker::PhantomData,
12    pin::Pin,
13    task::{Context, Poll},
14};
15use futures::future::poll_fn;
16use taffy::NodeId;
17
18pub(crate) struct ComponentHelper<C: Component> {
19    _marker: PhantomData<C>,
20}
21
22impl<C: Component> ComponentHelper<C> {
23    pub fn boxed() -> Box<dyn ComponentHelperExt> {
24        Box::new(Self {
25            _marker: PhantomData,
26        })
27    }
28}
29
30#[doc(hidden)]
31pub trait ComponentHelperExt: Any + Send + Sync {
32    fn new_component(&self, props: AnyProps) -> Box<dyn AnyComponent>;
33    fn update_component(
34        &self,
35        component: &mut Box<dyn AnyComponent>,
36        props: AnyProps,
37        hooks: Hooks,
38        updater: &mut ComponentUpdater,
39    );
40    fn component_type_id(&self) -> TypeId;
41    fn copy(&self) -> Box<dyn ComponentHelperExt>;
42}
43
44impl<C: Component> ComponentHelperExt for ComponentHelper<C> {
45    fn new_component(&self, props: AnyProps) -> Box<dyn AnyComponent> {
46        Box::new(C::new(unsafe { props.downcast_ref_unchecked() }))
47    }
48
49    fn update_component(
50        &self,
51        component: &mut Box<dyn AnyComponent>,
52        props: AnyProps,
53        hooks: Hooks,
54        updater: &mut ComponentUpdater,
55    ) {
56        component.update(props, hooks, updater);
57    }
58
59    fn component_type_id(&self) -> TypeId {
60        TypeId::of::<C>()
61    }
62
63    fn copy(&self) -> Box<dyn ComponentHelperExt> {
64        Self::boxed()
65    }
66}
67
68/// `Component` defines a component type and the methods required for instantiating and rendering
69/// the component.
70///
71/// Most users will not need to implement this trait directly. This is only required for new, low
72/// level component type definitions. Instead, the [`component`](macro@crate::component) macro should be used.
73pub trait Component: Any + Send + Sync + Unpin {
74    /// The type of properties that the component accepts.
75    type Props<'a>: Props
76    where
77        Self: 'a;
78
79    /// Creates a new instance of the component from a set of properties.
80    fn new(props: &Self::Props<'_>) -> Self;
81
82    /// Invoked whenever the properties of the component or layout may have changed.
83    fn update(
84        &mut self,
85        _props: &mut Self::Props<'_>,
86        _hooks: Hooks,
87        _updater: &mut ComponentUpdater,
88    ) {
89    }
90
91    /// Invoked to draw the component.
92    fn draw(&mut self, _drawer: &mut ComponentDrawer<'_>) {}
93
94    /// Invoked to determine whether a change has occurred that would require the component to be
95    /// updated and redrawn.
96    fn poll_change(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
97        Poll::Pending
98    }
99}
100
101impl<C: Component> ElementType for C {
102    type Props<'a> = C::Props<'a>;
103}
104
105#[doc(hidden)]
106pub trait AnyComponent: Any + Send + Sync + Unpin {
107    fn update(&mut self, props: AnyProps, hooks: Hooks, updater: &mut ComponentUpdater);
108    fn draw(&mut self, drawer: &mut ComponentDrawer<'_>);
109    fn poll_change(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()>;
110}
111
112impl<C: Any + Component> AnyComponent for C {
113    fn update(&mut self, mut props: AnyProps, hooks: Hooks, updater: &mut ComponentUpdater) {
114        Component::update(
115            self,
116            unsafe { props.downcast_mut_unchecked() },
117            hooks,
118            updater,
119        );
120    }
121
122    fn draw(&mut self, drawer: &mut ComponentDrawer<'_>) {
123        Component::draw(self, drawer);
124    }
125
126    fn poll_change(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
127        Component::poll_change(self, cx)
128    }
129}
130
131pub(crate) struct InstantiatedComponent {
132    node_id: NodeId,
133    component: Box<dyn AnyComponent>,
134    children: Components,
135    helper: Box<dyn ComponentHelperExt>,
136    hooks: Vec<Box<dyn AnyHook>>,
137    first_update: bool,
138    has_transparent_layout: bool,
139}
140
141impl InstantiatedComponent {
142    pub fn new(node_id: NodeId, props: AnyProps, helper: Box<dyn ComponentHelperExt>) -> Self {
143        Self {
144            node_id,
145            component: helper.new_component(props),
146            children: Components::default(),
147            helper,
148            hooks: Default::default(),
149            first_update: true,
150            has_transparent_layout: false,
151        }
152    }
153
154    pub fn node_id(&self) -> NodeId {
155        self.node_id
156    }
157
158    pub fn component(&self) -> &dyn AnyComponent {
159        &*self.component
160    }
161
162    pub fn update(
163        &mut self,
164        context: &mut UpdateContext<'_>,
165        unattached_child_node_ids: &mut Vec<NodeId>,
166        component_context_stack: &mut ContextStack<'_>,
167        props: AnyProps,
168    ) {
169        let mut updater = ComponentUpdater::new(
170            self.node_id,
171            &mut self.children,
172            unattached_child_node_ids,
173            context,
174            component_context_stack,
175        );
176        self.hooks.pre_component_update(&mut updater);
177        self.helper.update_component(
178            &mut self.component,
179            props,
180            Hooks::new(&mut self.hooks, self.first_update),
181            &mut updater,
182        );
183        self.hooks.post_component_update(&mut updater);
184        self.first_update = false;
185        self.has_transparent_layout = updater.has_transparent_layout();
186    }
187
188    pub fn draw(&mut self, drawer: &mut ComponentDrawer<'_>) {
189        if self.has_transparent_layout {
190            // If the component has a transparent layout, provide the first child's layout to the
191            // hooks and component.
192            if let Some(child) = self.children.components.iter().next().as_ref() {
193                drawer.for_child_node_layout(child.node_id, |drawer| {
194                    self.hooks.pre_component_draw(drawer);
195                    self.component.draw(drawer);
196                });
197            } else {
198                self.hooks.pre_component_draw(drawer);
199                self.component.draw(drawer);
200            }
201        } else {
202            self.hooks.pre_component_draw(drawer);
203            self.component.draw(drawer);
204        }
205
206        drawer.with_clip_rect_for_children(|drawer| {
207            self.children.draw(drawer);
208        });
209
210        if self.has_transparent_layout {
211            if let Some(child) = self.children.components.iter().next().as_ref() {
212                drawer.for_child_node_layout(child.node_id, |drawer| {
213                    self.hooks.post_component_draw(drawer);
214                });
215            } else {
216                self.hooks.post_component_draw(drawer);
217            }
218        } else {
219            self.hooks.post_component_draw(drawer);
220        }
221    }
222
223    pub async fn wait(&mut self) {
224        let mut self_mut = Pin::new(self);
225        poll_fn(|cx| self_mut.as_mut().poll_change(cx)).await;
226    }
227
228    fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
229        let component_status = Pin::new(&mut *self.component).poll_change(cx);
230        let children_status = Pin::new(&mut self.children).poll_change(cx);
231        let hooks_status = Pin::new(&mut self.hooks).poll_change(cx);
232        if component_status.is_ready() || children_status.is_ready() || hooks_status.is_ready() {
233            Poll::Ready(())
234        } else {
235            Poll::Pending
236        }
237    }
238}
239
240#[derive(Default)]
241pub(crate) struct Components {
242    pub components: RemoveOnlyMultimap<ElementKey, InstantiatedComponent>,
243}
244
245impl Components {
246    pub fn draw(&mut self, drawer: &mut ComponentDrawer<'_>) {
247        for component in self.components.iter_mut() {
248            if component.has_transparent_layout {
249                component.draw(drawer);
250            } else {
251                drawer.for_child_node_layout(component.node_id, |drawer| {
252                    component.draw(drawer);
253                });
254            }
255        }
256    }
257
258    pub fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
259        let mut is_ready = false;
260        for component in self.components.iter_mut() {
261            if Pin::new(&mut *component).poll_change(cx).is_ready() {
262                is_ready = true;
263            }
264        }
265        if is_ready {
266            Poll::Ready(())
267        } else {
268            Poll::Pending
269        }
270    }
271}