raui_core/layout/
mod.rs

1//! Layout engine
2
3pub mod default_layout_engine;
4
5use crate::{
6    Scalar,
7    widget::{
8        WidgetId,
9        unit::WidgetUnit,
10        utils::{Rect, Vec2},
11    },
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) = self
57                .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    Constant(Scalar),
170    Stretch(Vec2),
171    FitHorizontal(Scalar),
172    FitVertical(Scalar),
173    FitMinimum(Vec2),
174    FitMaximum(Vec2),
175    FitToView(Vec2, bool),
176}
177
178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
179pub struct CoordsMapping {
180    #[serde(default)]
181    scale: Vec2,
182    #[serde(default)]
183    offset: Vec2,
184    #[serde(default)]
185    real_area: Rect,
186    #[serde(default)]
187    virtual_area: Rect,
188}
189
190impl Default for CoordsMapping {
191    fn default() -> Self {
192        Self::new(Default::default())
193    }
194}
195
196impl CoordsMapping {
197    pub fn new(real_area: Rect) -> Self {
198        Self::new_scaling(real_area, CoordsMappingScaling::None)
199    }
200
201    pub fn new_scaling(real_area: Rect, scaling: CoordsMappingScaling) -> Self {
202        match scaling {
203            CoordsMappingScaling::None => Self {
204                scale: 1.0.into(),
205                offset: Vec2::default(),
206                real_area,
207                virtual_area: Rect {
208                    left: 0.0,
209                    right: real_area.width(),
210                    top: 0.0,
211                    bottom: real_area.height(),
212                },
213            },
214            CoordsMappingScaling::Constant(value) => {
215                let value = if value > 0.0 { value } else { 1.0 };
216                Self {
217                    scale: value.into(),
218                    offset: Vec2::default(),
219                    real_area,
220                    virtual_area: Rect {
221                        left: 0.0,
222                        right: real_area.width() / value,
223                        top: 0.0,
224                        bottom: real_area.height() / value,
225                    },
226                }
227            }
228            CoordsMappingScaling::Stretch(size) => {
229                let vw = size.x;
230                let vh = size.y;
231                let rw = real_area.width();
232                let rh = real_area.height();
233                let scale_x = rw / vw;
234                let scale_y = rh / vh;
235                let w = vw * scale_x;
236                let h = vh * scale_y;
237                Self {
238                    scale: Vec2 {
239                        x: scale_x,
240                        y: scale_y,
241                    },
242                    offset: Vec2 {
243                        x: (rw - w) * 0.5,
244                        y: (rh - h) * 0.5,
245                    },
246                    real_area,
247                    virtual_area: Rect {
248                        left: 0.0,
249                        right: vw,
250                        top: 0.0,
251                        bottom: vh,
252                    },
253                }
254            }
255            CoordsMappingScaling::FitHorizontal(vw) => {
256                let rw = real_area.width();
257                let rh = real_area.height();
258                let scale = rw / vw;
259                let vh = rh / scale;
260                Self {
261                    scale: scale.into(),
262                    offset: Vec2::default(),
263                    real_area,
264                    virtual_area: Rect {
265                        left: 0.0,
266                        right: vw,
267                        top: 0.0,
268                        bottom: vh,
269                    },
270                }
271            }
272            CoordsMappingScaling::FitVertical(vh) => {
273                let rw = real_area.width();
274                let rh = real_area.height();
275                let scale = rh / vh;
276                let vw = rw / scale;
277                Self {
278                    scale: scale.into(),
279                    offset: Vec2::default(),
280                    real_area,
281                    virtual_area: Rect {
282                        left: 0.0,
283                        right: vw,
284                        top: 0.0,
285                        bottom: vh,
286                    },
287                }
288            }
289            CoordsMappingScaling::FitMinimum(size) => {
290                if size.x < size.y {
291                    Self::new_scaling(real_area, CoordsMappingScaling::FitHorizontal(size.x))
292                } else {
293                    Self::new_scaling(real_area, CoordsMappingScaling::FitVertical(size.y))
294                }
295            }
296            CoordsMappingScaling::FitMaximum(size) => {
297                if size.x > size.y {
298                    Self::new_scaling(real_area, CoordsMappingScaling::FitHorizontal(size.x))
299                } else {
300                    Self::new_scaling(real_area, CoordsMappingScaling::FitVertical(size.y))
301                }
302            }
303            CoordsMappingScaling::FitToView(size, keep_aspect_ratio) => {
304                let rw = real_area.width();
305                let rh = real_area.height();
306                let av = size.x / size.y;
307                let ar = rw / rh;
308                let (scale, vw, vh) = if keep_aspect_ratio {
309                    let vw = size.x;
310                    let vh = size.y;
311                    let scale = if ar >= av { rh / vh } else { rw / vw };
312                    (scale, vw, vh)
313                } else if ar >= av {
314                    (rh / size.y, size.x * rw / rh, size.y)
315                } else {
316                    (rw / size.x, size.x, size.y * rh / rw)
317                };
318                let w = vw * scale;
319                let h = vh * scale;
320                Self {
321                    scale: scale.into(),
322                    offset: Vec2 {
323                        x: (rw - w) * 0.5,
324                        y: (rh - h) * 0.5,
325                    },
326                    real_area,
327                    virtual_area: Rect {
328                        left: 0.0,
329                        right: vw,
330                        top: 0.0,
331                        bottom: vh,
332                    },
333                }
334            }
335        }
336    }
337
338    #[inline]
339    pub fn scale(&self) -> Vec2 {
340        self.scale
341    }
342
343    #[inline]
344    pub fn scalar_scale(&self, max: bool) -> Scalar {
345        if max {
346            self.scale.x.max(self.scale.y)
347        } else {
348            self.scale.x.min(self.scale.y)
349        }
350    }
351
352    #[inline]
353    pub fn offset(&self) -> Vec2 {
354        self.offset
355    }
356
357    #[inline]
358    pub fn virtual_area(&self) -> Rect {
359        self.virtual_area
360    }
361
362    #[inline]
363    pub fn virtual_to_real_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: self.offset.x + (coord.x * self.scale.x),
372                y: self.offset.y + (coord.y * self.scale.y),
373            }
374        }
375    }
376
377    #[inline]
378    pub fn real_to_virtual_vec2(&self, coord: Vec2, local_space: bool) -> Vec2 {
379        if local_space {
380            Vec2 {
381                x: coord.x / self.scale.x,
382                y: coord.y / self.scale.y,
383            }
384        } else {
385            Vec2 {
386                x: (coord.x - self.offset.x) / self.scale.x,
387                y: (coord.y - self.offset.y) / self.scale.y,
388            }
389        }
390    }
391
392    #[inline]
393    pub fn virtual_to_real_rect(&self, area: Rect, local_space: bool) -> Rect {
394        if local_space {
395            Rect {
396                left: area.left * self.scale.x,
397                right: area.right * self.scale.x,
398                top: area.top * self.scale.y,
399                bottom: area.bottom * self.scale.y,
400            }
401        } else {
402            Rect {
403                left: self.offset.x + (area.left * self.scale.x),
404                right: self.offset.x + (area.right * self.scale.x),
405                top: self.offset.y + (area.top * self.scale.y),
406                bottom: self.offset.y + (area.bottom * self.scale.y),
407            }
408        }
409    }
410
411    #[inline]
412    pub fn real_to_virtual_rect(&self, area: Rect, local_space: bool) -> Rect {
413        if local_space {
414            Rect {
415                left: area.left / self.scale.x,
416                right: area.right / self.scale.x,
417                top: area.top / self.scale.y,
418                bottom: area.bottom / self.scale.y,
419            }
420        } else {
421            Rect {
422                left: (area.left - self.offset.x) / self.scale.x,
423                right: (area.right - self.offset.x) / self.scale.x,
424                top: (area.top - self.offset.y) / self.scale.y,
425                bottom: (area.bottom - self.offset.y) / self.scale.y,
426            }
427        }
428    }
429}