Skip to main content

repose_ui/
adaptive.rs

1//! Adaptive layouts that respond to the current [`WindowSizeClass`].
2//! Currently provides [`ListDetailPaneScaffold`]: the list and the detail
3//! render side-by-side on Medium/Expanded widths, and only the selected pane
4//! renders on Compact.
5
6use repose_core::prelude::*;
7use repose_core::{Modifier, PaddingValues, View, WindowSizeClass};
8
9use crate::{Box, Column, Row, Text, ViewExt};
10
11#[derive(Clone, Copy, Debug)]
12pub struct PaneScaffoldDirective {
13    pub max_horizontal_partitions: u32,
14    pub horizontal_part_spacing: f32,
15    pub list_pane_width: f32,
16    pub content_padding: PaddingValues,
17}
18
19impl Default for PaneScaffoldDirective {
20    fn default() -> Self {
21        Self {
22            max_horizontal_partitions: 1,
23            horizontal_part_spacing: 0.0,
24            list_pane_width: 0.0,
25            content_padding: PaddingValues::default(),
26        }
27    }
28}
29
30impl PaneScaffoldDirective {
31    pub fn from_window_size_class(class: WindowSizeClass) -> Self {
32        if class.is_at_least_medium_width() {
33            Self {
34                max_horizontal_partitions: 2,
35                horizontal_part_spacing: 0.0,
36                list_pane_width: 360.0,
37                content_padding: PaddingValues::default(),
38            }
39        } else {
40            Self {
41                max_horizontal_partitions: 1,
42                horizontal_part_spacing: 0.0,
43                list_pane_width: 0.0,
44                content_padding: PaddingValues::default(),
45            }
46        }
47    }
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum ListDetailPaneValue {
52    List,
53    Detail,
54}
55
56pub fn ListDetailPaneScaffold<F1, F2>(
57    directive: PaneScaffoldDirective,
58    value: ListDetailPaneValue,
59    list: F1,
60    detail: F2,
61) -> View
62where
63    F1: Fn() -> View + 'static,
64    F2: Fn() -> View + 'static,
65{
66    if directive.max_horizontal_partitions >= 2 {
67        let spacing = directive.horizontal_part_spacing;
68        let list_mod = if directive.list_pane_width > 0.0 {
69            Modifier::new()
70                .width(directive.list_pane_width)
71                .fill_max_height()
72        } else {
73            Modifier::new().flex_grow(1.0).fill_max_height()
74        };
75        Row(Modifier::new().fill_max_size().gap(spacing)).child((
76            Box(list_mod).child(list()),
77            Box(Modifier::new().flex_grow(1.0).fill_max_height()).child(detail()),
78        ))
79    } else {
80        match value {
81            ListDetailPaneValue::List => list(),
82            ListDetailPaneValue::Detail => detail(),
83        }
84    }
85}
86
87pub fn ScaffoldPane(directive: &PaneScaffoldDirective, content: View) -> View {
88    Box(Modifier::new()
89        .fill_max_size()
90        .padding_values(directive.content_padding))
91    .child(content)
92}
93
94pub fn TwoPaneTopBar(title: &str, leading: Option<View>, trailing: Option<View>) -> View {
95    let th = theme();
96    let leading = leading.unwrap_or_else(|| Box(Modifier::new().width(0.0).height(0.0)));
97    let trailing = trailing.unwrap_or_else(|| Box(Modifier::new().width(0.0).height(0.0)));
98    Box(Modifier::new()
99        .fill_max_width()
100        .height(56.0)
101        .background(th.surface)
102        .padding_values(PaddingValues {
103            left: 16.0,
104            right: 16.0,
105            top: 0.0,
106            bottom: 0.0,
107        }))
108    .child((
109        leading,
110        Column(
111            Modifier::new()
112                .flex_grow(1.0)
113                .padding_values(PaddingValues {
114                    left: 16.0,
115                    right: 16.0,
116                    top: 0.0,
117                    bottom: 0.0,
118                }),
119        )
120        .child(Text(title)),
121        trailing,
122    ))
123}