Skip to main content

rosace_core/
render_object.rs

1use crate::types::{Point, Size};
2
3/// Placeholder canvas type for Phase 1.
4///
5/// The real Skia-backed canvas is provided by `rosace-render`; this stub
6/// keeps `rosace-core` free of rendering dependencies during the component
7/// model phase.
8pub struct Canvas;
9
10/// Describes a bound along a single layout axis.
11#[derive(Clone, Copy, Debug, PartialEq)]
12pub enum AxisBound {
13    /// The axis has a finite pixel limit.
14    Bounded(f32),
15    /// The axis is unconstrained — the child may be any size.
16    Unbounded,
17    /// The axis must shrink to fit its content (intrinsic sizing).
18    Shrink,
19}
20
21/// Layout constraints passed down the render tree during the measure pass.
22///
23/// Distinct from `rosace_trace::event::TraceConstraints`, which is a
24/// simplified snapshot for tracing purposes only.
25#[derive(Clone, Copy, Debug, PartialEq)]
26pub struct Constraints {
27    /// Minimum allowed width in logical pixels.
28    pub min_width: f32,
29    /// Maximum allowed width.
30    pub max_width: AxisBound,
31    /// Minimum allowed height in logical pixels.
32    pub min_height: f32,
33    /// Maximum allowed height.
34    pub max_height: AxisBound,
35}
36
37impl Constraints {
38    /// Loose constraints: minimum is zero, maximum is the given dimensions.
39    ///
40    /// The child may be any size up to `width` × `height`.
41    pub fn loose(width: f32, height: f32) -> Self {
42        Constraints {
43            min_width: 0.0,
44            max_width: AxisBound::Bounded(width),
45            min_height: 0.0,
46            max_height: AxisBound::Bounded(height),
47        }
48    }
49
50    /// Tight constraints: minimum equals maximum at the given dimensions.
51    ///
52    /// The child must be exactly `width` × `height`.
53    pub fn tight(width: f32, height: f32) -> Self {
54        Constraints {
55            min_width: width,
56            max_width: AxisBound::Bounded(width),
57            min_height: height,
58            max_height: AxisBound::Bounded(height),
59        }
60    }
61
62    /// Fully unbounded constraints: the child may take any size on both axes.
63    pub fn unbounded() -> Self {
64        Constraints {
65            min_width: 0.0,
66            max_width: AxisBound::Unbounded,
67            min_height: 0.0,
68            max_height: AxisBound::Unbounded,
69        }
70    }
71
72    /// Returns the maximum width as `f32`, or [`f32::INFINITY`] if the axis is
73    /// unbounded or shrink-to-fit.
74    pub fn max_width_f32(&self) -> f32 {
75        match &self.max_width {
76            AxisBound::Bounded(v) => *v,
77            _ => f32::INFINITY,
78        }
79    }
80
81    /// Returns the maximum height as `f32`, or [`f32::INFINITY`] if the axis is
82    /// unbounded or shrink-to-fit.
83    pub fn max_height_f32(&self) -> f32 {
84        match &self.max_height {
85            AxisBound::Bounded(v) => *v,
86            _ => f32::INFINITY,
87        }
88    }
89
90    /// Clamp `size` so that it satisfies these constraints.
91    ///
92    /// Width and height are each clamped to `[min, max]`.
93    pub fn constrain(&self, size: Size) -> Size {
94        let width = size.width.max(self.min_width).min(self.max_width_f32());
95        let height = size.height.max(self.min_height).min(self.max_height_f32());
96        Size { width, height }
97    }
98
99    /// Returns `true` when both axes are tightly bounded (`min == max`).
100    pub fn is_tight(&self) -> bool {
101        let w_tight = matches!(&self.max_width, AxisBound::Bounded(v) if (v - self.min_width).abs() < f32::EPSILON);
102        let h_tight = matches!(&self.max_height, AxisBound::Bounded(v) if (v - self.min_height).abs() < f32::EPSILON);
103        w_tight && h_tight
104    }
105}
106
107/// The core trait implemented by every node in the render tree.
108///
109/// Each `RenderObject` is responsible for measuring itself given layout
110/// `Constraints`, painting into a `Canvas`, and reporting hit-test results.
111pub trait RenderObject: 'static {
112    /// Measures the object under the given constraints and returns its size.
113    fn layout(&mut self, constraints: Constraints) -> Size;
114
115    /// Paints the object into `canvas` at the origin, occupying `size`.
116    fn paint(&self, canvas: &mut Canvas, size: Size);
117
118    /// Returns `true` if `point` lies within the object's bounding box.
119    ///
120    /// The default implementation uses the full axis-aligned bounding box
121    /// anchored at the origin.
122    fn hit_test(&self, point: Point, size: Size) -> bool {
123        point.x >= 0.0
124            && point.x <= size.width
125            && point.y >= 0.0
126            && point.y <= size.height
127    }
128}