use std::{borrow::Cow, sync::Arc};
use topcoat_core::context::Cx;
use topcoat_view::{
BoxView, Child,
internal::{MoveView, ScopeView},
};
use crate::{
Body, IntoPath, Methods, OwnedMethods, Path, Route, RouteFuture, RouteId,
response::AsyncIntoResponse, route,
};
pub trait Page: Send + Sync + 'static {
fn id(&self) -> RouteId;
fn methods(&self) -> Methods<'_>;
fn path(&self) -> &Path;
fn render<'a>(&'a self, cx: &'a Cx, body: Body) -> BoxView<'a>;
fn is_current(&self, cx: &Cx) -> bool {
route(cx).id() == self.id()
}
}
impl<P: Page + ?Sized> Page for &'static P {
fn id(&self) -> RouteId {
(**self).id()
}
fn methods(&self) -> Methods<'_> {
(**self).methods()
}
fn path(&self) -> &Path {
(**self).path()
}
fn render<'a>(&'a self, cx: &'a Cx, body: Body) -> BoxView<'a> {
(**self).render(cx, body)
}
}
#[cfg(feature = "discover")]
inventory::collect!(&'static dyn Page);
pub type PageRenderFn = for<'a> fn(cx: &'a Cx, body: Body) -> BoxView<'a>;
#[derive(Debug, Clone)]
pub struct PageFn {
id: RouteId,
methods: OwnedMethods,
path: Cow<'static, Path>,
render: PageRenderFn,
}
impl PageFn {
#[track_caller]
pub fn new(
methods: impl Into<OwnedMethods>,
path: impl IntoPath,
render: PageRenderFn,
) -> Self {
Self {
id: RouteId::new(),
methods: methods.into(),
path: path.into_path(),
render,
}
}
}
impl Page for PageFn {
fn id(&self) -> RouteId {
self.id
}
fn methods(&self) -> Methods<'_> {
self.methods.as_methods()
}
fn path(&self) -> &Path {
&self.path
}
fn render<'a>(&'a self, cx: &'a Cx, body: Body) -> BoxView<'a> {
(self.render)(cx, body)
}
}
pub type Slot<'a> = Child<'a>;
pub trait Layout: Send + Sync + 'static {
fn path(&self) -> &Path;
fn render<'a>(&'a self, cx: &'a Cx, slot: Slot<'a>) -> BoxView<'a>;
}
impl<L: Layout + ?Sized> Layout for &'static L {
fn path(&self) -> &Path {
(**self).path()
}
fn render<'a>(&'a self, cx: &'a Cx, slot: Slot<'a>) -> BoxView<'a> {
(**self).render(cx, slot)
}
}
#[cfg(feature = "discover")]
inventory::collect!(&'static dyn Layout);
pub type LayoutRenderFn = for<'a> fn(cx: &'a Cx, slot: Slot<'a>) -> BoxView<'a>;
#[derive(Debug, Clone)]
pub struct LayoutFn {
path: Cow<'static, Path>,
render: LayoutRenderFn,
}
impl LayoutFn {
#[track_caller]
pub fn new(path: impl IntoPath, render: LayoutRenderFn) -> Self {
Self {
path: path.into_path(),
render,
}
}
}
impl Layout for LayoutFn {
fn path(&self) -> &Path {
&self.path
}
fn render<'a>(&'a self, cx: &'a Cx, slot: Slot<'a>) -> BoxView<'a> {
(self.render)(cx, slot)
}
}
pub struct PageWithLayouts {
inner: Arc<PageWithLayoutsInner>,
}
struct PageWithLayoutsInner {
page: Box<dyn Page>,
layouts: Vec<Arc<dyn Layout>>,
}
impl PageWithLayoutsInner {
fn render<'a>(&'a self, cx: &'a Cx, body: Body) -> BoxView<'a> {
let mut view = self.page.render(cx, body);
for layout in self.layouts.iter().rev() {
view = layout.render(cx, Slot::new(view));
}
view
}
}
impl PageWithLayouts {
#[must_use]
pub fn new(page: Box<dyn Page>, layouts: Vec<Arc<dyn Layout>>) -> Self {
Self {
inner: Arc::new(PageWithLayoutsInner { page, layouts }),
}
}
}
impl Route for PageWithLayouts {
fn id(&self) -> RouteId {
self.inner.page.id()
}
fn methods(&self) -> Methods<'_> {
self.inner.page.methods()
}
fn path(&self) -> &Path {
self.inner.page.path()
}
fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx> {
let inner = Arc::clone(&self.inner);
let owned = cx.clone();
Box::pin(async move {
let view = MoveView::new(async move {
let view = inner.render(&owned, body);
MoveView::drive(ScopeView::new(view)).await
});
view.async_into_response(cx).await
})
}
}