vizia_core/systems/
layout.rs

1use morphorm::Node;
2use vizia_storage::LayoutTreeIterator;
3
4use crate::layout::node::SubLayout;
5use crate::prelude::*;
6
7use super::{text_layout_system, text_system};
8
9/// Determines the size and position of views.
10/// TODO: Currently relayout is done on an entire tree rather than incrementally.
11/// Incremental relayout can be done by keeping a list of nodes that need relayout,
12/// and when a node undergoes relayout remove the descendants that have been processed from the list,
13/// then continue relayout on the remaining nodes in the list.
14pub(crate) fn layout_system(cx: &mut Context) {
15    text_system(cx);
16
17    if cx.style.system_flags.contains(SystemFlags::RELAYOUT) {
18        // Perform layout on the whole tree.
19        Entity::root().layout(
20            &mut cx.cache,
21            &cx.tree,
22            &cx.style,
23            &mut SubLayout {
24                text_context: &mut cx.text_context,
25                resource_manager: &cx.resource_manager,
26            },
27        );
28
29        let cx = &mut EventContext::new(cx);
30
31        let iter = LayoutTreeIterator::full(cx.tree);
32
33        for entity in iter {
34            cx.current = entity;
35            if cx.style.display.get(entity).copied().unwrap_or_default() == Display::None {
36                continue;
37            }
38            // Morphorm produces relative positions so convert to absolute.
39            if let Some(parent) = cx.tree.get_layout_parent(entity) {
40                let parent_bounds = cx.cache.get_bounds(parent);
41                if let Some(bounds) = cx.cache.bounds.get_mut(entity) {
42                    if let Some(relative_bounds) = cx.cache.relative_bounds.get(entity) {
43                        let x = relative_bounds.x + parent_bounds.x;
44                        let y = relative_bounds.y + parent_bounds.y;
45                        let w = relative_bounds.w;
46                        let h = relative_bounds.h;
47
48                        let mut geo_changed = GeoChanged::empty();
49
50                        if x != bounds.x {
51                            geo_changed.set(GeoChanged::POSX_CHANGED, true);
52                        }
53
54                        if y != bounds.y {
55                            geo_changed.set(GeoChanged::POSY_CHANGED, true);
56                        }
57
58                        if w != bounds.w {
59                            geo_changed.set(GeoChanged::WIDTH_CHANGED, true);
60                            cx.cache.path.remove(entity);
61                        }
62
63                        if h != bounds.h {
64                            geo_changed.set(GeoChanged::HEIGHT_CHANGED, true);
65                            cx.cache.path.remove(entity);
66                        }
67
68                        if let Some(geo) = cx.cache.geo_changed.get_mut(entity) {
69                            *geo = geo_changed;
70                        }
71
72                        let new_bounds = BoundingBox { x, y, w, h };
73
74                        // if new_bounds != *bounds && *bounds != BoundingBox::default() {
75                        //     cx.needs_redraw();
76                        // }
77
78                        *bounds = new_bounds;
79                    }
80                }
81            }
82
83            if let Some(geo) = cx.cache.geo_changed.get(entity).copied() {
84                if !geo.is_empty()
85                // && cx.style.text.get(entity).is_some()
86                {
87                    cx.needs_redraw();
88                    cx.style.needs_text_layout(entity);
89                }
90
91                // TODO: Use geo changed to determine whether an entity needs to be redrawn.
92
93                if !geo.is_empty() {
94                    let mut event = Event::new(WindowEvent::GeometryChanged(geo))
95                        .target(entity)
96                        .origin(entity)
97                        .propagate(Propagation::Direct);
98                    visit_entity(cx, entity, &mut event);
99                }
100            }
101
102            if let Some(geo) = cx.cache.geo_changed.get_mut(entity) {
103                *geo = GeoChanged::empty();
104            }
105        }
106
107        // A relayout, retransform, or reclip, can cause the element under the cursor to change. So we push a mouse move event here to force
108        // a new event cycle and the hover system to trigger.
109        if let Some(proxy) = &cx.event_proxy {
110            let event = Event::new(WindowEvent::MouseMove(cx.mouse.cursor_x, cx.mouse.cursor_y))
111                .target(Entity::root())
112                .origin(Entity::root())
113                .propagate(Propagation::Up);
114
115            proxy.send(event).expect("Failed to send event");
116        }
117
118        cx.style.system_flags.set(SystemFlags::RELAYOUT, false);
119    }
120
121    text_layout_system(cx);
122}
123
124fn visit_entity(cx: &mut EventContext, entity: Entity, event: &mut Event) {
125    // Send event to models attached to the entity
126    if let Some(ids) =
127        cx.models.get(&entity).map(|models| models.keys().cloned().collect::<Vec<_>>())
128    {
129        for id in ids {
130            if let Some(mut model) =
131                cx.models.get_mut(&entity).and_then(|models| models.remove(&id))
132            {
133                cx.current = entity;
134
135                model.event(cx, event);
136
137                cx.models.get_mut(&entity).and_then(|models| models.insert(id, model));
138            }
139        }
140    }
141
142    // Return early if the event was consumed by a model
143    if event.meta.consumed {
144        return;
145    }
146
147    // Send event to the view attached to the entity
148    if let Some(mut view) = cx.views.remove(&entity) {
149        cx.current = entity;
150        view.event(cx, event);
151
152        cx.views.insert(entity, view);
153    }
154}