raui_core/widget/unit/
image.rs

1use crate::{
2    PrefabValue, Scalar,
3    layout::CoordsMappingScaling,
4    props::Props,
5    widget::{
6        WidgetId,
7        node::WidgetNode,
8        unit::WidgetUnitData,
9        utils::{Color, Rect, Transform, Vec2},
10    },
11};
12use serde::{Deserialize, Serialize};
13use std::{collections::HashMap, convert::TryFrom, sync::Arc};
14
15#[derive(Debug, Default, Clone, Serialize, Deserialize)]
16pub struct ImageBoxFrame {
17    #[serde(default)]
18    pub source: Rect,
19    #[serde(default)]
20    pub destination: Rect,
21    #[serde(default)]
22    pub frame_only: bool,
23    #[serde(default)]
24    pub frame_keep_aspect_ratio: bool,
25}
26
27impl From<Scalar> for ImageBoxFrame {
28    fn from(v: Scalar) -> Self {
29        Self {
30            source: v.into(),
31            destination: v.into(),
32            frame_only: false,
33            frame_keep_aspect_ratio: false,
34        }
35    }
36}
37
38impl From<(Scalar, bool)> for ImageBoxFrame {
39    fn from((v, fo): (Scalar, bool)) -> Self {
40        Self {
41            source: v.into(),
42            destination: v.into(),
43            frame_only: fo,
44            frame_keep_aspect_ratio: false,
45        }
46    }
47}
48
49#[derive(Debug, Default, Clone, Serialize, Deserialize)]
50pub enum ImageBoxImageScaling {
51    #[default]
52    Stretch,
53    Frame(ImageBoxFrame),
54}
55
56#[derive(Debug, Default, Clone, Serialize, Deserialize)]
57pub struct ImageBoxColor {
58    #[serde(default)]
59    pub color: Color,
60    #[serde(default)]
61    pub scaling: ImageBoxImageScaling,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct ImageBoxImage {
66    #[serde(default)]
67    pub id: String,
68    #[serde(default)]
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub source_rect: Option<Rect>,
71    #[serde(default)]
72    pub scaling: ImageBoxImageScaling,
73    #[serde(default = "ImageBoxImage::default_tint")]
74    pub tint: Color,
75}
76
77impl Default for ImageBoxImage {
78    fn default() -> Self {
79        Self {
80            id: Default::default(),
81            source_rect: Default::default(),
82            scaling: Default::default(),
83            tint: Self::default_tint(),
84        }
85    }
86}
87
88impl ImageBoxImage {
89    fn default_tint() -> Color {
90        Color {
91            r: 1.0,
92            g: 1.0,
93            b: 1.0,
94            a: 1.0,
95        }
96    }
97}
98
99#[derive(Debug, Default, Clone, Serialize, Deserialize)]
100pub struct ImageBoxProceduralVertex {
101    #[serde(default)]
102    pub position: Vec2,
103    #[serde(default)]
104    pub page: Scalar,
105    #[serde(default)]
106    pub tex_coord: Vec2,
107    #[serde(default)]
108    pub color: Color,
109}
110
111#[derive(Debug, Default, Clone, Serialize, Deserialize)]
112pub struct ImageBoxProceduralMeshData {
113    #[serde(default)]
114    #[serde(skip_serializing_if = "Vec::is_empty")]
115    pub vertices: Vec<ImageBoxProceduralVertex>,
116    #[serde(default)]
117    #[serde(skip_serializing_if = "Vec::is_empty")]
118    pub triangles: Vec<[u32; 3]>,
119}
120
121#[derive(Clone, Serialize, Deserialize)]
122pub enum ImageBoxProceduralMesh {
123    Owned(ImageBoxProceduralMeshData),
124    Shared(Arc<ImageBoxProceduralMeshData>),
125    /// fn(widget local space rect, procedural image parameters map) -> mesh data
126    #[serde(skip)]
127    #[allow(clippy::type_complexity)]
128    Generator(
129        Arc<
130            dyn Fn(Rect, &HashMap<String, Scalar>) -> ImageBoxProceduralMeshData
131                + 'static
132                + Send
133                + Sync,
134        >,
135    ),
136}
137
138impl std::fmt::Debug for ImageBoxProceduralMesh {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        match self {
141            Self::Owned(data) => write!(f, "Owned({data:?})"),
142            Self::Shared(data) => write!(f, "Shared({data:?})"),
143            Self::Generator(_) => write!(f, "Generator(...)"),
144        }
145    }
146}
147
148impl Default for ImageBoxProceduralMesh {
149    fn default() -> Self {
150        Self::Owned(Default::default())
151    }
152}
153
154#[derive(Debug, Default, Clone, Serialize, Deserialize)]
155pub struct ImageBoxProcedural {
156    #[serde(default)]
157    pub id: String,
158    #[serde(default)]
159    #[serde(skip_serializing_if = "HashMap::is_empty")]
160    pub parameters: HashMap<String, Scalar>,
161    #[serde(default)]
162    #[serde(skip_serializing_if = "Vec::is_empty")]
163    pub images: Vec<String>,
164    #[serde(default)]
165    pub mesh: ImageBoxProceduralMesh,
166    #[serde(default)]
167    pub vertex_mapping: CoordsMappingScaling,
168}
169
170impl ImageBoxProcedural {
171    pub fn new(id: impl ToString) -> Self {
172        Self {
173            id: id.to_string(),
174            parameters: Default::default(),
175            images: Default::default(),
176            mesh: Default::default(),
177            vertex_mapping: Default::default(),
178        }
179    }
180
181    pub fn param(mut self, id: impl ToString, value: Scalar) -> Self {
182        self.parameters.insert(id.to_string(), value);
183        self
184    }
185
186    pub fn image(mut self, id: impl ToString) -> Self {
187        self.images.push(id.to_string());
188        self
189    }
190
191    pub fn mesh(mut self, mesh: ImageBoxProceduralMesh) -> Self {
192        self.mesh = mesh;
193        self
194    }
195
196    pub fn triangle(mut self, vertices: [ImageBoxProceduralVertex; 3]) -> Self {
197        if let ImageBoxProceduralMesh::Owned(mesh) = &mut self.mesh {
198            let count = mesh.vertices.len() as u32;
199            mesh.vertices.extend(vertices);
200            mesh.triangles.push([count, count + 1, count + 2]);
201        }
202        self
203    }
204
205    pub fn quad(mut self, vertices: [ImageBoxProceduralVertex; 4]) -> Self {
206        if let ImageBoxProceduralMesh::Owned(mesh) = &mut self.mesh {
207            let count = mesh.vertices.len() as u32;
208            mesh.vertices.extend(vertices);
209            mesh.triangles.push([count, count + 1, count + 2]);
210            mesh.triangles.push([count + 2, count + 3, count]);
211        }
212        self
213    }
214
215    pub fn extend(
216        mut self,
217        vertices: impl IntoIterator<Item = ImageBoxProceduralVertex>,
218        triangles: impl IntoIterator<Item = [u32; 3]>,
219    ) -> Self {
220        if let ImageBoxProceduralMesh::Owned(mesh) = &mut self.mesh {
221            let count = mesh.vertices.len() as u32;
222            mesh.vertices.extend(vertices);
223            mesh.triangles.extend(
224                triangles
225                    .into_iter()
226                    .map(|[a, b, c]| [a + count, b + count, c + count]),
227            );
228        }
229        self
230    }
231
232    pub fn vertex_mapping(mut self, value: CoordsMappingScaling) -> Self {
233        self.vertex_mapping = value;
234        self
235    }
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
239pub enum ImageBoxMaterial {
240    Color(ImageBoxColor),
241    Image(ImageBoxImage),
242    Procedural(ImageBoxProcedural),
243}
244
245impl Default for ImageBoxMaterial {
246    fn default() -> Self {
247        Self::Color(Default::default())
248    }
249}
250
251#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
252pub enum ImageBoxSizeValue {
253    #[default]
254    Fill,
255    Exact(Scalar),
256}
257
258#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
259pub struct ImageBoxAspectRatio {
260    #[serde(default)]
261    pub horizontal_alignment: Scalar,
262    #[serde(default)]
263    pub vertical_alignment: Scalar,
264    #[serde(default)]
265    pub outside: bool,
266}
267
268#[derive(Debug, Default, Clone, Serialize, Deserialize)]
269pub struct ImageBox {
270    #[serde(default)]
271    pub id: WidgetId,
272    #[serde(default)]
273    pub width: ImageBoxSizeValue,
274    #[serde(default)]
275    pub height: ImageBoxSizeValue,
276    #[serde(default)]
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub content_keep_aspect_ratio: Option<ImageBoxAspectRatio>,
279    #[serde(default)]
280    pub material: ImageBoxMaterial,
281    #[serde(default)]
282    pub transform: Transform,
283}
284
285impl WidgetUnitData for ImageBox {
286    fn id(&self) -> &WidgetId {
287        &self.id
288    }
289}
290
291impl TryFrom<ImageBoxNode> for ImageBox {
292    type Error = ();
293
294    fn try_from(node: ImageBoxNode) -> Result<Self, Self::Error> {
295        let ImageBoxNode {
296            id,
297            width,
298            height,
299            content_keep_aspect_ratio,
300            material,
301            transform,
302            ..
303        } = node;
304        Ok(Self {
305            id,
306            width,
307            height,
308            content_keep_aspect_ratio,
309            material,
310            transform,
311        })
312    }
313}
314
315#[derive(Debug, Default, Clone)]
316pub struct ImageBoxNode {
317    pub id: WidgetId,
318    pub props: Props,
319    pub width: ImageBoxSizeValue,
320    pub height: ImageBoxSizeValue,
321    pub content_keep_aspect_ratio: Option<ImageBoxAspectRatio>,
322    pub material: ImageBoxMaterial,
323    pub transform: Transform,
324}
325
326impl ImageBoxNode {
327    pub fn remap_props<F>(&mut self, mut f: F)
328    where
329        F: FnMut(Props) -> Props,
330    {
331        let props = std::mem::take(&mut self.props);
332        self.props = (f)(props);
333    }
334}
335
336impl From<ImageBoxNode> for WidgetNode {
337    fn from(data: ImageBoxNode) -> Self {
338        Self::Unit(data.into())
339    }
340}
341
342#[derive(Debug, Default, Clone, Serialize, Deserialize)]
343pub(crate) struct ImageBoxNodePrefab {
344    #[serde(default)]
345    pub id: WidgetId,
346    #[serde(default)]
347    pub props: PrefabValue,
348    #[serde(default)]
349    pub width: ImageBoxSizeValue,
350    #[serde(default)]
351    pub height: ImageBoxSizeValue,
352    #[serde(default)]
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub content_keep_aspect_ratio: Option<ImageBoxAspectRatio>,
355    #[serde(default)]
356    pub material: ImageBoxMaterial,
357    #[serde(default)]
358    pub transform: Transform,
359}