1use iced_core::{
4 self as core, Alignment, Color, Element, Length, Padding, Pixels,
5 alignment::Vertical, color,
6};
7use iced_widget::{
8 Column, Container, Row, Theme, center, container, mouse_area, opaque,
9 stack, text, vertical_space,
10};
11
12pub struct Dialog<
19 'a,
20 Message,
21 Theme = iced_widget::Theme,
22 Renderer = iced_widget::Renderer,
23> where
24 Renderer: 'a + core::text::Renderer,
25 Theme: 'a + Catalog,
26{
27 is_open: bool,
28 base: Element<'a, Message, Theme, Renderer>,
29 title: Option<String>,
30 content: Element<'a, Message, Theme, Renderer>,
31 buttons: Vec<Element<'a, Message, Theme, Renderer>>,
32 font: Option<Renderer::Font>,
33 width: Length,
34 height: Length,
35 spacing: f32,
36 padding: Padding,
37 button_alignment: Alignment,
38 class: <Theme as Catalog>::Class<'a>,
39 title_class: <Theme as text::Catalog>::Class<'a>,
40 container_class: <Theme as container::Catalog>::Class<'a>,
41}
42
43impl<'a, Message, Theme, Renderer> Dialog<'a, Message, Theme, Renderer>
44where
45 Renderer: 'a + core::Renderer + core::text::Renderer,
46 Theme: 'a + Catalog,
47 Message: 'a + Clone,
48{
49 pub fn new(
51 is_open: bool,
52 base: impl Into<Element<'a, Message, Theme, Renderer>>,
53 content: impl Into<Element<'a, Message, Theme, Renderer>>,
54 ) -> Self {
55 Self::with_buttons(is_open, base, content, Vec::new())
56 }
57
58 pub fn with_buttons(
60 is_open: bool,
61 base: impl Into<Element<'a, Message, Theme, Renderer>>,
62 content: impl Into<Element<'a, Message, Theme, Renderer>>,
63 buttons: Vec<Element<'a, Message, Theme, Renderer>>,
64 ) -> Self {
65 Self {
66 is_open,
67 base: base.into(),
68 title: None,
69 content: content.into(),
70 buttons,
71 font: None,
72 width: 400.into(),
73 height: 260.into(),
74 spacing: 8.0,
75 padding: 24.into(),
76 button_alignment: Alignment::Start,
77 class: <Theme as Catalog>::default(),
78 title_class: <Theme as Catalog>::default_title(),
79 container_class: <Theme as Catalog>::default_container(),
80 }
81 }
82
83 pub fn title(mut self, title: impl Into<String>) -> Self {
85 self.title = Some(title.into());
86 self
87 }
88
89 pub fn width(mut self, width: impl Into<Length>) -> Self {
91 self.width = width.into();
92 self
93 }
94
95 pub fn height(mut self, height: impl Into<Length>) -> Self {
97 self.height = height.into();
98 self
99 }
100
101 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
103 self.padding = padding.into();
104 self
105 }
106
107 pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self {
109 self.spacing = spacing.into().0;
110 self
111 }
112
113 pub fn align_buttons(mut self, align: impl Into<Vertical>) -> Self {
115 self.button_alignment = Alignment::from(align.into());
116 self
117 }
118
119 pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
123 self.font = Some(font.into());
124 self
125 }
126
127 pub fn push_button(
129 mut self,
130 button: impl Into<Element<'a, Message, Theme, Renderer>>,
131 ) -> Self {
132 self.buttons.push(button.into());
133 self
134 }
135
136 pub fn push_button_maybe(
138 self,
139 button: Option<impl Into<Element<'a, Message, Theme, Renderer>>>,
140 ) -> Self {
141 if let Some(button) = button {
142 self.push_button(button)
143 } else {
144 self
145 }
146 }
147
148 pub fn extend_buttons(
150 self,
151 buttons: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
152 ) -> Self {
153 buttons.into_iter().fold(self, Self::push_button)
154 }
155
156 pub fn backdrop(self, color: impl Into<Color>) -> Self
158 where
159 <Theme as Catalog>::Class<'a>: From<StyleFn<'a, Theme>>,
160 {
161 let backdrop_color = color.into();
162
163 self.style(move |_theme| Style { backdrop_color })
164 }
165
166 #[must_use]
168 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
169 where
170 <Theme as Catalog>::Class<'a>: From<StyleFn<'a, Theme>>,
171 {
172 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
173 self
174 }
175
176 #[must_use]
178 pub fn title_style(
179 mut self,
180 style: impl Fn(&Theme) -> text::Style + 'a,
181 ) -> Self
182 where
183 <Theme as text::Catalog>::Class<'a>: From<text::StyleFn<'a, Theme>>,
184 {
185 self.title_class = (Box::new(style) as text::StyleFn<'a, Theme>).into();
186 self
187 }
188
189 #[must_use]
191 pub fn container_style(
192 mut self,
193 style: impl Fn(&Theme) -> container::Style + 'a,
194 ) -> Self
195 where
196 <Theme as container::Catalog>::Class<'a>:
197 From<container::StyleFn<'a, Theme>>,
198 {
199 self.container_class =
200 (Box::new(style) as container::StyleFn<'a, Theme>).into();
201 self
202 }
203
204 #[must_use]
206 pub fn class(
207 mut self,
208 class: impl Into<<Theme as Catalog>::Class<'a>>,
209 ) -> Self {
210 self.class = class.into();
211 self
212 }
213
214 #[must_use]
216 pub fn title_class(
217 mut self,
218 class: impl Into<<Theme as text::Catalog>::Class<'a>>,
219 ) -> Self {
220 self.title_class = class.into();
221 self
222 }
223
224 #[must_use]
226 pub fn container_class(
227 mut self,
228 class: impl Into<<Theme as container::Catalog>::Class<'a>>,
229 ) -> Self {
230 self.container_class = class.into();
231 self
232 }
233
234 fn view(self) -> Element<'a, Message, Theme, Renderer>
235 where
236 <Theme as container::Catalog>::Class<'a>:
237 From<container::StyleFn<'a, Theme>>,
238 {
239 if self.is_open {
240 let contents = Container::new(
241 Column::new()
242 .push_maybe(self.title.map(|title| {
243 let text = text(title)
244 .size(20)
245 .line_height(text::LineHeight::Absolute(Pixels(
246 26.0,
247 )))
248 .class(self.title_class);
249
250 if let Some(font) = self.font {
251 text.font(font)
252 } else {
253 text
254 }
255 }))
256 .push(vertical_space().height(12))
257 .push(self.content),
258 )
259 .width(Length::Fill)
260 .padding(self.padding);
261
262 let buttons = Container::new(
263 Row::with_children(self.buttons).spacing(self.spacing),
264 )
265 .height(80)
266 .padding(self.padding);
267
268 let dialog = Container::new(
269 Column::new()
270 .push(contents)
271 .push(vertical_space())
272 .push(buttons),
273 )
274 .width(self.width)
275 .height(self.height)
276 .class(self.container_class)
277 .clip(true);
278
279 modal(self.base, dialog, self.class)
280 } else {
281 self.base
282 }
283 }
284}
285
286impl<'a, Message, Theme, Renderer> From<Dialog<'a, Message, Theme, Renderer>>
287 for Element<'a, Message, Theme, Renderer>
288where
289 Renderer: 'a + core::Renderer + core::text::Renderer,
290 Theme: 'a + Catalog,
291 Message: 'a + Clone,
292 <Theme as container::Catalog>::Class<'a>:
293 From<container::StyleFn<'a, Theme>>,
294{
295 fn from(dialog: Dialog<'a, Message, Theme, Renderer>) -> Self {
296 dialog.view()
297 }
298}
299
300fn modal<'a, Message, Theme, Renderer>(
301 base: impl Into<Element<'a, Message, Theme, Renderer>>,
302 content: impl Into<Element<'a, Message, Theme, Renderer>>,
303 class: <Theme as Catalog>::Class<'a>,
304) -> Element<'a, Message, Theme, Renderer>
305where
306 Message: 'a + Clone,
307 Renderer: 'a + core::Renderer,
308 Theme: 'a + container::Catalog + Catalog,
309 <Theme as container::Catalog>::Class<'a>:
310 From<container::StyleFn<'a, Theme>>,
311{
312 let area = mouse_area(center(opaque(content)).style(move |theme| {
313 container::Style {
314 background: Some(
315 Catalog::style(theme, &class).backdrop_color.into(),
316 ),
317 ..Default::default()
318 }
319 }));
320
321 stack![base.into(), opaque(area)].into()
322}
323
324#[derive(Debug, Clone, Copy, PartialEq)]
326pub struct Style {
327 pub backdrop_color: Color,
329}
330
331pub trait Catalog: text::Catalog + container::Catalog {
333 type Class<'a>;
335
336 fn default<'a>() -> <Self as Catalog>::Class<'a>;
338
339 fn default_title<'a>() -> <Self as text::Catalog>::Class<'a> {
341 <Self as text::Catalog>::default()
342 }
343
344 fn default_container<'a>() -> <Self as container::Catalog>::Class<'a> {
346 <Self as container::Catalog>::default()
347 }
348
349 fn style(&self, class: &<Self as Catalog>::Class<'_>) -> Style;
351}
352
353pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
355
356impl Catalog for Theme {
357 type Class<'a> = StyleFn<'a, Self>;
358
359 fn default<'a>() -> <Self as Catalog>::Class<'a> {
360 Box::new(default)
361 }
362
363 fn default_container<'a>() -> <Self as container::Catalog>::Class<'a> {
364 Box::new(|theme| {
365 container::background(
366 theme.extended_palette().background.base.color,
367 )
368 })
369 }
370
371 fn style(&self, class: &<Self as Catalog>::Class<'_>) -> Style {
372 class(self)
373 }
374}
375
376pub fn default(_theme: &Theme) -> Style {
378 Style {
379 backdrop_color: color!(0x000000, 0.3),
380 }
381}