cotis-layout 0.1.0-alpha.1

Flexbox-style layout engine for Cotis
Documentation
//! Cotis trait implementations wiring [`CotisLayoutManager`](crate::preamble::CotisLayoutManager)
//! into the standard frame and element configuration API.
//!
//! # Default integration path
//!
//! When using [`ElementConfig`](cotis_defaults::element_configs::ElementConfig) from
//! `cotis-defaults`, this module provides everything needed out of the box:
//!
//! - [`LayoutManagerCompatible`](cotis::layout::LayoutManagerCompatible) — installs the renderer's text measurer and viewport size.
//! - [`LayoutManager`](cotis::layout::LayoutManager) / [`LayoutFrameManager`](cotis::layout::LayoutFrameManager) — `begin_frame` → [`CotisLayoutRun`](crate::preamble::CotisLayoutRun) → `end`.
//! - [`ConfigureElements`](cotis::element_configuring::ConfigureElements) — open/set/close elements on `CotisLayoutRun<ElementConfig<…>>`.
//! - [`ConfigureLeafElements`](cotis::element_configuring::ConfigureLeafElements) with [`TextElementConfig`](cotis_defaults::element_configs::text_config::TextElementConfig) — text leaf expansion.
//! - [`CotisLayoutCompatible`](crate::layout_traits::CotisLayoutCompatible) and [`TextConfigurable`](crate::preamble::TextConfigurable) for
//!   both `Sizing` and `DoubleAxisSizing` style parameterizations.
//! - [`From`](core::convert::From)`<&`[`CotisStyle`](cotis_defaults::element_configs::style::CotisStyle)`>` → `LayoutElementConfig` — style conversion (always sets
//!   internal `ChildWrapping::None`).
//!
//! # Text layout
//!
//! [`ConfigureLeafElements::set_leaf_config`](cotis::element_configuring::ConfigureLeafElements::set_leaf_config)
//! on a text leaf expands the string into synthetic child nodes:
//!
//! - Lines split on `\n`, laid out top-to-bottom.
//! - Each line becomes an LTR row with internal `ChildWrapping::Text`.
//! - [`TextElementConfigWrapMode::Words`](cotis_defaults::element_configs::text_config::TextElementConfigWrapMode::Words)
//!   creates word and spacer children with [`TextDrawPayload`](crate::layout_struct::TextDrawPayload).
//! - `Newline` / `None` wrap modes keep one fragment per line.
//!
//! Custom config types must implement [`CotisLayoutCompatible`](crate::layout_traits::CotisLayoutCompatible)
//! and [`TextConfigurable`](crate::preamble::TextConfigurable) to support text leaves.
//!
//! # Element state queries
//!
//! See [`secondary_traits`] for debugging and hit-testing hooks on the post-frame cache.

use crate::layout_management::{CotisLayoutManager, CotisLayoutRun};
use crate::layout_struct::RenderCommandOutput;
use crate::layout_struct::info_types::{ClipElement, FloatingElementConfig, FloatingElementStyle};
use crate::layout_struct::layout_states::{ChildWrapping, LayoutElementConfig, LayoutElementStyle};
use crate::layout_traits::CotisLayoutCompatible;
use cotis::element_configuring::{
    ConfigureElements, ConfigureLeafElements, ConfiguredParentElement, Seal,
};
use cotis::layout::{LayoutFrameManager, LayoutManager, LayoutManagerCompatible};
use cotis::utils::{ElementId, ElementIdConfig};
use cotis::utils::{OwnedOrRef, OwnedOrRefMut};
use cotis_defaults::element_configs::ElementConfig;
use cotis_defaults::element_configs::style::CotisStyle;
use cotis_defaults::element_configs::style::sizing::{AxisSizing, DoubleAxisSizing, Sizing};
use cotis_defaults::element_configs::style::types::LayoutDirection;
use cotis_defaults::element_configs::text_config::{TextConfig, TextElementConfig};
use cotis_utils::text::{LayoutTextMeasuring, RendererTextMeasuringProvider};
use cotis_utils::traits::CotisWindowContext;
use std::sync::Arc;

