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}