raui_core/layout/
mod.rs

1//! Layout engine
2
3pub mod default_layout_engine;
4
5use crate::{
6    widget::{
7        unit::WidgetUnit,
8        utils::{Rect, Vec2},
9        WidgetId,
10    },
11    Scalar,
12};
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15
16pub trait LayoutEngine<E> {
17    fn layout(&mut self, mapping: &CoordsMapping, tree: &WidgetUnit) -> Result<Layout, E>;
18}
19
20struct LayoutSortedItems<'a>(Vec<(&'a WidgetId, &'a LayoutItem)>);
21
22impl<'a> LayoutSortedItems<'a> {
23    fn new(items: &'a HashMap<WidgetId, LayoutItem>) -> Self {
24        let mut items = items.iter().collect::<Vec<_>>();
25        items.sort_by(|a, b| a.0.path().cmp(b.0.path()));
26        Self(items)
27    }
28}
29
30impl std::fmt::Debug for LayoutSortedItems<'_> {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        f.debug_map()
33            .entries(self.0.iter().map(|&(k, v)| (k, v)))
34            .finish()
35    }
36}
37
38#[derive(Default, Clone, Serialize, Deserialize)]
39pub struct Layout {
40    pub ui_space: Rect,
41    pub items: HashMap<WidgetId, LayoutItem>,
42}
43
44impl std::fmt::Debug for Layout {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        f.debug_struct("Layout")
47            .field("ui_space", &self.ui_space)
48            .field("items", &LayoutSortedItems::new(&self.items))
49            .finish()
50    }
51}
52
53impl Layout {
54    pub fn find(&self, mut path: &str) -> Option<&LayoutItem> {
55        loop {
56            if let Some(item) =
57                self.items
58                    .iter()
59                    .find_map(|(k, v)| if k.path() == path { Some(v) } else { None })
60            {
61                return Some(item);
62            } else if let Some(index) = path.rfind('/') {
63                path = &path[0..index];
64            } else {
65                break;
66            }
67        }
68        None
69    }
70
71    pub fn find_or_ui_space(&self, path: &str) -> LayoutItem {
72        match self.find(path) {
73            Some(item) => item.to_owned(),
74            None => LayoutItem {
75                local_space: self.ui_space,
76                ui_space: self.ui_space,
77                parent: None,
78            },
79        }
80    }
81
82    pub fn virtual_to_real(&self, mapping: &CoordsMapping) -> Self {
83        Self {
84            ui_space: mapping.virtual_to_real_rect(self.ui_space, false),
85            items: self
86                .items
87                .iter()
88                .map(|(k, v)| (k.to_owned(), v.virtual_to_real(mapping)))
89                .collect::<HashMap<_, _>>(),
90        }
91    }
92
93    pub fn real_to_virtual(&self, mapping: &CoordsMapping) -> Self {
94        Self {
95            ui_space: mapping.real_to_virtual_rect(self.ui_space, false),
96            items: self
97                .items
98                .iter()
99                .map(|(k, v)| (k.to_owned(), v.real_to_virtual(mapping)))
100                .collect::<HashMap<_, _>>(),
101        }
102    }
103
104    pub fn rect_relative_to(&self, id: &WidgetId, to: &WidgetId) -> Option<Rect> {
105        let a = self.items.get(id)?;
106        let b = self.items.get(to)?;
107        let x = a.ui_space.left - b.ui_space.left;
108        let y = a.ui_space.top - b.ui_space.top;
109        Some(Rect {
110            left: x,
111            right: x + a.ui_space.width(),
112            top: y,
113            bottom: y + a.ui_space.height(),
114        })
115    }
116}
117
118#[derive(Debug, Default, Clone, Serialize, Deserialize)]
119pub struct LayoutNode {
120    pub id: WidgetId,
121    pub local_space: Rect,
122    pub children: Vec<LayoutNode>,
123}
124
125impl LayoutNode {
126    pub fn count(&self) -> usize {
127        1 + self.children.iter().map(Self::count).sum::<usize>()
128    }
129}
130
131#[derive(Debug, Default, Clone, Serialize, Deserialize)]
132pub struct LayoutItem {
133    pub local_space: Rect,
134    pub ui_space: Rect,
135    pub parent: Option<WidgetId>,
136}
137
138impl LayoutItem {
139    pub fn virtual_to_real(&self, mapping: &CoordsMapping) -> Self {
140        Self {
141            local_space: mapping.virtual_to_real_rect(self.local_space, true),
142            ui_space: mapping.virtual_to_real_rect(self.ui_space, false),
143            parent: self.parent.to_owned(),
144        }
145    }
146
147    pub fn real_to_virtual(&self, mapping: &CoordsMapping) -> Self {
148        Self {
149            local_space: mapping.real_to_virtual_rect(self.local_space, true),
150            ui_space: mapping.real_to_virtual_rect(self.ui_space, false),
151            parent: self.parent.to_owned(),
152        }
153    }
154}
155
156impl LayoutEngine<()> for () {
157    fn layout(&mut self, mapping: &CoordsMapping, _: &WidgetUnit) -> Result<Layout, ()> {
158        Ok(Layout {
159            ui_space: mapping.virtual_area(),
160            items: Default::default(),
161        })
162    }
163}
164
165#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
166pub enum CoordsMappingScaling {
167    #[default]
168    None,
169    Stretch(Vec2),
170    FitHorizontal(Scalar),
171    FitVertical(Scalar),
172    FitMinimum(Vec2),
173    FitMaximum(Vec2),
174    FitToView(Vec2, bool),
175}
176
177#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
178pub struct CoordsMapping {
179    #[serde(default)]
180    scale: Vec2,
181    #[serde(default)]
182    offset: Vec2,
183    #[serde(default)]
184    real_area: Rect,
185    #[serde(default)]
186    virtual_area: Rect,
187}
188
189impl Default for CoordsMapping {
190    fn default() -> Self {
191        Self::new(Default::default())
192    }
193}
194
195impl CoordsMapping {
196    pub fn new(real_area: Rect) -> Self {
197        Self::new_scaling(real_area, CoordsMappingScaling::None)
198    }
199
200    pub fn new_scaling(real_area: Rect, scaling: CoordsMappingScaling) -> Self {
201        match scaling {
202            CoordsMappingScaling::None => Self {
203                scale: 1.0.into(),
204                offset: Vec2::default(),
205                real_area,
206                virtual_area: Rect {
207                    left: 0.0,
208                    right: real_area.width(),
209                    top: 0.0,
210                    bottom: real_area.height(),
211                },
212            },
213            CoordsMappingScaling::Stretch(size) => {
214                let vw = size.x;
215                let vh = size.y;
216                let rw = real_area.width();
217                let rh = real_area.height();
218                let scale_x = rw / vw;
219                let scale_y = rh / vh;
220                let w = vw * scale_x;
221                let h = vh * scale_y;
222                Self {
223                    scale: Vec2 {
224                        x: scale_x,
225                        y: scale_y,
226                    },
227                    offset: Vec2 {
228                        x: (rw - w) * 0.5,
229                        y: (rh - h) * 0.5,
230                    },
231                    real_area,
232                    virtual_area: Rect {
233                        left: 0.0,
234                        right: vw,
235                        top: 0.0,
236                        bottom: vh,
237                    },
238                }
239            }
240            CoordsMappingScaling::FitHorizontal(vw) => {
241                let rw = real_area.width();
242                let rh = real_area.height();
243                let scale = rw / vw;
244                let vh = rh / scale;
245                Self {
246                    scale: scale.into(),
247                    offset: Vec2::default(),
248                    real_area,
249                    virtual_area: Rect {
250                        left: 0.0,
251                        right: vw,
252                        top: 0.0,
253                        bottom: vh,
254                    },
255                }
256            }
257            CoordsMappingScaling::FitVertical(vh) => {
258                let rw = real_area.width();
259                let rh = real_area.height();
260                let scale = rh / vh;
261                let vw = rw / scale;
262                Self {
263                    scale: scale.into(),
264                    offset: Vec2::default(),
265                    real_area,
266                    virtual_area: Rect {
267                        left: 0.0,
268                        right: vw,
269                        top: 0.0,
270                        bottom: vh,
271                    },
272                }
273            }
274            CoordsMappingScaling::FitMinimum(size) => {
275                if size.x < size.y {
276                    Self::new_scaling(real_area, CoordsMappingScaling::FitHorizontal(size.x))
277                } else {
278                    Self::new_scaling(real_area, CoordsMappingScaling::FitVertical(size.y))
279                }
280            }
281            CoordsMappingScaling::FitMaximum(size) => {
282                if size.x > size.y {
283                    Self::new_scaling(real_area, CoordsMappingScaling::FitHorizontal(size.x))
284                } else {
285                    Self::new_scaling(real_area, CoordsMappingScaling::FitVertical(size.y))
286                }
287            }
288            CoordsMappingScaling::FitToView(size, keep_aspect_ratio) => {
289                let rw = real_area.width();
290                let rh = real_area.height();
291                let av = size.x / size.y;
292                let ar = rw / rh;
293                let (scale, vw, vh) = if keep_aspect_ratio {
294                    let vw = size.x;
295                    let vh = size.y;
296                    let scale = if ar >= av { rh / vh } else { rw / vw };
297                    (scale, vw, vh)
298                } else if ar >= av {
299                    (rh / size.y, size.x * rw / rh, size.y)
300                } else {
301                    (rw / size.x, size.x, size.y * rh / rw)
302                };
303                let w = vw * scale;
304                let h = vh * scale;
305                Self {
306                    scale: scale.into(),
307                    offset: Vec2 {
308                        x: (rw - w) * 0.5,
309                        y: (rh - h) * 0.5,
310                    },
311                    real_area,
312                    virtual_area: Rect {
313                        left: 0.0,
314                        right: vw,
315                        top: 0.0,
316                        bottom: vh,
317                    },
318                }
319            }
320        }
321    }
322
323    #[inline]
324    pub fn scale(&self) -> Vec2 {
325        self.scale
326    }
327
328    #[inline]
329    pub fn scalar_scale(&self, max: bool) -> Scalar {
330        if max {
331            self.scale.x.max(self.scale.y)
332        } else {
333            self.scale.x.min(self.scale.y)
334        }
335    }
336
337    #[inline]
338    pub fn offset(&self) -> Vec2 {
339        self.offset
340    }
341
342    #[inline]
343    pub fn virtual_area(&self) -> Rect {
344        self.virtual_area
345    }
346
347    #[inline]
348    pub fn virtual_to_real_vec2(&self, coord: Vec2, local_space: bool) -> Vec2 {
349        if local_space {
350            Vec2 {
351                x: coord.x * self.scale.x,
352                y: coord.y * self.scale.y,
353            }
354        } else {
355            Vec2 {
356                x: self.offset.x + (coord.x * self.scale.x),
357                y: self.offset.y + (coord.y * self.scale.y),
358            }
359        }
360    }
361
362    #[inline]
363    pub fn real_to_virtual_vec2(&self, coord: Vec2, local_space: bool) -> Vec2 {
364        if local_space {
365            Vec2 {
366                x: coord.x / self.scale.x,
367                y: coord.y / self.scale.y,
368            }
369        } else {
370            Vec2 {
371                x: (coord.x - self.offset.x) / self.scale.x,
372                y: (coord.y - self.offset.y) / self.scale.y,
373            }
374        }
375    }
376
377    #[inline]
378    pub fn virtual_to_real_rect(&self, area: Rect, local_space: bool) -> Rect {
379        if local_space {
380            Rect {
381                left: area.left * self.scale.x,
382                right: area.right * self.scale.x,
383                top: area.top * self.scale.y,
384                bottom: area.bottom * self.scale.y,
385            }
386        } else {
387            Rect {
388                left: self.offset.x + (area.left * self.scale.x),
389                right: self.offset.x + (area.right * self.scale.x),
390                top: self.offset.y + (area.top * self.scale.y),
391                bottom: self.offset.y + (area.bottom * self.scale.y),
392            }
393        }
394    }
395
396    #[inline]
397    pub fn real_to_virtual_rect(&self, area: Rect, local_space: bool) -> Rect {
398        if local_space {
399            Rect {
400                left: area.left / self.scale.x,
401                right: area.right / self.scale.x,
402                top: area.top / self.scale.y,
403                bottom: area.bottom / self.scale.y,
404            }
405        } else {
406            Rect {
407                left: (area.left - self.offset.x) / self.scale.x,
408                right: (area.right - self.offset.x) / self.scale.x,
409                top: (area.top - self.offset.y) / self.scale.y,
410                bottom: (area.bottom - self.offset.y) / self.scale.y,
411            }
412        }
413    }
414}