pub mod secondary_traits;

impl<Renderer: CotisWindowContext + RendererTextMeasuringProvider<TextConfig>>
    LayoutManagerCompatible<Renderer> for CotisLayoutManager
{
    fn init(&mut self, renderer: &mut Renderer) {
        self.set_text_measuring_function(renderer.provide_measurer());
    }
    fn prepare(&mut self, render: &Renderer) {
        self.set_dimensions(render.window_dimensions());
    }
}

impl<'frame, T> LayoutManager<'frame, T, RenderCommandOutput<T>> for CotisLayoutManager
where
    T: CotisLayoutCompatible<'frame> + 'frame,
{
    type ElementConfigurer<'layout>
        = CotisLayoutRun<'layout, T>
    where
        Self: 'layout;

    fn begin_frame<'layout>(
        &'layout mut self,
    ) -> impl LayoutFrameManager<'frame, T, RenderCommandOutput<T>, Self::ElementConfigurer<'layout>>
    where
        T: 'frame,
        RenderCommandOutput<T>: 'frame,
        Self: 'layout,
    {
        CotisLayoutRun::new(self)
    }
}

impl<'frame, T> LayoutFrameManager<'frame, T, RenderCommandOutput<T>, Self>
    for CotisLayoutRun<'_, T>
where
    T: CotisLayoutCompatible<'frame> + 'frame,
{
    fn begin(&'_ mut self) -> ConfiguredParentElement<'_, Self, T> {
        ConfiguredParentElement::new(OwnedOrRefMut::Ref(self))
    }

    fn end(mut self) -> impl Iterator<Item = RenderCommandOutput<T>> {
        self.finalize_layouts().into_iter()
    }
}

impl<'frame, I, C, E> CotisLayoutCompatible<'frame>
    for ElementConfig<'frame, DoubleAxisSizing, I, C, E>
{
    fn get_style(&self) -> LayoutElementConfig {
        (&self.style).into()
    }

    fn get_id(&self) -> ElementId {
        self.id_config.get_handle()
    }

    fn try_get_name(&self) -> Option<OwnedOrRef<'frame, str>> {
        self.id_config
            .get_id_name()
            .map(|name| OwnedOrRef::AsOwnedRef(Arc::new(name.to_string())))
    }
}

impl<'frame, I, C, E> CotisLayoutCompatible<'frame> for ElementConfig<'frame, Sizing, I, C, E> {
    fn get_style(&self) -> LayoutElementConfig {
        (&self.style).into()
    }

    fn get_id(&self) -> ElementId {
        self.id_config.get_handle()
    }

    fn try_get_name(&self) -> Option<OwnedOrRef<'frame, str>> {
        self.id_config
            .get_id_name()
            .map(|name| OwnedOrRef::AsOwnedRef(Arc::new(name.to_string())))
    }
}

impl<'name, T> ConfigureElements<T> for CotisLayoutRun<'_, T>
where
    T: CotisLayoutCompatible<'name>,
{
    fn open_new_element(&mut self, _: Seal) {
        self.new_child_element();
    }

    fn set_config(&mut self, config: T, _: Seal) {
        let style = config.get_style();
        let id = config.get_id();
        let name = config.try_get_name();
        self.set_open_element_config(style, id, name, config);
    }

    fn close_element(&mut self, _: Seal) {
        self.close_open_element();
    }
}

impl<'frame, I, C, E> crate::preamble::TextConfigurable for ElementConfig<'frame, Sizing, I, C, E> {
    fn synthetic_text_child(
        &self,
        layout_direction: LayoutDirection,
        child_gap: f32,
        width: AxisSizing,
        height: AxisSizing,
    ) -> Self {
        #[allow(clippy::clone_on_copy)] // To allow or disallow complex color
        let mut style = self.style.clone();
        crate::text::text_leafs::reset_text_child_style(&mut style, layout_direction, child_gap);
        style.sizing = Sizing::DoubleAxis(DoubleAxisSizing { width, height });

        ElementConfig {
            id_config: ElementIdConfig::new_empty(),
            style,
            image: None,
            custom: None,
            extra_data: None,
        }
    }
}

