1pub 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}