Skip to main content

topcoat_view/
view.rs

1use std::{
2    future::poll_fn,
3    ops::DerefMut,
4    pin::{Pin, pin},
5    task::{Context, Poll},
6};
7
8use topcoat_core::error::Result;
9
10use crate::{RegionId, buffer::ViewHandle};
11
12/// The initial content returned by [`View::poll_first`].
13#[derive(Debug)]
14pub struct ViewFirst {
15    /// The content, ready to render with the surrounding document.
16    pub content: ViewHandle,
17    /// Whether [`View::poll_swap`] can produce updates to this content.
18    pub live: bool,
19}
20
21/// An update to a live region, returned by [`View::poll_swap`].
22#[derive(Debug)]
23pub struct ViewSwap {
24    /// The region the replacement belongs to.
25    pub region: RegionId,
26    /// The content that replaces what the region currently shows.
27    pub replacement: ViewHandle,
28}
29
30/// The return token for a live region's body.
31///
32/// `emit!` returns a [`Result`] containing this token, so a `live!` body can
33/// end with an emission. Constructing the token directly does not emit
34/// content. The body must still emit at least once.
35#[derive(Debug)]
36pub struct EmitToken;
37
38/// A piece of HTML that can keep changing while a response streams.
39///
40/// First, [`poll_first`](Self::poll_first) returns the initial content.
41/// If it is live, call [`poll_swap`](Self::poll_swap) for region updates
42/// until it returns `None`.
43///
44/// The `view!` and `live!` macros build implementations of this trait;
45/// application code composes those rather than implementing it by hand.
46pub trait View: Send {
47    /// Resolves the view's first content.
48    fn poll_first(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<ViewFirst>>;
49
50    /// Yields the next replacement for a region of the first content, or
51    /// `None` when the view is done changing.
52    ///
53    /// Only meaningful after [`poll_first`](Self::poll_first) resolved to
54    /// live content.
55    fn poll_swap(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<Option<ViewSwap>>>;
56}
57
58/// Methods available on every [`View`].
59pub trait ViewExt: View {
60    /// Resolves the view's first content and discards the view.
61    ///
62    /// Later updates from a live view are discarded.
63    fn first(self) -> impl Future<Output = Result<ViewHandle>> + Send
64    where
65        Self: Sized,
66    {
67        async move {
68            let mut view = pin!(self);
69            let first = poll_fn(|cx| view.as_mut().poll_first(cx)).await?;
70            Ok(first.content)
71        }
72    }
73
74    /// Resolves the content of a view that has no live updates.
75    ///
76    /// # Panics
77    ///
78    /// Panics if the view's first content is live.
79    fn single(self) -> impl Future<Output = Result<ViewHandle>> + Send
80    where
81        Self: Sized,
82    {
83        async move {
84            let mut view = pin!(self);
85            let first = poll_fn(|cx| view.as_mut().poll_first(cx)).await?;
86            assert!(!first.live, "used `.single()` on a View that is live");
87            Ok(first.content)
88        }
89    }
90
91    /// Boxes the view to give different view types a common return type.
92    ///
93    /// Use this when a function returns different `view!` expressions, or
94    /// when a recursive component needs a view type with a known size.
95    fn boxed<'a>(self) -> BoxView<'a>
96    where
97        Self: Sized + 'a,
98    {
99        Box::pin(self)
100    }
101}
102
103impl<V: View + ?Sized> ViewExt for V {}
104
105/// The empty view: renders nothing and never changes.
106impl View for () {
107    fn poll_first(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<ViewFirst>> {
108        Poll::Ready(Ok(ViewFirst {
109            content: ViewHandle::empty(),
110            live: false,
111        }))
112    }
113
114    fn poll_swap(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<Option<ViewSwap>>> {
115        Poll::Ready(Ok(None))
116    }
117}
118
119/// A [`View`] with its concrete type erased, built with [`ViewExt::boxed`].
120pub type BoxView<'a> = Pin<Box<dyn View + 'a>>;
121
122/// A pinned pointer to a view, like a [`BoxView`], polls the view it points at.
123impl<P> View for Pin<P>
124where
125    P: DerefMut + Unpin + Send,
126    P::Target: View,
127{
128    fn poll_first(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<ViewFirst>> {
129        self.get_mut().as_mut().poll_first(cx)
130    }
131
132    fn poll_swap(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<Option<ViewSwap>>> {
133        self.get_mut().as_mut().poll_swap(cx)
134    }
135}