cotis 0.1.0-alpha

Modular Rust UI framework core
Documentation
use crate::element_configuring::{ConfigureElements, ConfiguredParentElement};
use crate::layout::{LayoutFrameManager, LayoutManager, LayoutManagerCompatible};
use crate::pipes::LayoutRenderPipe;
use crate::renders::{CotisRenderer, CotisRendererAsync, RenderCompatibleWith};

/// Connects a layout manager, renderer, and [`LayoutRenderPipe`].
///
/// `CotisApp` is the central runtime object used to compute frames.
/// It wires the layout stage and the rendering stage together so each
/// frame can be built from a UI closure and then drawn.
///
/// A pipe is always required: it converts layout output into renderer commands.
/// When both sides already use the same item type, pass [`()`] as the
/// [identity pipe](crate::pipes::LayoutRenderPipe) instead of a custom adapter.
pub struct CotisApp<
    Renderer: RenderCompatibleWith<Manager>,
    Manager: LayoutManagerCompatible<Renderer>,
    Pipe,
> {
    layout_manager: Manager,
    render_engine: Renderer,
    layout_render_pipe: Pipe,
}

impl<Renderer: RenderCompatibleWith<Manager>, Manager: LayoutManagerCompatible<Renderer>, Pipe>
    CotisApp<Renderer, Manager, Pipe>
{
    /// Returns a mutable reference to the layout manager.
    pub fn layout_manager(&mut self) -> &mut Manager {
        &mut self.layout_manager
    }

    /// Returns a mutable reference to the render engine.
    pub fn render(&mut self) -> &mut Renderer {
        &mut self.render_engine
    }

    /// Returns an immutable reference to the layout manager.
    pub fn layout_manager_borrow(&self) -> &Manager {
        &self.layout_manager
    }

    /// Returns an immutable reference to the render engine.
    pub fn render_borrow(&self) -> &Renderer {
        &self.render_engine
    }

    /// Creates a new `App` that is capable of computing frames synchronously.
    ///
    /// `layout_render_pipe` adapts layout output into renderer commands via
    /// [`LayoutRenderPipe`]. When no adaptation is needed, pass [`()`] (see
    /// [`LayoutRenderPipe` for `()`](crate::pipes::LayoutRenderPipe)).
    ///
    /// Consider using [`compute_frame`](Self::compute_frame) to render the UI.
    pub fn new(
        mut render_engine: Renderer,
        mut layout_manager: Manager,
        layout_render_pipe: Pipe,
    ) -> Self {
        render_engine.init(&mut layout_manager);
        layout_manager.init(&mut render_engine);
        CotisApp {
            render_engine,
            layout_manager,
            layout_render_pipe,
        }
    }
}

/// Trait implemented by UI entry closures passed to frame computation.
///
/// This abstraction allows APIs such as [`RenderApp::compute_frame`] to accept
/// closures while preserving strict type/lifetime contracts for the configured root.
pub trait UiFn<'inside, Configurer: ConfigureElements<ConfigType>, ConfigType>: Sized {
    /// Draws UI into the provided root element scope.
    fn draw_in(self, layout: &mut ConfiguredParentElement<'inside, Configurer, ConfigType>);
}

impl<
    'inside,
    Configurer: ConfigureElements<ConfigType>,
    ConfigType,
    T: FnOnce(&mut ConfiguredParentElement<'inside, Configurer, ConfigType>),
> UiFn<'inside, Configurer, ConfigType> for T
{
    fn draw_in(self, layout: &mut ConfiguredParentElement<'inside, Configurer, ConfigType>) {
        self(layout);
    }
}

/// Async frame computation API for application runners.
///
/// Implemented by [`CotisApp`] when:
/// - the renderer implements [`CotisRendererAsync`], and
/// - the layout manager implements [`LayoutManager`].
pub trait AsyncRenderApp<'frame, ConfigType, ElementType, RenderCommandType, Renderer, Manager>
where
    ConfigType: 'frame,
    ElementType: 'frame,
    Renderer: RenderCompatibleWith<Manager> + CotisRendererAsync<RenderCommandType>,
    Manager: LayoutManager<'frame, ConfigType, ElementType> + LayoutManagerCompatible<Renderer>,
{
    /// Computes and draws one frame asynchronously using `ui` as the entry point.
    ///
    /// The frame lifecycle is:
    /// 1. call `prepare` on the layout manager,
    /// 2. build the element tree by executing `ui`,
    /// 3. end the frame and transform layout output through the pipe,
    /// 4. draw the transformed command stream asynchronously.
    ///
    /// Consider looking at [`UiFn`] to understand accepted UI entry points.
    #[allow(async_fn_in_trait)]
    async fn compute_frame_async<'layout>(
        &'layout mut self,
        ui: impl for<'a> UiFn<'a, Manager::ElementConfigurer<'layout>, ConfigType>,
    ) where
        Manager: 'layout;
}

