Skip to content

Commit

Permalink
Implement delay for pop widget 🎉
Browse files Browse the repository at this point in the history
  • Loading branch information
hecrj committed Feb 19, 2025
1 parent 42f6018 commit ffc412d
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 39 deletions.
4 changes: 2 additions & 2 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ impl<F, A, B, O> Function<A, B, O> for F
where
F: Fn(A, B) -> O,
Self: Sized,
A: Copy,
A: Clone,
{
fn with(self, prefix: A) -> impl Fn(B) -> O {
move |result| self(prefix, result)
move |result| self(prefix.clone(), result)
}
}
38 changes: 10 additions & 28 deletions examples/markdown/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ mod icon;
use iced::animation;
use iced::clipboard;
use iced::highlighter;
use iced::task;
use iced::time::{self, milliseconds, Instant};
use iced::widget::{
self, button, center_x, container, horizontal_space, hover, image,
markdown, pop, right, row, scrollable, text_editor, toggler,
};
use iced::window;
use iced::{Animation, Element, Fill, Font, Subscription, Task, Theme};
use iced::{
Animation, Element, Fill, Font, Function, Subscription, Task, Theme,
};

use std::collections::HashMap;
use std::io;
Expand Down Expand Up @@ -39,9 +40,7 @@ enum Mode {
}

enum Image {
Loading {
_download: task::Handle,
},
Loading,
Ready {
handle: image::Handle,
fade_in: Animation<bool>,
Expand Down Expand Up @@ -89,9 +88,6 @@ impl Markdown {
if is_edit {
self.content = markdown::Content::parse(&self.raw.text());
self.mode = Mode::Preview;

let images = self.content.images();
self.images.retain(|url, _image| images.contains(url));
}

Task::none()
Expand All @@ -107,27 +103,12 @@ impl Markdown {
return Task::none();
}

let (download_image, handle) = Task::future({
let url = url.clone();

async move {
// Wait half a second for further editions before attempting download
tokio::time::sleep(milliseconds(500)).await;
download_image(url).await
}
})
.abortable();
let _ = self.images.insert(url.clone(), Image::Loading);

let _ = self.images.insert(
url.clone(),
Image::Loading {
_download: handle.abort_on_drop(),
},
);

download_image.map(move |result| {
Message::ImageDownloaded(url.clone(), result)
})
Task::perform(
download_image(url.clone()),
Message::ImageDownloaded.with(url),
)
}
Message::ImageDownloaded(url, result) => {
let _ = self.images.insert(
Expand Down Expand Up @@ -286,6 +267,7 @@ impl<'a> markdown::Viewer<'a, Message> for CustomViewer<'a> {
} else {
pop(horizontal_space())
.key(url.as_str())
.delay(milliseconds(500))
.on_show(|_size| Message::ImageShown(url.clone()))
.into()
}
Expand Down
47 changes: 38 additions & 9 deletions widget/src/pop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::text;
use crate::core::time::{Duration, Instant};
use crate::core::widget;
use crate::core::widget::tree::{self, Tree};
use crate::core::window;
Expand All @@ -23,6 +24,7 @@ pub struct Pop<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> {
on_resize: Option<Box<dyn Fn(Size) -> Message + 'a>>,
on_hide: Option<Message>,
anticipate: Pixels,
delay: Duration,
}

impl<'a, Message, Theme, Renderer> Pop<'a, Message, Theme, Renderer>
Expand All @@ -41,6 +43,7 @@ where
on_resize: None,
on_hide: None,
anticipate: Pixels::ZERO,
delay: Duration::ZERO,
}
}

Expand Down Expand Up @@ -86,11 +89,21 @@ where
self.anticipate = distance.into();
self
}

/// Sets the amount of time to wait before firing an [`on_show`] or

Check failure on line 93 in widget/src/pop.rs

View workflow job for this annotation

GitHub Actions / all

unresolved link to `on_show`

Check failure on line 93 in widget/src/pop.rs

View workflow job for this annotation

GitHub Actions / all

unresolved link to `on_show`
/// [`on_hide`] event; after the content is shown or hidden.

Check failure on line 94 in widget/src/pop.rs

View workflow job for this annotation

GitHub Actions / all

unresolved link to `on_hide`

Check failure on line 94 in widget/src/pop.rs

View workflow job for this annotation

GitHub Actions / all

unresolved link to `on_hide`
///
/// When combined with [`key`], this can be useful to debounce key changes.

Check failure on line 96 in widget/src/pop.rs

View workflow job for this annotation

GitHub Actions / all

unresolved link to `key`

Check failure on line 96 in widget/src/pop.rs

View workflow job for this annotation

GitHub Actions / all

unresolved link to `key`
pub fn delay(mut self, delay: impl Into<Duration>) -> Self {
self.delay = delay.into();
self
}
}

#[derive(Debug, Clone, Default)]
struct State {
has_popped_in: bool,
should_notify_at: Option<(bool, Instant)>,
last_size: Option<Size>,
last_key: Option<String>,
}
Expand Down Expand Up @@ -128,13 +141,14 @@ where
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) {
if let Event::Window(window::Event::RedrawRequested(_)) = &event {
if let Event::Window(window::Event::RedrawRequested(now)) = &event {
let state = tree.state.downcast_mut::<State>();

if state.has_popped_in
&& state.last_key.as_deref() != self.key.as_deref()
{
state.has_popped_in = false;
state.should_notify_at = None;
state.last_key =
self.key.as_ref().cloned().map(text::Fragment::into_owned);
}
Expand All @@ -157,19 +171,34 @@ where
shell.publish(on_resize(size));
}
}
} else if let Some(on_hide) = &self.on_hide {
} else if self.on_hide.is_some() {
state.has_popped_in = false;
shell.publish(on_hide.clone());
state.should_notify_at = Some((false, *now + self.delay));
}
} else if let Some(on_show) = &self.on_show {
if distance <= self.anticipate.0 {
let size = bounds.size();
} else if self.on_show.is_some() && distance <= self.anticipate.0 {
let size = bounds.size();

state.has_popped_in = true;
state.last_size = Some(size);
state.has_popped_in = true;
state.should_notify_at = Some((true, *now + self.delay));
state.last_size = Some(size);
}

shell.publish(on_show(size));
match &state.should_notify_at {
Some((has_popped_in, at)) if at <= now => {
if *has_popped_in {
if let Some(on_show) = &self.on_show {
shell.publish(on_show(layout.bounds().size()));
}
} else if let Some(on_hide) = &self.on_hide {
shell.publish(on_hide.clone());
}

state.should_notify_at = None;
}
Some((_, at)) => {
shell.request_redraw_at(*at);
}
None => {}
}
}

Expand Down

0 comments on commit ffc412d

Please sign in to comment.