1use fret_core::{AppWindowId, Px};
16use fret_runtime::Model;
17use fret_ui::action::UiActionHost;
18use fret_ui::element::AnyElement;
19use fret_ui::{ElementContext, UiHost};
20
21use crate::window_overlays;
22use crate::{OverlayController, OverlayRequest};
23
24pub use window_overlays::{
25 DEFAULT_MAX_TOASTS, DEFAULT_SWIPE_DRAGGING_THRESHOLD_PX, DEFAULT_SWIPE_MAX_DRAG_PX,
26 DEFAULT_SWIPE_THRESHOLD_PX, ToastAction, ToastId, ToastPosition, ToastRequest, ToastStore,
27 ToastSwipeConfig, ToastSwipeDirection, ToastVariant,
28};
29
30#[derive(Debug, Clone, Copy)]
31pub struct ToastViewport {
32 position: ToastPosition,
33 margin: Option<Px>,
34 gap: Option<Px>,
35 toast_min_width: Option<Px>,
36 toast_max_width: Option<Px>,
37 max_toasts: Option<usize>,
38 swipe_direction: ToastSwipeDirection,
39 swipe_threshold: Px,
40 swipe_max_drag: Px,
41 swipe_dragging_threshold: Px,
42}
43
44#[derive(Debug, Default)]
45struct ToastViewportConfigState {
46 max_toasts: Option<usize>,
47 swipe_direction: Option<ToastSwipeDirection>,
48 swipe_threshold: Option<Px>,
49 swipe_max_drag: Option<Px>,
50 swipe_dragging_threshold: Option<Px>,
51}
52
53impl Default for ToastViewport {
54 fn default() -> Self {
55 Self {
56 position: ToastPosition::BottomRight,
57 margin: None,
58 gap: None,
59 toast_min_width: None,
60 toast_max_width: None,
61 max_toasts: Some(DEFAULT_MAX_TOASTS),
62 swipe_direction: ToastSwipeDirection::default(),
63 swipe_threshold: Px(DEFAULT_SWIPE_THRESHOLD_PX),
64 swipe_max_drag: Px(DEFAULT_SWIPE_MAX_DRAG_PX),
65 swipe_dragging_threshold: Px(DEFAULT_SWIPE_DRAGGING_THRESHOLD_PX),
66 }
67 }
68}
69
70impl ToastViewport {
71 pub fn new() -> Self {
72 Self::default()
73 }
74
75 pub fn position(mut self, position: ToastPosition) -> Self {
76 self.position = position;
77 self
78 }
79
80 pub fn margin(mut self, margin: Px) -> Self {
81 self.margin = Some(margin);
82 self
83 }
84
85 pub fn gap(mut self, gap: Px) -> Self {
86 self.gap = Some(gap);
87 self
88 }
89
90 pub fn toast_min_width(mut self, width: Px) -> Self {
91 self.toast_min_width = Some(width);
92 self
93 }
94
95 pub fn toast_max_width(mut self, width: Px) -> Self {
96 self.toast_max_width = Some(width);
97 self
98 }
99
100 pub fn max_toasts(mut self, max_toasts: usize) -> Self {
101 self.max_toasts = Some(max_toasts.max(1));
102 self
103 }
104
105 pub fn unlimited(mut self) -> Self {
106 self.max_toasts = None;
107 self
108 }
109
110 pub fn swipe_direction(mut self, direction: ToastSwipeDirection) -> Self {
111 self.swipe_direction = direction;
112 self
113 }
114
115 pub fn swipe_threshold(mut self, threshold: Px) -> Self {
116 self.swipe_threshold = threshold;
117 self
118 }
119
120 pub fn swipe_max_drag(mut self, max_drag: Px) -> Self {
121 self.swipe_max_drag = max_drag;
122 self
123 }
124
125 pub fn swipe_dragging_threshold(mut self, threshold: Px) -> Self {
126 self.swipe_dragging_threshold = threshold;
127 self
128 }
129
130 #[track_caller]
131 pub fn into_element<H: UiHost>(self, cx: &mut ElementContext<'_, H>) -> AnyElement {
132 cx.scope(|cx| {
133 let id = cx.root_id();
134 let store = OverlayController::toast_store(&mut *cx.app);
135
136 let config_changed = cx.slot_state(ToastViewportConfigState::default, |st| {
137 let mut changed = false;
138 if st.max_toasts != self.max_toasts {
139 st.max_toasts = self.max_toasts;
140 changed = true;
141 }
142 if st.swipe_direction != Some(self.swipe_direction) {
143 st.swipe_direction = Some(self.swipe_direction);
144 changed = true;
145 }
146 if st.swipe_threshold != Some(self.swipe_threshold) {
147 st.swipe_threshold = Some(self.swipe_threshold);
148 changed = true;
149 }
150 if st.swipe_max_drag != Some(self.swipe_max_drag) {
151 st.swipe_max_drag = Some(self.swipe_max_drag);
152 changed = true;
153 }
154 if st.swipe_dragging_threshold != Some(self.swipe_dragging_threshold) {
155 st.swipe_dragging_threshold = Some(self.swipe_dragging_threshold);
156 changed = true;
157 }
158 changed
159 });
160 if config_changed {
161 let _ = cx.app.models_mut().update(&store, |st| {
162 st.set_window_max_toasts(cx.window, self.max_toasts);
163 st.set_window_swipe_config_with_options(
164 cx.window,
165 ToastSwipeConfig {
166 direction: self.swipe_direction,
167 threshold: self.swipe_threshold,
168 max_drag: self.swipe_max_drag,
169 dragging_threshold: self.swipe_dragging_threshold,
170 },
171 );
172 });
173 }
174
175 let mut request = OverlayRequest::toast_layer(id, store).toast_position(self.position);
176 if let Some(margin) = self.margin {
177 request = request.toast_margin(margin);
178 }
179 if let Some(gap) = self.gap {
180 request = request.toast_gap(gap);
181 }
182 if let Some(width) = self.toast_min_width {
183 request = request.toast_min_width(width);
184 }
185 if let Some(width) = self.toast_max_width {
186 request = request.toast_max_width(width);
187 }
188 OverlayController::request(cx, request);
189
190 cx.stack(|_cx| Vec::new())
191 })
192 }
193}
194
195#[derive(Clone)]
196pub struct ToastController {
197 store: Model<ToastStore>,
198}
199
200impl std::fmt::Debug for ToastController {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 f.debug_struct("ToastController")
203 .field("store", &"<model>")
204 .finish()
205 }
206}
207
208impl ToastController {
209 pub fn global<H: UiHost>(app: &mut H) -> Self {
210 Self {
211 store: OverlayController::toast_store(app),
212 }
213 }
214
215 pub fn toast(
220 &self,
221 host: &mut dyn UiActionHost,
222 window: AppWindowId,
223 request: ToastRequest,
224 ) -> ToastId {
225 OverlayController::toast_action(host, self.store.clone(), window, request)
226 }
227
228 pub fn dismiss(&self, host: &mut dyn UiActionHost, window: AppWindowId, id: ToastId) -> bool {
229 OverlayController::dismiss_toast_action(host, self.store.clone(), window, id)
230 }
231
232 pub fn dismiss_all(&self, host: &mut dyn UiActionHost, window: AppWindowId) -> usize {
233 OverlayController::dismiss_all_toasts_action(host, self.store.clone(), window)
234 }
235}