1use iced_widget::core::text as core_text;
4use iced_widget::core::{Element, Length, alignment};
5use iced_widget::text;
6use iced_widget::{Column, Container, Row, Scrollable, Text};
7
8use crate::{Theme, text as material_text, tokens};
9
10pub const MAX_WIDTH: f32 = 980.0;
12
13pub const PADDING: f32 = 28.0;
15
16pub const SPACING: f32 = 28.0;
18
19pub const SECTION_SPACING: f32 = 24.0;
21
22pub const STACK_SPACING: f32 = 16.0;
24
25pub const COMPACT_STACK_SPACING: f32 = 8.0;
27
28pub const COMPONENT_STACK_SPACING: f32 = 12.0;
30
31pub const DENSE_STACK_SPACING: f32 = 10.0;
33
34pub const SPACIOUS_STACK_SPACING: f32 = 18.0;
36
37pub const ROW_SPACING: f32 = 12.0;
39
40pub const COMPACT_ROW_SPACING: f32 = 8.0;
42
43pub const INDICATOR_ROW_SPACING: f32 = 16.0;
45
46pub const DIVIDER_ROW_HEIGHT: f32 = 32.0;
48
49pub const DIVIDER_ROW_SPACING: f32 = 16.0;
51
52pub const CARD_PADDING: f32 = 12.0;
54
55pub const CARD_HEIGHT: f32 = 78.0;
57
58pub const PREVIEW_MAX_WIDTH: f32 = tokens::component::bottom_sheet::SHEET_MAX_WIDTH;
60
61pub const PREVIEW_HORIZONTAL_RESERVE: f32 = 160.0;
63
64pub const PREVIEW_MIN_WIDTH: f32 = 320.0;
66
67pub const PREVIEW_HEIGHT: f32 = 260.0;
69
70pub fn surface<'a, Message, Renderer>(
72 header: impl Into<Element<'a, Message, Theme, Renderer>>,
73 body: impl Into<Element<'a, Message, Theme, Renderer>>,
74) -> Scrollable<'a, Message, Theme, Renderer>
75where
76 Message: 'a,
77 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
78{
79 let page = Column::new()
80 .push(header)
81 .push(body)
82 .spacing(SPACING)
83 .padding(PADDING)
84 .width(Length::Fill)
85 .max_width(MAX_WIDTH);
86
87 Scrollable::new(
88 super::container::surface_container_high(page)
89 .width(Length::Fill)
90 .center_x(Length::Fill),
91 )
92 .height(Length::Fill)
93}
94
95pub fn preview_width(viewport_width: f32) -> f32 {
97 (viewport_width - PREVIEW_HORIZONTAL_RESERVE).clamp(PREVIEW_MIN_WIDTH, PREVIEW_MAX_WIDTH)
98}
99
100pub fn centered_preview<'a, Message, Renderer>(
102 width: f32,
103 content: impl Into<Element<'a, Message, Theme, Renderer>>,
104) -> Container<'a, Message, Theme, Renderer>
105where
106 Message: 'a,
107 Renderer: iced_widget::core::Renderer + 'a,
108{
109 Container::new(Container::new(content).width(Length::Fixed(width)))
110 .width(Length::Fill)
111 .align_x(alignment::Horizontal::Center)
112}
113
114pub fn preview_pane<'a, Message, Renderer>(
116 content: impl Into<Element<'a, Message, Theme, Renderer>>,
117) -> Container<'a, Message, Theme, Renderer>
118where
119 Message: 'a,
120 Renderer: iced_widget::core::Renderer + 'a,
121{
122 Container::new(content)
123 .width(Length::Fill)
124 .height(Length::Fixed(PREVIEW_HEIGHT))
125}
126
127pub fn aligned_preview_pane<'a, Message, Renderer>(
129 alignment: alignment::Horizontal,
130 content: impl Into<Element<'a, Message, Theme, Renderer>>,
131) -> Container<'a, Message, Theme, Renderer>
132where
133 Message: 'a,
134 Renderer: iced_widget::core::Renderer + 'a,
135{
136 preview_pane(content).align_x(alignment)
137}
138
139pub fn header<'a, Message, Renderer>(
141 title: impl text::IntoFragment<'a>,
142 subtitle: impl text::IntoFragment<'a>,
143) -> Column<'a, Message, Theme, Renderer>
144where
145 Message: 'a,
146 Renderer: core_text::Renderer + 'a,
147{
148 Column::new()
149 .push(type_scale_text(title, tokens::typography::HEADLINE_LARGE))
150 .push(type_scale_text(subtitle, tokens::typography::BODY_LARGE))
151 .spacing(6)
152}
153
154pub fn section<'a, Message, Renderer>(
156 title: impl text::IntoFragment<'a>,
157 body: impl Into<Element<'a, Message, Theme, Renderer>>,
158) -> Column<'a, Message, Theme, Renderer>
159where
160 Message: 'a,
161 Renderer: core_text::Renderer + 'a,
162{
163 Column::new()
164 .push(type_scale_text(title, tokens::typography::TITLE_MEDIUM))
165 .push(body)
166 .spacing(12)
167 .width(Length::Fill)
168}
169
170pub fn sections<'a, Message, Renderer>(
172 sections: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
173) -> Column<'a, Message, Theme, Renderer>
174where
175 Message: 'a,
176 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
177{
178 let mut content = Column::new().spacing(SECTION_SPACING).width(Length::Fill);
179 let mut first = true;
180
181 for section in sections {
182 if first {
183 first = false;
184 } else {
185 content = content.push(super::rule::horizontal_inset());
186 }
187
188 content = content.push(section);
189 }
190
191 content
192}
193
194pub fn stack<'a, Message, Renderer>(
196 children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
197) -> Column<'a, Message, Theme, Renderer>
198where
199 Message: 'a,
200 Renderer: iced_widget::core::Renderer + 'a,
201{
202 Column::with_children(children)
203 .spacing(STACK_SPACING)
204 .width(Length::Fill)
205}
206
207pub fn compact_stack<'a, Message, Renderer>(
209 children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
210) -> Column<'a, Message, Theme, Renderer>
211where
212 Message: 'a,
213 Renderer: iced_widget::core::Renderer + 'a,
214{
215 stack(children).spacing(COMPACT_STACK_SPACING)
216}
217
218pub fn component_stack<'a, Message, Renderer>(
220 children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
221) -> Column<'a, Message, Theme, Renderer>
222where
223 Message: 'a,
224 Renderer: iced_widget::core::Renderer + 'a,
225{
226 stack(children).spacing(COMPONENT_STACK_SPACING)
227}
228
229pub fn dense_stack<'a, Message, Renderer>(
231 children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
232) -> Column<'a, Message, Theme, Renderer>
233where
234 Message: 'a,
235 Renderer: iced_widget::core::Renderer + 'a,
236{
237 stack(children).spacing(DENSE_STACK_SPACING)
238}
239
240pub fn spacious_stack<'a, Message, Renderer>(
242 children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
243) -> Column<'a, Message, Theme, Renderer>
244where
245 Message: 'a,
246 Renderer: iced_widget::core::Renderer + 'a,
247{
248 stack(children).spacing(SPACIOUS_STACK_SPACING)
249}
250
251pub fn row<'a, Message, Renderer>(
253 children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
254) -> Row<'a, Message, Theme, Renderer>
255where
256 Message: 'a,
257 Renderer: iced_widget::core::Renderer + 'a,
258{
259 Row::with_children(children)
260 .spacing(ROW_SPACING)
261 .align_y(alignment::Vertical::Center)
262}
263
264pub fn indicator_row<'a, Message, Renderer>(
266 children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
267) -> Row<'a, Message, Theme, Renderer>
268where
269 Message: 'a,
270 Renderer: iced_widget::core::Renderer + 'a,
271{
272 row(children).spacing(INDICATOR_ROW_SPACING)
273}
274
275pub fn compact_row<'a, Message, Renderer>(
277 children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
278) -> Row<'a, Message, Theme, Renderer>
279where
280 Message: 'a,
281 Renderer: iced_widget::core::Renderer + 'a,
282{
283 Row::with_children(children)
284 .spacing(COMPACT_ROW_SPACING)
285 .align_y(alignment::Vertical::Center)
286}
287
288pub fn labeled_value_row<'a, Message, Renderer>(
290 label: impl text::IntoFragment<'a>,
291 value: impl text::IntoFragment<'a>,
292) -> Row<'a, Message, Theme, Renderer>
293where
294 Message: 'a,
295 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
296{
297 row([
298 type_scale_text(label, tokens::typography::BODY_LARGE)
299 .width(Length::Fill)
300 .into(),
301 type_scale_text(value, tokens::typography::BODY_LARGE).into(),
302 ])
303}
304
305pub fn divider_row<'a, Message, Renderer>(
307 children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
308) -> Row<'a, Message, Theme, Renderer>
309where
310 Message: 'a,
311 Renderer: iced_widget::core::Renderer + 'a,
312{
313 row(children)
314 .height(Length::Fixed(DIVIDER_ROW_HEIGHT))
315 .spacing(DIVIDER_ROW_SPACING)
316}
317
318pub fn card<'a, Message, Renderer>(
320 style: fn(Element<'a, Message, Theme, Renderer>) -> Container<'a, Message, Theme, Renderer>,
321 title: impl text::IntoFragment<'a>,
322 subtitle: impl text::IntoFragment<'a>,
323) -> Container<'a, Message, Theme, Renderer>
324where
325 Message: 'a,
326 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
327{
328 style(
329 stack([
330 type_scale_text(title, tokens::typography::TITLE_MEDIUM).into(),
331 type_scale_text(subtitle, tokens::typography::BODY_MEDIUM).into(),
332 ])
333 .spacing(2)
334 .into(),
335 )
336 .padding(CARD_PADDING)
337 .height(Length::Fixed(CARD_HEIGHT))
338 .width(Length::Fill)
339}
340
341pub fn type_scale_text<'a, Renderer>(
343 content: impl text::IntoFragment<'a>,
344 scale: tokens::typography::TypeScale,
345) -> Text<'a, Theme, Renderer>
346where
347 Renderer: core_text::Renderer + 'a,
348{
349 material_text::type_scale(content, scale)
350}
351
352#[cfg(test)]
353#[path = "../../../tests/widget/component/page.rs"]
354mod tests;