tessera_ui/component_tree/node.rs
1use std::{
2 any::TypeId,
3 collections::HashMap,
4 ops::{Add, AddAssign},
5 sync::Arc,
6 time::Instant,
7};
8
9use dashmap::DashMap;
10use indextree::NodeId;
11use parking_lot::RwLock;
12use rayon::prelude::*;
13use tracing::debug;
14use winit::window::CursorIcon;
15
16use crate::{
17 Clipboard, ComputeCommand, ComputeResourceManager, DrawCommand, Px,
18 cursor::CursorEvent,
19 px::{PxPosition, PxSize},
20 renderer::Command,
21};
22
23use super::constraint::{Constraint, DimensionValue};
24
25/// A ComponentNode is a node in the component tree.
26/// It represents all information about a component.
27pub struct ComponentNode {
28 /// Component function's name, for debugging purposes.
29 pub fn_name: String,
30 /// Describes the component in layout.
31 /// None means using default measure policy which places children at the top-left corner
32 /// of the parent node, with no offset.
33 pub measure_fn: Option<Box<MeasureFn>>,
34 /// Describes the input handler for the component.
35 /// This is used to handle state changes.
36 pub input_handler_fn: Option<Box<InputHandlerFn>>,
37}
38
39/// Contains metadata of the component node.
40#[derive(Default)]
41pub struct ComponentNodeMetaData {
42 /// The computed data (size) of the node.
43 /// None if the node is not computed yet.
44 pub computed_data: Option<ComputedData>,
45 /// The node's start position, relative to its parent.
46 /// None if the node is not placed yet.
47 pub rel_position: Option<PxPosition>,
48 /// The node's start position, relative to the root window.
49 /// This will be computed during drawing command's generation.
50 /// None if the node is not drawn yet.
51 pub abs_position: Option<PxPosition>,
52 /// The effective clipping rectangle for this node, considering all its ancestors.
53 /// This is calculated once per frame before event handling.
54 pub event_clip_rect: Option<crate::PxRect>,
55 /// Commands associated with this node.
56 ///
57 /// This stores both draw and compute commands in a unified vector using the
58 /// new `Command` enum. Commands are collected during the measure phase and
59 /// executed during rendering. The order of commands in this vector determines
60 /// their execution order.
61 pub(crate) commands: Vec<(Command, TypeId)>,
62 /// Whether this node clips its children.
63 pub clips_children: bool,
64}
65
66impl ComponentNodeMetaData {
67 /// Creates a new `ComponentNodeMetaData` with default values.
68 pub fn none() -> Self {
69 Self {
70 computed_data: None,
71 rel_position: None,
72 abs_position: None,
73 event_clip_rect: None,
74 commands: Vec::new(),
75 clips_children: false,
76 }
77 }
78
79 /// Pushes a draw command to the node's metadata.
80 ///
81 /// Draw commands are responsible for rendering visual content (shapes, text, images).
82 /// This method wraps the command in the unified `Command::Draw` variant and adds it
83 /// to the command queue. Commands are executed in the order they are added.
84 ///
85 /// # Example
86 /// ```rust,ignore
87 /// metadata.push_draw_command(ShapeCommand::Rect {
88 /// color: [1.0, 0.0, 0.0, 1.0],
89 /// corner_radius: 8.0,
90 /// shadow: None,
91 /// });
92 /// ```
93 pub fn push_draw_command<C: DrawCommand + 'static>(&mut self, command: C) {
94 let command = Box::new(command);
95 let command = command as Box<dyn DrawCommand>;
96 let command = Command::Draw(command);
97 self.commands.push((command, TypeId::of::<C>()));
98 }
99
100 /// Pushes a compute command to the node's metadata.
101 ///
102 /// Compute commands perform GPU computation tasks (post-processing effects,
103 /// complex calculations). This method wraps the command in the unified
104 /// `Command::Compute` variant and adds it to the command queue.
105 ///
106 /// # Example
107 /// ```rust,ignore
108 /// metadata.push_compute_command(DualBlurCommand::horizontal_then_vertical(5.0, Px::new(16)));
109 /// ```
110 pub fn push_compute_command<C: ComputeCommand + 'static>(&mut self, command: C) {
111 let command = Box::new(command);
112 let command = command as Box<dyn ComputeCommand>;
113 let command = Command::Compute(command);
114 self.commands.push((command, TypeId::of::<C>()));
115 }
116}
117
118/// A tree of component nodes, using `indextree::Arena` for storage.
119pub type ComponentNodeTree = indextree::Arena<ComponentNode>;
120/// Contains all component nodes' metadatas, using a thread-safe `DashMap`.
121pub type ComponentNodeMetaDatas = DashMap<NodeId, ComponentNodeMetaData>;
122
123/// Represents errors that can occur during node measurement.
124#[derive(Debug, Clone, PartialEq)]
125pub enum MeasurementError {
126 /// Indicates that the specified node was not found in the component tree.
127 NodeNotFoundInTree,
128 /// Indicates that metadata for the specified node was not found (currently not a primary error source in measure_node).
129 NodeNotFoundInMeta,
130 /// Indicates that the custom measure function (`MeasureFn`) for a node failed.
131 /// Contains a string detailing the failure.
132 MeasureFnFailed(String),
133 /// Indicates that the measurement of a child node failed during a parent's layout calculation (e.g., in `DEFAULT_LAYOUT_DESC`).
134 /// Contains the `NodeId` of the child that failed.
135 ChildMeasurementFailed(NodeId),
136}
137
138/// A `MeasureFn` is a function that takes an input `Constraint` and its children nodes,
139/// finishes placementing inside, and returns its size (`ComputedData`) or an error.
140pub type MeasureFn =
141 dyn Fn(&MeasureInput<'_>) -> Result<ComputedData, MeasurementError> + Send + Sync;
142
143/// Input for the measure function (`MeasureFn`).
144pub struct MeasureInput<'a> {
145 /// The `NodeId` of the current node being measured.
146 pub current_node_id: indextree::NodeId,
147 /// The component tree containing all nodes.
148 pub tree: &'a ComponentNodeTree,
149 /// The effective constraint for this node, merged with its parent's constraint.
150 pub parent_constraint: &'a Constraint,
151 /// The children nodes of the current node.
152 pub children_ids: &'a [indextree::NodeId],
153 /// Metadata for all component nodes, used to access cached data and constraints.
154 pub metadatas: &'a ComponentNodeMetaDatas,
155 /// Compute resources manager
156 pub compute_resource_manager: Arc<RwLock<ComputeResourceManager>>,
157 /// Gpu device
158 pub gpu: &'a wgpu::Device,
159}
160
161impl<'a> MeasureInput<'a> {
162 /// Returns a mutable reference to the metadata of the current node.
163 ///
164 /// This is a convenience method that simplifies accessing the current node's metadata
165 /// from within a `measure` function. It encapsulates the `DashMap::get_mut` call and panics
166 /// if the metadata is not found, as it's an invariant that it must exist.
167 pub fn metadata_mut(&self) -> dashmap::mapref::one::RefMut<'_, NodeId, ComponentNodeMetaData> {
168 self.metadatas
169 .get_mut(&self.current_node_id)
170 .expect("Metadata for current node must exist during measure")
171 }
172
173 /// Measures all specified child nodes under the given constraint.
174 ///
175 /// Returns a map of each child's computed layout data, or the first measurement error encountered.
176 pub fn measure_children(
177 &self,
178 nodes_to_measure: Vec<(NodeId, Constraint)>,
179 ) -> Result<HashMap<NodeId, ComputedData>, MeasurementError> {
180 let results = measure_nodes(
181 nodes_to_measure,
182 self.tree,
183 self.metadatas,
184 self.compute_resource_manager.clone(),
185 self.gpu,
186 );
187
188 let mut successful_results = HashMap::new();
189 for (child_id, result) in results {
190 match result {
191 Ok(size) => successful_results.insert(child_id, size),
192 Err(e) => {
193 debug!("Measurement error for child {child_id:?}: {e:?}");
194 return Err(e);
195 }
196 };
197 }
198 Ok(successful_results)
199 }
200
201 /// Measures a single child node under the given constraint.
202 ///
203 /// Returns the computed layout data or a measurement error.
204 pub fn measure_child(
205 &self,
206 child_id: NodeId,
207 constraint: &Constraint,
208 ) -> Result<ComputedData, MeasurementError> {
209 measure_node(
210 child_id,
211 constraint,
212 self.tree,
213 self.metadatas,
214 self.compute_resource_manager.clone(),
215 self.gpu,
216 )
217 }
218
219 /// Sets the relative position of a child node.
220 pub fn place_child(&self, child_id: NodeId, position: PxPosition) {
221 place_node(child_id, position, self.metadatas);
222 }
223
224 /// Enables clipping for the current node.
225 pub fn enable_clipping(&self) {
226 // Set the clipping flag to true for this node.
227 self.metadata_mut().clips_children = true;
228 }
229
230 /// Disables clipping for the current node.
231 pub fn disable_clipping(&self) {
232 // Set the clipping flag to false for this node.
233 self.metadata_mut().clips_children = false;
234 }
235}
236
237/// A `InputHandlerFn` is a function that handles state changes for a component.
238///
239/// The rule of execution order is:
240///
241/// 1. Children's input handlers are executed earlier than parent's.
242/// 2. Newer components' input handlers are executed earlier than older ones.
243///
244/// Acutally, rule 2 includes rule 1, because a newer component is always a child of an older component :)
245pub type InputHandlerFn = dyn Fn(InputHandlerInput) + Send + Sync;
246
247/// Input for the input handler function (`InputHandlerFn`).
248///
249/// Note that you can modify the `cursor_events` and `keyboard_events` vectors
250/// for exmaple block some keyboard events or cursor events to prevent them from propagating
251/// to parent components and older brother components.
252pub struct InputHandlerInput<'a> {
253 /// The size of the component node, computed during the measure stage.
254 pub computed_data: ComputedData,
255 /// The position of the cursor, if available.
256 /// Relative to the root position of the component.
257 pub cursor_position_rel: Option<PxPosition>,
258 /// The mut ref of absolute position of the cursor in the window.
259 /// Used to block cursor fully if needed, since cursor_position_rel use this.
260 /// Not a public field for now.
261 pub(crate) cursor_position_abs: &'a mut Option<PxPosition>,
262 /// Cursor events from the event loop, if any.
263 pub cursor_events: &'a mut Vec<CursorEvent>,
264 /// Keyboard events from the event loop, if any.
265 pub keyboard_events: &'a mut Vec<winit::event::KeyEvent>,
266 /// IME events from the event loop, if any.
267 pub ime_events: &'a mut Vec<winit::event::Ime>,
268 /// The current state of the keyboard modifiers at the time of the event.
269 /// This allows for implementing keyboard shortcuts (e.g., Ctrl+C).
270 pub key_modifiers: winit::keyboard::ModifiersState,
271 /// A context for making requests to the window for the current frame.
272 pub requests: &'a mut WindowRequests,
273 /// Clipboard
274 pub clipboard: &'a mut Clipboard,
275}
276
277impl InputHandlerInput<'_> {
278 /// Blocks the cursor to other components.
279 pub fn block_cursor(&mut self) {
280 // Block the cursor by setting its position to None.
281 self.cursor_position_abs.take();
282 // Clear all cursor events to prevent them from propagating.
283 self.cursor_events.clear();
284 }
285
286 /// Blocks the keyboard events to other components.
287 pub fn block_keyboard(&mut self) {
288 // Clear all keyboard events to prevent them from propagating.
289 self.keyboard_events.clear();
290 }
291
292 /// Blocks the IME events to other components.
293 pub fn block_ime(&mut self) {
294 // Clear all IME events to prevent them from propagating.
295 self.ime_events.clear();
296 }
297
298 /// Block all events (cursor, keyboard, IME) to other components.
299 pub fn block_all(&mut self) {
300 self.block_cursor();
301 self.block_keyboard();
302 self.block_ime();
303 }
304}
305
306/// A collection of requests that components can make to the windowing system for the current frame.
307/// This struct's lifecycle is confined to a single `compute` pass.
308#[derive(Default, Debug)]
309pub struct WindowRequests {
310 /// The cursor icon requested by a component. If multiple components request a cursor,
311 /// the last one to make a request in a frame "wins", since it's executed later.
312 pub cursor_icon: CursorIcon,
313 /// An Input Method Editor (IME) request.
314 /// If multiple components request IME, the one from the "newer" component (which is
315 /// processed later in the state handling pass) will overwrite previous requests.
316 pub ime_request: Option<ImeRequest>,
317}
318
319/// A request to the windowing system to open an Input Method Editor (IME).
320/// This is typically used for text input components.
321#[derive(Debug)]
322pub struct ImeRequest {
323 /// The size of the area where the IME is requested.
324 pub size: PxSize,
325 /// The absolute position where the IME should be placed.
326 /// This is set internally by the component tree during the compute pass.
327 pub(crate) position: Option<PxPosition>, // should be setted in tessera node tree compute
328}
329
330impl ImeRequest {
331 pub fn new(size: PxSize) -> Self {
332 Self {
333 size,
334 position: None, // Position will be set during the compute phase
335 }
336 }
337}
338
339/// Measures a single node recursively, returning its size or an error.
340///
341/// See [`measure_nodes`] for concurrent measurement of multiple nodes.
342/// Which is very recommended for most cases. You should only use this function
343/// when your're very sure that you only need to measure a single node.
344pub fn measure_node(
345 node_id: NodeId,
346 parent_constraint: &Constraint,
347 tree: &ComponentNodeTree,
348 component_node_metadatas: &ComponentNodeMetaDatas,
349 compute_resource_manager: Arc<RwLock<ComputeResourceManager>>,
350 gpu: &wgpu::Device,
351) -> Result<ComputedData, MeasurementError> {
352 // Make sure metadata and default value exists for the node.
353 component_node_metadatas.insert(node_id, Default::default());
354
355 let node_data_ref = tree
356 .get(node_id)
357 .ok_or(MeasurementError::NodeNotFoundInTree)?;
358 let node_data = node_data_ref.get();
359
360 let children: Vec<_> = node_id.children(tree).collect(); // No .as_ref() needed for &Arena
361 let timer = Instant::now();
362
363 debug!(
364 "Measuring node {} with {} children, parent constraint: {:?}",
365 node_data.fn_name,
366 children.len(),
367 parent_constraint
368 );
369
370 let size = if let Some(measure_fn) = &node_data.measure_fn {
371 measure_fn(&MeasureInput {
372 current_node_id: node_id,
373 tree,
374 parent_constraint,
375 children_ids: &children,
376 metadatas: component_node_metadatas,
377 compute_resource_manager,
378 gpu,
379 })
380 } else {
381 DEFAULT_LAYOUT_DESC(&MeasureInput {
382 current_node_id: node_id,
383 tree,
384 parent_constraint,
385 children_ids: &children,
386 metadatas: component_node_metadatas,
387 compute_resource_manager,
388 gpu,
389 })
390 }?;
391
392 debug!(
393 "Measured node {} in {:?} with size {:?}",
394 node_data.fn_name,
395 timer.elapsed(),
396 size
397 );
398
399 let mut metadata = component_node_metadatas.entry(node_id).or_default();
400 metadata.computed_data = Some(size);
401
402 Ok(size)
403}
404
405/// Places a node at the specified relative position within its parent.
406pub fn place_node(
407 node: indextree::NodeId,
408 rel_position: PxPosition,
409 component_node_metadatas: &ComponentNodeMetaDatas,
410) {
411 component_node_metadatas
412 .entry(node)
413 .or_default()
414 .rel_position = Some(rel_position);
415}
416
417/// A default layout descriptor (`MeasureFn`) that places children at the top-left corner ([0,0])
418/// of the parent node with no offset. Children are measured concurrently using `measure_nodes`.
419pub const DEFAULT_LAYOUT_DESC: &MeasureFn = &|input| {
420 if input.children_ids.is_empty() {
421 // If there are no children, the size depends on the parent_constraint
422 // For Fixed, it's the fixed size. For Wrap/Fill, it's typically 0 if no content.
423 // This part might need refinement based on how min constraints in Wrap/Fill should behave for empty nodes.
424 // For now, returning ZERO, assuming intrinsic size of an empty node is zero before min constraints are applied.
425 // The actual min size enforcement happens when the parent (or this node itself if it has intrinsic min)
426 // considers its own DimensionValue.
427 return Ok(ComputedData::min_from_constraint(input.parent_constraint));
428 }
429
430 let nodes_to_measure: Vec<(NodeId, Constraint)> = input
431 .children_ids
432 .iter()
433 .map(|&child_id| (child_id, *input.parent_constraint)) // Children inherit parent's effective constraint
434 .collect();
435
436 let children_results_map = measure_nodes(
437 nodes_to_measure,
438 input.tree,
439 input.metadatas,
440 input.compute_resource_manager.clone(),
441 input.gpu,
442 );
443
444 let mut aggregate_size = ComputedData::ZERO;
445 let mut first_error: Option<MeasurementError> = None;
446 let mut successful_children_data = Vec::new();
447
448 for &child_id in input.children_ids {
449 match children_results_map.get(&child_id) {
450 Some(Ok(child_size)) => {
451 successful_children_data.push((child_id, *child_size));
452 }
453 Some(Err(e)) => {
454 debug!(
455 "Child node {child_id:?} measurement failed for parent {:?}: {e:?}",
456 input.current_node_id
457 );
458 if first_error.is_none() {
459 first_error = Some(MeasurementError::ChildMeasurementFailed(child_id));
460 }
461 }
462 None => {
463 debug!(
464 "Child node {child_id:?} was not found in measure_nodes results for parent {:?}",
465 input.current_node_id
466 );
467 if first_error.is_none() {
468 first_error = Some(MeasurementError::MeasureFnFailed(format!(
469 "Result for child {child_id:?} missing"
470 )));
471 }
472 }
473 }
474 }
475
476 if let Some(error) = first_error {
477 return Err(error);
478 }
479 if successful_children_data.is_empty() && !input.children_ids.is_empty() {
480 // This case should ideally be caught by first_error if all children failed.
481 // If it's reached, it implies some logic issue.
482 return Err(MeasurementError::MeasureFnFailed(
483 "All children failed to measure or results missing in DEFAULT_LAYOUT_DESC".to_string(),
484 ));
485 }
486
487 // For default layout (stacking), the aggregate size is the max of children's sizes.
488 for (child_id, child_size) in successful_children_data {
489 aggregate_size = aggregate_size.max(child_size);
490 place_node(child_id, PxPosition::ZERO, input.metadatas); // All children at [0,0] for simple stacking
491 }
492
493 // The aggregate_size is based on children. Now apply current node's own constraints.
494 // If current node is Fixed, its size is fixed.
495 // If current node is Wrap, its size is aggregate_size (clamped by its own min/max).
496 // If current node is Fill, its size is aggregate_size (clamped by its own min/max, and parent's available space if parent was Fill).
497 // This final clamping/adjustment based on `parent_constraint` should ideally happen
498 // when `ComputedData` is returned from `measure_node` itself, or by the caller of `measure_node`.
499 // For DEFAULT_LAYOUT_DESC, it should return the size required by its children,
500 // and then `measure_node` will finalize it based on `parent_constraint`.
501
502 // Let's refine: DEFAULT_LAYOUT_DESC should calculate the "natural" size based on children.
503 // Then, `measure_node` (or its caller) would apply the `parent_constraint` to this natural size.
504 // However, `measure_node` currently directly returns the result of `DEFAULT_LAYOUT_DESC` or custom `measure_fn`.
505 // So, `DEFAULT_LAYOUT_DESC` itself needs to consider `parent_constraint` for its final size.
506
507 let mut final_width = aggregate_size.width;
508 let mut final_height = aggregate_size.height;
509
510 match input.parent_constraint.width {
511 DimensionValue::Fixed(w) => final_width = w,
512 DimensionValue::Wrap { min, max } => {
513 if let Some(min_w) = min {
514 final_width = final_width.max(min_w);
515 }
516 if let Some(max_w) = max {
517 final_width = final_width.min(max_w);
518 }
519 }
520 DimensionValue::Fill { min, max } => {
521 // Fill behaves like wrap for default layout unless children expand
522 if let Some(min_w) = min {
523 final_width = final_width.max(min_w);
524 }
525 if let Some(max_w) = max {
526 final_width = final_width.min(max_w);
527 }
528 // If parent was Fill, this node would have gotten a Fill constraint too.
529 // The actual "filling" happens because children might be Fill.
530 // If children are not Fill, this node wraps them.
531 }
532 }
533 match input.parent_constraint.height {
534 DimensionValue::Fixed(h) => final_height = h,
535 DimensionValue::Wrap { min, max } => {
536 if let Some(min_h) = min {
537 final_height = final_height.max(min_h);
538 }
539 if let Some(max_h) = max {
540 final_height = final_height.min(max_h);
541 }
542 }
543 DimensionValue::Fill { min, max } => {
544 if let Some(min_h) = min {
545 final_height = final_height.max(min_h);
546 }
547 if let Some(max_h) = max {
548 final_height = final_height.min(max_h);
549 }
550 }
551 }
552 Ok(ComputedData {
553 width: final_width,
554 height: final_height,
555 })
556};
557
558/// Concurrently measures multiple nodes using Rayon for parallelism.
559pub fn measure_nodes(
560 nodes_to_measure: Vec<(NodeId, Constraint)>,
561 tree: &ComponentNodeTree,
562 component_node_metadatas: &ComponentNodeMetaDatas,
563 compute_resource_manager: Arc<RwLock<ComputeResourceManager>>,
564 gpu: &wgpu::Device,
565) -> HashMap<NodeId, Result<ComputedData, MeasurementError>> {
566 if nodes_to_measure.is_empty() {
567 return HashMap::new();
568 }
569 // metadata must be reseted and initialized for each node to measure.
570 for (node_id, _) in &nodes_to_measure {
571 component_node_metadatas.insert(*node_id, Default::default());
572 }
573 nodes_to_measure
574 .into_par_iter()
575 .map(|(node_id, parent_constraint)| {
576 let result = measure_node(
577 node_id,
578 &parent_constraint,
579 tree,
580 component_node_metadatas,
581 compute_resource_manager.clone(),
582 gpu,
583 );
584 (node_id, result)
585 })
586 .collect::<HashMap<NodeId, Result<ComputedData, MeasurementError>>>()
587}
588
589/// Layout information computed at the measure stage, representing the size of a node.
590#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
591pub struct ComputedData {
592 pub width: Px,
593 pub height: Px,
594}
595
596impl Add for ComputedData {
597 type Output = Self;
598 fn add(self, rhs: Self) -> Self::Output {
599 Self {
600 width: self.width + rhs.width,
601 height: self.height + rhs.height,
602 }
603 }
604}
605
606impl AddAssign for ComputedData {
607 fn add_assign(&mut self, rhs: Self) {
608 *self = *self + rhs;
609 }
610}
611
612impl ComputedData {
613 pub const ZERO: Self = Self {
614 width: Px(0),
615 height: Px(0),
616 };
617
618 /// Calculates a "minimum" size based on a constraint.
619 /// For Fixed, it's the fixed value. For Wrap/Fill, it's their 'min' if Some, else 0.
620 pub fn min_from_constraint(constraint: &Constraint) -> Self {
621 let width = match constraint.width {
622 DimensionValue::Fixed(w) => w,
623 DimensionValue::Wrap { min, .. } => min.unwrap_or(Px(0)),
624 DimensionValue::Fill { min, .. } => min.unwrap_or(Px(0)),
625 };
626 let height = match constraint.height {
627 DimensionValue::Fixed(h) => h,
628 DimensionValue::Wrap { min, .. } => min.unwrap_or(Px(0)),
629 DimensionValue::Fill { min, .. } => min.unwrap_or(Px(0)),
630 };
631 Self { width, height }
632 }
633
634 pub fn min(self, rhs: Self) -> Self {
635 Self {
636 width: self.width.min(rhs.width),
637 height: self.height.min(rhs.height),
638 }
639 }
640
641 pub fn max(self, rhs: Self) -> Self {
642 Self {
643 width: self.width.max(rhs.width),
644 height: self.height.max(rhs.height),
645 }
646 }
647}