mod constraint;
mod node;
use std::{any::TypeId, num::NonZero, sync::Arc, time::Instant};
use parking_lot::RwLock;
use rayon::prelude::*;
use tracing::{debug, warn};
use crate::{
Clipboard, ComputeResourceManager, Px, PxRect,
cursor::CursorEvent,
px::{PxPosition, PxSize},
renderer::Command,
};
pub use constraint::{Constraint, DimensionValue};
pub use node::{
ComponentNode, ComponentNodeMetaData, ComponentNodeMetaDatas, ComponentNodeTree, ComputedData,
ImeRequest, InputHandlerFn, InputHandlerInput, MeasureFn, MeasureInput, MeasurementError,
WindowRequests, measure_node, measure_nodes, place_node,
};
pub struct ComputeParams<'a> {
pub screen_size: PxSize,
pub cursor_position: Option<PxPosition>,
pub cursor_events: Vec<CursorEvent>,
pub keyboard_events: Vec<winit::event::KeyEvent>,
pub ime_events: Vec<winit::event::Ime>,
pub modifiers: winit::keyboard::ModifiersState,
pub compute_resource_manager: Arc<RwLock<ComputeResourceManager>>,
pub gpu: &'a wgpu::Device,
pub clipboard: &'a mut Clipboard,
}
pub struct ComponentTree {
tree: indextree::Arena<ComponentNode>,
metadatas: ComponentNodeMetaDatas,
node_queue: Vec<indextree::NodeId>,
}
impl Default for ComponentTree {
fn default() -> Self {
Self::new()
}
}
impl ComponentTree {
pub fn new() -> Self {
let tree = indextree::Arena::new();
let node_queue = Vec::new();
let metadatas = ComponentNodeMetaDatas::new();
Self {
tree,
node_queue,
metadatas,
}
}
pub fn clear(&mut self) {
self.tree.clear();
self.metadatas.clear();
self.node_queue.clear();
}
pub fn get(&self, node_id: indextree::NodeId) -> Option<&ComponentNode> {
self.tree.get(node_id).map(|n| n.get())
}
pub fn get_mut(&mut self, node_id: indextree::NodeId) -> Option<&mut ComponentNode> {
self.tree.get_mut(node_id).map(|n| n.get_mut())
}
pub fn current_node(&self) -> Option<&ComponentNode> {
self.node_queue
.last()
.and_then(|node_id| self.get(*node_id))
}
pub fn current_node_mut(&mut self) -> Option<&mut ComponentNode> {
let node_id = self.node_queue.last()?;
self.get_mut(*node_id)
}
pub fn add_node(&mut self, node_component: ComponentNode) {
let new_node_id = self.tree.new_node(node_component);
if let Some(current_node_id) = self.node_queue.last_mut() {
current_node_id.append(new_node_id, &mut self.tree);
}
let metadata = ComponentNodeMetaData::none();
self.metadatas.insert(new_node_id, metadata);
self.node_queue.push(new_node_id);
}
pub fn pop_node(&mut self) {
self.node_queue.pop();
}
pub(crate) fn tree(&self) -> &indextree::Arena<ComponentNode> {
&self.tree
}
pub(crate) fn metadatas(&self) -> &ComponentNodeMetaDatas {
&self.metadatas
}
#[tracing::instrument(level = "debug", skip(self, params))]
pub fn compute(
&mut self,
params: ComputeParams<'_>,
) -> (Vec<(Command, TypeId, PxSize, PxPosition)>, WindowRequests) {
let ComputeParams {
screen_size,
mut cursor_position,
mut cursor_events,
mut keyboard_events,
mut ime_events,
modifiers,
compute_resource_manager,
gpu,
clipboard,
} = params;
let Some(root_node) = self.tree.get_node_id_at(NonZero::new(1).unwrap()) else {
return (vec![], WindowRequests::default());
};
let screen_constraint = Constraint::new(
DimensionValue::Fixed(screen_size.width),
DimensionValue::Fixed(screen_size.height),
);
let measure_timer = Instant::now();
debug!("Start measuring the component tree...");
match measure_node(
root_node,
&screen_constraint,
&self.tree,
&self.metadatas,
compute_resource_manager,
gpu,
) {
Ok(_root_computed_data) => {
debug!("Component tree measured in {:?}", measure_timer.elapsed());
}
Err(e) => {
panic!(
"Root node ({root_node:?}) measurement failed: {e:?}. Aborting draw command computation."
);
}
}
let compute_draw_timer = Instant::now();
debug!("Start computing draw commands...");
let commands = compute_draw_commands_parallel(
root_node,
&self.tree,
&self.metadatas,
screen_size.width.0,
screen_size.height.0,
);
debug!(
"Draw commands computed in {:?}, total commands: {}",
compute_draw_timer.elapsed(),
commands.len()
);
let input_handler_timer = Instant::now();
let mut window_requests = WindowRequests::default();
debug!("Start executing input handlers...");
for node_id in root_node
.reverse_traverse(&self.tree)
.filter_map(|edge| match edge {
indextree::NodeEdge::Start(id) => Some(id),
indextree::NodeEdge::End(_) => None,
})
{
let Some(input_handler) = self
.tree
.get(node_id)
.and_then(|n| n.get().input_handler_fn.as_ref())
else {
continue;
};
let metadata = self.metadatas.get(&node_id).unwrap();
let abs_pos = metadata.abs_position.unwrap();
let event_clip_rect = metadata.event_clip_rect;
let node_computed_data = metadata.computed_data;
drop(metadata);
let mut cursor_position_ref = &mut cursor_position;
let mut dummy_cursor_position = None;
let mut cursor_events_ref = &mut cursor_events;
let mut empty_dummy_cursor_events = Vec::new();
if let (Some(cursor_pos), Some(clip_rect)) = (*cursor_position_ref, event_clip_rect) {
if !clip_rect.contains(cursor_pos) {
cursor_position_ref = &mut dummy_cursor_position;
cursor_events_ref = &mut empty_dummy_cursor_events;
}
}
let current_cursor_position = cursor_position_ref.map(|pos| pos - abs_pos);
if let Some(node_computed_data) = node_computed_data {
let input = InputHandlerInput {
computed_data: node_computed_data,
cursor_position_rel: current_cursor_position,
cursor_position_abs: cursor_position_ref,
cursor_events: cursor_events_ref,
keyboard_events: &mut keyboard_events,
ime_events: &mut ime_events,
key_modifiers: modifiers,
requests: &mut window_requests,
clipboard,
current_node_id: node_id,
metadatas: &self.metadatas,
};
input_handler(input);
if let Some(ref mut ime_request) = window_requests.ime_request
&& ime_request.position.is_none()
{
ime_request.position = Some(abs_pos);
}
} else {
warn!(
"Computed data not found for node {:?} during input handler execution.",
node_id
);
}
}
debug!(
"Input Handlers executed in {:?}",
input_handler_timer.elapsed()
);
(commands, window_requests)
}
}
#[tracing::instrument(level = "trace", skip(tree, metadatas))]
fn compute_draw_commands_parallel(
node_id: indextree::NodeId,
tree: &ComponentNodeTree,
metadatas: &ComponentNodeMetaDatas,
screen_width: i32,
screen_height: i32,
) -> Vec<(Command, TypeId, PxSize, PxPosition)> {
compute_draw_commands_inner_parallel(
PxPosition::ZERO,
true,
node_id,
tree,
metadatas,
screen_width,
screen_height,
None,
)
}
#[tracing::instrument(level = "trace", skip(tree, metadatas))]
fn compute_draw_commands_inner_parallel(
start_pos: PxPosition,
is_root: bool,
node_id: indextree::NodeId,
tree: &ComponentNodeTree,
metadatas: &ComponentNodeMetaDatas,
screen_width: i32,
screen_height: i32,
clip_rect: Option<PxRect>,
) -> Vec<(Command, TypeId, PxSize, PxPosition)> {
let mut local_commands = Vec::new();
let mut metadata = metadatas.get_mut(&node_id).unwrap();
let rel_pos = match metadata.rel_position {
Some(pos) => pos,
None if is_root => PxPosition::ZERO,
_ => return local_commands, };
let self_pos = start_pos + rel_pos;
metadata.abs_position = Some(self_pos);
let size = metadata
.computed_data
.map(|d| PxSize {
width: d.width,
height: d.height,
})
.unwrap_or_default();
let node_rect = PxRect {
x: self_pos.x,
y: self_pos.y,
width: size.width,
height: size.height,
};
let mut clip_rect = clip_rect;
if let Some(clip_rect) = clip_rect {
metadata.event_clip_rect = Some(clip_rect);
}
let clips_children = metadata.clips_children;
if clips_children {
let new_clip_rect = if let Some(existing_clip) = clip_rect {
existing_clip
.intersection(&node_rect)
.unwrap_or(PxRect::ZERO)
} else {
node_rect
};
clip_rect = Some(new_clip_rect);
local_commands.push((
Command::ClipPush(new_clip_rect),
TypeId::of::<Command>(),
size,
self_pos,
));
}
let screen_rect = PxRect {
x: Px(0),
y: Px(0),
width: Px(screen_width),
height: Px(screen_height),
};
if size.width.0 > 0 && size.height.0 > 0 && !node_rect.is_orthogonal(&screen_rect) {
for (cmd, type_id) in metadata.commands.drain(..) {
local_commands.push((cmd, type_id, size, self_pos));
}
}
drop(metadata);
let children: Vec<_> = node_id.children(tree).collect();
let child_results: Vec<Vec<_>> = children
.into_par_iter()
.map(|child| {
let parent_abs_pos = metadatas.get(&node_id).unwrap().abs_position.unwrap();
compute_draw_commands_inner_parallel(
parent_abs_pos, false,
child,
tree,
metadatas,
screen_width,
screen_height,
clip_rect,
)
})
.collect();
for child_cmds in child_results {
local_commands.extend(child_cmds);
}
if clips_children {
local_commands.push((Command::ClipPop, TypeId::of::<Command>(), size, self_pos));
}
local_commands
}