Skip to main content

day_core/
modal_sheet.rs

1// Copyright © The Daybrite Project
2// SPDX-License-Identifier: MPL-2.0
3
4//! Shared card composition for sheet covers and modal window fallbacks.
5use crate::{AnyPiece, Boundary, BuildCx, Flex, Layout, LayoutOps, Piece, RNode};
6use day_spec::{Proposal, Rect, Size, kinds};
7use std::rc::Rc;
8
9/// The dimming laid over the page under a composed sheet.
10const SHEET_SCRIM: day_spec::Color = day_spec::Color::rgba(0.0, 0.0, 0.0, 0.32);
11/// A composed sheet card's corner radius.
12const SHEET_RADIUS: f64 = 12.0;
13
14/// A composed modal sheet (docs/cover.md "Modal sheet"): the cover's content on a card in the
15/// window's own background color, centered over a dimmed fill that spans the cover. The cover
16/// itself is clear and takes every click, so the page shows through but stays out of reach.
17pub struct ComposedSheet {
18    pub content: AnyPiece,
19}
20
21impl Piece for ComposedSheet {
22    fn build(self, cx: &mut BuildCx) -> RNode {
23        let fill = Flex {
24            grow_w: true,
25            grow_h: true,
26            ..Default::default()
27        };
28        let node = cx.native(
29            kinds::CONTAINER,
30            &day_spec::props::ContainerProps::default(),
31            Rc::new(SheetLayout),
32            fill,
33            Boundary::Yes,
34        );
35        cx.under(node, |cx| {
36            cx.native(
37                kinds::CONTAINER,
38                &day_spec::props::ContainerProps {
39                    background: Some(SHEET_SCRIM),
40                    role: Some(day_spec::SurfaceRole::Scrim),
41                    ..Default::default()
42                },
43                Rc::new(FillLayout),
44                fill,
45                Boundary::No,
46            );
47            let card = cx.native(
48                kinds::CONTAINER,
49                &day_spec::props::ContainerProps {
50                    background: None,
51                    corner_radius: SHEET_RADIUS,
52                    clips: true,
53                    role: Some(day_spec::SurfaceRole::Window),
54                },
55                Rc::new(FillLayout),
56                fill,
57                Boundary::Yes,
58            );
59            cx.under(card, |cx| {
60                let _ = self.content.build(cx);
61            });
62        });
63        node
64    }
65}
66
67/// A composed sheet card's size in a cover `area` large: the native AppKit sheet's proportions
68/// (the window less a margin, at most 640 by 620), never larger than the area itself.
69fn sheet_card_size(area: Size) -> Size {
70    Size::new(
71        (area.width - 40.0).clamp(320.0, 640.0).min(area.width),
72        (area.height - 80.0).clamp(300.0, 620.0).min(area.height),
73    )
74}
75
76/// [`ComposedSheet`]'s layout: the dimmed fill over the whole cover, the card centered on it.
77struct SheetLayout;
78
79impl Layout for SheetLayout {
80    fn measure(&self, _cx: &mut dyn LayoutOps, _children: &[RNode], p: Proposal) -> Size {
81        Size::new(p.width.unwrap_or(0.0), p.height.unwrap_or(0.0))
82    }
83    fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
84        let area = bounds.size;
85        if let Some(&scrim) = children.first() {
86            cx.place_child(scrim, Rect::from_size(area));
87        }
88        if let Some(&card) = children.get(1) {
89            let size = sheet_card_size(area);
90            cx.place_child(
91                card,
92                Rect::new(
93                    ((area.width - size.width) / 2.0).floor(),
94                    ((area.height - size.height) / 2.0).floor(),
95                    size.width,
96                    size.height,
97                ),
98            );
99        }
100    }
101}
102
103/// One child filling the container: the dimmed fill (no children) and the card's content.
104struct FillLayout;
105
106impl Layout for FillLayout {
107    fn measure(&self, _cx: &mut dyn LayoutOps, _children: &[RNode], p: Proposal) -> Size {
108        Size::new(p.width.unwrap_or(0.0), p.height.unwrap_or(0.0))
109    }
110    fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
111        for &child in children {
112            cx.place_child(child, Rect::from_size(bounds.size));
113        }
114    }
115}