repose_material/material3/
dialog.rs1#![allow(non_snake_case)]
2
3use std::rc::Rc;
4use std::sync::atomic::{AtomicU64, Ordering};
5
6use repose_core::*;
7use repose_ui::overlay::OverlayHandle;
8use repose_ui::{Box, Column, Row, Spacer, Text, ViewExt, ZStack};
9use web_time::Duration;
10
11use super::{AlertDialogDefaults, Button, ButtonConfig, TextButton};
12use super::{DatePicker, DatePickerState};
13use super::{TimePicker, TimePickerState};
14
15static DIALOG_COUNTER: AtomicU64 = AtomicU64::new(0);
16
17pub struct DialogState {
19 visible: Signal<bool>,
20 id: u64,
21}
22
23impl Default for DialogState {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl DialogState {
30 pub fn new() -> Self {
31 Self {
32 visible: signal(false),
33 id: DIALOG_COUNTER.fetch_add(1, Ordering::Relaxed),
34 }
35 }
36
37 pub fn key(&self, suffix: &str) -> String {
38 format!("dlg_{}_{}", self.id, suffix)
39 }
40
41 pub fn is_visible(&self) -> bool {
42 self.visible.get()
43 }
44
45 pub fn show(&self) {
46 self.visible.set(true);
47 }
48
49 pub fn dismiss(&self) {
50 self.visible.set(false);
51 }
52}
53
54pub fn Dialog(
61 state: Rc<DialogState>,
62 overlay: OverlayHandle,
63 modifier: Modifier,
64 content: View,
65) -> View {
66 let overlay_id = remember_with_key(state.key("oid"), || signal(0u64));
67
68 let current_content = remember_state_with_key(state.key("c"), || Box(Modifier::new()));
70 *current_content.borrow_mut() = content;
71
72 let spec = AnimationSpec::tween(Duration::from_millis(200), Easing::FastOutSlowIn);
74 let anim = remember_state_with_key(state.key("anim"), || AnimatedValue::new(0.0, spec));
75 let last_target = remember_state_with_key(state.key("atarget"), || f32::NAN);
76 let anim_target = if state.is_visible() { 1.0 } else { 0.0 };
77
78 {
79 let mut a = anim.borrow_mut();
80 let mut lt = last_target.borrow_mut();
81 if lt.is_nan() || (*lt - anim_target).abs() > 1e-6 {
82 a.set_spec(spec);
83 a.set_target(anim_target);
84 *lt = anim_target;
85 }
86 drop(lt);
87 if a.update() {
88 request_frame();
89 }
90 }
91
92 let progress = *anim.borrow().get();
93 let visible = state.is_visible() || progress > 0.01;
94
95 if visible {
96 if overlay_id.get() == 0 {
97 let builder: Rc<dyn Fn() -> View> = Rc::new({
98 let state = state.clone();
99 let anim = anim.clone();
100 let modifier = modifier.clone();
101 let current_content = current_content.clone();
102 move || {
103 let progress = *anim.borrow().get();
104 let alpha = progress.min(1.0);
105 let th = theme();
106 let content = current_content.borrow().clone();
107
108 let dialog = Box(Modifier::new()
109 .min_width(280.0)
110 .max_width(560.0)
111 .then(modifier.clone())
112 .justify_content(JustifyContent::Center)
113 .background(th.surface_container_high)
114 .clip_rounded(th.shapes.extra_large)
115 .alpha(alpha))
116 .child(content);
117
118 let scrim = Box(Modifier::new()
119 .fill_max_size()
120 .background(th.scrim.with_alpha((85.0 * alpha) as u8))
121 .on_pointer_down({
122 let s = state.clone();
123 move |_| s.dismiss()
124 }));
125
126 ZStack(Modifier::new().fill_max_size().absolute()).child((
127 scrim,
128 Box(Modifier::new()
129 .fill_max_size()
130 .justify_content(JustifyContent::Center)
131 .align_items(AlignItems::Center)
132 .hit_passthrough())
133 .child(dialog),
134 ))
135 }
136 });
137
138 let id = overlay.show_entry(builder, 900.0, false);
139 overlay_id.set(id);
140 }
141 } else {
142 let prev = overlay_id.get();
143 if prev != 0 {
144 let _ = overlay.dismiss(prev);
145 overlay_id.set(0);
146 }
147 }
148
149 Box(Modifier::new())
150}
151
152#[derive(Clone, Debug)]
154pub struct AlertDialogConfig {
155 pub modifier: Modifier,
156 pub scrim_color: Color,
157 pub min_width: f32,
158 pub max_width: f32,
159 pub horizontal_padding: f32,
160}
161
162impl Default for AlertDialogConfig {
163 fn default() -> Self {
164 Self {
165 modifier: Modifier::new(),
166 scrim_color: AlertDialogDefaults::scrim_color(),
167 min_width: AlertDialogDefaults::MIN_WIDTH,
168 max_width: AlertDialogDefaults::MAX_WIDTH,
169 horizontal_padding: AlertDialogDefaults::HORIZONTAL_PADDING,
170 }
171 }
172}
173
174pub fn AlertDialog(
179 state: Rc<DialogState>,
180 overlay: OverlayHandle,
181 title: View,
182 text: View,
183 confirm_button: View,
184 dismiss_button: Option<View>,
185 config: AlertDialogConfig,
186) -> View {
187 Dialog(
188 state,
189 overlay,
190 Modifier::new()
191 .min_width(config.min_width)
192 .max_width(config.max_width)
193 .then(config.modifier),
194 super::alert_dialog_body(title, text, confirm_button, dismiss_button),
195 )
196}
197
198pub fn DatePickerDialog(
204 state: Rc<DialogState>,
205 overlay: OverlayHandle,
206 picker_state: Rc<DatePickerState>,
207 on_confirm: Rc<dyn Fn(i32, u32, u32)>,
208 on_dismiss: Rc<dyn Fn()>,
209) -> View {
210 Dialog(
211 state,
212 overlay,
213 Modifier::new(),
214 Column(Modifier::new()).child((DatePicker(picker_state.clone(), on_confirm, on_dismiss),)),
215 )
216}
217
218pub fn TimePickerDialog(
224 state: Rc<DialogState>,
225 overlay: OverlayHandle,
226 picker_state: Rc<TimePickerState>,
227 on_confirm: Rc<dyn Fn(u32, u32)>,
228 on_dismiss: Rc<dyn Fn()>,
229) -> View {
230 Dialog(
231 state,
232 overlay,
233 Modifier::new(),
234 Column(Modifier::new()).child((TimePicker(picker_state.clone(), on_confirm, on_dismiss),)),
235 )
236}