use super::flexbox::inner_area;
use super::*;
use std::sync::Arc;
#[derive(Default)]
pub(crate) struct FrameData {
pub scroll_infos: Vec<(u32, u32, bool)>,
pub scroll_rects: Vec<Rect>,
pub hit_areas: Vec<Rect>,
pub group_rects: Vec<(Arc<str>, Rect)>,
pub content_areas: Vec<(Rect, Rect)>,
pub focus_rects: Vec<(usize, Rect)>,
pub focus_groups: Vec<Option<Arc<str>>>,
pub raw_draw_rects: Vec<RawDrawRect>,
}
impl FrameData {
pub(crate) fn clear(&mut self) {
self.scroll_infos.clear();
self.scroll_rects.clear();
self.hit_areas.clear();
self.group_rects.clear();
self.content_areas.clear();
self.focus_rects.clear();
self.focus_groups.clear();
self.raw_draw_rects.clear();
}
}
pub(crate) struct RawDrawRect {
pub draw_id: usize,
pub rect: Rect,
pub top_clip_rows: u32,
pub original_height: u32,
}
pub(crate) fn collect_all(node: &LayoutNode, data: &mut FrameData) {
data.clear();
if node.is_scrollable {
push_scroll_info(node, data);
data.scroll_rects
.push(Rect::new(node.pos.0, node.pos.1, node.size.0, node.size.1));
}
if let Some(id) = node.focus_id {
if node.pos.1 + node.size.1 > 0 {
data.focus_rects.push((
id,
Rect::new(node.pos.0, node.pos.1, node.size.0, node.size.1),
));
}
}
if let Some(id) = node.interaction_id {
let rect = if node.pos.1 + node.size.1 > 0 {
Rect::new(node.pos.0, node.pos.1, node.size.0, node.size.1)
} else {
Rect::new(0, 0, 0, 0)
};
if id >= data.hit_areas.len() {
data.hit_areas.resize(id + 1, Rect::new(0, 0, 0, 0));
}
data.hit_areas[id] = rect;
}
let (child_x_offset, child_y_offset) = if node.is_scrollable {
(node.scroll_offset_x, node.scroll_offset)
} else {
(0, 0)
};
for child in &node.children {
collect_all_inner(child, data, child_x_offset, child_y_offset, None, None, 1);
}
for overlay in &node.overlays {
collect_all_inner(&overlay.node, data, 0, 0, None, None, 1);
}
}
fn push_scroll_info(node: &LayoutNode, data: &mut FrameData) {
if matches!(node.kind, NodeKind::Container(Direction::Row)) {
let viewport_w = node.size.0.saturating_sub(node.frame_horizontal());
data.scroll_infos
.push((node.content_width, viewport_w, true));
} else {
let viewport_h = node.size.1.saturating_sub(node.frame_vertical());
data.scroll_infos
.push((node.content_height, viewport_h, false));
}
}
#[allow(clippy::too_many_arguments)]
fn collect_all_inner(
node: &LayoutNode,
data: &mut FrameData,
x_offset: u32,
y_offset: u32,
active_group: Option<&Arc<str>>,
viewport: Option<Rect>,
depth: usize,
) {
if depth > super::tree::MAX_LAYOUT_DEPTH {
panic!(
"layout tree depth exceeds {}: check for recursive container nesting",
super::tree::MAX_LAYOUT_DEPTH
);
}
let adj_x = node.pos.0.saturating_sub(x_offset);
let adj_y = node.pos.1.saturating_sub(y_offset);
if node.is_scrollable {
push_scroll_info(node, data);
data.scroll_rects
.push(Rect::new(adj_x, adj_y, node.size.0, node.size.1));
}
if let Some(id) = node.interaction_id {
let rect = if node.pos.1 + node.size.1 > y_offset && node.pos.0 + node.size.0 > x_offset {
Rect::new(adj_x, adj_y, node.size.0, node.size.1)
} else {
Rect::new(0, 0, 0, 0)
};
if id >= data.hit_areas.len() {
data.hit_areas.resize(id + 1, Rect::new(0, 0, 0, 0));
}
data.hit_areas[id] = rect;
}
if let NodeKind::RawDraw(draw_id) = node.kind {
let node_x = adj_x;
let node_w = node.size.0;
let node_h = node.size.1;
let screen_y = node.pos.1 as i64 - y_offset as i64;
if let Some(vp) = viewport {
let img_top = screen_y;
let img_bottom = screen_y + node_h as i64;
let vp_top = vp.y as i64;
let vp_bottom = vp.bottom() as i64;
if img_bottom > vp_top && img_top < vp_bottom {
let visible_top = img_top.max(vp_top) as u32;
let visible_bottom = img_bottom.min(vp_bottom) as u32;
let visible_height = visible_bottom.saturating_sub(visible_top);
let top_clip_rows = (vp_top - img_top).max(0) as u32;
data.raw_draw_rects.push(RawDrawRect {
draw_id,
rect: Rect::new(node_x, visible_top, node_w, visible_height),
top_clip_rows,
original_height: node_h,
});
}
} else {
data.raw_draw_rects.push(RawDrawRect {
draw_id,
rect: Rect::new(node_x, screen_y.max(0) as u32, node_w, node_h),
top_clip_rows: 0,
original_height: node_h,
});
}
}
let node_group_arc: Option<Arc<str>> = node.group_name.clone();
if let Some(name) = &node_group_arc {
if node.pos.1 + node.size.1 > y_offset && node.pos.0 + node.size.0 > x_offset {
data.group_rects.push((
Arc::clone(name),
Rect::new(adj_x, adj_y, node.size.0, node.size.1),
));
}
}
if matches!(node.kind, NodeKind::Container(_)) {
let full = Rect::new(adj_x, adj_y, node.size.0, node.size.1);
let inset_x = node.padding.left + node.border_left_inset();
let inset_y = node.padding.top + node.border_top_inset();
let inner_w = node.size.0.saturating_sub(node.frame_horizontal());
let inner_h = node.size.1.saturating_sub(node.frame_vertical());
let content = Rect::new(adj_x + inset_x, adj_y + inset_y, inner_w, inner_h);
data.content_areas.push((full, content));
}
if let Some(id) = node.focus_id {
if node.pos.1 + node.size.1 > y_offset && node.pos.0 + node.size.0 > x_offset {
data.focus_rects
.push((id, Rect::new(adj_x, adj_y, node.size.0, node.size.1)));
}
}
let current_group = node_group_arc.as_ref().or(active_group);
if let Some(id) = node.focus_id {
if id >= data.focus_groups.len() {
data.focus_groups.resize(id + 1, None);
}
data.focus_groups[id] = current_group.cloned();
}
let (child_x_offset, child_y_offset, child_viewport) = if node.is_scrollable {
let area = Rect::new(adj_x, adj_y, node.size.0, node.size.1);
let inner = inner_area(node, area);
(
x_offset.saturating_add(node.scroll_offset_x),
y_offset.saturating_add(node.scroll_offset),
Some(inner),
)
} else {
(x_offset, y_offset, viewport)
};
for child in &node.children {
collect_all_inner(
child,
data,
child_x_offset,
child_y_offset,
current_group,
child_viewport,
depth + 1,
);
}
}