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