1use serde::{Deserialize, Serialize};
29use teksilo_canvas::Rect;
30
31const EPS: f32 = 0.01;
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
38pub enum DockSide {
39 Leading,
41 Trailing,
43 Top,
44 Bottom,
45}
46
47impl DockSide {
48 pub const ALL: [DockSide; 4] = [
50 DockSide::Leading,
51 DockSide::Trailing,
52 DockSide::Top,
53 DockSide::Bottom,
54 ];
55
56 pub fn is_horizontal_axis(self) -> bool {
59 matches!(self, DockSide::Leading | DockSide::Trailing)
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66pub enum DockCorner {
67 TopLeading,
68 TopTrailing,
69 BottomLeading,
70 BottomTrailing,
71}
72
73impl DockCorner {
74 pub const ALL: [DockCorner; 4] = [
76 DockCorner::TopLeading,
77 DockCorner::TopTrailing,
78 DockCorner::BottomLeading,
79 DockCorner::BottomTrailing,
80 ];
81
82 pub fn adjacent_sides(self) -> (DockSide, DockSide) {
85 match self {
86 DockCorner::TopLeading => (DockSide::Leading, DockSide::Top),
87 DockCorner::TopTrailing => (DockSide::Trailing, DockSide::Top),
88 DockCorner::BottomLeading => (DockSide::Leading, DockSide::Bottom),
89 DockCorner::BottomTrailing => (DockSide::Trailing, DockSide::Bottom),
90 }
91 }
92
93 fn other(self, side: DockSide) -> DockSide {
95 let (h, v) = self.adjacent_sides();
96 if side == h { v } else { h }
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
104pub struct CornerOwners {
105 pub top_leading: DockSide,
106 pub top_trailing: DockSide,
107 pub bottom_leading: DockSide,
108 pub bottom_trailing: DockSide,
109}
110
111impl Default for CornerOwners {
112 fn default() -> Self {
113 Self {
114 top_leading: DockSide::Top,
115 top_trailing: DockSide::Top,
116 bottom_leading: DockSide::Bottom,
117 bottom_trailing: DockSide::Bottom,
118 }
119 }
120}
121
122impl CornerOwners {
123 pub fn owner(&self, corner: DockCorner) -> DockSide {
124 match corner {
125 DockCorner::TopLeading => self.top_leading,
126 DockCorner::TopTrailing => self.top_trailing,
127 DockCorner::BottomLeading => self.bottom_leading,
128 DockCorner::BottomTrailing => self.bottom_trailing,
129 }
130 }
131
132 pub fn set(&mut self, corner: DockCorner, owner: DockSide) {
133 match corner {
134 DockCorner::TopLeading => self.top_leading = owner,
135 DockCorner::TopTrailing => self.top_trailing = owner,
136 DockCorner::BottomLeading => self.bottom_leading = owner,
137 DockCorner::BottomTrailing => self.bottom_trailing = owner,
138 }
139 }
140}
141
142#[derive(Debug, Clone, Copy)]
144pub struct SideLayout {
145 pub size: f32,
148 pub visible_progress: f32,
150 pub gutter: f32,
152 pub min_size: f32,
154 pub rail_thickness: f32,
157 pub has_rail: bool,
159}
160
161impl SideLayout {
162 pub fn empty() -> Self {
164 Self {
165 size: 0.0,
166 visible_progress: 0.0,
167 gutter: 0.0,
168 min_size: 0.0,
169 rail_thickness: 0.0,
170 has_rail: false,
171 }
172 }
173
174 fn rail_extent(&self) -> f32 {
175 if self.has_rail {
176 self.rail_thickness.max(0.0)
177 } else {
178 0.0
179 }
180 }
181
182 fn content_extent(&self) -> f32 {
183 (self.size * self.visible_progress.clamp(0.0, 1.0)).max(0.0)
184 }
185
186 fn gutter_extent(&self) -> f32 {
187 if self.content_extent() > EPS {
188 self.gutter.max(0.0)
189 } else {
190 0.0
191 }
192 }
193
194 fn total_extent(&self) -> f32 {
198 self.rail_extent() + self.content_extent() + self.gutter_extent()
199 }
200
201 fn band_depth(&self) -> f32 {
215 self.content_extent() + self.gutter_extent()
216 }
217
218 fn present(&self) -> bool {
220 self.total_extent() > EPS
221 }
222
223 fn band_present(&self) -> bool {
225 self.band_depth() > EPS
226 }
227}
228
229#[derive(Debug, Clone, Copy, PartialEq)]
233pub struct SideRects {
234 pub rail: Rect,
235 pub content: Rect,
236 pub handle: Rect,
237}
238
239impl SideRects {
240 const ZERO: SideRects = SideRects {
241 rail: Rect::ZERO,
242 content: Rect::ZERO,
243 handle: Rect::ZERO,
244 };
245}
246
247#[derive(Debug, Clone, Copy, PartialEq)]
249pub struct DockingRects {
250 pub leading: SideRects,
251 pub trailing: SideRects,
252 pub top: SideRects,
253 pub bottom: SideRects,
254 pub center: Rect,
255}
256
257fn effective_owner(
261 corner: DockCorner,
262 owners: &CornerOwners,
263 present: impl Fn(DockSide) -> bool,
264) -> DockSide {
265 let declared = owners.owner(corner);
266 if present(declared) {
267 declared
268 } else {
269 let other = corner.other(declared);
270 if present(other) { other } else { declared }
271 }
272}
273
274pub fn compute_rects(
279 container: Rect,
280 leading: SideLayout,
281 trailing: SideLayout,
282 top: SideLayout,
283 bottom: SideLayout,
284 owners: CornerOwners,
285 rtl: bool,
286) -> DockingRects {
287 let x = container.x;
288 let y = container.y;
289 let w = container.width.max(0.0);
290 let h = container.height.max(0.0);
291
292 let mut l = leading.total_extent();
295 let mut r = trailing.total_extent();
296 let mut t = top.band_depth();
297 let mut b = bottom.band_depth();
298 if l + r > w {
299 let scale = if l + r > 0.0 { w / (l + r) } else { 0.0 };
300 l *= scale;
301 r *= scale;
302 }
303 if t + b > h {
304 let scale = if t + b > 0.0 { h / (t + b) } else { 0.0 };
305 t *= scale;
306 b *= scale;
307 }
308
309 let present = |side: DockSide| match side {
310 DockSide::Leading => leading.present(),
311 DockSide::Trailing => trailing.present(),
312 DockSide::Top => top.band_present(),
313 DockSide::Bottom => bottom.band_present(),
314 };
315 let eff = |corner: DockCorner| effective_owner(corner, &owners, present);
316
317 let top_x_left = if eff(DockCorner::TopLeading) == DockSide::Top {
319 x
320 } else {
321 x + l
322 };
323 let top_x_right = if eff(DockCorner::TopTrailing) == DockSide::Top {
324 x + w
325 } else {
326 x + w - r
327 };
328 let bottom_x_left = if eff(DockCorner::BottomLeading) == DockSide::Bottom {
329 x
330 } else {
331 x + l
332 };
333 let bottom_x_right = if eff(DockCorner::BottomTrailing) == DockSide::Bottom {
334 x + w
335 } else {
336 x + w - r
337 };
338
339 let leading_y_top = if eff(DockCorner::TopLeading) == DockSide::Leading {
341 y
342 } else {
343 y + t
344 };
345 let leading_y_bottom = if eff(DockCorner::BottomLeading) == DockSide::Leading {
346 y + h
347 } else {
348 y + h - b
349 };
350 let trailing_y_top = if eff(DockCorner::TopTrailing) == DockSide::Trailing {
351 y
352 } else {
353 y + t
354 };
355 let trailing_y_bottom = if eff(DockCorner::BottomTrailing) == DockSide::Trailing {
356 y + h
357 } else {
358 y + h - b
359 };
360
361 let leading_region = Rect::new(
363 x,
364 leading_y_top,
365 l,
366 (leading_y_bottom - leading_y_top).max(0.0),
367 );
368 let trailing_region = Rect::new(
369 x + w - r,
370 trailing_y_top,
371 r,
372 (trailing_y_bottom - trailing_y_top).max(0.0),
373 );
374 let top_region = Rect::new(top_x_left, y, (top_x_right - top_x_left).max(0.0), t);
375 let bottom_region = Rect::new(
376 bottom_x_left,
377 y + h - b,
378 (bottom_x_right - bottom_x_left).max(0.0),
379 b,
380 );
381 let center = Rect::new(x + l, y + t, (w - l - r).max(0.0), (h - t - b).max(0.0));
382
383 DockingRects {
384 leading: split_side(DockSide::Leading, leading_region, &leading, l, rtl),
385 trailing: split_side(DockSide::Trailing, trailing_region, &trailing, r, rtl),
386 top: split_side(DockSide::Top, top_region, &top, t, rtl),
387 bottom: split_side(DockSide::Bottom, bottom_region, &bottom, b, rtl),
388 center,
389 }
390}
391
392fn split_side(
400 side: DockSide,
401 region: Rect,
402 layout: &SideLayout,
403 total: f32,
404 rtl: bool,
405) -> SideRects {
406 if total <= EPS || region.width <= 0.0 || region.height <= 0.0 {
407 if layout.rail_extent() > EPS && region.width > 0.0 && region.height > 0.0 {
409 return rail_only(
410 side,
411 region,
412 layout.rail_extent().min(extent_along(side, region)),
413 rtl,
414 );
415 }
416 return SideRects::ZERO;
417 }
418
419 match side {
420 DockSide::Leading | DockSide::Trailing => {
421 let raw = layout.total_extent();
424 let scale = if raw > 0.0 { total / raw } else { 0.0 };
425 let rail = layout.rail_extent() * scale;
426 let content = layout.content_extent() * scale;
427 let gutter = layout.gutter_extent() * scale;
428 match side {
429 DockSide::Leading => SideRects {
430 rail: Rect::new(region.x, region.y, rail, region.height),
431 content: Rect::new(region.x + rail, region.y, content, region.height),
432 handle: Rect::new(region.x + rail + content, region.y, gutter, region.height),
433 },
434 _ => SideRects {
435 handle: Rect::new(region.x, region.y, gutter, region.height),
436 content: Rect::new(region.x + gutter, region.y, content, region.height),
437 rail: Rect::new(region.x + gutter + content, region.y, rail, region.height),
438 },
439 }
440 }
441 DockSide::Top | DockSide::Bottom => {
442 let rail_w = layout.rail_extent().min(region.width);
445 let body_w = (region.width - rail_w).max(0.0);
446 let (rail_x, body_x) = if rtl {
448 (region.x + body_w, region.x)
449 } else {
450 (region.x, region.x + rail_w)
451 };
452 let raw_depth = layout.content_extent() + layout.gutter_extent();
453 let scale = if raw_depth > 0.0 {
454 total / raw_depth
455 } else {
456 0.0
457 };
458 let content = layout.content_extent() * scale;
459 let gutter = layout.gutter_extent() * scale;
460 let rail = Rect::new(rail_x, region.y, rail_w, region.height);
473 match side {
474 DockSide::Top => SideRects {
476 rail,
477 content: Rect::new(body_x, region.y, body_w, content),
478 handle: Rect::new(body_x, region.y + content, body_w, gutter),
479 },
480 _ => SideRects {
482 rail,
483 handle: Rect::new(body_x, region.y, body_w, gutter),
484 content: Rect::new(body_x, region.y + gutter, body_w, content),
485 },
486 }
487 }
488 }
489}
490
491fn extent_along(side: DockSide, region: Rect) -> f32 {
492 if side.is_horizontal_axis() {
493 region.width
494 } else {
495 region.height
496 }
497}
498
499fn rail_only(side: DockSide, region: Rect, rail: f32, rtl: bool) -> SideRects {
503 let mut rects = SideRects::ZERO;
504 rects.rail = match side {
505 DockSide::Leading => Rect::new(region.x, region.y, rail, region.height),
506 DockSide::Trailing => Rect::new(
507 region.x + region.width - rail,
508 region.y,
509 rail,
510 region.height,
511 ),
512 DockSide::Top | DockSide::Bottom => {
513 let rail_x = if rtl {
514 region.x + region.width - rail
515 } else {
516 region.x
517 };
518 Rect::new(rail_x, region.y, rail, region.height)
519 }
520 };
521 rects
522}
523
524#[cfg(test)]
525mod tests {
526 use super::*;
527
528 fn container() -> Rect {
529 Rect::new(0.0, 0.0, 1000.0, 800.0)
530 }
531
532 fn shown(size: f32) -> SideLayout {
534 SideLayout {
535 size,
536 visible_progress: 1.0,
537 gutter: 6.0,
538 min_size: 50.0,
539 rail_thickness: 0.0,
540 has_rail: false,
541 }
542 }
543
544 fn hidden(size: f32) -> SideLayout {
545 SideLayout {
546 visible_progress: 0.0,
547 ..shown(size)
548 }
549 }
550
551 #[test]
552 fn all_hidden_center_fills() {
553 let r = compute_rects(
554 container(),
555 SideLayout::empty(),
556 SideLayout::empty(),
557 SideLayout::empty(),
558 SideLayout::empty(),
559 CornerOwners::default(),
560 false,
561 );
562 assert_eq!(r.center, container());
563 }
564
565 #[test]
566 fn single_leading_side() {
567 let r = compute_rects(
568 container(),
569 shown(200.0),
570 SideLayout::empty(),
571 SideLayout::empty(),
572 SideLayout::empty(),
573 CornerOwners::default(),
574 false,
575 );
576 assert_eq!(r.leading.content.x, 0.0);
577 assert!((r.leading.content.width - 200.0).abs() < 0.01);
578 assert!(
579 (r.center.x - 206.0).abs() < 0.01,
580 "center after content+gutter"
581 );
582 assert!((r.center.width - (1000.0 - 206.0)).abs() < 0.01);
583 assert_eq!(r.trailing, SideRects::ZERO);
584 }
585
586 #[test]
587 fn four_sides_default_corners_inset_center() {
588 let r = compute_rects(
589 container(),
590 shown(200.0),
591 shown(150.0),
592 shown(100.0),
593 shown(120.0),
594 CornerOwners::default(),
595 false,
596 );
597 assert_eq!(r.top.content.x, 0.0);
599 assert!((r.top.content.width - 1000.0).abs() < 0.01);
600 assert_eq!(r.bottom.content.x, 0.0);
601 assert!((r.leading.content.y - 106.0).abs() < 0.01);
603 assert!((r.center.x - 206.0).abs() < 0.01);
605 assert!((r.center.y - 106.0).abs() < 0.01);
606 }
607
608 #[test]
609 fn corner_owned_by_leading_extends_column_up() {
610 let mut owners = CornerOwners::default();
611 owners.set(DockCorner::TopLeading, DockSide::Leading);
612 let r = compute_rects(
613 container(),
614 shown(200.0),
615 SideLayout::empty(),
616 shown(100.0),
617 SideLayout::empty(),
618 owners,
619 false,
620 );
621 assert_eq!(r.leading.content.y, 0.0);
623 assert!(
624 (r.top.content.x - 206.0).abs() < 0.01,
625 "top pushed right of leading"
626 );
627 }
628
629 #[test]
630 fn corner_degrades_when_owner_hidden() {
631 let mut owners = CornerOwners::default();
633 owners.set(DockCorner::TopLeading, DockSide::Leading);
634 let r = compute_rects(
635 container(),
636 hidden(200.0),
637 SideLayout::empty(),
638 shown(100.0),
639 SideLayout::empty(),
640 owners,
641 false,
642 );
643 assert_eq!(
644 r.top.content.x, 0.0,
645 "top fills since its corner-owner is gone"
646 );
647 }
648
649 #[test]
650 fn visible_progress_half_scales_content() {
651 let mut s = shown(200.0);
652 s.visible_progress = 0.5;
653 let r = compute_rects(
654 container(),
655 s,
656 SideLayout::empty(),
657 SideLayout::empty(),
658 SideLayout::empty(),
659 CornerOwners::default(),
660 false,
661 );
662 assert!((r.leading.content.width - 100.0).abs() < 0.01);
663 assert!(
664 r.leading.handle.width > 0.0,
665 "gutter present while expanding"
666 );
667 }
668
669 #[test]
670 fn hidden_side_with_rail_keeps_rail_drops_handle() {
671 let s = SideLayout {
672 size: 240.0,
673 visible_progress: 0.0,
674 gutter: 6.0,
675 min_size: 60.0,
676 rail_thickness: 48.0,
677 has_rail: true,
678 };
679 let r = compute_rects(
680 container(),
681 s,
682 SideLayout::empty(),
683 SideLayout::empty(),
684 SideLayout::empty(),
685 CornerOwners::default(),
686 false,
687 );
688 assert!((r.leading.rail.width - 48.0).abs() < 0.01, "rail persists");
689 assert!(r.leading.content.width.abs() < 0.01, "content hidden");
690 assert!(r.leading.handle.width.abs() < 0.01, "no handle when hidden");
691 assert!(
692 (r.center.x - 48.0).abs() < 0.01,
693 "center inset only by rail"
694 );
695 }
696
697 #[test]
698 fn shown_side_with_rail_orders_rail_content_handle() {
699 let s = SideLayout {
700 size: 200.0,
701 visible_progress: 1.0,
702 gutter: 6.0,
703 min_size: 60.0,
704 rail_thickness: 48.0,
705 has_rail: true,
706 };
707 let r = compute_rects(
708 container(),
709 s,
710 SideLayout::empty(),
711 SideLayout::empty(),
712 SideLayout::empty(),
713 CornerOwners::default(),
714 false,
715 );
716 assert_eq!(r.leading.rail.x, 0.0);
717 assert!((r.leading.rail.width - 48.0).abs() < 0.01);
718 assert!((r.leading.content.x - 48.0).abs() < 0.01);
719 assert!((r.leading.content.width - 200.0).abs() < 0.01);
720 assert!((r.leading.handle.x - 248.0).abs() < 0.01);
721 assert!((r.center.x - 254.0).abs() < 0.01);
722 }
723
724 fn band_with_rail(progress: f32) -> SideLayout {
726 SideLayout {
727 size: 100.0,
728 visible_progress: progress,
729 gutter: 6.0,
730 min_size: 80.0,
731 rail_thickness: 48.0,
732 has_rail: true,
733 }
734 }
735
736 #[test]
737 fn top_rail_is_a_leading_column_not_a_band() {
738 let r = compute_rects(
739 container(),
740 SideLayout::empty(),
741 SideLayout::empty(),
742 band_with_rail(1.0),
743 SideLayout::empty(),
744 CornerOwners::default(),
745 false,
746 );
747 assert_eq!(r.top.rail.x, 0.0);
750 assert!((r.top.rail.width - 48.0).abs() < 0.01);
751 assert!((r.top.rail.height - 106.0).abs() < 0.01);
752 assert!((r.top.content.x - 48.0).abs() < 0.01);
754 assert!((r.top.content.width - (1000.0 - 48.0)).abs() < 0.01);
755 assert!((r.top.content.height - 100.0).abs() < 0.01);
756 assert!((r.center.y - 106.0).abs() < 0.01);
758 }
759
760 #[test]
761 fn top_rail_column_mirrors_to_the_right_in_rtl() {
762 let r = compute_rects(
763 container(),
764 SideLayout::empty(),
765 SideLayout::empty(),
766 band_with_rail(1.0),
767 SideLayout::empty(),
768 CornerOwners::default(),
769 true,
770 );
771 assert!(
772 (r.top.rail.x - (1000.0 - 48.0)).abs() < 0.01,
773 "rail on the right in RTL"
774 );
775 assert_eq!(r.top.content.x, 0.0, "content on the left in RTL");
776 }
777
778 #[test]
779 fn hidden_top_with_rail_fully_collapses() {
780 let r = compute_rects(
781 container(),
782 SideLayout::empty(),
783 SideLayout::empty(),
784 band_with_rail(0.0),
785 SideLayout::empty(),
786 CornerOwners::default(),
787 false,
788 );
789 assert!(r.top.rail.height.abs() < 0.01, "no rail column when hidden");
792 assert!(r.top.content.height.abs() < 0.01, "no content when hidden");
793 assert_eq!(r.center.y, 0.0, "centre fills — no top inset");
794 assert!((r.center.height - 800.0).abs() < 0.01);
795 }
796
797 #[test]
798 fn bottom_rail_column_keeps_handle_inboard() {
799 let r = compute_rects(
800 container(),
801 SideLayout::empty(),
802 SideLayout::empty(),
803 SideLayout::empty(),
804 band_with_rail(1.0),
805 CornerOwners::default(),
806 false,
807 );
808 assert_eq!(r.bottom.rail.x, 0.0);
810 assert!((r.bottom.rail.width - 48.0).abs() < 0.01);
811 assert!((r.bottom.handle.x - 48.0).abs() < 0.01);
814 assert!((r.bottom.handle.y - (800.0 - 106.0)).abs() < 0.01);
815 assert!(
816 r.bottom.content.y > r.bottom.handle.y,
817 "content below the inboard handle"
818 );
819 }
820
821 #[test]
822 fn trailing_rail_sits_on_the_outer_edge() {
823 let s = SideLayout {
824 size: 200.0,
825 visible_progress: 1.0,
826 gutter: 6.0,
827 min_size: 60.0,
828 rail_thickness: 48.0,
829 has_rail: true,
830 };
831 let r = compute_rects(
832 container(),
833 SideLayout::empty(),
834 s,
835 SideLayout::empty(),
836 SideLayout::empty(),
837 CornerOwners::default(),
838 false,
839 );
840 assert!((r.trailing.rail.right() - 1000.0).abs() < 0.01);
842 assert!((r.trailing.rail.width - 48.0).abs() < 0.01);
843 assert!(r.trailing.handle.x < r.trailing.content.x);
844 assert!(r.trailing.content.x < r.trailing.rail.x);
845 }
846
847 #[test]
848 fn handle_spans_same_cross_extent_as_content() {
849 let r = compute_rects(
850 container(),
851 shown(200.0),
852 SideLayout::empty(),
853 shown(100.0),
854 SideLayout::empty(),
855 CornerOwners::default(),
856 false,
857 );
858 assert!((r.leading.handle.height - r.leading.content.height).abs() < 0.01);
859 assert!((r.leading.handle.y - r.leading.content.y).abs() < 0.01);
860 }
861
862 #[test]
863 fn center_never_negative_under_over_constraint() {
864 let big = shown(900.0);
866 let r = compute_rects(
867 container(),
868 big,
869 big,
870 big,
871 big,
872 CornerOwners::default(),
873 false,
874 );
875 assert!(r.center.width >= 0.0);
876 assert!(r.center.height >= 0.0);
877 assert!(r.leading.content.width + r.trailing.content.width <= 1000.0 + 0.01);
879 }
880
881 #[test]
882 fn zero_by_zero_container_no_panic() {
883 let r = compute_rects(
884 Rect::new(0.0, 0.0, 0.0, 0.0),
885 shown(200.0),
886 shown(200.0),
887 shown(100.0),
888 shown(100.0),
889 CornerOwners::default(),
890 false,
891 );
892 assert_eq!(r.center, Rect::new(0.0, 0.0, 0.0, 0.0));
893 }
894
895 #[test]
896 fn degenerate_corner_clamps_to_zero() {
897 let mut owners = CornerOwners::default();
900 owners.set(DockCorner::TopLeading, DockSide::Leading);
901 owners.set(DockCorner::BottomLeading, DockSide::Leading);
902 let r = compute_rects(
903 Rect::new(0.0, 0.0, 1000.0, 100.0),
904 shown(200.0),
905 SideLayout::empty(),
906 shown(80.0),
907 shown(80.0),
908 owners,
909 false,
910 );
911 assert!(r.leading.content.height >= 0.0);
912 }
913
914 #[test]
915 fn idempotent() {
916 let a = compute_rects(
917 container(),
918 shown(200.0),
919 shown(150.0),
920 shown(100.0),
921 shown(120.0),
922 CornerOwners::default(),
923 false,
924 );
925 let b = compute_rects(
926 container(),
927 shown(200.0),
928 shown(150.0),
929 shown(100.0),
930 shown(120.0),
931 CornerOwners::default(),
932 false,
933 );
934 assert_eq!(a, b);
935 }
936
937 #[test]
938 fn corner_other_side_helper() {
939 assert_eq!(
940 DockCorner::TopLeading.other(DockSide::Leading),
941 DockSide::Top
942 );
943 assert_eq!(
944 DockCorner::TopLeading.other(DockSide::Top),
945 DockSide::Leading
946 );
947 }
948
949 #[test]
950 fn rtl_mirror_is_caller_swap() {
951 let ltr = compute_rects(
954 container(),
955 shown(200.0),
956 shown(150.0),
957 SideLayout::empty(),
958 SideLayout::empty(),
959 CornerOwners::default(),
960 false,
961 );
962 let rtl = compute_rects(
963 container(),
964 shown(150.0),
965 shown(200.0),
966 SideLayout::empty(),
967 SideLayout::empty(),
968 CornerOwners::default(),
969 false,
970 );
971 assert!((ltr.leading.content.width - rtl.trailing.content.width).abs() < 0.01);
973 }
974}