initial macos porting work

This commit is contained in:
Will Temple
2026-03-23 15:16:20 -04:00
parent 4193457fc4
commit 861bf63621
40 changed files with 4575 additions and 233 deletions

View File

@@ -128,7 +128,12 @@ impl LayoutNode {
/// Return a copy of this node (and all descendants) with all positions shifted by `offset`.
pub fn translated(self, offset: Point) -> Self {
fn translate_rect(r: Rect, o: Point) -> Rect {
Rect::new(r.origin.x + o.x, r.origin.y + o.y, r.size.width, r.size.height)
Rect::new(
r.origin.x + o.x,
r.origin.y + o.y,
r.size.width,
r.size.height,
)
}
let rect = translate_rect(self.rect, offset);
let clip_rect = self.clip_rect.map(|r| translate_rect(r, offset));
@@ -143,7 +148,11 @@ impl LayoutNode {
rect: translate_rect(img.rect, offset),
..img
});
let children = self.children.into_iter().map(|c| c.translated(offset)).collect();
let children = self
.children
.into_iter()
.map(|c| c.translated(offset))
.collect();
LayoutNode {
rect,
clip_rect,
@@ -241,6 +250,7 @@ pub fn layout_snapshot_with_cache(
}
}
#[allow(clippy::too_many_arguments)]
fn layout_element(
element: &Element,
rect: Rect,
@@ -256,24 +266,24 @@ fn layout_element(
perf_stats.nodes += 1;
// Viewport culling: skip fully off-screen elements inside a scroll box.
if let Some(clip) = clip_rect {
if !rects_overlap(rect, clip) {
perf_stats.viewport_culled += 1;
return LayoutNode {
path,
element_id: element.id,
rect,
corner_radius: 0.0,
clip_rect: None,
pointer_events: element.style.pointer_events,
focusable: element.style.focusable,
cursor: element.style.cursor.unwrap_or(CursorIcon::Default),
scroll_metrics: None,
prepared_image: None,
prepared_text: None,
children: Vec::new(),
};
}
if let Some(clip) = clip_rect
&& !rects_overlap(rect, clip)
{
perf_stats.viewport_culled += 1;
return LayoutNode {
path,
element_id: element.id,
rect,
corner_radius: 0.0,
clip_rect: None,
pointer_events: element.style.pointer_events,
focusable: element.style.focusable,
cursor: element.style.cursor.unwrap_or(CursorIcon::Default),
scroll_metrics: None,
prepared_image: None,
prepared_text: None,
children: Vec::new(),
};
}
// Incremental layout cache check.
@@ -368,7 +378,13 @@ fn layout_element(
if pushed_clip {
scene.pop_clip();
}
cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin);
cache_layout(
layout_cache,
cache_key,
&interaction,
&scene.items[scene_start..],
rect.origin,
);
return interaction;
}
@@ -382,7 +398,13 @@ fn layout_element(
if pushed_clip {
scene.pop_clip();
}
cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin);
cache_layout(
layout_cache,
cache_key,
&interaction,
&scene.items[scene_start..],
rect.origin,
);
return interaction;
}
@@ -508,7 +530,13 @@ fn layout_element(
if pushed_clip {
scene.pop_clip();
}
cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin);
cache_layout(
layout_cache,
cache_key,
&interaction,
&scene.items[scene_start..],
rect.origin,
);
return interaction;
}
@@ -518,7 +546,13 @@ fn layout_element(
if pushed_clip {
scene.pop_clip();
}
cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin);
cache_layout(
layout_cache,
cache_key,
&interaction,
&scene.items[scene_start..],
rect.origin,
);
return interaction;
}
@@ -527,7 +561,13 @@ fn layout_element(
if pushed_clip {
scene.pop_clip();
}
cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin);
cache_layout(
layout_cache,
cache_key,
&interaction,
&scene.items[scene_start..],
rect.origin,
);
return interaction;
}
interaction.children = layout_container_children(
@@ -545,7 +585,13 @@ fn layout_element(
scene.pop_clip();
}
cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin);
cache_layout(
layout_cache,
cache_key,
&interaction,
&scene.items[scene_start..],
rect.origin,
);
interaction
}
@@ -558,10 +604,13 @@ fn cache_layout(
origin: Point,
) {
let neg = Point::new(-origin.x, -origin.y);
cache.results.insert(key, CachedLayout {
interaction_node: interaction.clone().translated(neg),
scene_items: scene_items.iter().map(|i| i.translated(neg)).collect(),
});
cache.results.insert(
key,
CachedLayout {
interaction_node: interaction.clone().translated(neg),
scene_items: scene_items.iter().map(|i| i.translated(neg)).collect(),
},
);
}
fn hit_path_node(node: &LayoutNode, point: crate::scene::Point) -> Option<Vec<HitTarget>> {
@@ -735,6 +784,7 @@ struct MeasuredChild {
is_flex: bool,
}
#[allow(clippy::too_many_arguments)]
fn layout_container_children(
element: &Element,
content: Rect,
@@ -986,7 +1036,14 @@ fn intrinsic_size(
return cached;
}
let insets = content_insets(&element.style);
let result = intrinsic_size_inner(element, available_size, insets, text_system, perf_stats, layout_cache);
let result = intrinsic_size_inner(
element,
available_size,
insets,
text_system,
perf_stats,
layout_cache,
);
layout_cache.intrinsic_cache.insert(cache_key, result);
result
}
@@ -1047,8 +1104,13 @@ fn intrinsic_size_inner(
);
}
let intrinsic_content =
intrinsic_container_content_size(element, content_size, text_system, perf_stats, layout_cache);
let intrinsic_content = intrinsic_container_content_size(
element,
content_size,
text_system,
perf_stats,
layout_cache,
);
UiSize::new(
explicit_width
@@ -1539,9 +1601,9 @@ fn child_rect(
mod tests {
use super::{LayoutCache, layout_scene, layout_snapshot, layout_snapshot_with_cache};
use crate::scene::{Color, DisplayItem, Point, Quad, Rect, UiSize};
use crate::text::TextSystem;
use crate::text::{TextStyle, TextWrap};
use crate::tree::{Edges, Element, ElementId, FlexDirection};
use crate::text::TextSystem;
#[test]
fn row_layout_apportions_fixed_and_flex_children() {
@@ -2156,10 +2218,12 @@ mod tests {
let size = UiSize::new(400.0, 600.0);
let snap1 = layout_snapshot_with_cache(1, size, &root, &mut text_system, &mut layout_cache);
let snap2 = layout_snapshot_with_cache(2, size, &root, &mut text_system, &mut layout_cache);
assert_eq!(snap1.scene.items, snap2.scene.items, "scene items should be identical");
assert_eq!(
snap1.interaction_tree.root,
snap2.interaction_tree.root,
snap1.scene.items, snap2.scene.items,
"scene items should be identical"
);
assert_eq!(
snap1.interaction_tree.root, snap2.interaction_tree.root,
"interaction trees should be identical"
);
}
@@ -2198,12 +2262,14 @@ mod tests {
let item_height = 40.0;
let viewport_height = 200.0;
let n = 100;
let mut scroll_box = Element::scroll_box(0.0).width(400.0).height(viewport_height);
let mut scroll_box = Element::scroll_box(0.0)
.width(400.0)
.height(viewport_height);
for i in 0..n {
scroll_box = scroll_box.child(
Element::new()
.height(item_height)
.child(Element::text(format!("item {i}"), text_style()))
.child(Element::text(format!("item {i}"), text_style())),
);
}
let root = Element::new().child(scroll_box);
@@ -2214,11 +2280,20 @@ mod tests {
let snap = layout_snapshot_with_cache(1, size, &root, &mut text_system, &mut layout_cache);
// Only text items within the 200px viewport should appear in the scene.
let text_items = snap.scene.items.iter().filter(|item| {
matches!(item, DisplayItem::Text(_))
}).count();
let text_items = snap
.scene
.items
.iter()
.filter(|item| matches!(item, DisplayItem::Text(_)))
.count();
// At most 6 text items should be visible (5 fit + 1 partial).
assert!(text_items <= 6, "expected <= 6 text items in scene, got {text_items} (culling not working)");
assert!(text_items >= 4, "expected >= 4 text items visible, got {text_items}");
assert!(
text_items <= 6,
"expected <= 6 text items in scene, got {text_items} (culling not working)"
);
assert!(
text_items >= 4,
"expected >= 4 text items visible, got {text_items}"
);
}
}