Skip to main content

cotis_layout/
layout_management.rs

1use crate::layout_algorithm::axis_utils::Axis;
2use crate::layout_algorithm::commands::{calculate_render_commands, sort_commands};
3use crate::layout_algorithm::positions::calculate_child_positions;
4use crate::layout_algorithm::sizing::fit_grow_shrink_children_axis;
5use crate::layout_algorithm::wrapping::recursive_wrap_left_to_right;
6use crate::layout_struct::layout_states::{
7    ChildWrapping, LayoutElementConfig, LayoutElementInfo, LayoutElementStyle, element_info_states,
8};
9use crate::layout_struct::layout_tree::{
10    LayoutElement, LayoutTree, LayoutTreeCursor, LayoutTreeCursorBorrow, LayoutTreeIndex,
11};
12use crate::layout_struct::{RenderCommandOutput, TextDrawPayload};
13use crate::text::TextMeasuringFun;
14use cotis::utils::ElementId;
15use cotis::utils::ElementIdConfig;
16use cotis::utils::OwnedOrRef;
17use cotis_defaults::element_configs::style::sizing::Sizing::DoubleAxis;
18use cotis_defaults::element_configs::style::sizing::{AxisSizing, DoubleAxisSizing};
19use cotis_defaults::element_configs::style::types::{
20    Alignment, LayoutAlignmentX, LayoutAlignmentY, LayoutDirection, Padding,
21};
22use cotis_defaults::element_configs::text_config::TextConfig as DefaultTextConfig;
23use cotis_utils::math::Dimensions;
24use cotis_utils::text::{LayoutTextMeasuring, TextMeasurer};
25use std::collections::{HashMap, VecDeque};
26
27/// Owns the layout tree, viewport size, text measurer, and post-frame element cache.
28///
29/// Implement [`LayoutManager`](cotis::layout::LayoutManager) via [`crate::cotis_traits`] and
30/// wire into [`CotisApp`](cotis::cotis_app::CotisApp) with a pipe (e.g.
31/// `CotisLayoutToRenderListPipeForGenerics` from `cotis-pipes`).
32///
33/// The renderer initializes text measurement and viewport dimensions through
34/// [`LayoutManagerCompatible`](cotis::layout::LayoutManagerCompatible).
35pub struct CotisLayoutManager {
36    root_id: ElementIdConfig<'static>,
37    root: LayoutTree,
38    screen_dimensions: Dimensions,
39    cache_elements: HashMap<ElementId, LayoutElement>,
40    /// Child layout slot (`ElementId::get_id()`) → parent slot, from the last `clear_tree`.
41    element_parent: HashMap<ElementId, ElementId>,
42    text_dim_fun: Option<TextMeasuringFun>,
43}
44
45/// Per-frame element tree configurator returned by [`LayoutManager::begin_frame`](cotis::layout::LayoutManager::begin_frame).
46///
47/// Implements [`ConfigureElements`](cotis::element_configuring::ConfigureElements) when `Config`
48/// implements [`CotisLayoutCompatible`](crate::layout_traits::CotisLayoutCompatible). Call
49/// [`LayoutFrameManager::end`](cotis::layout::LayoutFrameManager::end) (or
50/// [`finalize_layouts`](Self::finalize_layouts) directly) to run layout and obtain
51/// [`RenderCommandOutput`](crate::layout_struct::RenderCommandOutput) values.
52pub struct CotisLayoutRun<'layout, Config> {
53    pub(crate) layout_manager: &'layout mut CotisLayoutManager,
54    open_element_index: LayoutTreeIndex,
55    configs: HashMap<ElementId, Config>,
56    pub(crate) text_fragments: HashMap<ElementId, TextDrawPayload>,
57}
58
59impl CotisLayoutManager {
60    /// Installs a function that measures text for layout.
61    ///
62    /// Normally set automatically by [`LayoutManagerCompatible::init`](cotis::layout::LayoutManagerCompatible::init)
63    /// from the renderer's [`RendererTextMeasuringProvider`](cotis_utils::text::RendererTextMeasuringProvider).
64    /// Override only for custom measurement or testing.
65    pub fn set_text_dim_fun(&mut self, fun: Box<dyn Fn(&str, &DefaultTextConfig) -> Dimensions>) {
66        self.text_dim_fun = Some(fun);
67    }
68
69    pub(crate) fn text_dim_fun(&self) -> Option<&TextMeasuringFun> {
70        self.text_dim_fun.as_ref()
71    }
72}
73
74impl LayoutTextMeasuring<DefaultTextConfig> for CotisLayoutManager {
75    fn set_text_measuring_function(&mut self, text: Box<TextMeasurer<'_, DefaultTextConfig>>) {
76        // SAFETY: Renderer implementations return 'static closures (they clone owned
77        // resources such as font tables into the closure). The trait's elided lifetime is
78        // tied to `&mut self`, not the closure's capture.
79        self.text_dim_fun = Some(unsafe {
80            std::mem::transmute::<Box<TextMeasurer<'_, DefaultTextConfig>>, TextMeasuringFun>(text)
81        });
82    }
83}
84
85impl CotisLayoutManager {
86    fn default_node(&self) -> LayoutElement {
87        LayoutElement {
88            id: self.root_id.get_handle(),
89            name: "Root".to_string(),
90            info: LayoutElementInfo::NotInitialized,
91            config: LayoutElementConfig {
92                layout: LayoutElementStyle {
93                    sizing: DoubleAxis(DoubleAxisSizing {
94                        width: AxisSizing::Fixed(self.screen_dimensions.width),
95                        height: AxisSizing::Fixed(self.screen_dimensions.height),
96                    }),
97                    padding: Padding {
98                        top: 0.0,
99                        bottom: 0.0,
100                        right: 0.0,
101                        left: 0.0,
102                    },
103                    child_gap: 0.0,
104                    child_alignment: Alignment {
105                        x: LayoutAlignmentX::Left,
106                        y: LayoutAlignmentY::Top,
107                    },
108                    layout_direction: LayoutDirection::LeftToRight,
109                    wrapping: ChildWrapping::None,
110                },
111                ..LayoutElementConfig::default()
112            },
113        }
114    }
115
116    /// Creates a layout manager whose root element fills `screen_dimensions`.
117    pub fn new(screen_dimensions: Dimensions) -> Self {
118        let root = LayoutTree::new();
119        let root_id = ElementIdConfig::new_empty();
120        let mut res = Self {
121            root_id,
122            root,
123            screen_dimensions,
124            cache_elements: Default::default(),
125            element_parent: Default::default(),
126            text_dim_fun: None,
127        };
128        res.root.add_root(res.default_node());
129        res.configure_roots();
130        res
131    }
132
133    /// Updates the viewport size used for the root element.
134    ///
135    /// Also called from [`LayoutManagerCompatible::prepare`](cotis::layout::LayoutManagerCompatible::prepare)
136    /// each frame.
137    pub fn set_dimensions(&mut self, screen_dimensions: Dimensions) {
138        self.screen_dimensions = screen_dimensions;
139    }
140
141    /// Returns an element from the **last completed frame** layout cache.
142    ///
143    /// Populated when [`CotisLayoutRun::finalize_layouts`](CotisLayoutRun::finalize_layouts) finishes. The returned
144    /// `LayoutElement` is an internal engine type — not a stable public path API; visibility
145    /// is under review. Use [`crate::cotis_traits::secondary_traits`] and
146    /// [`cotis_utils::element_state`] traits for structured inspection.
147    pub fn get_cache_element(&self, id: ElementId) -> Option<&LayoutElement> {
148        self.cache_elements.get(&id)
149    }
150
151    pub(crate) fn cached_element_ids(&self) -> Vec<ElementId> {
152        self.cache_elements.values().map(|e| e.id.clone()).collect()
153    }
154
155    pub(crate) fn parent_layout_slot_of(&self, child_slot: ElementId) -> Option<ElementId> {
156        self.element_parent.get(&child_slot).copied()
157    }
158
159    pub(crate) fn direct_child_slots(&self, parent_slot: ElementId) -> Vec<ElementId> {
160        self.element_parent
161            .iter()
162            .filter_map(|(child, &parent)| (parent == parent_slot).then_some(*child))
163            .collect()
164    }
165
166    pub(crate) fn clear_tree_no_cache(&mut self) {
167        self.root.clear_tree_structure();
168        // Rebuild tree roots
169        self.root.add_root(self.default_node());
170
171        self.configure_roots();
172    }
173    pub(crate) fn clear_tree(&mut self) {
174        let (cache_elements, parent_map) = self.root.clear_tree_structure_with_parent_map();
175        self.cache_elements = cache_elements;
176        self.element_parent = parent_map;
177    }
178
179    pub(crate) fn tree(&mut self) -> &mut LayoutTree {
180        &mut self.root
181    }
182    pub(crate) fn tree_borrow(&self) -> &LayoutTree {
183        &self.root
184    }
185
186    pub(crate) fn configure_roots(&mut self) {
187        //make roots size of screen
188        let mut root_element = self
189            .root
190            .get_node_mut(&LayoutTreeIndex::new(&[self.root_id.get_handle()]))
191            .unwrap();
192        let root_element = root_element.get_local().self_element;
193        root_element.info =
194            LayoutElementInfo::TotalSizedAndPosition(element_info_states::TotalSizedAndPosition {
195                x: 0.0,
196                y: 0.0,
197                width: self.screen_dimensions.width,
198                height: self.screen_dimensions.height,
199            });
200    }
201}
202
203impl<'layout, Config> CotisLayoutRun<'layout, Config> {
204    /// Starts a new frame: clears the live tree and opens the root element.
205    pub fn new(layout_manager: &'layout mut CotisLayoutManager) -> Self {
206        layout_manager.clear_tree_no_cache();
207        let root_id = layout_manager.root_id.get_handle();
208        Self {
209            layout_manager,
210            open_element_index: LayoutTreeIndex::new(&[root_id]),
211            configs: Default::default(),
212            text_fragments: Default::default(),
213        }
214    }
215
216    /// Returns a mutable cursor to the currently open element in the layout tree.
217    ///
218    /// # Panics
219    ///
220    /// Panics if the open element index is invalid (internal inconsistency).
221    pub fn get_tree_cursor(&mut self) -> LayoutTreeCursor {
222        LayoutTreeCursor::new_from_index(self.layout_manager.tree(), &self.open_element_index)
223            .unwrap()
224    }
225
226    /// Adds a new child under the currently open element and opens it.
227    ///
228    /// # Panics
229    ///
230    /// Panics if the open element no longer exists in the tree.
231    pub fn new_child_element(&mut self) {
232        let id_conf = ElementIdConfig::new_empty();
233        let element = LayoutElement::new(id_conf.get_handle(), String::new());
234        let child_index = self
235            .layout_manager
236            .root
237            .add_child(&self.open_element_index, element)
238            .expect("[Cotis Layout] New Element added to missing parent element");
239        self.open_element_index.push(child_index);
240    }
241
242    /// Applies layout style and user config to the currently open element, then initializes sizing state.
243    ///
244    /// Called internally by [`ConfigureElements::set_config`](cotis::element_configuring::ConfigureElements::set_config).
245    ///
246    /// # Panics
247    ///
248    /// Panics if no element is open.
249    pub fn set_open_element_config(
250        &mut self,
251        config: LayoutElementConfig,
252        id: ElementId,
253        name: Option<OwnedOrRef<str>>,
254        true_config: Config,
255    ) {
256        let mut current_id = self.open_element_id();
257        let mut node = self
258            .layout_manager
259            .root
260            .get_node_mut(&self.open_element_index)
261            .expect("[Cotis Layout] Tried to set config for open element, but no element is open");
262        if id != current_id {
263            current_id = id;
264            node.change_node_id(id.clone());
265            self.open_element_index.pop();
266            self.open_element_index.push(id);
267        }
268        {
269            let node = node.get_local().self_element;
270            node.config = config;
271            if let Some(name) = name {
272                node.name += name.as_ref();
273            }
274        }
275        self.configs.insert(current_id, true_config);
276        node.get_local().open_configuration()
277    }
278
279    /// Returns the [`ElementId`] of the currently open tree node.
280    ///
281    /// # Panics
282    ///
283    /// Panics if no element is open.
284    pub fn open_element_id(&self) -> ElementId {
285        let node = self
286            .layout_manager
287            .root
288            .get_node(&self.open_element_index)
289            .expect("[Cotis Layout] Tried to set config for open element, but no element is open");
290        node.get_local().self_element.id
291    }
292
293    pub(crate) fn set_open_element_layout_direction(&mut self, layout_direction: LayoutDirection) {
294        let mut node = self.layout_manager.root.get_node_mut(&self.open_element_index).expect("[Cotis Layout] Tried to set layout direction for open element, but no element is open");
295        node.get_local().self_element.config.layout.layout_direction = layout_direction;
296    }
297
298    pub(crate) fn get_config(&self, id: ElementId) -> Option<&Config> {
299        self.configs.get(&id)
300    }
301
302    pub(crate) fn open_element_config_mut(&mut self) -> Option<&mut Config> {
303        let id = self.open_element_id();
304        self.configs.get_mut(&id)
305    }
306
307    /// Finalizes sizing for the open element and moves the cursor to its parent.
308    pub fn close_open_element(&mut self) {
309        let mut cursor = self.get_tree_cursor();
310        cursor.get_local().close_element();
311        self.open_element_index.pop();
312    }
313
314    fn calculate_final_layout_sizes(&mut self) {
315        self.layout_manager.configure_roots();
316
317        for root in self
318            .layout_manager
319            .root
320            .get_root_nodes()
321            .map(|c| LayoutTreeIndex::new(&[c.tree_node_id]))
322            .collect::<Vec<_>>()
323        {
324            let tree = &mut self.layout_manager.root;
325            // Width: Fit -> Grow & Shrink
326            fit_grow_shrink_children_axis(
327                &mut LayoutTreeCursor::new_from_index(tree, &root).unwrap(),
328                Axis::Width,
329            );
330            // Wrap text
331            recursive_wrap_left_to_right(
332                &mut LayoutTreeCursor::new_from_index(tree, &root).unwrap(),
333            );
334            // Height: Fit -> Grow & Shrink
335            fit_grow_shrink_children_axis(
336                &mut LayoutTreeCursor::new_from_index(tree, &root).unwrap(),
337                Axis::Height,
338            );
339        }
340    }
341
342    fn calculate_positions(&mut self) {
343        for root in self
344            .layout_manager
345            .root
346            .get_root_nodes()
347            .map(|c| LayoutTreeIndex::new(&[c.tree_node_id]))
348            .collect::<Vec<_>>()
349        {
350            let tree = self.layout_manager.tree();
351            calculate_child_positions(&mut LayoutTreeCursor::new_from_index(tree, &root).unwrap());
352        }
353    }
354
355    fn calculate_render_commands(&mut self) -> Vec<RenderCommandOutput<Config>> {
356        let mut render_commands = Vec::new();
357        let mut open_nodes = VecDeque::new();
358        for root_id in self
359            .layout_manager
360            .root
361            .get_root_nodes_borrow()
362            .map(|c| c.tree_node_id)
363            .collect::<Vec<_>>()
364        {
365            let mut render_commands_buffer = Vec::new();
366            let root = LayoutTreeIndex::new(&[root_id]);
367            open_nodes.push_back(root);
368            while let Some(node) = open_nodes.pop_front() {
369                calculate_render_commands(
370                    &self.layout_manager.root,
371                    node,
372                    &mut open_nodes,
373                    &mut render_commands_buffer,
374                    &mut self.configs,
375                    &self.text_fragments,
376                );
377            }
378            let root = LayoutTreeIndex::new(&[root_id]);
379            let self_node =
380                LayoutTreeCursorBorrow::new_from_index(&self.layout_manager.root, &root).unwrap();
381            sort_commands(
382                &mut render_commands_buffer,
383                self_node.get_local().self_element.config.floating.z_index,
384            );
385            render_commands.append(&mut render_commands_buffer);
386        }
387        render_commands
388    }
389
390    /// Runs the full layout pipeline and returns render commands for this frame.
391    ///
392    /// Order: fit/grow/shrink (width) → text wrap → fit/grow/shrink (height) → positioning →
393    /// render command emission. Clears the live tree and populates the manager's element cache
394    /// for inspection traits.
395    ///
396    /// Prefer [`LayoutFrameManager::end`](cotis::layout::LayoutFrameManager::end) when using
397    /// the standard Cotis frame API.
398    pub fn finalize_layouts(&mut self) -> Vec<RenderCommandOutput<Config>> {
399        if let Some(id) = self.open_element_index.pop() {
400            if id != self.layout_manager.root_id.get_handle() {
401                println!("[Cotis Layout] Not all elements have been closed");
402            }
403        }
404        // Finalized layout
405        self.calculate_final_layout_sizes();
406        self.calculate_positions();
407        let res = self.calculate_render_commands();
408        self.layout_manager.clear_tree();
409        res
410    }
411
412    /// Removes and returns the user config for `element_id` after render command extraction.
413    ///
414    /// # Panics
415    ///
416    /// Panics if no config exists for `element_id`.
417    pub fn remove_custom(&mut self, element_id: ElementId) -> Config {
418        self.configs
419            .remove(&element_id)
420            .expect("[Cotis Layout] Can't find config from element id")
421    }
422}