mod dispatcher;
mod nav;
mod run;
pub use run::{MAIN, run};
use std::any::{Any, TypeId};
use std::cell::RefCell;
use std::collections::HashMap;
use guinea_app::feature::{FeatureInitContext, Reaches, Segment};
use guinea_core::binding::ReducerBinding;
use guinea_core::scope::Reducer;
use guinea_router::router::{
Mount, NavigateHandle, RouteChain, SegmentEntry, SegmentProps, Ui, single_entry_chain,
};
pub struct Egui;
impl Ui for Egui {
type View<'a> = Node;
type Nodes = ();
}
pub struct Node(Box<dyn FnOnce(&mut egui::Ui)>);
impl Node {
pub fn new(draw: impl FnOnce(&mut egui::Ui) + 'static) -> Self {
Self(Box::new(draw))
}
pub fn draw(self, ui: &mut egui::Ui) {
(self.0)(ui)
}
}
pub trait Page: Default + Sized + 'static {
const CACHE_STATE_IN_MEMORY: bool = false;
const DECLARED: Option<guinea_core::actor::shape::Declared> = None;
type Params: PartialEq + '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 render(&mut self, cx: &mut PageCx<'_, Self>);
}
pub trait Layout: Default + Sized + 'static {
const DECLARED: Option<guinea_core::actor::shape::Declared> = None;
type Params: PartialEq + '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 render(&mut self, cx: &mut LayoutCx<'_, Self>);
}
pub const fn segment_entry<P: Page>() -> SegmentEntry<Egui> {
SegmentEntry::new::<P>(
install_page::<P>,
guinea_router::router::same_params::<P::Params>,
&const { MountPage::<P>(std::marker::PhantomData) },
P::CACHE_STATE_IN_MEMORY,
)
.written(P::DECLARED)
}
pub const fn layout_entry<L: Layout>() -> SegmentEntry<Egui> {
SegmentEntry::new::<L>(
install_layout::<L>,
guinea_router::router::same_params::<L::Params>,
&const { MountLayout::<L>(std::marker::PhantomData) },
false,
)
.written(L::DECLARED)
}
fn install_page<P: Page>(
ctx: &FeatureInitContext,
params: &dyn std::any::Any,
) -> anyhow::Result<()> {
let params = guinea_router::router::narrow::<P::Params, P>(params)?;
own(ctx, P::install(ctx, params)?);
keep(ctx, P::init(ctx, params));
Ok(())
}
fn own<T: 'static>(ctx: &FeatureInitContext, installed: T) {
ctx.scope.own(guinea_core::scope::DropGuard(installed));
}
fn install_layout<L: Layout>(
ctx: &FeatureInitContext,
params: &dyn std::any::Any,
) -> anyhow::Result<()> {
let params = guinea_router::router::narrow::<L::Params, L>(params)?;
own(ctx, L::install(ctx, params)?);
keep(ctx, L::init(ctx, params));
Ok(())
}
thread_local! {
static MOUNTED: RefCell<HashMap<(usize, TypeId), Option<Box<dyn Any>>>> =
RefCell::new(HashMap::new());
}
fn keep<S: 'static>(ctx: &FeatureInitContext, node: S) {
let at = (ctx.scope.key(), TypeId::of::<S>());
MOUNTED.with(|mounted| mounted.borrow_mut().insert(at, Some(Box::new(node))));
ctx.scope.own(Forget(at));
}
struct Forget((usize, TypeId));
impl guinea_core::scope::Teardown for Forget {
fn teardown(self) {
let _ = MOUNTED.try_with(|mounted| mounted.borrow_mut().remove(&self.0));
}
}
fn with_mounted<S: Default + 'static, R>(scope: usize, draw: impl FnOnce(&mut S) -> R) -> R {
let at = (scope, TypeId::of::<S>());
let taken = MOUNTED.with(|mounted| mounted.borrow_mut().get_mut(&at).and_then(Option::take));
let mut node = taken
.and_then(|node| node.downcast::<S>().ok())
.map_or_else(S::default, |node| *node);
let drawn = draw(&mut node);
MOUNTED.with(|mounted| {
if let Some(slot) = mounted.borrow_mut().get_mut(&at) {
*slot = Some(Box::new(node));
}
});
drawn
}
pub struct MountPage<P>(pub std::marker::PhantomData<P>);
pub struct MountLayout<L>(pub std::marker::PhantomData<L>);
impl<P: Page> Mount<Egui> for MountPage<P> {
fn view<'a>(&self, props: SegmentProps<Egui>, _nodes: &'a ()) -> Node {
let at = props.scopes[props.cursor].key();
Node::new(move |ui| {
let _drawing = guinea_core::devtools::Rendering::of(std::any::type_name::<P>());
with_mounted::<P, _>(at, |page| {
page.render(&mut PageCx {
ui,
props,
page: std::marker::PhantomData,
})
})
})
}
}
impl<L: Layout> Mount<Egui> for MountLayout<L> {
fn view<'a>(&self, props: SegmentProps<Egui>, _nodes: &'a ()) -> Node {
let at = props.scopes[props.cursor].key();
Node::new(move |ui| {
let _drawing = guinea_core::devtools::Rendering::of(std::any::type_name::<L>());
with_mounted::<L, _>(at, |layout| {
layout.render(&mut LayoutCx {
ui,
props,
layout: std::marker::PhantomData,
})
})
})
}
}
pub fn page_chain<P: Page>() -> &'static [SegmentEntry<Egui>] {
single_entry_chain(segment_entry::<P>())
}
pub struct PageCx<'a, P> {
ui: &'a mut egui::Ui,
props: SegmentProps<Egui>,
page: std::marker::PhantomData<fn() -> P>,
}
impl<P: Segment> PageCx<'_, P> {
pub fn state<R, I>(&self) -> (std::rc::Rc<R>, guinea_core::feature::Dispatch)
where
R: Reducer,
P: Reaches<R, I>,
{
let binding = self.props.binding::<R>();
(binding.get(), binding.dispatch())
}
pub fn binding<R, I>(&self) -> ReducerBinding<R>
where
R: Reducer,
P: Reaches<R, I>,
{
self.props.binding::<R>()
}
}
impl<P> PageCx<'_, P> {
pub fn ui(&mut self) -> &mut egui::Ui {
self.ui
}
pub fn navigate<R>(&self) -> NavigateHandle<Egui, R>
where
R: RouteChain<Egui> + Clone + PartialEq + 'static,
{
nav::current::<R>()
}
}
pub struct LayoutCx<'a, L> {
ui: &'a mut egui::Ui,
props: SegmentProps<Egui>,
layout: std::marker::PhantomData<fn() -> L>,
}
impl<L: Segment> LayoutCx<'_, L> {
pub fn state<R, I>(&self) -> (std::rc::Rc<R>, guinea_core::feature::Dispatch)
where
R: Reducer,
L: Reaches<R, I>,
{
let binding = self.props.binding::<R>();
(binding.get(), binding.dispatch())
}
pub fn binding<R, I>(&self) -> ReducerBinding<R>
where
R: Reducer,
L: Reaches<R, I>,
{
self.props.binding::<R>()
}
}
impl<L> LayoutCx<'_, L> {
pub fn ui(&mut self) -> &mut egui::Ui {
self.ui
}
pub fn navigate<R>(&self) -> NavigateHandle<Egui, R>
where
R: RouteChain<Egui> + Clone + PartialEq + 'static,
{
nav::current::<R>()
}
pub fn outlet(&self) -> Node {
self.props.outlet(&())
}
pub fn child_is<P: 'static>(&self) -> bool {
self.props
.chain
.get(self.props.cursor + 1)
.is_some_and(|entry| (entry.type_id)() == std::any::TypeId::of::<P>())
}
}