1use crate::core::layout;
3use crate::core::mouse;
4use crate::core::overlay;
5use crate::core::renderer;
6use crate::core::text;
7use crate::core::time::{Duration, Instant};
8use crate::core::widget;
9use crate::core::widget::tree::{self, Tree};
10use crate::core::window;
11use crate::core::{
12 self, Clipboard, Element, Event, Layout, Length, Pixels, Rectangle, Shell,
13 Size, Vector, Widget,
14};
15
16#[allow(missing_debug_implementations)]
20pub struct Pop<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> {
21 content: Element<'a, Message, Theme, Renderer>,
22 key: Option<text::Fragment<'a>>,
23 on_show: Option<Box<dyn Fn(Size) -> Message + 'a>>,
24 on_resize: Option<Box<dyn Fn(Size) -> Message + 'a>>,
25 on_hide: Option<Message>,
26 anticipate: Pixels,
27 delay: Duration,
28}
29
30impl<'a, Message, Theme, Renderer> Pop<'a, Message, Theme, Renderer>
31where
32 Renderer: core::Renderer,
33 Message: Clone,
34{
35 pub fn new(
37 content: impl Into<Element<'a, Message, Theme, Renderer>>,
38 ) -> Self {
39 Self {
40 content: content.into(),
41 key: None,
42 on_show: None,
43 on_resize: None,
44 on_hide: None,
45 anticipate: Pixels::ZERO,
46 delay: Duration::ZERO,
47 }
48 }
49
50 pub fn on_show(mut self, on_show: impl Fn(Size) -> Message + 'a) -> Self {
54 self.on_show = Some(Box::new(on_show));
55 self
56 }
57
58 pub fn on_resize(
62 mut self,
63 on_resize: impl Fn(Size) -> Message + 'a,
64 ) -> Self {
65 self.on_resize = Some(Box::new(on_resize));
66 self
67 }
68
69 pub fn on_hide(mut self, on_hide: Message) -> Self {
71 self.on_hide = Some(on_hide);
72 self
73 }
74
75 pub fn key(mut self, key: impl text::IntoFragment<'a>) -> Self {
79 self.key = Some(key.into_fragment());
80 self
81 }
82
83 pub fn anticipate(mut self, distance: impl Into<Pixels>) -> Self {
89 self.anticipate = distance.into();
90 self
91 }
92
93 pub fn delay(mut self, delay: impl Into<Duration>) -> Self {
102 self.delay = delay.into();
103 self
104 }
105}
106
107#[derive(Debug, Clone, Default)]
108struct State {
109 has_popped_in: bool,
110 should_notify_at: Option<(bool, Instant)>,
111 last_size: Option<Size>,
112 last_key: Option<String>,
113}
114
115impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
116 for Pop<'_, Message, Theme, Renderer>
117where
118 Message: Clone,
119 Renderer: core::Renderer,
120{
121 fn tag(&self) -> tree::Tag {
122 tree::Tag::of::<State>()
123 }
124
125 fn state(&self) -> tree::State {
126 tree::State::new(State::default())
127 }
128
129 fn children(&self) -> Vec<Tree> {
130 vec![Tree::new(&self.content)]
131 }
132
133 fn diff(&self, tree: &mut Tree) {
134 tree.diff_children(&[&self.content]);
135 }
136
137 fn update(
138 &mut self,
139 tree: &mut Tree,
140 event: &Event,
141 layout: Layout<'_>,
142 cursor: mouse::Cursor,
143 renderer: &Renderer,
144 clipboard: &mut dyn Clipboard,
145 shell: &mut Shell<'_, Message>,
146 viewport: &Rectangle,
147 ) {
148 if let Event::Window(window::Event::RedrawRequested(now)) = &event {
149 let state = tree.state.downcast_mut::<State>();
150
151 if state.has_popped_in
152 && state.last_key.as_deref() != self.key.as_deref()
153 {
154 state.has_popped_in = false;
155 state.should_notify_at = None;
156 state.last_key =
157 self.key.as_ref().cloned().map(text::Fragment::into_owned);
158 }
159
160 let bounds = layout.bounds();
161 let top_left_distance = viewport.distance(bounds.position());
162
163 let bottom_right_distance = viewport
164 .distance(bounds.position() + Vector::from(bounds.size()));
165
166 let distance = top_left_distance.min(bottom_right_distance);
167
168 if state.has_popped_in {
169 if distance <= self.anticipate.0 {
170 if let Some(on_resize) = &self.on_resize {
171 let size = bounds.size();
172
173 if Some(size) != state.last_size {
174 state.last_size = Some(size);
175 shell.publish(on_resize(size));
176 }
177 }
178 } else if self.on_hide.is_some() {
179 state.has_popped_in = false;
180 state.should_notify_at = Some((false, *now + self.delay));
181 }
182 } else if self.on_show.is_some() && distance <= self.anticipate.0 {
183 let size = bounds.size();
184
185 state.has_popped_in = true;
186 state.should_notify_at = Some((true, *now + self.delay));
187 state.last_size = Some(size);
188 }
189
190 match &state.should_notify_at {
191 Some((has_popped_in, at)) if at <= now => {
192 if *has_popped_in {
193 if let Some(on_show) = &self.on_show {
194 shell.publish(on_show(layout.bounds().size()));
195 }
196 } else if let Some(on_hide) = &self.on_hide {
197 shell.publish(on_hide.clone());
198 }
199
200 state.should_notify_at = None;
201 }
202 Some((_, at)) => {
203 shell.request_redraw_at(*at);
204 }
205 None => {}
206 }
207 }
208
209 self.content.as_widget_mut().update(
210 &mut tree.children[0],
211 event,
212 layout,
213 cursor,
214 renderer,
215 clipboard,
216 shell,
217 viewport,
218 );
219 }
220
221 fn size(&self) -> Size<Length> {
222 self.content.as_widget().size()
223 }
224
225 fn size_hint(&self) -> Size<Length> {
226 self.content.as_widget().size_hint()
227 }
228
229 fn layout(
230 &self,
231 tree: &mut Tree,
232 renderer: &Renderer,
233 limits: &layout::Limits,
234 ) -> layout::Node {
235 self.content
236 .as_widget()
237 .layout(&mut tree.children[0], renderer, limits)
238 }
239
240 fn draw(
241 &self,
242 tree: &Tree,
243 renderer: &mut Renderer,
244 theme: &Theme,
245 style: &renderer::Style,
246 layout: layout::Layout<'_>,
247 cursor: mouse::Cursor,
248 viewport: &Rectangle,
249 ) {
250 self.content.as_widget().draw(
251 &tree.children[0],
252 renderer,
253 theme,
254 style,
255 layout,
256 cursor,
257 viewport,
258 );
259 }
260
261 fn operate(
262 &self,
263 tree: &mut Tree,
264 layout: core::Layout<'_>,
265 renderer: &Renderer,
266 operation: &mut dyn widget::Operation,
267 ) {
268 self.content.as_widget().operate(
269 &mut tree.children[0],
270 layout,
271 renderer,
272 operation,
273 );
274 }
275
276 fn mouse_interaction(
277 &self,
278 tree: &Tree,
279 layout: core::Layout<'_>,
280 cursor: mouse::Cursor,
281 viewport: &Rectangle,
282 renderer: &Renderer,
283 ) -> mouse::Interaction {
284 self.content.as_widget().mouse_interaction(
285 &tree.children[0],
286 layout,
287 cursor,
288 viewport,
289 renderer,
290 )
291 }
292
293 fn overlay<'b>(
294 &'b mut self,
295 tree: &'b mut Tree,
296 layout: core::Layout<'_>,
297 renderer: &Renderer,
298 translation: core::Vector,
299 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
300 self.content.as_widget_mut().overlay(
301 &mut tree.children[0],
302 layout,
303 renderer,
304 translation,
305 )
306 }
307}
308
309impl<'a, Message, Theme, Renderer> From<Pop<'a, Message, Theme, Renderer>>
310 for Element<'a, Message, Theme, Renderer>
311where
312 Renderer: core::Renderer + 'a,
313 Theme: 'a,
314 Message: Clone + 'a,
315{
316 fn from(pop: Pop<'a, Message, Theme, Renderer>) -> Self {
317 Element::new(pop)
318 }
319}