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, ViewExt, ZStack};
9use web_time::Duration;
10
11static DIALOG_COUNTER: AtomicU64 = AtomicU64::new(0);
12
13pub struct DialogState {
15 visible: Signal<bool>,
16 id: u64,
17}
18
19impl Default for DialogState {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl DialogState {
26 pub fn new() -> Self {
27 Self {
28 visible: signal(false),
29 id: DIALOG_COUNTER.fetch_add(1, Ordering::Relaxed),
30 }
31 }
32
33 pub fn key(&self, suffix: &str) -> String {
34 format!("dlg_{}_{}", self.id, suffix)
35 }
36
37 pub fn is_visible(&self) -> bool {
38 self.visible.get()
39 }
40
41 pub fn show(&self) {
42 self.visible.set(true);
43 }
44
45 pub fn dismiss(&self) {
46 self.visible.set(false);
47 }
48}
49
50pub fn Dialog(
57 state: Rc<DialogState>,
58 overlay: OverlayHandle,
59 modifier: Modifier,
60 content: View,
61) -> View {
62 let overlay_id = remember_with_key(state.key("oid"), || signal(0u64));
63
64 let current_content = remember_state_with_key(state.key("c"), || Box(Modifier::new()));
66 *current_content.borrow_mut() = content;
67
68 let spec = AnimationSpec::tween(Duration::from_millis(200), Easing::FastOutSlowIn);
70 let anim = remember_state_with_key(state.key("anim"), || AnimatedValue::new(0.0, spec));
71 let last_target = remember_state_with_key(state.key("atarget"), || f32::NAN);
72 let anim_target = if state.is_visible() { 1.0 } else { 0.0 };
73
74 {
75 let mut a = anim.borrow_mut();
76 let mut lt = last_target.borrow_mut();
77 if lt.is_nan() || (*lt - anim_target).abs() > 1e-6 {
78 a.set_spec(spec);
79 a.set_target(anim_target);
80 *lt = anim_target;
81 }
82 drop(lt);
83 if a.update() {
84 request_frame();
85 }
86 }
87
88 let progress = *anim.borrow().get();
89 let visible = state.is_visible() || progress > 0.01;
90
91 if visible {
92 if overlay_id.get() == 0 {
93 let builder: Rc<dyn Fn() -> View> = Rc::new({
94 let state = state.clone();
95 let anim = anim.clone();
96 let modifier = modifier.clone();
97 let current_content = current_content.clone();
98 move || {
99 let progress = *anim.borrow().get();
100 let alpha = progress.min(1.0);
101 let th = theme();
102 let content = current_content.borrow().clone();
103
104 let dialog = Box(Modifier::new()
105 .min_width(280.0)
106 .max_width(560.0)
107 .then(modifier.clone())
108 .justify_content(JustifyContent::Center)
109 .background(th.surface_container_high)
110 .clip_rounded(th.shapes.extra_large)
111 .alpha(alpha))
112 .child(content);
113
114 let scrim = Box(Modifier::new()
115 .fill_max_size()
116 .background(th.scrim.with_alpha((85.0 * alpha) as u8))
117 .on_pointer_down({
118 let s = state.clone();
119 move |_| s.dismiss()
120 }));
121
122 ZStack(Modifier::new().fill_max_size().absolute()).child((
123 scrim,
124 Box(Modifier::new()
125 .fill_max_size()
126 .justify_content(JustifyContent::Center)
127 .align_items(AlignItems::Center)
128 .hit_passthrough())
129 .child(dialog),
130 ))
131 }
132 });
133
134 let id = overlay.show_entry(builder, 900.0, false);
135 overlay_id.set(id);
136 }
137 } else {
138 let prev = overlay_id.get();
139 if prev != 0 {
140 let _ = overlay.dismiss(prev);
141 overlay_id.set(0);
142 }
143 }
144
145 Box(Modifier::new())
146}
147
148pub fn AlertDialog(
153 state: Rc<DialogState>,
154 overlay: OverlayHandle,
155 title: View,
156 text: View,
157 confirm_button: View,
158 dismiss_button: Option<View>,
159) -> View {
160 Dialog(
161 state,
162 overlay,
163 Modifier::new(),
164 super::alert_dialog_body(title, text, confirm_button, dismiss_button),
165 )
166}