Skip to main content

egui_async/egui/widgets/
refresh_button.rs

1//! A specialized button that manages a periodic background fetch and user-triggered refetches.
2
3use crate::bind::{Bind, MaybeSend};
4
5/// A button widget that tracks an asynchronous `Bind` to provide manual and automated periodic refreshing.
6#[must_use = "You should call .show() on this widget to render it"]
7pub struct RefreshButton<'a, T, E> {
8    bind: &'a mut Bind<T, E>,
9    interval_secs: f64,
10    debounce_factor: f64,
11    text: String,
12}
13
14impl<'a, T, E> RefreshButton<'a, T, E> {
15    /// Creates a new refresh button attached to a `Bind`.
16    pub fn new(bind: &'a mut Bind<T, E>) -> Self {
17        Self {
18            bind,
19            interval_secs: 60.0,
20            debounce_factor: 4.0,
21            text: "🔄".to_string(),
22        }
23    }
24
25    /// Sets the automated interval in seconds.
26    pub const fn interval_secs(mut self, secs: f64) -> Self {
27        self.interval_secs = secs;
28        self
29    }
30
31    /// Shows the button in the UI. Automatically manages `Pending` visual states.
32    pub fn show<Fut>(self, ui: &mut egui::Ui, f: impl FnOnce() -> Fut) -> egui::Response
33    where
34        Fut: Future<Output = Result<T, E>> + MaybeSend + 'static,
35        T: MaybeSend + 'static,
36        E: MaybeSend + 'static,
37    {
38        let is_pending = self.bind.is_pending();
39        let resp = ui.add_enabled(!is_pending, egui::Button::new(self.text));
40
41        // Only actually refresh when clicked if the last completion was more than 1/4 of the interval ago
42        let diff = if self.bind.since_completed() > self.interval_secs / self.debounce_factor
43            && resp.clicked()
44            && !is_pending
45        {
46            self.bind.refresh(f());
47            -1.0
48        } else {
49            self.bind.request_every_sec(f, self.interval_secs)
50        };
51
52        let hover_text = if is_pending {
53            "Refreshing...".to_string()
54        } else if diff < 0.0 {
55            "Refreshing now!".to_string()
56        } else {
57            format!("Refreshing automatically in {diff:.0}s...")
58        };
59
60        resp.on_hover_text(hover_text)
61    }
62}