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