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