1use std::cell::RefCell;
2use std::cmp::{max, min};
3use std::collections::HashMap;
4
5use cassowary::strength::{REQUIRED, WEAK};
6use cassowary::WeightedRelation::*;
7use cassowary::{Constraint as CassowaryConstraint, Expression, Solver, Variable};
8
9#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
10pub enum Corner {
11 TopLeft,
12 TopRight,
13 BottomRight,
14 BottomLeft,
15}
16
17#[derive(Debug, Hash, Clone, PartialEq, Eq)]
18pub enum Direction {
19 Horizontal,
20 Vertical,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum Constraint {
25 Percentage(u16),
27 Ratio(u32, u32),
28 Length(u16),
29 Max(u16),
30 Min(u16),
31}
32
33impl Constraint {
34 pub fn apply(&self, length: u16) -> u16 {
35 match *self {
36 Constraint::Percentage(p) => length * p / 100,
37 Constraint::Ratio(num, den) => {
38 let r = num * u32::from(length) / den;
39 r as u16
40 }
41 Constraint::Length(l) => length.min(l),
42 Constraint::Max(m) => length.min(m),
43 Constraint::Min(m) => length.max(m),
44 }
45 }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Hash)]
49pub struct Margin {
50 pub vertical: u16,
51 pub horizontal: u16,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq)]
55pub enum Alignment {
56 Left,
57 Center,
58 Right,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Hash)]
62pub struct Layout {
63 direction: Direction,
64 margin: Margin,
65 constraints: Vec<Constraint>,
66 expand_to_fill: bool,
69}
70
71thread_local! {
72 static LAYOUT_CACHE: RefCell<HashMap<(Rect, Layout), Vec<Rect>>> = RefCell::new(HashMap::new());
73}
74
75impl Default for Layout {
76 fn default() -> Layout {
77 Layout {
78 direction: Direction::Vertical,
79 margin: Margin {
80 horizontal: 0,
81 vertical: 0,
82 },
83 constraints: Vec::new(),
84 expand_to_fill: true,
85 }
86 }
87}
88
89impl Layout {
90 pub fn constraints<C>(mut self, constraints: C) -> Layout
91 where
92 C: Into<Vec<Constraint>>,
93 {
94 self.constraints = constraints.into();
95 self
96 }
97
98 pub fn margin(mut self, margin: u16) -> Layout {
99 self.margin = Margin {
100 horizontal: margin,
101 vertical: margin,
102 };
103 self
104 }
105
106 pub fn horizontal_margin(mut self, horizontal: u16) -> Layout {
107 self.margin.horizontal = horizontal;
108 self
109 }
110
111 pub fn vertical_margin(mut self, vertical: u16) -> Layout {
112 self.margin.vertical = vertical;
113 self
114 }
115
116 pub fn direction(mut self, direction: Direction) -> Layout {
117 self.direction = direction;
118 self
119 }
120
121 pub(crate) fn expand_to_fill(mut self, expand_to_fill: bool) -> Layout {
122 self.expand_to_fill = expand_to_fill;
123 self
124 }
125
126 pub fn split(&self, area: Rect) -> Vec<Rect> {
187 LAYOUT_CACHE.with(|c| {
189 c.borrow_mut()
190 .entry((area, self.clone()))
191 .or_insert_with(|| split(area, self))
192 .clone()
193 })
194 }
195}
196
197fn split(area: Rect, layout: &Layout) -> Vec<Rect> {
198 let mut solver = Solver::new();
199 let mut vars: HashMap<Variable, (usize, usize)> = HashMap::new();
200 let elements = layout
201 .constraints
202 .iter()
203 .map(|_| Element::new())
204 .collect::<Vec<Element>>();
205 let mut results = layout
206 .constraints
207 .iter()
208 .map(|_| Rect::default())
209 .collect::<Vec<Rect>>();
210
211 let dest_area = area.inner(&layout.margin);
212 for (i, e) in elements.iter().enumerate() {
213 vars.insert(e.x, (i, 0));
214 vars.insert(e.y, (i, 1));
215 vars.insert(e.width, (i, 2));
216 vars.insert(e.height, (i, 3));
217 }
218 let mut ccs: Vec<CassowaryConstraint> =
219 Vec::with_capacity(elements.len() * 4 + layout.constraints.len() * 6);
220 for elt in &elements {
221 ccs.push(elt.width | GE(REQUIRED) | 0f64);
222 ccs.push(elt.height | GE(REQUIRED) | 0f64);
223 ccs.push(elt.left() | GE(REQUIRED) | f64::from(dest_area.left()));
224 ccs.push(elt.top() | GE(REQUIRED) | f64::from(dest_area.top()));
225 ccs.push(elt.right() | LE(REQUIRED) | f64::from(dest_area.right()));
226 ccs.push(elt.bottom() | LE(REQUIRED) | f64::from(dest_area.bottom()));
227 }
228 if let Some(first) = elements.first() {
229 ccs.push(match layout.direction {
230 Direction::Horizontal => first.left() | EQ(REQUIRED) | f64::from(dest_area.left()),
231 Direction::Vertical => first.top() | EQ(REQUIRED) | f64::from(dest_area.top()),
232 });
233 }
234 if layout.expand_to_fill {
235 if let Some(last) = elements.last() {
236 ccs.push(match layout.direction {
237 Direction::Horizontal => last.right() | EQ(REQUIRED) | f64::from(dest_area.right()),
238 Direction::Vertical => last.bottom() | EQ(REQUIRED) | f64::from(dest_area.bottom()),
239 });
240 }
241 }
242 match layout.direction {
243 Direction::Horizontal => {
244 for pair in elements.windows(2) {
245 ccs.push((pair[0].x + pair[0].width) | EQ(REQUIRED) | pair[1].x);
246 }
247 for (i, size) in layout.constraints.iter().enumerate() {
248 ccs.push(elements[i].y | EQ(REQUIRED) | f64::from(dest_area.y));
249 ccs.push(elements[i].height | EQ(REQUIRED) | f64::from(dest_area.height));
250 ccs.push(match *size {
251 Constraint::Length(v) => elements[i].width | EQ(WEAK) | f64::from(v),
252 Constraint::Percentage(v) => {
253 elements[i].width | EQ(WEAK) | (f64::from(v * dest_area.width) / 100.0)
254 }
255 Constraint::Ratio(n, d) => {
256 elements[i].width
257 | EQ(WEAK)
258 | (f64::from(dest_area.width) * f64::from(n) / f64::from(d))
259 }
260 Constraint::Min(v) => elements[i].width | GE(WEAK) | f64::from(v),
261 Constraint::Max(v) => elements[i].width | LE(WEAK) | f64::from(v),
262 });
263 }
264 }
265 Direction::Vertical => {
266 for pair in elements.windows(2) {
267 ccs.push((pair[0].y + pair[0].height) | EQ(REQUIRED) | pair[1].y);
268 }
269 for (i, size) in layout.constraints.iter().enumerate() {
270 ccs.push(elements[i].x | EQ(REQUIRED) | f64::from(dest_area.x));
271 ccs.push(elements[i].width | EQ(REQUIRED) | f64::from(dest_area.width));
272 ccs.push(match *size {
273 Constraint::Length(v) => elements[i].height | EQ(WEAK) | f64::from(v),
274 Constraint::Percentage(v) => {
275 elements[i].height | EQ(WEAK) | (f64::from(v * dest_area.height) / 100.0)
276 }
277 Constraint::Ratio(n, d) => {
278 elements[i].height
279 | EQ(WEAK)
280 | (f64::from(dest_area.height) * f64::from(n) / f64::from(d))
281 }
282 Constraint::Min(v) => elements[i].height | GE(WEAK) | f64::from(v),
283 Constraint::Max(v) => elements[i].height | LE(WEAK) | f64::from(v),
284 });
285 }
286 }
287 }
288 solver.add_constraints(&ccs).unwrap();
289 for &(var, value) in solver.fetch_changes() {
290 let (index, attr) = vars[&var];
291 let value = if value.is_sign_negative() {
292 0
293 } else {
294 value as u16
295 };
296 match attr {
297 0 => {
298 results[index].x = value;
299 }
300 1 => {
301 results[index].y = value;
302 }
303 2 => {
304 results[index].width = value;
305 }
306 3 => {
307 results[index].height = value;
308 }
309 _ => {}
310 }
311 }
312
313 if layout.expand_to_fill {
314 if let Some(last) = results.last_mut() {
316 match layout.direction {
317 Direction::Vertical => {
318 last.height = dest_area.bottom() - last.y;
319 }
320 Direction::Horizontal => {
321 last.width = dest_area.right() - last.x;
322 }
323 }
324 }
325 }
326 results
327}
328
329struct Element {
331 x: Variable,
332 y: Variable,
333 width: Variable,
334 height: Variable,
335}
336
337impl Element {
338 fn new() -> Element {
339 Element {
340 x: Variable::new(),
341 y: Variable::new(),
342 width: Variable::new(),
343 height: Variable::new(),
344 }
345 }
346
347 fn left(&self) -> Variable {
348 self.x
349 }
350
351 fn top(&self) -> Variable {
352 self.y
353 }
354
355 fn right(&self) -> Expression {
356 self.x + self.width
357 }
358
359 fn bottom(&self) -> Expression {
360 self.y + self.height
361 }
362}
363
364#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
367pub struct Rect {
368 pub x: u16,
369 pub y: u16,
370 pub width: u16,
371 pub height: u16,
372}
373
374impl Default for Rect {
375 fn default() -> Rect {
376 Rect {
377 x: 0,
378 y: 0,
379 width: 0,
380 height: 0,
381 }
382 }
383}
384
385impl Rect {
386 pub fn new(x: u16, y: u16, width: u16, height: u16) -> Rect {
389 let max_area = u16::max_value();
390 let (clipped_width, clipped_height) =
391 if u32::from(width) * u32::from(height) > u32::from(max_area) {
392 let aspect_ratio = f64::from(width) / f64::from(height);
393 let max_area_f = f64::from(max_area);
394 let height_f = (max_area_f / aspect_ratio).sqrt();
395 let width_f = height_f * aspect_ratio;
396 (width_f as u16, height_f as u16)
397 } else {
398 (width, height)
399 };
400 Rect {
401 x,
402 y,
403 width: clipped_width,
404 height: clipped_height,
405 }
406 }
407
408 pub fn area(self) -> u16 {
409 self.width * self.height
410 }
411
412 pub fn left(self) -> u16 {
413 self.x
414 }
415
416 pub fn right(self) -> u16 {
417 self.x.saturating_add(self.width)
418 }
419
420 pub fn top(self) -> u16 {
421 self.y
422 }
423
424 pub fn bottom(self) -> u16 {
425 self.y.saturating_add(self.height)
426 }
427
428 pub fn inner(self, margin: &Margin) -> Rect {
429 if self.width < 2 * margin.horizontal || self.height < 2 * margin.vertical {
430 Rect::default()
431 } else {
432 Rect {
433 x: self.x + margin.horizontal,
434 y: self.y + margin.vertical,
435 width: self.width - 2 * margin.horizontal,
436 height: self.height - 2 * margin.vertical,
437 }
438 }
439 }
440
441 pub fn union(self, other: Rect) -> Rect {
442 let x1 = min(self.x, other.x);
443 let y1 = min(self.y, other.y);
444 let x2 = max(self.x + self.width, other.x + other.width);
445 let y2 = max(self.y + self.height, other.y + other.height);
446 Rect {
447 x: x1,
448 y: y1,
449 width: x2 - x1,
450 height: y2 - y1,
451 }
452 }
453
454 pub fn intersection(self, other: Rect) -> Rect {
455 let x1 = max(self.x, other.x);
456 let y1 = max(self.y, other.y);
457 let x2 = min(self.x + self.width, other.x + other.width);
458 let y2 = min(self.y + self.height, other.y + other.height);
459 Rect {
460 x: x1,
461 y: y1,
462 width: x2 - x1,
463 height: y2 - y1,
464 }
465 }
466
467 pub fn intersects(self, other: Rect) -> bool {
468 self.x < other.x + other.width
469 && self.x + self.width > other.x
470 && self.y < other.y + other.height
471 && self.y + self.height > other.y
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478
479 #[test]
480 fn test_vertical_split_by_height() {
481 let target = Rect {
482 x: 2,
483 y: 2,
484 width: 10,
485 height: 10,
486 };
487
488 let chunks = Layout::default()
489 .direction(Direction::Vertical)
490 .constraints(
491 [
492 Constraint::Percentage(10),
493 Constraint::Max(5),
494 Constraint::Min(1),
495 ]
496 .as_ref(),
497 )
498 .split(target);
499
500 assert_eq!(target.height, chunks.iter().map(|r| r.height).sum::<u16>());
501 chunks.windows(2).for_each(|w| assert!(w[0].y <= w[1].y));
502 }
503
504 #[test]
505 fn test_rect_size_truncation() {
506 for width in 256u16..300u16 {
507 for height in 256u16..300u16 {
508 let rect = Rect::new(0, 0, width, height);
509 rect.area(); assert!(rect.width < width || rect.height < height);
511 assert!(
514 (f64::from(rect.width) / f64::from(rect.height)
515 - f64::from(width) / f64::from(height))
516 .abs()
517 < 1.0
518 )
519 }
520 }
521
522 let width = 900;
524 let height = 100;
525 let rect = Rect::new(0, 0, width, height);
526 assert_ne!(rect.width, 900);
527 assert_ne!(rect.height, 100);
528 assert!(rect.width < width || rect.height < height);
529 }
530
531 #[test]
532 fn test_rect_size_preservation() {
533 for width in 0..256u16 {
534 for height in 0..256u16 {
535 let rect = Rect::new(0, 0, width, height);
536 rect.area(); assert_eq!(rect.width, width);
538 assert_eq!(rect.height, height);
539 }
540 }
541
542 let rect = Rect::new(0, 0, 300, 100);
544 assert_eq!(rect.width, 300);
545 assert_eq!(rect.height, 100);
546 }
547}