mod dispatcher;
mod envelope;
mod nav;
mod nodes;
mod run;
pub use envelope::Envelope;
pub use nodes::Nodes;
pub use run::{MAIN, run};
pub use guinea_core::guard::{Ask, Verdict};
pub use guinea_app::feature::FeatureInitContext;
pub use guinea_macros::{iced_layout as layout, iced_page as page};
use std::any::{Any, TypeId};
use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
use guinea_app::feature::{Reaches, Segment};
use guinea_core::feature::Dispatch;
use guinea_core::scope::Reducer;
use guinea_router::router::{
Mount, NavigateHandle, RouteChain, SegmentEntry, SegmentProps, Ui, narrow, same_params,
single_entry_chain,
};
use envelope::Deliver;
use nodes::{Held, Placement};
pub struct Iced;
impl Ui for Iced {
type View<'a> = iced::Element<'a, Envelope>;
type Nodes = Nodes;
}
pub type Element<'a, Message> = iced::Element<'a, Message>;
pub trait Page: Default + Sized + 'static {
const CACHE_STATE_IN_MEMORY: bool = false;
const DECLARED: Option<guinea_core::actor::shape::Declared> = None;
type Params: Clone + PartialEq + 'static;
type Message: Send + 'static;
type Installs: 'static;
fn install(ctx: &FeatureInitContext, params: &Self::Params) -> anyhow::Result<Self::Installs>;
fn init(_ctx: &FeatureInitContext, _params: &Self::Params) -> Self {
Self::default()
}
fn observes(_cx: &Observing<'_, Self::Message>) {}
fn leaving(&self) -> Verdict {
Verdict::Allow
}
fn update(&mut self, message: Self::Message, cx: &mut UpdateCx<'_, Self>);
fn view(&self, cx: &PageCx<'_, Self>) -> Element<'_, Self::Message>;
}
pub trait Layout: Default + Sized + 'static {
const DECLARED: Option<guinea_core::actor::shape::Declared> = None;
type Params: Clone + PartialEq + 'static;
type Message: Send + 'static;
type Installs: 'static;
fn install(ctx: &FeatureInitContext, params: &Self::Params) -> anyhow::Result<Self::Installs>;
fn init(_ctx: &FeatureInitContext, _params: &Self::Params) -> Self {
Self::default()
}
fn observes(_cx: &Observing<'_, Self::Message>) {}
fn leaving(&self) -> Verdict {
Verdict::Allow
}
fn update(&mut self, message: Self::Message, cx: &mut UpdateCx<'_, Self>);
fn view<'a>(&'a self, cx: &LayoutCx<'a, Self>) -> Element<'a, Envelope>;
}
pub struct Observing<'a, Message> {
ctx: &'a FeatureInitContext,
cursor: usize,
deliver: Deliver,
message: PhantomData<fn() -> Message>,
}
impl<'a, Message: Send + 'static> Observing<'a, Message> {
fn new(ctx: &'a FeatureInitContext, deliver: Deliver) -> Self {
Self {
cursor: ctx.ancestors.len(),
ctx,
deliver,
message: PhantomData,
}
}
pub fn on<R: Reducer>(&self, translate: fn(&R::Update) -> Option<Message>) {
let cursor = self.cursor;
let deliver = self.deliver;
self.ctx.observe::<R>(move |update| {
if let Some(message) = translate(update) {
envelope::park(Envelope::new(cursor, deliver, Box::new(message)));
}
});
}
}
pub type Feature<R> = (Rc<R>, Dispatch);
fn feature_of<R>(props: &SegmentProps<Iced>) -> Feature<R>
where
R: Reducer,
{
let binding = props.binding::<R>();
(binding.get(), binding.dispatch())
}
pub struct PageCx<'a, P> {
props: SegmentProps<Iced>,
page: PhantomData<fn() -> P>,
borrow: PhantomData<&'a Nodes>,
}
impl<P: Segment> PageCx<'_, P> {
pub fn state<R, I>(&self) -> Feature<R>
where
R: Reducer + Clone,
P: Reaches<R, I>,
{
feature_of::<R>(&self.props)
}
}
pub struct LayoutCx<'a, L: Layout> {
props: SegmentProps<Iced>,
nodes: &'a Nodes,
layout: PhantomData<fn() -> L>,
}
impl<'a, L: Layout> LayoutCx<'a, L> {
pub fn state<R, I>(&self) -> Feature<R>
where
R: Reducer + Clone,
L: Reaches<R, I>,
{
feature_of::<R>(&self.props)
}
pub fn mine(&self, element: impl Into<Element<'a, L::Message>>) -> Element<'a, Envelope> {
let cursor = self.props.cursor;
element
.into()
.map(move |message| Envelope::new(cursor, deliver_layout::<L>, Box::new(message)))
}
pub fn outlet(&self) -> Element<'a, Envelope> {
self.props.outlet(self.nodes)
}
pub fn child_is<P: 'static>(&self) -> bool {
self.props
.chain
.get(self.props.cursor + 1)
.is_some_and(|entry| (entry.type_id)() == TypeId::of::<P>())
}
}
pub struct UpdateCx<'a, S> {
props: &'a SegmentProps<Iced>,
segment: PhantomData<fn() -> S>,
}
impl<S: Segment> UpdateCx<'_, S> {
pub fn state<R, I>(&self) -> Feature<R>
where
R: Reducer + Clone,
S: Reaches<R, I>,
{
feature_of::<R>(self.props)
}
}
impl<S> UpdateCx<'_, S> {
pub fn navigate<R>(&self) -> NavigateHandle<Iced, R>
where
R: RouteChain<Iced> + Clone + PartialEq + 'static,
{
nav::current::<R>()
}
}
fn deliver_page<P: Page>(
props: &SegmentProps<Iced>,
nodes: &mut Nodes,
message: Box<dyn Any + Send>,
) {
envelope::deliver::<P, P::Message>(props, nodes, message, P::update, P::leaving);
}
fn deliver_layout<L: Layout>(
props: &SegmentProps<Iced>,
nodes: &mut Nodes,
message: Box<dyn Any + Send>,
) {
envelope::deliver::<L, L::Message>(props, nodes, message, L::update, L::leaving);
}
pub struct MountPage<P>(pub PhantomData<P>);
pub struct MountLayout<L>(pub PhantomData<L>);
impl<P: Page> Mount<Iced> for MountPage<P> {
fn view<'a>(&self, props: SegmentProps<Iced>, nodes: &'a Nodes) -> Element<'a, Envelope> {
let cursor = props.cursor;
let Some(page) = nodes.get::<P>(cursor) else {
return iced::widget::text("").into();
};
let cx = PageCx {
props,
page: PhantomData,
borrow: PhantomData,
};
let _drawing = guinea_core::devtools::Rendering::of(std::any::type_name::<P>());
page.view(&cx)
.map(move |message| Envelope::new(cursor, deliver_page::<P>, Box::new(message)))
}
}
impl<L: Layout> Mount<Iced> for MountLayout<L> {
fn view<'a>(&self, props: SegmentProps<Iced>, nodes: &'a Nodes) -> Element<'a, Envelope> {
let cursor = props.cursor;
let Some(layout) = nodes.get::<L>(cursor) else {
return iced::widget::text("").into();
};
let cx = LayoutCx {
props,
nodes,
layout: PhantomData,
};
let _drawing = guinea_core::devtools::Rendering::of(std::any::type_name::<L>());
layout.view(&cx)
}
}
pub const fn segment_entry<P: Page>() -> SegmentEntry<Iced> {
SegmentEntry::new::<P>(
install_page::<P>,
same_params::<P::Params>,
&const { MountPage::<P>(PhantomData) },
P::CACHE_STATE_IN_MEMORY,
)
.written(P::DECLARED)
}
pub const fn layout_entry<L: Layout>() -> SegmentEntry<Iced> {
SegmentEntry::new::<L>(
install_layout::<L>,
same_params::<L::Params>,
&const { MountLayout::<L>(PhantomData) },
false,
)
.written(L::DECLARED)
}
fn stage_node<Node: Default + 'static>(
ctx: &FeatureInitContext,
node: Node,
params: Box<dyn Any>,
leaving: fn(&Node) -> Verdict,
cache_state: bool,
) {
let verdict = Rc::new(RefCell::new(leaving(&node)));
let held = Held {
node: Box::new(node),
params,
verdict: verdict.clone(),
};
ctx.on_leave(move || verdict.borrow().clone());
nodes::stage(
Placement {
cursor: ctx.ancestors.len(),
segment: TypeId::of::<Node>(),
},
held,
cache_state,
);
}
fn own<T: 'static>(ctx: &FeatureInitContext, installed: T) {
ctx.scope.own(guinea_core::scope::DropGuard(installed));
}
fn install_page<P: Page>(ctx: &FeatureInitContext, params: &dyn Any) -> anyhow::Result<()> {
let params = narrow::<P::Params, P>(params)?;
own(ctx, P::install(ctx, params)?);
stage_node(
ctx,
P::init(ctx, params),
Box::new(params.clone()),
P::leaving,
P::CACHE_STATE_IN_MEMORY,
);
P::observes(&Observing::new(ctx, deliver_page::<P>));
Ok(())
}
fn install_layout<L: Layout>(ctx: &FeatureInitContext, params: &dyn Any) -> anyhow::Result<()> {
let params = narrow::<L::Params, L>(params)?;
own(ctx, L::install(ctx, params)?);
stage_node(
ctx,
L::init(ctx, params),
Box::new(params.clone()),
L::leaving,
false,
);
L::observes(&Observing::new(ctx, deliver_layout::<L>));
Ok(())
}
pub fn page_chain<P: Page>() -> &'static [SegmentEntry<Iced>] {
single_entry_chain(segment_entry::<P>())
}
#[cfg(test)]
mod tests;