mod dispatcher;
mod nav;
mod run;
pub use run::{MAIN, run};
use guinea_app::feature::{FeatureInitContext, Reads};
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 = ();
type Mount = dyn Mount<Self>;
}
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: guinea_app::feature::Lists;
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: guinea_app::feature::Lists;
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>,
<P::Installs as guinea_app::feature::Lists>::list,
&const { MountPage::<P>(std::marker::PhantomData) } as &dyn Mount<Egui>,
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>,
<L::Installs as guinea_app::feature::Lists>::list,
&const { MountLayout::<L>(std::marker::PhantomData) } as &dyn Mount<Egui>,
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)?);
guinea_router::mounted::keep(&ctx.scope, 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)?);
guinea_router::mounted::keep(&ctx.scope, L::init(ctx, params));
Ok(())
}
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];
Node::new(move |ui| {
let _drawing = guinea_core::observability::Rendering::of(std::any::type_name::<P>());
guinea_router::mounted::with::<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];
Node::new(move |ui| {
let _drawing = guinea_core::observability::Rendering::of(std::any::type_name::<L>());
guinea_router::mounted::with::<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>,
}
fn feature_of<R: Reducer>(
props: &SegmentProps<Egui>,
) -> (std::rc::Rc<R>, guinea_core::feature::Dispatch) {
let binding = props.binding::<R>();
(binding.get(), binding.dispatch())
}
impl<P> PageCx<'_, P> {
pub fn read<R>(&mut self) -> (std::rc::Rc<R>, guinea_core::feature::Dispatch)
where
R: Reducer,
{
feature_of::<R>(&self.props)
}
pub fn binding<R>(&self) -> ReducerBinding<R>
where
R: Reducer,
{
self.props.binding::<R>()
}
}
impl<P> Reads for PageCx<'_, P> {
fn read<R>(&mut self) -> (std::rc::Rc<R>, guinea_core::feature::Dispatch)
where
R: Reducer + PartialEq,
{
feature_of::<R>(&self.props)
}
fn dispatch<R>(&self) -> guinea_core::feature::Dispatch
where
R: Reducer,
{
self.props.binding::<R>().dispatch()
}
}
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> LayoutCx<'_, L> {
pub fn read<R>(&mut self) -> (std::rc::Rc<R>, guinea_core::feature::Dispatch)
where
R: Reducer,
{
feature_of::<R>(&self.props)
}
pub fn binding<R>(&self) -> ReducerBinding<R>
where
R: Reducer,
{
self.props.binding::<R>()
}
}
impl<L> Reads for LayoutCx<'_, L> {
fn read<R>(&mut self) -> (std::rc::Rc<R>, guinea_core::feature::Dispatch)
where
R: Reducer + PartialEq,
{
feature_of::<R>(&self.props)
}
fn dispatch<R>(&self) -> guinea_core::feature::Dispatch
where
R: Reducer,
{
self.props.binding::<R>().dispatch()
}
}
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>())
}
}