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: TextMeasuringFun) {
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).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
183    pub(crate) fn configure_roots(&mut self) {
184        //make roots size of screen
185        let mut root_element = self
186            .root
187            .get_node_mut(&LayoutTreeIndex::new(&[self.root_id.get_handle()]))
188            .unwrap();
189        let root_element = root_element.get_local().self_element;
190        root_element.info =
191            LayoutElementInfo::TotalSizedAndPosition(element_info_states::TotalSizedAndPosition {
192                x: 0.0,
193                y: 0.0,
194                width: self.screen_dimensions.width,
195                height: self.screen_dimensions.height,
196            });
197    }
198}
199
200impl<'layout, Config> CotisLayoutRun<'layout, Config> {
201    /// Starts a new frame: clears the live tree and opens the root element.
202    pub fn new(layout_manager: &'layout mut CotisLayoutManager) -> Self {
203        layout_manager.clear_tree_no_cache();
204        let root_id = layout_manager.root_id.get_handle();
205        Self {
206            layout_manager,
207            open_element_index: LayoutTreeIndex::new(&[root_id]),
208            configs: Default::default(),
209            text_fragments: Default::default(),
210        }
211    }
212
213    /// Returns a mutable cursor to the currently open element in the layout tree.
214    ///
215    /// # Panics
216    ///
217    /// Panics if the open element index is invalid (internal inconsistency).
218    pub fn get_tree_cursor(&mut self) -> LayoutTreeCursor<'_> {
219        LayoutTreeCursor::new_from_index(self.layout_manager.tree(), &self.open_element_index)
220            .unwrap()
221    }
222
223    /// Adds a new child under the currently open element and opens it.
224    ///
225    /// # Panics
226    ///
227    /// Panics if the open element no longer exists in the tree.
228    pub fn new_child_element(&mut self) {
229        let id_conf = ElementIdConfig::new_empty();
230        let element = LayoutElement::new(id_conf.get_handle(), String::new());
231        let child_index = self
232            .layout_manager
233            .root
234            .add_child(&self.open_element_index, element)
235            .expect("[Cotis Layout] New Element added to missing parent element");
236        self.open_element_index.push(child_index);
237    }
238
239    /// Applies layout style and user config to the currently open element, then initializes sizing state.
240    ///
241    /// Called internally by [`ConfigureElements::set_config`](cotis::element_configuring::ConfigureElements::set_config).
242    ///
243    /// # Panics
244    ///
245    /// Panics if no element is open.
246    pub fn set_open_element_config(
247        &mut self,
248        config: LayoutElementConfig,
249        id: ElementId,
250        name: Option<OwnedOrRef<str>>,
251        true_config: Config,
252    ) {
253        let mut current_id = self.open_element_id();
254        let mut node = self
255            .layout_manager
256            .root
257            .get_node_mut(&self.open_element_index)
258            .expect("[Cotis Layout] Tried to set config for open element, but no element is open");
259        if id != current_id {
260            current_id = id;
261            node.change_node_id(id);
262            self.open_element_index.pop();
263            self.open_element_index.push(id);
264        }
265        {
266            let node = node.get_local().self_element;
267            node.config = config;
268            if let Some(name) = name {
269                node.name += name.as_ref();
270            }
271        }
272        self.configs.insert(current_id, true_config);
273        node.get_local().open_configuration()
274    }
275
276    /// Returns the [`ElementId`] of the currently open tree node.
277    ///
278    /// # Panics
279    ///
280    /// Panics if no element is open.
281    pub fn open_element_id(&self) -> ElementId {
282        let node = self
283            .layout_manager
284            .root
285            .get_node(&self.open_element_index)
286            .expect("[Cotis Layout] Tried to set config for open element, but no element is open");
287        node.get_local().self_element.id
288    }
289
290    pub(crate) fn set_open_element_layout_direction(&mut self, layout_direction: LayoutDirection) {
291        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");
292        node.get_local().self_element.config.layout.layout_direction = layout_direction;
293    }
294
295    pub(crate) fn get_config(&self, id: ElementId) -> Option<&Config> {
296        self.configs.get(&id)
297    }
298
299    /// Finalizes sizing for the open element and moves the cursor to its parent.
300    pub fn close_open_element(&mut self) {
301        let mut cursor = self.get_tree_cursor();
302        cursor.get_local().close_element();
303        self.open_element_index.pop();
304    }
305
306    fn calculate_final_layout_sizes(&mut self) {
307        self.layout_manager.configure_roots();
308
309        for root in self
310            .layout_manager
311            .root
312            .get_root_nodes()
313            .map(|c| LayoutTreeIndex::new(&[c.tree_node_id]))
314            .collect::<Vec<_>>()
315        {
316            let tree = &mut self.layout_manager.root;
317            // Width: Fit -> Grow & Shrink
318            fit_grow_shrink_children_axis(
319                &mut LayoutTreeCursor::new_from_index(tree, &root).unwrap(),
320                Axis::Width,
321            );
322            // Wrap text
323            recursive_wrap_left_to_right(
324                &mut LayoutTreeCursor::new_from_index(tree, &root).unwrap(),
325            );
326            // Height: Fit -> Grow & Shrink
327            fit_grow_shrink_children_axis(
328                &mut LayoutTreeCursor::new_from_index(tree, &root).unwrap(),
329                Axis::Height,
330            );
331        }
332    }
333
334    fn calculate_positions(&mut self) {
335        for root in self
336            .layout_manager
337            .root
338            .get_root_nodes()
339            .map(|c| LayoutTreeIndex::new(&[c.tree_node_id]))
340            .collect::<Vec<_>>()
341        {
342            let tree = self.layout_manager.tree();
343            calculate_child_positions(&mut LayoutTreeCursor::new_from_index(tree, &root).unwrap());
344        }
345    }
346
347    fn calculate_render_commands(&mut self) -> Vec<RenderCommandOutput<Config>> {
348        let mut render_commands = Vec::new();
349        let mut open_nodes = VecDeque::new();
350        for root_id in self
351            .layout_manager
352            .root
353            .get_root_nodes_borrow()
354            .map(|c| c.tree_node_id)
355            .collect::<Vec<_>>()
356        {
357            let mut render_commands_buffer = Vec::new();
358            let root = LayoutTreeIndex::new(&[root_id]);
359            open_nodes.push_back(root);
360            while let Some(node) = open_nodes.pop_front() {
361                calculate_render_commands(
362                    &self.layout_manager.root,
363                    node,
364                    &mut open_nodes,
365                    &mut render_commands_buffer,
366                    &mut self.configs,
367                    &self.text_fragments,
368                );
369            }
370            let root = LayoutTreeIndex::new(&[root_id]);
371            let self_node =
372                LayoutTreeCursorBorrow::new_from_index(&self.layout_manager.root, &root).unwrap();
373            sort_commands(
374                &mut render_commands_buffer,
375                self_node.get_local().self_element.config.floating.z_index,
376            );
377            render_commands.append(&mut render_commands_buffer);
378        }
379        render_commands
380    }
381
382    /// Runs the full layout pipeline and returns render commands for this frame.
383    ///
384    /// Order: fit/grow/shrink (width) → text wrap → fit/grow/shrink (height) → positioning →
385    /// render command emission. Clears the live tree and populates the manager's element cache
386    /// for inspection traits.
387    ///
388    /// Prefer [`LayoutFrameManager::end`](cotis::layout::LayoutFrameManager::end) when using
389    /// the standard Cotis frame API.
390    pub fn finalize_layouts(&mut self) -> Vec<RenderCommandOutput<Config>> {
391        if let Some(id) = self.open_element_index.pop()
392            && id != self.layout_manager.root_id.get_handle()
393        {
394            println!("[Cotis Layout] Not all elements have been closed");
395        }
396
397        // Finalized layout
398        self.calculate_final_layout_sizes();
399        self.calculate_positions();
400        let res = self.calculate_render_commands();
401        self.layout_manager.clear_tree();
402        res
403    }
404
405    /// Removes and returns the user config for `element_id` after render command extraction.
406    ///
407    /// # Panics
408    ///
409    /// Panics if no config exists for `element_id`.
410    pub fn remove_custom(&mut self, element_id: ElementId) -> Config {
411        self.configs
412            .remove(&element_id)
413            .expect("[Cotis Layout] Can't find config from element id")
414    }
415}