#[cfg(feature = "layout-engine")]
use std::collections::HashMap;
#[cfg(feature = "layout-engine")]
use accesskit::NodeId as AkNodeId;
#[cfg(feature = "layout-engine")]
use taffy::prelude::*;
#[cfg(feature = "layout-engine")]
use crate::{BoundingBox, LayoutContext, Viewport};
#[derive(Debug, Clone, derive_more::Display, derive_more::Error)]
#[display("Layout engine error: {} at {}:{}", message, file, line)]
pub struct LayoutEngineError {
pub message: String,
pub file: &'static str,
pub line: u32,
}
impl LayoutEngineError {
#[track_caller]
pub fn new(message: impl Into<String>) -> Self {
let loc = std::panic::Location::caller();
Self {
message: message.into(),
file: loc.file(),
line: loc.line(),
}
}
}
#[cfg(feature = "layout-engine")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LayoutMode {
#[default]
Block,
Flex,
Grid,
}
#[cfg(feature = "layout-engine")]
pub struct TaffyBridge {
tree: TaffyTree,
ak_to_taffy: HashMap<AkNodeId, taffy::NodeId>,
taffy_to_ak: HashMap<taffy::NodeId, AkNodeId>,
}
#[cfg(feature = "layout-engine")]
impl TaffyBridge {
#[tracing::instrument(level = "debug")]
pub fn new() -> Self {
Self {
tree: TaffyTree::new(),
ak_to_taffy: HashMap::new(),
taffy_to_ak: HashMap::new(),
}
}
#[tracing::instrument(level = "debug", skip(self, nodes))]
pub fn build_from_accesskit(
&mut self,
root_id: AkNodeId,
nodes: &HashMap<AkNodeId, accesskit::Node>,
) -> Result<taffy::NodeId, LayoutEngineError> {
self.build_node_recursive(root_id, nodes)
}
fn build_node_recursive(
&mut self,
ak_id: AkNodeId,
nodes: &HashMap<AkNodeId, accesskit::Node>,
) -> Result<taffy::NodeId, LayoutEngineError> {
let node = nodes
.get(&ak_id)
.ok_or_else(|| LayoutEngineError::new(format!("Node not found: {ak_id:?}")))?;
let style = self.accesskit_to_taffy_style(node);
let child_ids: Vec<taffy::NodeId> = node
.children()
.iter()
.filter_map(|child_ak_id| self.build_node_recursive(*child_ak_id, nodes).ok())
.collect();
let taffy_id = if child_ids.is_empty() {
self.tree
.new_leaf(style)
.map_err(|e| LayoutEngineError::new(format!("Failed to create leaf: {e}")))?
} else {
self.tree
.new_with_children(style, &child_ids)
.map_err(|e| LayoutEngineError::new(format!("Failed to create node: {e}")))?
};
self.ak_to_taffy.insert(ak_id, taffy_id);
self.taffy_to_ak.insert(taffy_id, ak_id);
Ok(taffy_id)
}
fn accesskit_to_taffy_style(&self, node: &accesskit::Node) -> Style {
let mut style = Style::default();
if let Some(bounds) = node.bounds() {
let width = bounds.x1 - bounds.x0;
let height = bounds.y1 - bounds.y0;
if width > 0.0 {
style.size.width = Dimension::length(width as f32);
}
if height > 0.0 {
style.size.height = Dimension::length(height as f32);
}
}
style
}
#[tracing::instrument(level = "debug", skip(self))]
pub fn compute_layout(
&mut self,
root: taffy::NodeId,
viewport: &Viewport,
) -> Result<(), LayoutEngineError> {
let available = Size {
width: AvailableSpace::Definite(viewport.width as f32),
height: AvailableSpace::Definite(viewport.height as f32),
};
self.tree
.compute_layout(root, available)
.map_err(|e| LayoutEngineError::new(format!("Layout computation failed: {e}")))
}
#[tracing::instrument(level = "debug", skip(self))]
pub fn extract_layout_context(
&self,
viewport: Viewport,
) -> Result<LayoutContext, LayoutEngineError> {
let mut bounds = HashMap::new();
for (taffy_id, ak_id) in &self.taffy_to_ak {
let layout = self
.tree
.layout(*taffy_id)
.map_err(|e| LayoutEngineError::new(format!("Failed to get layout: {e}")))?;
let bb = BoundingBox::new(
f64::from(layout.location.x),
f64::from(layout.location.y),
f64::from(layout.size.width),
f64::from(layout.size.height),
);
bounds.insert(*ak_id, bb);
}
Ok(LayoutContext::new(viewport, bounds))
}
#[tracing::instrument(level = "debug", skip(self))]
pub fn compute_at_width(
&mut self,
root: taffy::NodeId,
width: u32,
height: u32,
) -> Result<LayoutContext, LayoutEngineError> {
let viewport = Viewport::new(width, height);
self.compute_layout(root, &viewport)?;
self.extract_layout_context(viewport)
}
}
#[cfg(feature = "layout-engine")]
impl Default for TaffyBridge {
fn default() -> Self {
Self::new()
}
}