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, Copy, PartialEq)]
62pub enum ScrollMode {
63 Normal,
64 Tail,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Hash)]
68pub struct Layout {
69 direction: Direction,
70 margin: Margin,
71 constraints: Vec<Constraint>,
72}
73
74thread_local! {
75 static LAYOUT_CACHE: RefCell<HashMap<(Rect, Layout), Vec<Rect>>> = RefCell::new(HashMap::new());
76}
77
78impl Default for Layout {
79 fn default() -> Layout {
80 Layout {
81 direction: Direction::Vertical,
82 margin: Margin {
83 horizontal: 0,
84 vertical: 0,
85 },
86 constraints: Vec::new(),
87 }
88 }
89}
90
91impl Layout {
92 pub fn constraints<C>(mut self, constraints: C) -> Layout
93 where
94 C: Into<Vec<Constraint>>,
95 {
96 self.constraints = constraints.into();
97 self
98 }
99
100 pub fn margin(mut self, margin: u16) -> Layout {
101 self.margin = Margin {
102 horizontal: margin,
103 vertical: margin,
104 };
105 self
106 }
107
108 pub fn horizontal_margin(mut self, horizontal: u16) -> Layout {
109 self.margin.horizontal = horizontal;
110 self
111 }
112
113 pub fn vertical_margin(mut self, vertical: u16) -> Layout {
114 self.margin.vertical = vertical;
115 self
116 }
117
118 pub fn direction(mut self, direction: Direction) -> Layout {
119 self.direction = direction;
120 self
121 }
122
123 pub fn split(self, area: Rect) -> Vec<Rect> {
184 LAYOUT_CACHE.with(|c| {
186 c.borrow_mut()
187 .entry((area, self.clone()))
188 .or_insert_with(|| split(area, &self))
189 .clone()
190 })
191 }
192}
193
194fn split(area: Rect, layout: &Layout) -> Vec<Rect> {
195 let mut solver = Solver::new();
196 let mut vars: HashMap<Variable, (usize, usize)> = HashMap::new();
197 let elements = layout
198 .constraints
199 .iter()
200 .map(|_| Element::new())
201 .collect::<Vec<Element>>();
202 let mut results = layout
203 .constraints
204 .iter()
205 .map(|_| Rect::default())
206 .collect::<Vec<Rect>>();
207
208 let dest_area = area.inner(&layout.margin);
209 for (i, e) in elements.iter().enumerate() {
210 vars.insert(e.x, (i, 0));
211 vars.insert(e.y, (i, 1));
212 vars.insert(e.width, (i, 2));
213 vars.insert(e.height, (i, 3));
214 }
215 let mut ccs: Vec<CassowaryConstraint> =
216 Vec::with_capacity(elements.len() * 4 + layout.constraints.len() * 6);
217 for elt in &elements {
218 ccs.push(elt.left() | GE(REQUIRED) | f64::from(dest_area.left()));
219 ccs.push(elt.top() | GE(REQUIRED) | f64::from(dest_area.top()));
220 ccs.push(elt.right() | LE(REQUIRED) | f64::from(dest_area.right()));
221 ccs.push(elt.bottom() | LE(REQUIRED) | f64::from(dest_area.bottom()));
222 }
223 if let Some(first) = elements.first() {
224 ccs.push(match layout.direction {
225 Direction::Horizontal => first.left() | EQ(REQUIRED) | f64::from(dest_area.left()),
226 Direction::Vertical => first.top() | EQ(REQUIRED) | f64::from(dest_area.top()),
227 });
228 }
229 if let Some(last) = elements.last() {
230 ccs.push(match layout.direction {
231 Direction::Horizontal => last.right() | EQ(REQUIRED) | f64::from(dest_area.right()),
232 Direction::Vertical => last.bottom() | EQ(REQUIRED) | f64::from(dest_area.bottom()),
233 });
234 }
235 match layout.direction {
236 Direction::Horizontal => {
237 for pair in elements.windows(2) {
238 ccs.push((pair[0].x + pair[0].width) | EQ(REQUIRED) | pair[1].x);
239 }
240 for (i, size) in layout.constraints.iter().enumerate() {
241 ccs.push(elements[i].y | EQ(REQUIRED) | f64::from(dest_area.y));
242 ccs.push(elements[i].height | EQ(REQUIRED) | f64::from(dest_area.height));
243 ccs.push(match *size {
244 Constraint::Length(v) => elements[i].width | EQ(WEAK) | f64::from(v),
245 Constraint::Percentage(v) => {
246 elements[i].width | EQ(WEAK) | (f64::from(v * dest_area.width) / 100.0)
247 }
248 Constraint::Ratio(n, d) => {
249 elements[i].width
250 | EQ(WEAK)
251 | (f64::from(dest_area.width) * f64::from(n) / f64::from(d))
252 }
253 Constraint::Min(v) => elements[i].width | GE(WEAK) | f64::from(v),
254 Constraint::Max(v) => elements[i].width | LE(WEAK) | f64::from(v),
255 });
256 }
257 }
258 Direction::Vertical => {
259 for pair in elements.windows(2) {
260 ccs.push((pair[0].y + pair[0].height) | EQ(REQUIRED) | pair[1].y);
261 }
262 for (i, size) in layout.constraints.iter().enumerate() {
263 ccs.push(elements[i].x | EQ(REQUIRED) | f64::from(dest_area.x));
264 ccs.push(elements[i].width | EQ(REQUIRED) | f64::from(dest_area.width));
265 ccs.push(match *size {
266 Constraint::Length(v) => elements[i].height | EQ(WEAK) | f64::from(v),
267 Constraint::Percentage(v) => {
268 elements[i].height | EQ(WEAK) | (f64::from(v * dest_area.height) / 100.0)
269 }
270 Constraint::Ratio(n, d) => {
271 elements[i].height
272 | EQ(WEAK)
273 | (f64::from(dest_area.height) * f64::from(n) / f64::from(d))
274 }
275 Constraint::Min(v) => elements[i].height | GE(WEAK) | f64::from(v),
276 Constraint::Max(v) => elements[i].height | LE(WEAK) | f64::from(v),
277 });
278 }
279 }
280 }
281 solver.add_constraints(&ccs).unwrap();
282 for &(var, value) in solver.fetch_changes() {
283 let (index, attr) = vars[&var];
284 let value = if value.is_sign_negative() {
285 0
286 } else {
287 value as u16
288 };
289 match attr {
290 0 => {
291 results[index].x = value;
292 }
293 1 => {
294 results[index].y = value;
295 }
296 2 => {
297 results[index].width = value;
298 }
299 3 => {
300 results[index].height = value;
301 }
302 _ => {}
303 }
304 }
305
306 if let Some(last) = results.last_mut() {
308 match layout.direction {
309 Direction::Vertical => {
310 last.height = dest_area.bottom() - last.y;
311 }
312 Direction::Horizontal => {
313 last.width = dest_area.right() - last.x;
314 }
315 }
316 }
317 results
318}
319
320struct Element {
322 x: Variable,
323 y: Variable,
324 width: Variable,
325 height: Variable,
326}
327
328impl Element {
329 fn new() -> Element {
330 Element {
331 x: Variable::new(),
332 y: Variable::new(),
333 width: Variable::new(),
334 height: Variable::new(),
335 }
336 }
337
338 fn left(&self) -> Variable {
339 self.x
340 }
341
342 fn top(&self) -> Variable {
343 self.y
344 }
345
346 fn right(&self) -> Expression {
347 self.x + self.width
348 }
349
350 fn bottom(&self) -> Expression {
351 self.y + self.height
352 }
353}
354
355#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
358pub struct Rect {
359 pub x: u16,
360 pub y: u16,
361 pub width: u16,
362 pub height: u16,
363}
364
365impl Default for Rect {
366 fn default() -> Rect {
367 Rect {
368 x: 0,
369 y: 0,
370 width: 0,
371 height: 0,
372 }
373 }
374}
375
376impl Rect {
377 pub fn new(x: u16, y: u16, width: u16, height: u16) -> Rect {
380 let max_area = u16::max_value();
381 let (clipped_width, clipped_height) =
382 if u32::from(width) * u32::from(height) > u32::from(max_area) {
383 let aspect_ratio = f64::from(width) / f64::from(height);
384 let max_area_f = f64::from(max_area);
385 let height_f = (max_area_f / aspect_ratio).sqrt();
386 let width_f = height_f * aspect_ratio;
387 (width_f as u16, height_f as u16)
388 } else {
389 (width, height)
390 };
391 Rect {
392 x,
393 y,
394 width: clipped_width,
395 height: clipped_height,
396 }
397 }
398
399 pub fn area(self) -> u16 {
400 self.width * self.height
401 }
402
403 pub fn left(self) -> u16 {
404 self.x
405 }
406
407 pub fn right(self) -> u16 {
408 self.x + self.width
409 }
410
411 pub fn top(self) -> u16 {
412 self.y
413 }
414
415 pub fn bottom(self) -> u16 {
416 self.y + self.height
417 }
418
419 pub fn inner(self, margin: &Margin) -> Rect {
420 if self.width < 2 * margin.horizontal || self.height < 2 * margin.vertical {
421 Rect::default()
422 } else {
423 Rect {
424 x: self.x + margin.horizontal,
425 y: self.y + margin.vertical,
426 width: self.width - 2 * margin.horizontal,
427 height: self.height - 2 * margin.vertical,
428 }
429 }
430 }
431
432 pub fn union(self, other: Rect) -> Rect {
433 let x1 = min(self.x, other.x);
434 let y1 = min(self.y, other.y);
435 let x2 = max(self.x + self.width, other.x + other.width);
436 let y2 = max(self.y + self.height, other.y + other.height);
437 Rect {
438 x: x1,
439 y: y1,
440 width: x2 - x1,
441 height: y2 - y1,
442 }
443 }
444
445 pub fn intersection(self, other: Rect) -> Rect {
446 let x1 = max(self.x, other.x);
447 let y1 = max(self.y, other.y);
448 let x2 = min(self.x + self.width, other.x + other.width);
449 let y2 = min(self.y + self.height, other.y + other.height);
450 Rect {
451 x: x1,
452 y: y1,
453 width: x2 - x1,
454 height: y2 - y1,
455 }
456 }
457
458 pub fn intersects(self, other: Rect) -> bool {
459 self.x < other.x + other.width
460 && self.x + self.width > other.x
461 && self.y < other.y + other.height
462 && self.y + self.height > other.y
463 }
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469
470 #[test]
471 fn test_rect_size_truncation() {
472 for width in 256u16..300u16 {
473 for height in 256u16..300u16 {
474 let rect = Rect::new(0, 0, width, height);
475 rect.area(); assert!(rect.width < width || rect.height < height);
477 assert!(
480 (f64::from(rect.width) / f64::from(rect.height)
481 - f64::from(width) / f64::from(height))
482 .abs()
483 < 1.0
484 )
485 }
486 }
487
488 let width = 900;
490 let height = 100;
491 let rect = Rect::new(0, 0, width, height);
492 assert_ne!(rect.width, 900);
493 assert_ne!(rect.height, 100);
494 assert!(rect.width < width || rect.height < height);
495 }
496
497 #[test]
498 fn test_rect_size_preservation() {
499 for width in 0..256u16 {
500 for height in 0..256u16 {
501 let rect = Rect::new(0, 0, width, height);
502 rect.area(); assert_eq!(rect.width, width);
504 assert_eq!(rect.height, height);
505 }
506 }
507
508 let rect = Rect::new(0, 0, 300, 100);
510 assert_eq!(rect.width, 300);
511 assert_eq!(rect.height, 100);
512 }
513}