use rustc_hash::FxHashMap;
use std::sync::Arc;
use crate::compiler::CompileResult;
use crate::decoration::DecorationRole;
use crate::error::{PaneError, TreeError};
use crate::node::{Node, NodeId, PanelId};
use crate::overlay::{AnchorFailure, OverlayEntry, OverlayId};
use crate::rect::Rect;
use crate::tree::LayoutTree;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoundaryAxis {
Vertical,
Horizontal,
}
#[derive(Debug, Clone, Copy)]
pub struct BoundaryHit {
pub axis: BoundaryAxis,
pub sides: (NodeId, NodeId),
pub position: f32,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct BoundarySegment {
axis: BoundaryAxis,
position: f32,
span_start: f32,
span_end: f32,
before: NodeId,
after: NodeId,
}
#[derive(Debug)]
struct BoundaryIndex {
segments: Vec<BoundarySegment>,
vertical_count: usize,
}
impl BoundaryIndex {
fn from_scratch(buf: &mut Vec<BoundarySegment>) -> Self {
let mut segments = std::mem::take(buf);
segments.sort_by(|a, b| {
a.axis
.cmp_order()
.cmp(&b.axis.cmp_order())
.then(a.position.total_cmp(&b.position))
});
let vertical_count = segments
.iter()
.position(|s| s.axis == BoundaryAxis::Horizontal)
.unwrap_or(segments.len());
BoundaryIndex {
segments,
vertical_count,
}
}
fn empty() -> Self {
BoundaryIndex {
segments: Vec::new(),
vertical_count: 0,
}
}
fn reclaim(self) -> Vec<BoundarySegment> {
self.segments
}
fn vertical(&self) -> &[BoundarySegment] {
&self.segments[..self.vertical_count]
}
fn horizontal(&self) -> &[BoundarySegment] {
&self.segments[self.vertical_count..]
}
}
impl BoundaryAxis {
fn cmp_order(self) -> u8 {
match self {
BoundaryAxis::Vertical => 0,
BoundaryAxis::Horizontal => 1,
}
}
}
fn search_sorted_segments(
segments: &[BoundarySegment],
query_pos: f32,
query_span: f32,
tolerance: f32,
) -> Option<(f32, &BoundarySegment)> {
let lo = segments.partition_point(|s| s.position < query_pos - tolerance);
let hi = segments.partition_point(|s| s.position <= query_pos + tolerance);
segments[lo..hi]
.iter()
.filter(|s| query_span >= s.span_start && query_span < s.span_end)
.map(|s| ((query_pos - s.position).abs(), s))
.min_by(|(a, _), (b, _)| a.total_cmp(b))
}
pub struct PanelEntry<'a, R> {
pub id: PanelId,
pub kind: &'a str,
pub rect: R,
pub kind_index: usize,
}
impl<'a, R> PanelEntry<'a, R> {
pub fn map_rect<R2>(self, f: impl FnOnce(R) -> R2) -> PanelEntry<'a, R2> {
PanelEntry {
id: self.id,
kind: self.kind,
rect: f(self.rect),
kind_index: self.kind_index,
}
}
}
pub struct DecorationPanelInfo {
pub id: PanelId,
pub role: DecorationRole,
pub content_kind: Arc<str>,
}
pub(crate) type KindIndex = Arc<FxHashMap<Arc<str>, Box<[PanelId]>>>;
pub(crate) type DecorationIndex = Arc<[DecorationPanelInfo]>;
pub(crate) type DecorationRoleIndex = Arc<[Option<DecorationRole>]>;
fn sorted_keys(kinds: &KindIndex) -> Arc<[Arc<str>]> {
let mut keys: Vec<_> = kinds.keys().map(Arc::clone).collect();
keys.sort_by(|a, b| a.as_ref().cmp(b.as_ref()));
keys.into()
}
fn build_panel_kind_indices(
capacity: usize,
kinds: &KindIndex,
sorted_kind_keys: &[Arc<str>],
) -> Arc<[Option<u16>]> {
let mut buf = vec![None; capacity];
for (kind, pids) in kinds.iter() {
let Ok(idx) = sorted_kind_keys.binary_search_by(|k| k.as_ref().cmp(kind.as_ref())) else {
continue;
};
let idx16 = idx as u16;
for &pid in pids.iter() {
let slot = pid.raw() as usize;
if slot < buf.len() {
buf[slot] = Some(idx16);
}
}
}
buf.into()
}
fn build_decoration_role_index(
capacity: usize,
decorations: &[DecorationPanelInfo],
) -> DecorationRoleIndex {
let mut buf = vec![None; capacity];
for decoration in decorations {
let slot = decoration.id.raw() as usize;
if slot < buf.len() {
buf[slot] = Some(decoration.role);
}
}
buf.into()
}
pub struct ResolvedLayout {
rects: Vec<Option<Rect>>,
live_panel_ids: Arc<[PanelId]>,
kinds: KindIndex,
sorted_kind_keys: Arc<[Arc<str>]>,
panel_kind_indices: Arc<[Option<u16>]>,
overlay_rects: Vec<(OverlayId, Arc<str>, Rect)>,
overlay_failures: Vec<(OverlayId, Arc<str>, AnchorFailure)>,
boundaries: BoundaryIndex,
decorations: DecorationIndex,
decoration_roles: DecorationRoleIndex,
}
impl ResolvedLayout {
pub fn get(&self, id: PanelId) -> Option<&Rect> {
self.rects.get(id.raw() as usize)?.as_ref()
}
pub fn by_kind(&self, kind: &str) -> &[PanelId] {
match self.kinds.get(kind) {
Some(ids) => ids,
None => &[],
}
}
pub fn iter(&self) -> impl Iterator<Item = (PanelId, &Rect)> {
self.live_panel_ids.iter().filter_map(|&pid| {
self.rects
.get(pid.raw() as usize)?
.as_ref()
.map(|r| (pid, r))
})
}
pub fn panel_ids(&self) -> impl Iterator<Item = PanelId> + '_ {
self.live_panel_ids.iter().copied()
}
pub fn panel_count(&self) -> usize {
self.live_panel_ids.len()
}
pub fn kinds(&self) -> impl Iterator<Item = &str> {
self.kinds.keys().map(|k| k.as_ref())
}
pub fn sorted_kind_keys(&self) -> &[Arc<str>] {
&self.sorted_kind_keys
}
pub fn kind_of(&self, pid: PanelId) -> Option<&str> {
let idx = *self.panel_kind_indices.get(pid.raw() as usize)?.as_ref()?;
self.sorted_kind_keys.get(idx as usize).map(|k| k.as_ref())
}
pub fn kind_index_of_panel(&self, pid: PanelId) -> Option<usize> {
self.panel_kind_indices
.get(pid.raw() as usize)?
.map(|idx| idx as usize)
}
pub fn kind_index_of(&self, kind: &str) -> Option<usize> {
self.sorted_kind_keys
.binary_search_by(|k| k.as_ref().cmp(kind))
.ok()
}
pub fn decoration_entries(
&self,
kind: &str,
) -> impl Iterator<Item = (&DecorationPanelInfo, usize)> {
let kind_index = self.kind_index_of(kind);
self.decorations
.iter()
.filter(move |d| d.content_kind.as_ref() == kind)
.filter_map(move |d| Some((d, kind_index?)))
}
pub fn shift_x(&mut self, dx: f32) {
for rect in self.rects.iter_mut().flatten() {
rect.x += dx;
}
}
pub fn panels(&self) -> impl Iterator<Item = PanelEntry<'_, &Rect>> + '_ {
self.sorted_kind_keys
.iter()
.enumerate()
.flat_map(move |(kind_index, kind)| {
let pids = self.kinds.get(kind).map(|b| b.as_ref()).unwrap_or(&[]);
pids.iter().filter_map(move |&pid| {
self.get(pid).map(|rect| PanelEntry {
id: pid,
kind: kind.as_ref(),
rect,
kind_index,
})
})
})
}
pub fn overlays(&self) -> impl Iterator<Item = OverlayEntry<'_, &Rect>> {
self.overlay_rects
.iter()
.map(|(id, kind, rect)| OverlayEntry {
id: *id,
kind: kind.as_ref(),
rect,
})
}
pub fn decoration_panels(&self) -> &[DecorationPanelInfo] {
&self.decorations
}
pub fn decoration_role(&self, pid: PanelId) -> Option<DecorationRole> {
self.decoration_roles
.get(pid.raw() as usize)
.copied()
.flatten()
}
pub fn panel_at_point(&self, x: f32, y: f32) -> Option<PanelId> {
self.live_panel_ids
.iter()
.rev()
.copied()
.find(|&pid| self.get(pid).is_some_and(|rect| rect.contains(x, y)))
}
pub fn overlay_at_point(&self, x: f32, y: f32) -> Option<OverlayId> {
self.overlay_rects
.iter()
.rev()
.find(|(_, _, rect)| rect.contains(x, y))
.map(|(id, _, _)| *id)
}
pub fn overlay_rect(&self, id: OverlayId) -> Option<&Rect> {
self.overlay_rects
.iter()
.find(|(oid, _, _)| *oid == id)
.map(|(_, _, r)| r)
}
pub fn boundary_at_point(&self, x: f32, y: f32, tolerance: f32) -> Option<BoundaryHit> {
let v_hit = search_sorted_segments(self.boundaries.vertical(), x, y, tolerance);
let h_hit = search_sorted_segments(self.boundaries.horizontal(), y, x, tolerance);
let best = match (v_hit, h_hit) {
(Some(v), Some(h)) if v.0 <= h.0 => Some(v),
(Some(_), Some(h)) => Some(h),
(Some(v), None) => Some(v),
(None, h) => h,
};
best.map(|(_, seg)| BoundaryHit {
axis: seg.axis,
sides: (seg.before, seg.after),
position: seg.position,
})
}
pub fn overlay_failures(&self) -> &[(OverlayId, Arc<str>, AnchorFailure)] {
&self.overlay_failures
}
pub(crate) fn overlay_rects_raw(&self) -> &[(OverlayId, Arc<str>, Rect)] {
&self.overlay_rects
}
pub(crate) fn swap_overlay_rects(&mut self, buf: &mut Vec<(OverlayId, Arc<str>, Rect)>) {
std::mem::swap(&mut self.overlay_rects, buf);
}
pub(crate) fn swap_overlay_failures(
&mut self,
buf: &mut Vec<(OverlayId, Arc<str>, AnchorFailure)>,
) {
std::mem::swap(&mut self.overlay_failures, buf);
}
pub(crate) fn kinds_arc(&self) -> &KindIndex {
&self.kinds
}
pub(crate) fn sorted_kind_keys_arc(&self) -> &Arc<[Arc<str>]> {
&self.sorted_kind_keys
}
pub(crate) fn decorations_arc(&self) -> &DecorationIndex {
&self.decorations
}
pub(crate) fn decoration_roles_arc(&self) -> &DecorationRoleIndex {
&self.decoration_roles
}
pub(crate) fn live_panel_ids_arc(&self) -> &Arc<[PanelId]> {
&self.live_panel_ids
}
pub(crate) fn panel_kind_indices_arc(&self) -> &Arc<[Option<u16>]> {
&self.panel_kind_indices
}
pub fn take_rects(&mut self) -> Vec<Option<Rect>> {
std::mem::take(&mut self.rects)
}
pub fn take_overlay_rects(&mut self) -> Vec<(OverlayId, Arc<str>, Rect)> {
std::mem::take(&mut self.overlay_rects)
}
pub(crate) fn take_overlay_failures(&mut self) -> Vec<(OverlayId, Arc<str>, AnchorFailure)> {
std::mem::take(&mut self.overlay_failures)
}
pub(crate) fn take_boundaries(&mut self) -> Vec<BoundarySegment> {
std::mem::replace(&mut self.boundaries, BoundaryIndex::empty()).reclaim()
}
pub fn lerp(&self, other: &ResolvedLayout, t: f32) -> ResolvedLayout {
let mut buf = Vec::new();
self.lerp_into(other, t, &mut buf)
}
pub fn lerp_into(
&self,
other: &ResolvedLayout,
t: f32,
buf: &mut Vec<Option<Rect>>,
) -> ResolvedLayout {
let taken = std::mem::take(buf);
let mut rects = prepare_rects_buf(Some(taken), self.rects.len());
for (i, from_rect) in self.rects.iter().enumerate() {
let Some(from_rect) = from_rect else { continue };
let Some(raw) = u32::try_from(i).ok() else {
continue;
};
let pid = PanelId::from_raw(raw);
let to_rect = other.get(pid).unwrap_or(from_rect);
rects[i] = Some(from_rect.lerp(*to_rect, t));
}
let kinds = Arc::clone(&self.kinds);
let sorted_kind_keys = self.sorted_kind_keys.clone();
let panel_kind_indices = self.panel_kind_indices.clone();
let live_panel_ids = self.live_panel_ids.clone();
let decorations = Arc::clone(&self.decorations);
let decoration_roles = Arc::clone(&self.decoration_roles);
ResolvedLayout {
rects,
live_panel_ids,
kinds,
sorted_kind_keys,
panel_kind_indices,
overlay_rects: Vec::new(),
overlay_failures: Vec::new(),
boundaries: BoundaryIndex::empty(),
decorations,
decoration_roles,
}
}
}
fn set_panel_rect(
rects: &mut [Option<Rect>],
id: PanelId,
x: f32,
y: f32,
size: &taffy::Size<f32>,
) -> Result<(), PaneError> {
*rects
.get_mut(id.raw() as usize)
.ok_or(PaneError::PanelNotFound(id))? = Some(Rect {
x,
y,
w: size.width,
h: size.height,
});
Ok(())
}
fn resolve_iterative(
tree: &LayoutTree,
result: &CompileResult,
root_id: NodeId,
rects: &mut [Option<Rect>],
scratch: &mut ResolveScratch,
collect_kinds: bool,
) -> Result<(), PaneError> {
scratch.stack.clear();
scratch.boundary_buf.clear();
scratch.live_panel_buf.clear();
scratch.stack.push((root_id, 0.0, 0.0));
while let Some((node_id, parent_x, parent_y)) = scratch.stack.pop() {
let taffy_id = result
.node_map
.get(node_id.raw() as usize)
.and_then(|s| s.as_ref())
.ok_or(PaneError::NodeNotFound(node_id))?;
let layout = result
.taffy_tree
.layout(*taffy_id)
.map_err(|e| PaneError::InvalidTree(TreeError::TaffyError(e.to_string().into())))?;
let abs_x = parent_x + layout.location.x;
let abs_y = parent_y + layout.location.y;
match tree.node(node_id) {
Some(Node::Panel { id, kind, .. }) if collect_kinds && !tree.is_decoration(*id) => {
set_panel_rect(rects, *id, abs_x, abs_y, &layout.size)?;
scratch.live_panel_buf.push(*id);
scratch
.kinds_buf
.entry(Arc::clone(kind))
.or_default()
.push(*id);
}
Some(Node::Panel { id, .. }) if collect_kinds => {
set_panel_rect(rects, *id, abs_x, abs_y, &layout.size)?;
scratch.live_panel_buf.push(*id);
}
Some(Node::Panel { id, .. }) => {
set_panel_rect(rects, *id, abs_x, abs_y, &layout.size)?;
scratch.live_panel_buf.push(*id);
}
Some(Node::Row { children, .. }) => {
emit_boundaries(
scratch.collect_boundaries,
result,
children,
BoundaryAxis::Vertical,
(abs_x, abs_y),
&layout.size,
&mut scratch.boundary_buf,
);
for &child_id in children.iter().rev() {
scratch.stack.push((child_id, abs_x, abs_y));
}
}
Some(Node::Col { children, .. }) => {
emit_boundaries(
scratch.collect_boundaries,
result,
children,
BoundaryAxis::Horizontal,
(abs_x, abs_y),
&layout.size,
&mut scratch.boundary_buf,
);
for &child_id in children.iter().rev() {
scratch.stack.push((child_id, abs_x, abs_y));
}
}
Some(Node::Grid { children, .. }) => {
for &child_id in children.iter().rev() {
scratch.stack.push((child_id, abs_x, abs_y));
}
}
Some(Node::GridItemWrapper { child, .. }) => {
scratch.stack.push((*child, abs_x, abs_y));
}
Some(Node::TaffyPassthrough { children, .. }) => {
for &child_id in children.iter().rev() {
scratch.stack.push((child_id, abs_x, abs_y));
}
}
None => return Err(PaneError::NodeNotFound(node_id)),
}
}
Ok(())
}
pub(crate) struct ResolveScratch {
stack: Vec<(NodeId, f32, f32)>,
kinds_buf: FxHashMap<Arc<str>, Vec<PanelId>>,
boundary_buf: Vec<BoundarySegment>,
live_panel_buf: Vec<PanelId>,
pub(crate) collect_boundaries: bool,
}
impl Default for ResolveScratch {
fn default() -> Self {
Self {
stack: Vec::new(),
kinds_buf: FxHashMap::default(),
boundary_buf: Vec::new(),
live_panel_buf: Vec::new(),
collect_boundaries: true,
}
}
}
impl ResolveScratch {
pub(crate) fn donate_boundary_buf(&mut self, buf: Vec<BoundarySegment>) {
match self.boundary_buf.capacity() {
0 => self.boundary_buf = buf,
_ => {}
}
}
}
fn emit_boundaries(
collect: bool,
result: &CompileResult,
children: &[NodeId],
axis: BoundaryAxis,
abs: (f32, f32),
container_size: &taffy::Size<f32>,
boundaries: &mut Vec<BoundarySegment>,
) {
if !collect || children.len() < 2 {
return;
}
let (container_abs_x, container_abs_y) = abs;
let mut adjacent = children.iter().copied();
let Some(first_child_id) = adjacent.next() else {
return;
};
let mut previous = child_layout(result, first_child_id).map(|layout| (first_child_id, layout));
for child_id in adjacent {
let current = child_layout(result, child_id).map(|layout| (child_id, layout));
let (Some((a_id, a_layout)), Some((b_id, b_layout))) = (previous, current) else {
previous = current;
continue;
};
let (position, span_start, span_end) = match axis {
BoundaryAxis::Vertical => {
let a_abs_x = container_abs_x + a_layout.location.x;
let b_abs_x = container_abs_x + b_layout.location.x;
let pos = (a_abs_x + a_layout.size.width + b_abs_x) / 2.0;
(
pos,
container_abs_y,
container_abs_y + container_size.height,
)
}
BoundaryAxis::Horizontal => {
let a_abs_y = container_abs_y + a_layout.location.y;
let b_abs_y = container_abs_y + b_layout.location.y;
let pos = (a_abs_y + a_layout.size.height + b_abs_y) / 2.0;
(pos, container_abs_x, container_abs_x + container_size.width)
}
};
boundaries.push(BoundarySegment {
axis,
position,
span_start,
span_end,
before: a_id,
after: b_id,
});
previous = Some((b_id, b_layout));
}
}
fn child_layout(result: &CompileResult, node_id: NodeId) -> Option<&taffy::Layout> {
let taffy_id = result.node_map.get(node_id.raw() as usize)?.as_ref()?;
result.taffy_tree.layout(*taffy_id).ok()
}
fn prepare_rects_buf(buf: Option<Vec<Option<Rect>>>, capacity: usize) -> Vec<Option<Rect>> {
match buf {
Some(mut buf) => {
buf.fill(None);
buf.resize(capacity, None);
buf
}
None => vec![None; capacity],
}
}
fn collect_decorations(tree: &LayoutTree) -> DecorationIndex {
tree.decoration_entries()
.map(|(pid, meta)| DecorationPanelInfo {
id: pid,
role: meta.role,
content_kind: Arc::clone(&meta.content_kind),
})
.collect()
}
pub(crate) struct CachedLayoutState {
pub kinds: KindIndex,
pub sorted_kind_keys: Arc<[Arc<str>]>,
pub panel_kind_indices: Arc<[Option<u16>]>,
pub decorations: DecorationIndex,
pub decoration_roles: DecorationRoleIndex,
pub live_panel_ids: Arc<[PanelId]>,
}
pub(crate) fn resolve_with_cached_kinds(
result: &CompileResult,
tree: &LayoutTree,
cached: CachedLayoutState,
scratch: &mut ResolveScratch,
rects_buf: Option<Vec<Option<Rect>>>,
) -> Result<ResolvedLayout, PaneError> {
let root_id = tree
.root()
.ok_or(PaneError::InvalidTree(TreeError::RootNotSet))?;
let capacity = tree.panel_id_high_water() as usize;
let mut rects = prepare_rects_buf(rects_buf, capacity);
resolve_iterative(tree, result, root_id, &mut rects, scratch, false)?;
let boundaries = match scratch.collect_boundaries {
true => BoundaryIndex::from_scratch(&mut scratch.boundary_buf),
false => BoundaryIndex::empty(),
};
Ok(ResolvedLayout {
rects,
live_panel_ids: cached.live_panel_ids,
kinds: cached.kinds,
sorted_kind_keys: cached.sorted_kind_keys,
panel_kind_indices: cached.panel_kind_indices,
overlay_rects: Vec::new(),
overlay_failures: Vec::new(),
boundaries,
decorations: cached.decorations,
decoration_roles: cached.decoration_roles,
})
}
pub fn resolve(result: &CompileResult, tree: &LayoutTree) -> Result<ResolvedLayout, PaneError> {
resolve_dirty(result, tree, &mut ResolveScratch::default(), None)
}
pub(crate) fn resolve_dirty(
result: &CompileResult,
tree: &LayoutTree,
scratch: &mut ResolveScratch,
rects_buf: Option<Vec<Option<Rect>>>,
) -> Result<ResolvedLayout, PaneError> {
let root_id = tree
.root()
.ok_or(PaneError::InvalidTree(TreeError::RootNotSet))?;
let capacity = tree.panel_id_high_water() as usize;
let mut rects = prepare_rects_buf(rects_buf, capacity);
for v in scratch.kinds_buf.values_mut() {
v.clear();
}
resolve_iterative(tree, result, root_id, &mut rects, scratch, true)?;
scratch.kinds_buf.retain(|_, v| !v.is_empty());
let kinds = Arc::new(
scratch
.kinds_buf
.iter()
.map(|(k, v)| (Arc::clone(k), v.as_slice().into()))
.collect(),
);
let boundaries = match scratch.collect_boundaries {
true => BoundaryIndex::from_scratch(&mut scratch.boundary_buf),
false => BoundaryIndex::empty(),
};
let sorted_kind_keys = sorted_keys(&kinds);
let decorations = collect_decorations(tree);
let decoration_roles = build_decoration_role_index(capacity, &decorations);
let live_panel_ids = scratch.live_panel_buf.as_slice().into();
let panel_kind_indices = build_panel_kind_indices(capacity, &kinds, &sorted_kind_keys);
Ok(ResolvedLayout {
rects,
live_panel_ids,
kinds,
sorted_kind_keys,
panel_kind_indices,
overlay_rects: Vec::new(),
overlay_failures: Vec::new(),
boundaries,
decorations,
decoration_roles,
})
}