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();
}
#[allow(dead_code)]
pub(crate) fn swap_feedback(&mut self, feedback: &mut crate::LayoutFeedbackState) {
std::mem::swap(&mut self.scroll_infos, &mut feedback.prev_scroll_infos);
std::mem::swap(&mut self.scroll_rects, &mut feedback.prev_scroll_rects);
std::mem::swap(&mut self.hit_areas, &mut feedback.prev_hit_map);
std::mem::swap(&mut self.group_rects, &mut feedback.prev_group_rects);
std::mem::swap(&mut self.content_areas, &mut feedback.prev_content_map);
std::mem::swap(&mut self.focus_rects, &mut feedback.prev_focus_rects);
std::mem::swap(&mut self.focus_groups, &mut feedback.prev_focus_groups);
}
}
#[allow(dead_code)] pub(crate) struct RawDrawRect {
pub draw_id: usize,
pub rect: Rect,
pub left_clip_cols: u32,
pub top_clip_rows: u32,
pub original_width: u32,
pub original_height: u32,
}
#[derive(Clone, Copy)]
struct SignedRect {
left: i64,
top: i64,
right: i64,
bottom: i64,
}
impl SignedRect {
fn from_node(node: &LayoutNode, x_offset: i64, y_offset: i64) -> Self {
let left = i64::from(node.pos.0).saturating_sub(x_offset);
let top = i64::from(node.pos.1).saturating_sub(y_offset);
Self {
left,
top,
right: left.saturating_add(i64::from(node.size.0)),
bottom: top.saturating_add(i64::from(node.size.1)),
}
}
fn intersection(self, other: Self) -> Self {
Self {
left: self.left.max(other.left),
top: self.top.max(other.top),
right: self.right.min(other.right),
bottom: self.bottom.min(other.bottom),
}
}
fn is_empty(self) -> bool {
self.left >= self.right || self.top >= self.bottom
}
fn inset(self, node: &LayoutNode) -> Self {
let left_inset = i64::from(node.border_left_inset()) + i64::from(node.padding.left);
let right_inset = i64::from(node.border_right_inset()) + i64::from(node.padding.right);
let top_inset = i64::from(node.border_top_inset()) + i64::from(node.padding.top);
let bottom_inset = i64::from(node.border_bottom_inset()) + i64::from(node.padding.bottom);
let left = self.left.saturating_add(left_inset).min(self.right);
let top = self.top.saturating_add(top_inset).min(self.bottom);
Self {
left,
top,
right: self.right.saturating_sub(right_inset).max(left),
bottom: self.bottom.saturating_sub(bottom_inset).max(top),
}
}
fn visible_within(self, viewport: Option<Self>) -> Self {
viewport.map_or(self, |viewport| self.intersection(viewport))
}
fn to_rect(self) -> Option<Rect> {
let left = self.left.clamp(0, i64::from(u32::MAX));
let top = self.top.clamp(0, i64::from(u32::MAX));
let right = self.right.clamp(0, i64::from(u32::MAX));
let bottom = self.bottom.clamp(0, i64::from(u32::MAX));
if left >= right || top >= bottom {
return None;
}
Some(Rect::new(
left as u32,
top as u32,
(right - left) as u32,
(bottom - top) as u32,
))
}
}
pub(crate) fn collect_all(node: &LayoutNode, data: &mut FrameData) {
data.clear();
let screen_rect = SignedRect::from_node(node, 0, 0);
let visible_rect = screen_rect.to_rect().unwrap_or_default();
if node.is_scrollable {
push_scroll_info(node, data);
data.scroll_rects.push(visible_rect);
}
if let Some(id) = node.focus_id
&& !visible_rect.is_empty()
{
data.focus_rects.push((id, visible_rect));
}
if let Some(id) = node.interaction_id {
if id >= data.hit_areas.len() {
data.hit_areas.resize(id + 1, Rect::new(0, 0, 0, 0));
}
data.hit_areas[id] = visible_rect;
}
let (child_x_offset, child_y_offset) = if node.is_scrollable {
(
i64::from(node.scroll_offset_x),
i64::from(node.scroll_offset),
)
} else {
(0, 0)
};
let child_viewport = (matches!(node.kind, NodeKind::Container(_)) && !screen_rect.is_empty())
.then(|| screen_rect.inset(node));
for child in &node.children {
collect_all_inner(
child,
data,
child_x_offset,
child_y_offset,
None,
child_viewport,
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: i64,
y_offset: i64,
active_group: Option<&Arc<str>>,
viewport: Option<SignedRect>,
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 screen_rect = SignedRect::from_node(node, x_offset, y_offset);
let visible = screen_rect.visible_within(viewport);
let visible_rect = visible.to_rect();
if node.is_scrollable {
push_scroll_info(node, data);
data.scroll_rects.push(visible_rect.unwrap_or_default());
}
if let Some(id) = node.interaction_id {
if id >= data.hit_areas.len() {
data.hit_areas.resize(id + 1, Rect::new(0, 0, 0, 0));
}
data.hit_areas[id] = visible_rect.unwrap_or_default();
}
if let NodeKind::RawDraw(draw_id) = node.kind
&& let Some(rect) = visible_rect
{
data.raw_draw_rects.push(RawDrawRect {
draw_id,
rect,
left_clip_cols: i64::from(rect.x).saturating_sub(screen_rect.left) as u32,
top_clip_rows: i64::from(rect.y).saturating_sub(screen_rect.top) as u32,
original_width: node.size.0,
original_height: node.size.1,
});
}
let node_group_arc: Option<Arc<str>> = node.group_name.clone();
if let Some(name) = &node_group_arc
&& let Some(rect) = visible_rect
{
data.group_rects.push((Arc::clone(name), rect));
}
if matches!(node.kind, NodeKind::Container(_))
&& let Some(full) = visible_rect
&& let Some(content) = screen_rect.inset(node).visible_within(viewport).to_rect()
{
data.content_areas.push((full, content));
}
if let Some(id) = node.focus_id
&& let Some(rect) = visible_rect
{
data.focus_rects.push((id, rect));
}
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 = if node.is_scrollable {
x_offset.saturating_add(i64::from(node.scroll_offset_x))
} else {
x_offset
};
let child_y_offset = if node.is_scrollable {
y_offset.saturating_add(i64::from(node.scroll_offset))
} else {
y_offset
};
let child_viewport = if matches!(node.kind, NodeKind::Container(_)) {
let own_viewport = screen_rect.inset(node);
Some(viewport.map_or(own_viewport, |parent| own_viewport.intersection(parent)))
} else {
viewport
};
for child in &node.children {
collect_all_inner(
child,
data,
child_x_offset,
child_y_offset,
current_group,
child_viewport,
depth + 1,
);
}
}