impl<'frame, ConfigType, ElementType, RenderCommandType, Renderer, Manager, Pipe>
    AsyncRenderApp<'frame, ConfigType, ElementType, RenderCommandType, Renderer, Manager>
    for CotisApp<Renderer, Manager, Pipe>
where
    ConfigType: 'frame,
    ElementType: 'frame,
    Renderer: RenderCompatibleWith<Manager> + CotisRendererAsync<RenderCommandType>,
    Manager: LayoutManager<'frame, ConfigType, ElementType> + LayoutManagerCompatible<Renderer>,
    Pipe: LayoutRenderPipe<ElementType, RenderCommandType>,
{
    /// Computes and draws one frame asynchronously using `ui` as the entry point.
    ///
    /// Consider looking at [`UiFn`] to understand accepted UI entry points.
    async fn compute_frame_async<'layout>(
        &'layout mut self,
        ui: impl for<'a> UiFn<'a, Manager::ElementConfigurer<'layout>, ConfigType>,
    ) where
        Manager: 'layout,
    {
        self.layout_manager.prepare(&self.render_engine);
        let commands = {
            let mut layout = self.layout_manager.begin_frame();
            let mut parent = layout.begin();
            ui.draw_in(&mut parent);
            drop(parent);
            self.layout_render_pipe.transform(layout.end())
        };
        CotisRendererAsync::draw_frame(&mut self.render_engine, commands).await;
    }
}

/// Synchronous frame computation API for application runners.
///
/// Implemented by [`CotisApp`] when the renderer implements [`CotisRenderer`].
pub trait RenderApp<'frame, ConfigType, ElementType, RenderCommandType, Renderer, Manager>
where
    ConfigType: 'frame,
    ElementType: 'frame,
    Renderer: RenderCompatibleWith<Manager> + CotisRenderer<RenderCommandType>,
    Manager: LayoutManager<'frame, ConfigType, ElementType> + LayoutManagerCompatible<Renderer>,
{
    /// Computes and draws one frame using `ui` as the entry point.
    ///
    /// The frame lifecycle is:
    /// 1. call `prepare` on the layout manager,
    /// 2. build the element tree by executing `ui`,
    /// 3. end the frame and transform layout output through the pipe,
    /// 4. draw the transformed command stream synchronously.
    ///
    /// Consider looking at [`UiFn`] to understand accepted UI entry points.
    fn compute_frame<'layout>(
        &'layout mut self,
        ui: impl for<'a> UiFn<'a, Manager::ElementConfigurer<'layout>, ConfigType>,
    ) where
        Manager: 'layout;
}

impl<'frame, ConfigType, ElementType, RenderCommandType, Renderer, Manager, Pipe>
    RenderApp<'frame, ConfigType, ElementType, RenderCommandType, Renderer, Manager>
    for CotisApp<Renderer, Manager, Pipe>
where
    ConfigType: 'frame,
    ElementType: 'frame,
    Renderer: RenderCompatibleWith<Manager> + CotisRenderer<RenderCommandType>,
    Manager: LayoutManager<'frame, ConfigType, ElementType> + LayoutManagerCompatible<Renderer>,
    Pipe: LayoutRenderPipe<ElementType, RenderCommandType>,
{
    /// Computes and draws one frame using `ui` as the entry point.
    ///
    /// Consider looking at [`UiFn`] to understand accepted UI entry points.
    fn compute_frame<'layout>(
        &'layout mut self,
        ui: impl for<'a> UiFn<'a, Manager::ElementConfigurer<'layout>, ConfigType>,
    ) where
        Manager: 'layout,
    {
        self.layout_manager.prepare(&self.render_engine);
        let commands = {
            let mut layout = self.layout_manager.begin_frame();
            let mut parent = layout.begin();
            ui.draw_in(&mut parent);
            drop(parent);
            self.layout_render_pipe.transform(layout.end())
        };
        CotisRenderer::draw_frame(&mut self.render_engine, commands);
    }
}
// TODO Maybe move to Utils?
/// Helper used to annotate UI closures with the expected Cotis root signature.
///
/// This is useful when type inference around higher-ranked lifetimes is not enough.
///
/// # Examples
///
/// ```rust,ignore
/// use cotis::cotis_app::closure_helper;
///
/// let _ui = closure_helper(|_root| {
///     // Build UI here.
/// });
/// ```
pub fn closure_helper<'frame, 'layout, F, Manager, ConfigType: 'frame, ElementType: 'frame>(
    f: F,
) -> F
where
    F: for<'a> FnOnce(
        &mut ConfiguredParentElement<'a, Manager::ElementConfigurer<'layout>, ConfigType>,
    ),
    Manager: LayoutManager<'frame, ConfigType, ElementType>,
{
    f
}