tessera_components/
shape_def.rs

1//! Defines the [`Shape`] enum and its variants, used for describing the
2//! geometric form of UI components.
3//!
4//! This module provides a flexible way to define very basic components' shape,
5//! including [`crate::surface::surface`] and
6//! [`crate::fluid_glass::fluid_glass`].
7
8use tessera_ui::{PxSize, dp::Dp};
9
10/// Capsule shapes use a constant `g2_k_value` to maintain circular ends.
11pub const CAPSULE_G2_K_VALUE: f32 = 2.0;
12
13/// Corner definition: capsule or manual radius with per-corner G2.
14#[derive(Clone, Copy, Debug, PartialEq)]
15pub enum RoundedCorner {
16    /// Capsule radius derived from `min(width, height) / 2.0`, with
17    /// `CAPSULE_G2_K_VALUE`.
18    Capsule,
19    /// Manual radius (in `Dp`) with per-corner G2.
20    Manual {
21        /// Corner radius in device-independent pixels.
22        radius: Dp,
23        /// Corner G2 value (2.0 yields circular curvature).
24        g2_k_value: f32,
25    },
26}
27
28impl RoundedCorner {
29    /// A corner with zero radius.
30    pub const ZERO: Self = RoundedCorner::Manual {
31        radius: Dp(0.0),
32        g2_k_value: 3.0,
33    };
34
35    /// Helper to create a manual corner.
36    pub const fn manual(radius: Dp, g2_k_value: f32) -> Self {
37        Self::Manual { radius, g2_k_value }
38    }
39
40    /// Resolves into `(radius_px, g2)` using the provided size.
41    pub fn resolve(self, size: PxSize) -> (f32, f32) {
42        match self {
43            RoundedCorner::Capsule => (
44                size.width.to_f32().min(size.height.to_f32()) / 2.0,
45                CAPSULE_G2_K_VALUE,
46            ),
47            RoundedCorner::Manual { radius, g2_k_value } => (radius.to_pixels_f32(), g2_k_value),
48        }
49    }
50}
51
52/// Resolved representation of a shape for rendering.
53#[derive(Clone, Copy, Debug, PartialEq)]
54pub enum ResolvedShape {
55    /// Rounded rect with resolved radii and G2 values.
56    Rounded {
57        /// Pixel radii for each corner.
58        corner_radii: [f32; 4],
59        /// G2 parameters per corner.
60        corner_g2: [f32; 4],
61    },
62    /// Ellipse occupies the full bounds.
63    Ellipse,
64}
65
66/// Shape definitions for UI components.
67///
68/// `Shape` is used by multiple components (`surface`, `fluid_glass`, sliders,
69/// progress, buttons) to define visual outline, hit-testing, and pipeline
70/// behavior.
71///
72/// # Variants
73/// * [`Shape::RoundedRectangle`] – Per-corner capsule or manual radius +
74///   per-corner G2
75/// * [`Shape::Ellipse`] – Ellipse filling the component bounds
76///
77/// # Example
78///
79/// ```
80/// use tessera_components::shape_def::{RoundedCorner, Shape};
81/// use tessera_ui::dp::Dp;
82///
83/// // Explicit rounded rectangle
84/// let rr = Shape::RoundedRectangle {
85///     top_left: RoundedCorner::manual(Dp(8.0), 3.0),
86///     top_right: RoundedCorner::manual(Dp(8.0), 3.0),
87///     bottom_right: RoundedCorner::manual(Dp(8.0), 3.0),
88///     bottom_left: RoundedCorner::manual(Dp(8.0), 3.0),
89/// };
90///
91/// // Ellipse
92/// let ellipse = Shape::Ellipse;
93///
94/// // Mixed capsule/fixed corners (left side capsule, right side explicit)
95/// let mixed = Shape::RoundedRectangle {
96///     top_left: RoundedCorner::Capsule, // auto radius = min(width, height) / 2
97///     top_right: RoundedCorner::manual(Dp(8.0), 3.0),
98///     bottom_right: RoundedCorner::manual(Dp(8.0), 3.0),
99///     bottom_left: RoundedCorner::Capsule, // also capsule
100/// };
101/// ```
102#[derive(Clone, Copy, Debug, PartialEq)]
103pub enum Shape {
104    /// Rounded rectangle with per-corner capsule or manual radius + G2.
105    RoundedRectangle {
106        /// Top-left corner definition.
107        top_left: RoundedCorner,
108        /// Top-right corner definition.
109        top_right: RoundedCorner,
110        /// Bottom-right corner definition.
111        bottom_right: RoundedCorner,
112        /// Bottom-left corner definition.
113        bottom_left: RoundedCorner,
114    },
115    /// Ellipse fitting the component bounds.
116    Ellipse,
117}
118
119impl Default for Shape {
120    /// Returns the default shape, which is a rectangle with zero corner radius.
121    ///
122    /// # Example
123    ///
124    /// ```
125    /// use tessera_components::shape_def::{RoundedCorner, Shape};
126    /// use tessera_ui::dp::Dp;
127    /// let default_shape = Shape::default();
128    /// assert_eq!(
129    ///     default_shape,
130    ///     Shape::RoundedRectangle {
131    ///         top_left: RoundedCorner::manual(Dp(0.0), 3.0),
132    ///         top_right: RoundedCorner::manual(Dp(0.0), 3.0),
133    ///         bottom_right: RoundedCorner::manual(Dp(0.0), 3.0),
134    ///         bottom_left: RoundedCorner::manual(Dp(0.0), 3.0),
135    ///     }
136    /// );
137    /// ```
138    fn default() -> Self {
139        Shape::RoundedRectangle {
140            top_left: RoundedCorner::manual(Dp(0.0), 3.0),
141            top_right: RoundedCorner::manual(Dp(0.0), 3.0),
142            bottom_right: RoundedCorner::manual(Dp(0.0), 3.0),
143            bottom_left: RoundedCorner::manual(Dp(0.0), 3.0),
144        }
145    }
146}
147
148impl Shape {
149    /// A pure rectangle shape with no rounded corners.
150    pub const RECTANGLE: Self = Shape::RoundedRectangle {
151        top_left: RoundedCorner::manual(Dp(0.0), 3.0),
152        top_right: RoundedCorner::manual(Dp(0.0), 3.0),
153        bottom_right: RoundedCorner::manual(Dp(0.0), 3.0),
154        bottom_left: RoundedCorner::manual(Dp(0.0), 3.0),
155    };
156
157    /// A helper to create a uniform rounded rectangle shape with manual
158    /// corners.
159    pub const fn rounded_rectangle(radius: Dp) -> Self {
160        Shape::RoundedRectangle {
161            top_left: RoundedCorner::manual(radius, 3.0),
162            top_right: RoundedCorner::manual(radius, 3.0),
163            bottom_right: RoundedCorner::manual(radius, 3.0),
164            bottom_left: RoundedCorner::manual(radius, 3.0),
165        }
166    }
167
168    /// A helper to create a uniform capsule on all corners.
169    pub const fn capsule() -> Self {
170        Shape::RoundedRectangle {
171            top_left: RoundedCorner::Capsule,
172            top_right: RoundedCorner::Capsule,
173            bottom_right: RoundedCorner::Capsule,
174            bottom_left: RoundedCorner::Capsule,
175        }
176    }
177
178    /// Resolves a shape into pixel radii and per-corner G2 parameters for a
179    /// given size.
180    pub fn resolve_for_size(self, size: PxSize) -> ResolvedShape {
181        match self {
182            Shape::RoundedRectangle {
183                top_left,
184                top_right,
185                bottom_right,
186                bottom_left,
187            } => {
188                let (tl_r, tl_g2) = top_left.resolve(size);
189                let (tr_r, tr_g2) = top_right.resolve(size);
190                let (br_r, br_g2) = bottom_right.resolve(size);
191                let (bl_r, bl_g2) = bottom_left.resolve(size);
192
193                ResolvedShape::Rounded {
194                    corner_radii: [tl_r, tr_r, br_r, bl_r],
195                    corner_g2: [tl_g2, tr_g2, br_g2, bl_g2],
196                }
197            }
198            Shape::Ellipse => ResolvedShape::Ellipse,
199        }
200    }
201}