use std::fmt::Debug;
use crate::bind::{Bind, MaybeSend, StateWithData};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum StateLayout {
FillAndCenter,
#[default]
CenterHorizontal,
Inline,
}
#[must_use = "You should call .show() on this widget to render it"]
pub struct AsyncView<'a, T, E> {
bind: &'a mut Bind<T, E>,
loading_text: String,
error_retry_text: String,
state_layout: StateLayout,
}
impl<'a, T, E> AsyncView<'a, T, E> {
pub fn new(bind: &'a mut Bind<T, E>) -> Self {
Self {
bind,
loading_text: "Loading...".to_string(),
error_retry_text: "Retry".to_string(),
state_layout: StateLayout::default(),
}
}
pub fn loading_text(mut self, text: impl Into<String>) -> Self {
self.loading_text = text.into();
self
}
pub fn error_retry_text(mut self, text: impl Into<String>) -> Self {
self.error_retry_text = text.into();
self
}
pub const fn state_layout(mut self, layout: StateLayout) -> Self {
self.state_layout = layout;
self
}
pub fn show<Fut, R>(
self,
ui: &mut egui::Ui,
fetch: impl FnOnce() -> Fut,
on_ok: impl FnOnce(&mut egui::Ui, &T) -> R,
) -> Option<R>
where
Fut: Future<Output = Result<T, E>> + MaybeSend + 'static,
T: MaybeSend + 'static,
E: Debug + MaybeSend + 'static,
{
let mut should_clear = false;
let mut ret = None;
match self.bind.state_or_request(fetch) {
StateWithData::Idle | StateWithData::Pending => {
Self::apply_layout(self.state_layout, ui, |ui| {
ui.spinner();
ui.add_space(8.0);
ui.label(&self.loading_text);
});
}
StateWithData::Finished(data) => {
ret = Some(Self::apply_layout(self.state_layout, ui, |ui| {
on_ok(ui, data)
}));
}
StateWithData::Failed(err) => {
Self::apply_layout(self.state_layout, ui, |ui| {
ui.label(
egui::RichText::new("⚠ Request Failed")
.color(ui.visuals().error_fg_color)
.size(16.0)
.strong(),
);
ui.add_space(8.0);
ui.label(format!("{err:?}"));
ui.add_space(12.0);
if ui.button(&self.error_retry_text).clicked() {
should_clear = true;
}
});
}
}
if should_clear {
self.bind.clear(); }
ret
}
fn apply_layout<R>(
layout: StateLayout,
ui: &mut egui::Ui,
add_contents: impl FnOnce(&mut egui::Ui) -> R,
) -> R {
match layout {
StateLayout::FillAndCenter => {
ui.centered_and_justified(|ui| ui.vertical_centered(add_contents).inner)
.inner
}
StateLayout::CenterHorizontal => ui.vertical_centered(add_contents).inner,
StateLayout::Inline => add_contents(ui),
}
}
}