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