Skip to main content

cotis_layout/
cotis_traits.rs

1//! Cotis trait implementations wiring [`CotisLayoutManager`](crate::preamble::CotisLayoutManager)
2//! into the standard frame and element configuration API.
3//!
4//! # Default integration path
5//!
6//! When using [`ElementConfig`](cotis_defaults::element_configs::ElementConfig) from
7//! `cotis-defaults`, this module provides everything needed out of the box:
8//!
9//! - [`LayoutManagerCompatible`](cotis::layout::LayoutManagerCompatible) — installs the renderer's text measurer and viewport size.
10//! - [`LayoutManager`](cotis::layout::LayoutManager) / [`LayoutFrameManager`](cotis::layout::LayoutFrameManager) — `begin_frame` → [`CotisLayoutRun`](crate::preamble::CotisLayoutRun) → `end`.
11//! - [`ConfigureElements`](cotis::element_configuring::ConfigureElements) — open/set/close elements on `CotisLayoutRun<ElementConfig<…>>`.
12//! - [`ConfigureLeafElements`](cotis::element_configuring::ConfigureLeafElements) with [`TextElementConfig`](cotis_defaults::element_configs::text_config::TextElementConfig) — text leaf expansion.
13//! - [`CotisLayoutCompatible`](crate::layout_traits::CotisLayoutCompatible) and [`TextConfigurable`](crate::preamble::TextConfigurable) for
14//!   both `Sizing` and `DoubleAxisSizing` style parameterizations.
15//! - [`From`](core::convert::From)`<&`[`CotisStyle`](cotis_defaults::element_configs::style::CotisStyle)`>` → `LayoutElementConfig` — style conversion (always sets
16//!   internal `ChildWrapping::None`).
17//!
18//! # Text layout
19//!
20//! [`ConfigureLeafElements::set_leaf_config`](cotis::element_configuring::ConfigureLeafElements::set_leaf_config)
21//! on a text leaf expands the string into synthetic child nodes:
22//!
23//! - Lines split on `\n`, laid out top-to-bottom.
24//! - Each line becomes an LTR row with internal `ChildWrapping::Text`.
25//! - [`TextElementConfigWrapMode::Words`](cotis_defaults::element_configs::text_config::TextElementConfigWrapMode::Words)
26//!   creates word and spacer children with [`TextDrawPayload`](crate::layout_struct::TextDrawPayload).
27//! - `Newline` / `None` wrap modes keep one fragment per line.
28//!
29//! Custom config types must implement [`CotisLayoutCompatible`](crate::layout_traits::CotisLayoutCompatible)
30//! and [`TextConfigurable`](crate::preamble::TextConfigurable) to support text leaves.
31//!
32//! # Element state queries
33//!
34//! See [`secondary_traits`] for debugging and hit-testing hooks on the post-frame cache.
35
36use crate::layout_management::{CotisLayoutManager, CotisLayoutRun};
37use crate::layout_struct::RenderCommandOutput;
38use crate::layout_struct::info_types::{ClipElement, FloatingElementConfig, FloatingElementStyle};
39use crate::layout_struct::layout_states::{ChildWrapping, LayoutElementConfig, LayoutElementStyle};
40use crate::layout_traits::CotisLayoutCompatible;
41use cotis::element_configuring::{
42    ConfigureElements, ConfigureLeafElements, ConfiguredParentElement, Seal,
43};
44use cotis::layout::{LayoutFrameManager, LayoutManager, LayoutManagerCompatible};
45use cotis::utils::{ElementId, ElementIdConfig};
46use cotis::utils::{OwnedOrRef, OwnedOrRefMut};
47use cotis_defaults::element_configs::ElementConfig;
48use cotis_defaults::element_configs::style::CotisStyle;
49use cotis_defaults::element_configs::style::sizing::{AxisSizing, DoubleAxisSizing, Sizing};
50use cotis_defaults::element_configs::style::types::LayoutDirection;
51use cotis_defaults::element_configs::text_config::{TextConfig, TextElementConfig};
52use cotis_utils::text::{LayoutTextMeasuring, RendererTextMeasuringProvider};
53use cotis_utils::traits::CotisWindowContext;
54use std::sync::Arc;
55
56pub mod secondary_traits;
57
58impl<Renderer: CotisWindowContext + RendererTextMeasuringProvider<TextConfig>>
59    LayoutManagerCompatible<Renderer> for CotisLayoutManager
60{
61    fn init(&mut self, renderer: &mut Renderer) {
62        self.set_text_measuring_function(renderer.provide_measurer());
63    }
64    fn prepare(&mut self, render: &Renderer) {
65        self.set_dimensions(render.window_dimensions());
66    }
67}
68
69impl<'frame, T> LayoutManager<'frame, T, RenderCommandOutput<T>> for CotisLayoutManager
70where
71    T: CotisLayoutCompatible<'frame> + 'frame,
72{
73    type ElementConfigurer<'layout>
74        = CotisLayoutRun<'layout, T>
75    where
76        Self: 'layout;
77
78    fn begin_frame<'layout>(
79        &'layout mut self,
80    ) -> impl LayoutFrameManager<'frame, T, RenderCommandOutput<T>, Self::ElementConfigurer<'layout>>
81    where
82        T: 'frame,
83        RenderCommandOutput<T>: 'frame,
84        Self: 'layout,
85    {
86        CotisLayoutRun::new(self)
87    }
88}
89
90impl<'frame, T> LayoutFrameManager<'frame, T, RenderCommandOutput<T>, Self>
91    for CotisLayoutRun<'_, T>
92where
93    T: CotisLayoutCompatible<'frame> + 'frame,
94{
95    fn begin(&'_ mut self) -> ConfiguredParentElement<'_, Self, T> {
96        ConfiguredParentElement::new(OwnedOrRefMut::Ref(self))
97    }
98
99    fn end(mut self) -> impl Iterator<Item = RenderCommandOutput<T>> {
100        self.finalize_layouts().into_iter()
101    }
102}
103
104impl<'frame, I, C, E> CotisLayoutCompatible<'frame>
105    for ElementConfig<'frame, DoubleAxisSizing, I, C, E>
106{
107    fn get_style(&self) -> LayoutElementConfig {
108        (&self.style).into()
109    }
110
111    fn get_id(&self) -> ElementId {
112        self.id_config.get_handle()
113    }
114
115    fn try_get_name(&self) -> Option<OwnedOrRef<'frame, str>> {
116        self.id_config
117            .get_id_name()
118            .map(|name| OwnedOrRef::AsOwnedRef(Arc::new(name.to_string())))
119    }
120}
121
122impl<'frame, I, C, E> CotisLayoutCompatible<'frame> for ElementConfig<'frame, Sizing, I, C, E> {
123    fn get_style(&self) -> LayoutElementConfig {
124        (&self.style).into()
125    }
126
127    fn get_id(&self) -> ElementId {
128        self.id_config.get_handle()
129    }
130
131    fn try_get_name(&self) -> Option<OwnedOrRef<'frame, str>> {
132        self.id_config
133            .get_id_name()
134            .map(|name| OwnedOrRef::AsOwnedRef(Arc::new(name.to_string())))
135    }
136}
137
138impl<'name, T> ConfigureElements<T> for CotisLayoutRun<'_, T>
139where
140    T: CotisLayoutCompatible<'name>,
141{
142    fn open_new_element(&mut self, _: Seal) {
143        self.new_child_element();
144    }
145
146    fn set_config(&mut self, config: T, _: Seal) {
147        let style = config.get_style();
148        let id = config.get_id();
149        let name = config.try_get_name();
150        self.set_open_element_config(style, id, name, config);
151    }
152
153    fn close_element(&mut self, _: Seal) {
154        self.close_open_element();
155    }
156}
157
158impl<'frame, I, C, E> crate::preamble::TextConfigurable for ElementConfig<'frame, Sizing, I, C, E> {
159    fn synthetic_text_child(
160        &self,
161        layout_direction: LayoutDirection,
162        child_gap: f32,
163        width: AxisSizing,
164        height: AxisSizing,
165    ) -> Self {
166        #[allow(clippy::clone_on_copy)] // To allow or disallow complex color
167        let mut style = self.style.clone();
168        crate::text::text_leafs::reset_text_child_style(&mut style, layout_direction, child_gap);
169        style.sizing = Sizing::DoubleAxis(DoubleAxisSizing { width, height });
170
171        ElementConfig {
172            id_config: ElementIdConfig::new_empty(),
173            style,
174            image: None,
175            custom: None,
176            extra_data: None,
177        }
178    }
179}
180
181impl<'frame, I, C, E> crate::preamble::TextConfigurable
182    for ElementConfig<'frame, DoubleAxisSizing, I, C, E>
183{
184    fn synthetic_text_child(
185        &self,
186        layout_direction: LayoutDirection,
187        child_gap: f32,
188        width: AxisSizing,
189        height: AxisSizing,
190    ) -> Self {
191        #[allow(clippy::clone_on_copy)] // To allow or disallow complex color
192        let mut style = self.style.clone();
193        crate::text::text_leafs::reset_text_child_style(&mut style, layout_direction, child_gap);
194        style.sizing = DoubleAxisSizing { width, height };
195
196        ElementConfig {
197            id_config: ElementIdConfig::new_empty(),
198            style,
199            image: None,
200            custom: None,
201            extra_data: None,
202        }
203    }
204}
205
206impl<'frame, I, C, E: 'frame + Sync + Send> ConfigureLeafElements<TextElementConfig<'frame>>
207    for CotisLayoutRun<'_, ElementConfig<'frame, Sizing, I, C, E>>
208{
209    fn set_leaf_config(&mut self, leaf: TextElementConfig<'frame>, _: Seal) {
210        self.apply_text_leaf(leaf);
211    }
212}
213
214impl<'frame, I, C, E: 'frame + Sync + Send> ConfigureLeafElements<TextElementConfig<'frame>>
215    for CotisLayoutRun<'_, ElementConfig<'frame, DoubleAxisSizing, I, C, E>>
216{
217    fn set_leaf_config(&mut self, leaf: TextElementConfig<'frame>, _: Seal) {
218        self.apply_text_leaf(leaf);
219    }
220}
221
222impl From<&CotisStyle<Sizing>> for LayoutElementConfig {
223    fn from(value: &CotisStyle<Sizing>) -> Self {
224        LayoutElementConfig {
225            layout: LayoutElementStyle {
226                sizing: value.sizing,
227                padding: value.layout.padding,
228                child_gap: value.layout.child_gap,
229                child_alignment: value.layout.child_alignment,
230                layout_direction: value.layout.layout_direction,
231                wrapping: ChildWrapping::None,
232            },
233            floating: FloatingElementStyle {
234                config: value.floating.map(|value| FloatingElementConfig {
235                    offset: value.offset,
236                    expand: value.expand,
237                    attach_point: value.attach_point,
238                    pointer_capture_mode: value.pointer_capture_mode,
239                }),
240                z_index: value.floating.map(|f| f.z_index).unwrap_or(0) as i32,
241            },
242            clip: ClipElement {
243                horizontal: value.clip.horizontal,
244                vertical: value.clip.vertical,
245                offset: value.clip.child_offset,
246            },
247        }
248    }
249}
250
251impl From<&CotisStyle<DoubleAxisSizing>> for LayoutElementConfig {
252    fn from(value: &CotisStyle<DoubleAxisSizing>) -> Self {
253        LayoutElementConfig {
254            layout: LayoutElementStyle {
255                sizing: Sizing::DoubleAxis(value.sizing),
256                padding: value.layout.padding,
257                child_gap: value.layout.child_gap,
258                child_alignment: value.layout.child_alignment,
259                layout_direction: value.layout.layout_direction,
260                wrapping: ChildWrapping::None,
261            },
262            floating: FloatingElementStyle {
263                config: value.floating.map(|value| FloatingElementConfig {
264                    offset: value.offset,
265                    expand: value.expand,
266                    attach_point: value.attach_point,
267                    pointer_capture_mode: value.pointer_capture_mode,
268                }),
269                z_index: value.floating.map(|f| f.z_index).unwrap_or(0) as i32,
270            },
271            clip: ClipElement {
272                horizontal: value.clip.horizontal,
273                vertical: value.clip.vertical,
274                offset: value.clip.child_offset,
275            },
276        }
277    }
278}