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}