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