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 {
208 self.content_extent() + self.gutter_extent()
209 }
210
211 fn present(&self) -> bool {
213 self.total_extent() > EPS
214 }
215
216 fn band_present(&self) -> bool {
218 self.band_depth() > EPS
219 }
220}
221
222#[derive(Debug, Clone, Copy, PartialEq)]
226pub struct SideRects {
227 pub rail: Rect,
228 pub content: Rect,
229 pub handle: Rect,
230}
231
232impl SideRects {
233 const ZERO: SideRects = SideRects {
234 rail: Rect::ZERO,
235 content: Rect::ZERO,
236 handle: Rect::ZERO,
237 };
238}
239
240#[derive(Debug, Clone, Copy, PartialEq)]
242pub struct DockingRects {
243 pub leading: SideRects,
244 pub trailing: SideRects,
245 pub top: SideRects,
246 pub bottom: SideRects,
247 pub center: Rect,
248}
249
250fn effective_owner(
254 corner: DockCorner,
255 owners: &CornerOwners,
256 present: impl Fn(DockSide) -> bool,
257) -> DockSide {
258 let declared = owners.owner(corner);
259 if present(declared) {
260 declared
261 } else {
262 let other = corner.other(declared);
263 if present(other) { other } else { declared }
264 }
265}
266
267pub fn compute_rects(
272 container: Rect,
273 leading: SideLayout,
274 trailing: SideLayout,
275 top: SideLayout,
276 bottom: SideLayout,
277 owners: CornerOwners,
278 rtl: bool,
279) -> DockingRects {
280 let x = container.x;
281 let y = container.y;
282 let w = container.width.max(0.0);
283 let h = container.height.max(0.0);
284
285 let mut l = leading.total_extent();
288 let mut r = trailing.total_extent();
289 let mut t = top.band_depth();
290 let mut b = bottom.band_depth();
291 if l + r > w {
292 let scale = if l + r > 0.0 { w / (l + r) } else { 0.0 };
293 l *= scale;
294 r *= scale;
295 }
296 if t + b > h {
297 let scale = if t + b > 0.0 { h / (t + b) } else { 0.0 };
298 t *= scale;
299 b *= scale;
300 }
301
302 let present = |side: DockSide| match side {
303 DockSide::Leading => leading.present(),
304 DockSide::Trailing => trailing.present(),
305 DockSide::Top => top.band_present(),
306 DockSide::Bottom => bottom.band_present(),
307 };
308 let eff = |corner: DockCorner| effective_owner(corner, &owners, present);
309
310 let top_x_left = if eff(DockCorner::TopLeading) == DockSide::Top {
312 x
313 } else {
314 x + l
315 };
316 let top_x_right = if eff(DockCorner::TopTrailing) == DockSide::Top {
317 x + w
318 } else {
319 x + w - r
320 };
321 let bottom_x_left = if eff(DockCorner::BottomLeading) == DockSide::Bottom {
322 x
323 } else {
324 x + l
325 };
326 let bottom_x_right = if eff(DockCorner::BottomTrailing) == DockSide::Bottom {
327 x + w
328 } else {
329 x + w - r
330 };
331
332 let leading_y_top = if eff(DockCorner::TopLeading) == DockSide::Leading {
334 y
335 } else {
336 y + t
337 };
338 let leading_y_bottom = if eff(DockCorner::BottomLeading) == DockSide::Leading {
339 y + h
340 } else {
341 y + h - b
342 };
343 let trailing_y_top = if eff(DockCorner::TopTrailing) == DockSide::Trailing {
344 y
345 } else {
346 y + t
347 };
348 let trailing_y_bottom = if eff(DockCorner::BottomTrailing) == DockSide::Trailing {
349 y + h
350 } else {
351 y + h - b
352 };
353
354 let leading_region = Rect::new(
356 x,
357 leading_y_top,
358 l,
359 (leading_y_bottom - leading_y_top).max(0.0),
360 );
361 let trailing_region = Rect::new(
362 x + w - r,
363 trailing_y_top,
364 r,
365 (trailing_y_bottom - trailing_y_top).max(0.0),
366 );
367 let top_region = Rect::new(top_x_left, y, (top_x_right - top_x_left).max(0.0), t);
368 let bottom_region = Rect::new(
369 bottom_x_left,
370 y + h - b,
371 (bottom_x_right - bottom_x_left).max(0.0),
372 b,
373 );
374 let center = Rect::new(x + l, y + t, (w - l - r).max(0.0), (h - t - b).max(0.0));
375
376 DockingRects {
377 leading: split_side(DockSide::Leading, leading_region, &leading, l, rtl),
378 trailing: split_side(DockSide::Trailing, trailing_region, &trailing, r, rtl),
379 top: split_side(DockSide::Top, top_region, &top, t, rtl),
380 bottom: split_side(DockSide::Bottom, bottom_region, &bottom, b, rtl),
381 center,
382 }
383}
384
385fn split_side(
393 side: DockSide,
394 region: Rect,
395 layout: &SideLayout,
396 total: f32,
397 rtl: bool,
398) -> SideRects {
399 if total <= EPS || region.width <= 0.0 || region.height <= 0.0 {
400 if layout.rail_extent() > EPS && region.width > 0.0 && region.height > 0.0 {
402 return rail_only(
403 side,
404 region,
405 layout.rail_extent().min(extent_along(side, region)),
406 rtl,
407 );
408 }
409 return SideRects::ZERO;
410 }
411
412 match side {
413 DockSide::Leading | DockSide::Trailing => {
414 let raw = layout.total_extent();
417 let scale = if raw > 0.0 { total / raw } else { 0.0 };
418 let rail = layout.rail_extent() * scale;
419 let content = layout.content_extent() * scale;
420 let gutter = layout.gutter_extent() * scale;
421 match side {
422 DockSide::Leading => SideRects {
423 rail: Rect::new(region.x, region.y, rail, region.height),
424 content: Rect::new(region.x + rail, region.y, content, region.height),
425 handle: Rect::new(region.x + rail + content, region.y, gutter, region.height),
426 },
427 _ => SideRects {
428 handle: Rect::new(region.x, region.y, gutter, region.height),
429 content: Rect::new(region.x + gutter, region.y, content, region.height),
430 rail: Rect::new(region.x + gutter + content, region.y, rail, region.height),
431 },
432 }
433 }
434 DockSide::Top | DockSide::Bottom => {
435 let rail_w = layout.rail_extent().min(region.width);
438 let body_w = (region.width - rail_w).max(0.0);
439 let (rail_x, body_x) = if rtl {
441 (region.x + body_w, region.x)
442 } else {
443 (region.x, region.x + rail_w)
444 };
445 let raw_depth = layout.content_extent() + layout.gutter_extent();
446 let scale = if raw_depth > 0.0 {
447 total / raw_depth
448 } else {
449 0.0
450 };
451 let content = layout.content_extent() * scale;
452 let gutter = layout.gutter_extent() * scale;
453 let rail = Rect::new(rail_x, region.y, rail_w, region.height);
454 match side {
455 DockSide::Top => SideRects {
457 rail,
458 content: Rect::new(body_x, region.y, body_w, content),
459 handle: Rect::new(body_x, region.y + content, body_w, gutter),
460 },
461 _ => SideRects {
463 rail,
464 handle: Rect::new(body_x, region.y, body_w, gutter),
465 content: Rect::new(body_x, region.y + gutter, body_w, content),
466 },
467 }
468 }
469 }
470}
471
472fn extent_along(side: DockSide, region: Rect) -> f32 {
473 if side.is_horizontal_axis() {
474 region.width
475 } else {
476 region.height
477 }
478}
479
480fn rail_only(side: DockSide, region: Rect, rail: f32, rtl: bool) -> SideRects {
484 let mut rects = SideRects::ZERO;
485 rects.rail = match side {
486 DockSide::Leading => Rect::new(region.x, region.y, rail, region.height),
487 DockSide::Trailing => Rect::new(
488 region.x + region.width - rail,
489 region.y,
490 rail,
491 region.height,
492 ),
493 DockSide::Top | DockSide::Bottom => {
494 let rail_x = if rtl {
495 region.x + region.width - rail
496 } else {
497 region.x
498 };
499 Rect::new(rail_x, region.y, rail, region.height)
500 }
501 };
502 rects
503}
504
505#[cfg(test)]
506mod tests {
507 use super::*;
508
509 fn container() -> Rect {
510 Rect::new(0.0, 0.0, 1000.0, 800.0)
511 }
512
513 fn shown(size: f32) -> SideLayout {
515 SideLayout {
516 size,
517 visible_progress: 1.0,
518 gutter: 6.0,
519 min_size: 50.0,
520 rail_thickness: 0.0,
521 has_rail: false,
522 }
523 }
524
525 fn hidden(size: f32) -> SideLayout {
526 SideLayout {
527 visible_progress: 0.0,
528 ..shown(size)
529 }
530 }
531
532 #[test]
533 fn all_hidden_center_fills() {
534 let r = compute_rects(
535 container(),
536 SideLayout::empty(),
537 SideLayout::empty(),
538 SideLayout::empty(),
539 SideLayout::empty(),
540 CornerOwners::default(),
541 false,
542 );
543 assert_eq!(r.center, container());
544 }
545
546 #[test]
547 fn single_leading_side() {
548 let r = compute_rects(
549 container(),
550 shown(200.0),
551 SideLayout::empty(),
552 SideLayout::empty(),
553 SideLayout::empty(),
554 CornerOwners::default(),
555 false,
556 );
557 assert_eq!(r.leading.content.x, 0.0);
558 assert!((r.leading.content.width - 200.0).abs() < 0.01);
559 assert!(
560 (r.center.x - 206.0).abs() < 0.01,
561 "center after content+gutter"
562 );
563 assert!((r.center.width - (1000.0 - 206.0)).abs() < 0.01);
564 assert_eq!(r.trailing, SideRects::ZERO);
565 }
566
567 #[test]
568 fn four_sides_default_corners_inset_center() {
569 let r = compute_rects(
570 container(),
571 shown(200.0),
572 shown(150.0),
573 shown(100.0),
574 shown(120.0),
575 CornerOwners::default(),
576 false,
577 );
578 assert_eq!(r.top.content.x, 0.0);
580 assert!((r.top.content.width - 1000.0).abs() < 0.01);
581 assert_eq!(r.bottom.content.x, 0.0);
582 assert!((r.leading.content.y - 106.0).abs() < 0.01);
584 assert!((r.center.x - 206.0).abs() < 0.01);
586 assert!((r.center.y - 106.0).abs() < 0.01);
587 }
588
589 #[test]
590 fn corner_owned_by_leading_extends_column_up() {
591 let mut owners = CornerOwners::default();
592 owners.set(DockCorner::TopLeading, DockSide::Leading);
593 let r = compute_rects(
594 container(),
595 shown(200.0),
596 SideLayout::empty(),
597 shown(100.0),
598 SideLayout::empty(),
599 owners,
600 false,
601 );
602 assert_eq!(r.leading.content.y, 0.0);
604 assert!(
605 (r.top.content.x - 206.0).abs() < 0.01,
606 "top pushed right of leading"
607 );
608 }
609
610 #[test]
611 fn corner_degrades_when_owner_hidden() {
612 let mut owners = CornerOwners::default();
614 owners.set(DockCorner::TopLeading, DockSide::Leading);
615 let r = compute_rects(
616 container(),
617 hidden(200.0),
618 SideLayout::empty(),
619 shown(100.0),
620 SideLayout::empty(),
621 owners,
622 false,
623 );
624 assert_eq!(
625 r.top.content.x, 0.0,
626 "top fills since its corner-owner is gone"
627 );
628 }
629
630 #[test]
631 fn visible_progress_half_scales_content() {
632 let mut s = shown(200.0);
633 s.visible_progress = 0.5;
634 let r = compute_rects(
635 container(),
636 s,
637 SideLayout::empty(),
638 SideLayout::empty(),
639 SideLayout::empty(),
640 CornerOwners::default(),
641 false,
642 );
643 assert!((r.leading.content.width - 100.0).abs() < 0.01);
644 assert!(
645 r.leading.handle.width > 0.0,
646 "gutter present while expanding"
647 );
648 }
649
650 #[test]
651 fn hidden_side_with_rail_keeps_rail_drops_handle() {
652 let s = SideLayout {
653 size: 240.0,
654 visible_progress: 0.0,
655 gutter: 6.0,
656 min_size: 60.0,
657 rail_thickness: 48.0,
658 has_rail: true,
659 };
660 let r = compute_rects(
661 container(),
662 s,
663 SideLayout::empty(),
664 SideLayout::empty(),
665 SideLayout::empty(),
666 CornerOwners::default(),
667 false,
668 );
669 assert!((r.leading.rail.width - 48.0).abs() < 0.01, "rail persists");
670 assert!(r.leading.content.width.abs() < 0.01, "content hidden");
671 assert!(r.leading.handle.width.abs() < 0.01, "no handle when hidden");
672 assert!(
673 (r.center.x - 48.0).abs() < 0.01,
674 "center inset only by rail"
675 );
676 }
677
678 #[test]
679 fn shown_side_with_rail_orders_rail_content_handle() {
680 let s = SideLayout {
681 size: 200.0,
682 visible_progress: 1.0,
683 gutter: 6.0,
684 min_size: 60.0,
685 rail_thickness: 48.0,
686 has_rail: true,
687 };
688 let r = compute_rects(
689 container(),
690 s,
691 SideLayout::empty(),
692 SideLayout::empty(),
693 SideLayout::empty(),
694 CornerOwners::default(),
695 false,
696 );
697 assert_eq!(r.leading.rail.x, 0.0);
698 assert!((r.leading.rail.width - 48.0).abs() < 0.01);
699 assert!((r.leading.content.x - 48.0).abs() < 0.01);
700 assert!((r.leading.content.width - 200.0).abs() < 0.01);
701 assert!((r.leading.handle.x - 248.0).abs() < 0.01);
702 assert!((r.center.x - 254.0).abs() < 0.01);
703 }
704
705 fn band_with_rail(progress: f32) -> SideLayout {
707 SideLayout {
708 size: 100.0,
709 visible_progress: progress,
710 gutter: 6.0,
711 min_size: 80.0,
712 rail_thickness: 48.0,
713 has_rail: true,
714 }
715 }
716
717 #[test]
718 fn top_rail_is_a_leading_column_not_a_band() {
719 let r = compute_rects(
720 container(),
721 SideLayout::empty(),
722 SideLayout::empty(),
723 band_with_rail(1.0),
724 SideLayout::empty(),
725 CornerOwners::default(),
726 false,
727 );
728 assert_eq!(r.top.rail.x, 0.0);
731 assert!((r.top.rail.width - 48.0).abs() < 0.01);
732 assert!((r.top.rail.height - 106.0).abs() < 0.01);
733 assert!((r.top.content.x - 48.0).abs() < 0.01);
735 assert!((r.top.content.width - (1000.0 - 48.0)).abs() < 0.01);
736 assert!((r.top.content.height - 100.0).abs() < 0.01);
737 assert!((r.center.y - 106.0).abs() < 0.01);
739 }
740
741 #[test]
742 fn top_rail_column_mirrors_to_the_right_in_rtl() {
743 let r = compute_rects(
744 container(),
745 SideLayout::empty(),
746 SideLayout::empty(),
747 band_with_rail(1.0),
748 SideLayout::empty(),
749 CornerOwners::default(),
750 true,
751 );
752 assert!(
753 (r.top.rail.x - (1000.0 - 48.0)).abs() < 0.01,
754 "rail on the right in RTL"
755 );
756 assert_eq!(r.top.content.x, 0.0, "content on the left in RTL");
757 }
758
759 #[test]
760 fn hidden_top_with_rail_fully_collapses() {
761 let r = compute_rects(
762 container(),
763 SideLayout::empty(),
764 SideLayout::empty(),
765 band_with_rail(0.0),
766 SideLayout::empty(),
767 CornerOwners::default(),
768 false,
769 );
770 assert!(r.top.rail.height.abs() < 0.01, "no rail column when hidden");
773 assert!(r.top.content.height.abs() < 0.01, "no content when hidden");
774 assert_eq!(r.center.y, 0.0, "centre fills — no top inset");
775 assert!((r.center.height - 800.0).abs() < 0.01);
776 }
777
778 #[test]
779 fn bottom_rail_column_keeps_handle_inboard() {
780 let r = compute_rects(
781 container(),
782 SideLayout::empty(),
783 SideLayout::empty(),
784 SideLayout::empty(),
785 band_with_rail(1.0),
786 CornerOwners::default(),
787 false,
788 );
789 assert_eq!(r.bottom.rail.x, 0.0);
791 assert!((r.bottom.rail.width - 48.0).abs() < 0.01);
792 assert!((r.bottom.handle.x - 48.0).abs() < 0.01);
795 assert!((r.bottom.handle.y - (800.0 - 106.0)).abs() < 0.01);
796 assert!(
797 r.bottom.content.y > r.bottom.handle.y,
798 "content below the inboard handle"
799 );
800 }
801
802 #[test]
803 fn trailing_rail_sits_on_the_outer_edge() {
804 let s = SideLayout {
805 size: 200.0,
806 visible_progress: 1.0,
807 gutter: 6.0,
808 min_size: 60.0,
809 rail_thickness: 48.0,
810 has_rail: true,
811 };
812 let r = compute_rects(
813 container(),
814 SideLayout::empty(),
815 s,
816 SideLayout::empty(),
817 SideLayout::empty(),
818 CornerOwners::default(),
819 false,
820 );
821 assert!((r.trailing.rail.right() - 1000.0).abs() < 0.01);
823 assert!((r.trailing.rail.width - 48.0).abs() < 0.01);
824 assert!(r.trailing.handle.x < r.trailing.content.x);
825 assert!(r.trailing.content.x < r.trailing.rail.x);
826 }
827
828 #[test]
829 fn handle_spans_same_cross_extent_as_content() {
830 let r = compute_rects(
831 container(),
832 shown(200.0),
833 SideLayout::empty(),
834 shown(100.0),
835 SideLayout::empty(),
836 CornerOwners::default(),
837 false,
838 );
839 assert!((r.leading.handle.height - r.leading.content.height).abs() < 0.01);
840 assert!((r.leading.handle.y - r.leading.content.y).abs() < 0.01);
841 }
842
843 #[test]
844 fn center_never_negative_under_over_constraint() {
845 let big = shown(900.0);
847 let r = compute_rects(
848 container(),
849 big,
850 big,
851 big,
852 big,
853 CornerOwners::default(),
854 false,
855 );
856 assert!(r.center.width >= 0.0);
857 assert!(r.center.height >= 0.0);
858 assert!(r.leading.content.width + r.trailing.content.width <= 1000.0 + 0.01);
860 }
861
862 #[test]
863 fn zero_by_zero_container_no_panic() {
864 let r = compute_rects(
865 Rect::new(0.0, 0.0, 0.0, 0.0),
866 shown(200.0),
867 shown(200.0),
868 shown(100.0),
869 shown(100.0),
870 CornerOwners::default(),
871 false,
872 );
873 assert_eq!(r.center, Rect::new(0.0, 0.0, 0.0, 0.0));
874 }
875
876 #[test]
877 fn degenerate_corner_clamps_to_zero() {
878 let mut owners = CornerOwners::default();
881 owners.set(DockCorner::TopLeading, DockSide::Leading);
882 owners.set(DockCorner::BottomLeading, DockSide::Leading);
883 let r = compute_rects(
884 Rect::new(0.0, 0.0, 1000.0, 100.0),
885 shown(200.0),
886 SideLayout::empty(),
887 shown(80.0),
888 shown(80.0),
889 owners,
890 false,
891 );
892 assert!(r.leading.content.height >= 0.0);
893 }
894
895 #[test]
896 fn idempotent() {
897 let a = compute_rects(
898 container(),
899 shown(200.0),
900 shown(150.0),
901 shown(100.0),
902 shown(120.0),
903 CornerOwners::default(),
904 false,
905 );
906 let b = compute_rects(
907 container(),
908 shown(200.0),
909 shown(150.0),
910 shown(100.0),
911 shown(120.0),
912 CornerOwners::default(),
913 false,
914 );
915 assert_eq!(a, b);
916 }
917
918 #[test]
919 fn corner_other_side_helper() {
920 assert_eq!(
921 DockCorner::TopLeading.other(DockSide::Leading),
922 DockSide::Top
923 );
924 assert_eq!(
925 DockCorner::TopLeading.other(DockSide::Top),
926 DockSide::Leading
927 );
928 }
929
930 #[test]
931 fn rtl_mirror_is_caller_swap() {
932 let ltr = compute_rects(
935 container(),
936 shown(200.0),
937 shown(150.0),
938 SideLayout::empty(),
939 SideLayout::empty(),
940 CornerOwners::default(),
941 false,
942 );
943 let rtl = compute_rects(
944 container(),
945 shown(150.0),
946 shown(200.0),
947 SideLayout::empty(),
948 SideLayout::empty(),
949 CornerOwners::default(),
950 false,
951 );
952 assert!((ltr.leading.content.width - rtl.trailing.content.width).abs() < 0.01);
954 }
955}