feather_ui/component/
shape.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2025 Fundament Research Institute <https://fundament.institute>
3
4use crate::color::sRGB;
5use crate::layout::{Layout, leaf};
6use crate::{DAbsPoint, SourceID};
7use std::rc::Rc;
8use std::sync::Arc;
9
10#[repr(u8)]
11pub enum ShapeKind {
12    RoundRect,
13    Triangle,
14    Circle,
15    Arc,
16}
17
18pub struct Shape<T, const KIND: u8> {
19    id: std::sync::Arc<SourceID>,
20    props: Rc<T>,
21    border: f32,
22    blur: f32,
23    size: crate::DAbsPoint,
24    corners: [f32; 4],
25    fill: sRGB,
26    outline: sRGB,
27}
28
29pub fn round_rect<T: leaf::Padded + 'static>(
30    id: std::sync::Arc<SourceID>,
31    props: T,
32    border: f32,
33    blur: f32,
34    corners: wide::f32x4,
35    fill: sRGB,
36    outline: sRGB,
37    size: DAbsPoint,
38) -> Shape<T, { ShapeKind::RoundRect as u8 }> {
39    Shape {
40        id,
41        props: props.into(),
42        border,
43        blur,
44        corners: corners.to_array(),
45        fill,
46        outline,
47        size,
48    }
49}
50
51pub fn triangle<T: leaf::Padded + 'static>(
52    id: std::sync::Arc<SourceID>,
53    props: T,
54    border: f32,
55    blur: f32,
56    corners: [f32; 3],
57    offset: f32,
58    fill: sRGB,
59    outline: sRGB,
60    size: DAbsPoint,
61) -> Shape<T, { ShapeKind::Triangle as u8 }> {
62    Shape {
63        id,
64        props: props.into(),
65        border,
66        blur,
67        corners: [corners[0], corners[1], corners[2], offset],
68        fill,
69        outline,
70        size,
71    }
72}
73
74pub fn circle<T: leaf::Padded + 'static>(
75    id: std::sync::Arc<SourceID>,
76    props: T,
77    border: f32,
78    blur: f32,
79    radii: [f32; 2],
80    fill: sRGB,
81    outline: sRGB,
82    size: DAbsPoint,
83) -> Shape<T, { ShapeKind::Circle as u8 }> {
84    Shape {
85        id,
86        props: props.into(),
87        border,
88        blur,
89        corners: [radii[0], radii[1], 0.0, 0.0],
90        fill,
91        outline,
92        size,
93    }
94}
95
96pub fn arcs<T: leaf::Padded + 'static>(
97    id: std::sync::Arc<SourceID>,
98    props: T,
99    border: f32,
100    blur: f32,
101    inner_radius: f32,
102    arcs: [f32; 2],
103    fill: sRGB,
104    outline: sRGB,
105    size: DAbsPoint,
106) -> Shape<T, { ShapeKind::Arc as u8 }> {
107    Shape {
108        id,
109        props: props.into(),
110        border,
111        blur,
112        corners: [arcs[0] + arcs[1] * 0.5, arcs[1] * 0.5, inner_radius, 0.0],
113        fill,
114        outline,
115        size,
116    }
117}
118
119impl<T: leaf::Padded + 'static, const KIND: u8> Clone for Shape<T, KIND> {
120    fn clone(&self) -> Self {
121        Self {
122            id: self.id.duplicate(),
123            props: self.props.clone(),
124            border: self.border,
125            blur: self.blur,
126            corners: self.corners,
127            fill: self.fill,
128            outline: self.outline,
129            size: self.size,
130        }
131    }
132}
133
134impl<T: leaf::Padded + 'static> Shape<T, { ShapeKind::RoundRect as u8 }> {
135    pub fn new(
136        id: std::sync::Arc<SourceID>,
137        props: T,
138        border: f32,
139        blur: f32,
140        corners: wide::f32x4,
141        fill: sRGB,
142        outline: sRGB,
143        size: DAbsPoint,
144    ) -> Self {
145        Self {
146            id,
147            props: props.into(),
148            border,
149            blur,
150            corners: corners.to_array(),
151            fill,
152            outline,
153            size,
154        }
155    }
156}
157
158impl<T: leaf::Padded + 'static> Shape<T, { ShapeKind::Triangle as u8 }> {
159    pub fn new(
160        id: std::sync::Arc<SourceID>,
161        props: T,
162        border: f32,
163        blur: f32,
164        corners: [f32; 3],
165        offset: f32,
166        fill: sRGB,
167        outline: sRGB,
168        size: DAbsPoint,
169    ) -> Self {
170        Self {
171            id,
172            props: props.into(),
173            border,
174            blur,
175            corners: [corners[0], corners[1], corners[2], offset],
176            fill,
177            outline,
178            size,
179        }
180    }
181}
182
183impl<T: leaf::Padded + 'static> Shape<T, { ShapeKind::Circle as u8 }> {
184    pub fn new(
185        id: std::sync::Arc<SourceID>,
186        props: T,
187        border: f32,
188        blur: f32,
189        radii: [f32; 2],
190        fill: sRGB,
191        outline: sRGB,
192        size: DAbsPoint,
193    ) -> Self {
194        Self {
195            id,
196            props: props.into(),
197            border,
198            blur,
199            corners: [radii[0], radii[1], 0.0, 0.0],
200            fill,
201            outline,
202            size,
203        }
204    }
205}
206
207impl<T: leaf::Padded + 'static> Shape<T, { ShapeKind::Arc as u8 }> {
208    pub fn new(
209        id: std::sync::Arc<SourceID>,
210        props: T,
211        border: f32,
212        blur: f32,
213        inner_radius: f32,
214        arcs: [f32; 2],
215        fill: sRGB,
216        outline: sRGB,
217        size: DAbsPoint,
218    ) -> Self {
219        Self {
220            id,
221            props: props.into(),
222            border,
223            blur,
224            corners: [arcs[0] + arcs[1] * 0.5, arcs[1] * 0.5, inner_radius, 0.0],
225            fill,
226            outline,
227            size,
228        }
229    }
230}
231
232impl<T: leaf::Padded + 'static, const KIND: u8> crate::StateMachineChild for Shape<T, KIND> {
233    fn id(&self) -> std::sync::Arc<SourceID> {
234        self.id.clone()
235    }
236}
237
238impl<T: leaf::Padded + 'static, const KIND: u8> super::Component for Shape<T, KIND>
239where
240    for<'a> &'a T: Into<&'a (dyn leaf::Padded + 'static)>,
241{
242    type Props = T;
243
244    fn layout(
245        &self,
246        manager: &mut crate::StateManager,
247        _: &crate::graphics::Driver,
248        window: &Arc<SourceID>,
249    ) -> Box<dyn Layout<T>> {
250        let dpi = manager
251            .get::<super::window::WindowStateMachine>(window)
252            .map(|x| x.state.dpi)
253            .unwrap_or(crate::BASE_DPI);
254
255        let mut corners = self.corners;
256        if KIND == ShapeKind::RoundRect as u8 {
257            corners[0] *= dpi.width;
258            corners[1] *= dpi.height;
259            corners[2] *= dpi.width;
260            corners[3] *= dpi.height;
261        }
262
263        Box::new(leaf::Sized::<T> {
264            props: self.props.clone(),
265            id: Arc::downgrade(&self.id),
266            size: self.size.resolve(dpi).to_vector().to_size().cast_unit(),
267            renderable: Some(Rc::new(crate::render::shape::Instance::<KIND> {
268                padding: self.props.padding().as_perimeter(dpi),
269                border: self.border,
270                blur: self.blur,
271                fill: self.fill,
272                outline: self.outline,
273                corners,
274                id: self.id.clone(),
275            })),
276        })
277    }
278}