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}