impl<'frame, I, C, E> crate::preamble::TextConfigurable
    for ElementConfig<'frame, DoubleAxisSizing, I, C, E>
{
    fn synthetic_text_child(
        &self,
        layout_direction: LayoutDirection,
        child_gap: f32,
        width: AxisSizing,
        height: AxisSizing,
    ) -> Self {
        #[allow(clippy::clone_on_copy)] // To allow or disallow complex color
        let mut style = self.style.clone();
        crate::text::text_leafs::reset_text_child_style(&mut style, layout_direction, child_gap);
        style.sizing = DoubleAxisSizing { width, height };

        ElementConfig {
            id_config: ElementIdConfig::new_empty(),
            style,
            image: None,
            custom: None,
            extra_data: None,
        }
    }
}

impl<'frame, I, C, E: 'frame + Sync + Send> ConfigureLeafElements<TextElementConfig<'frame>>
    for CotisLayoutRun<'_, ElementConfig<'frame, Sizing, I, C, E>>
{
    fn set_leaf_config(&mut self, leaf: TextElementConfig<'frame>, _: Seal) {
        self.apply_text_leaf(leaf);
    }
}

impl<'frame, I, C, E: 'frame + Sync + Send> ConfigureLeafElements<TextElementConfig<'frame>>
    for CotisLayoutRun<'_, ElementConfig<'frame, DoubleAxisSizing, I, C, E>>
{
    fn set_leaf_config(&mut self, leaf: TextElementConfig<'frame>, _: Seal) {
        self.apply_text_leaf(leaf);
    }
}

impl From<&CotisStyle<Sizing>> for LayoutElementConfig {
    fn from(value: &CotisStyle<Sizing>) -> Self {
        LayoutElementConfig {
            layout: LayoutElementStyle {
                sizing: value.sizing,
                padding: value.layout.padding,
                child_gap: value.layout.child_gap,
                child_alignment: value.layout.child_alignment,
                layout_direction: value.layout.layout_direction,
                wrapping: ChildWrapping::None,
            },
            floating: FloatingElementStyle {
                config: value.floating.map(|value| FloatingElementConfig {
                    offset: value.offset,
                    expand: value.expand,
                    attach_point: value.attach_point,
                    pointer_capture_mode: value.pointer_capture_mode,
                }),
                z_index: value.floating.map(|f| f.z_index).unwrap_or(0) as i32,
            },
            clip: ClipElement {
                horizontal: value.clip.horizontal,
                vertical: value.clip.vertical,
                offset: value.clip.child_offset,
            },
        }
    }
}

impl From<&CotisStyle<DoubleAxisSizing>> for LayoutElementConfig {
    fn from(value: &CotisStyle<DoubleAxisSizing>) -> Self {
        LayoutElementConfig {
            layout: LayoutElementStyle {
                sizing: Sizing::DoubleAxis(value.sizing),
                padding: value.layout.padding,
                child_gap: value.layout.child_gap,
                child_alignment: value.layout.child_alignment,
                layout_direction: value.layout.layout_direction,
                wrapping: ChildWrapping::None,
            },
            floating: FloatingElementStyle {
                config: value.floating.map(|value| FloatingElementConfig {
                    offset: value.offset,
                    expand: value.expand,
                    attach_point: value.attach_point,
                    pointer_capture_mode: value.pointer_capture_mode,
                }),
                z_index: value.floating.map(|f| f.z_index).unwrap_or(0) as i32,
            },
            clip: ClipElement {
                horizontal: value.clip.horizontal,
                vertical: value.clip.vertical,
                offset: value.clip.child_offset,
            },
        }
    }
}