egui_async/egui/widgets/
async_view.rs1use std::fmt::Debug;
4
5use crate::bind::{Bind, MaybeSend, StateWithData};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum StateLayout {
10 FillAndCenter,
15 #[default]
19 CenterHorizontal,
20 Inline,
22}
23
24#[must_use = "You should call .show() on this widget to render it"]
29pub struct AsyncView<'a, T, E> {
30 bind: &'a mut Bind<T, E>,
31 loading_text: String,
32 error_retry_text: String,
33 state_layout: StateLayout,
34}
35
36impl<'a, T, E> AsyncView<'a, T, E> {
37 pub fn new(bind: &'a mut Bind<T, E>) -> Self {
39 Self {
40 bind,
41 loading_text: "Loading...".to_string(),
42 error_retry_text: "Retry".to_string(),
43 state_layout: StateLayout::default(),
44 }
45 }
46
47 pub fn loading_text(mut self, text: impl Into<String>) -> Self {
49 self.loading_text = text.into();
50 self
51 }
52
53 pub fn error_retry_text(mut self, text: impl Into<String>) -> Self {
55 self.error_retry_text = text.into();
56 self
57 }
58
59 pub const fn state_layout(mut self, layout: StateLayout) -> Self {
61 self.state_layout = layout;
62 self
63 }
64
65 pub fn show<Fut, R>(
71 self,
72 ui: &mut egui::Ui,
73 fetch: impl FnOnce() -> Fut,
74 on_ok: impl FnOnce(&mut egui::Ui, &T) -> R,
75 ) -> Option<R>
76 where
77 Fut: Future<Output = Result<T, E>> + MaybeSend + 'static,
78 T: MaybeSend + 'static,
79 E: Debug + MaybeSend + 'static,
80 {
81 let mut should_clear = false;
82 let mut ret = None;
83
84 match self.bind.state_or_request(fetch) {
85 StateWithData::Idle | StateWithData::Pending => {
86 Self::apply_layout(self.state_layout, ui, |ui| {
87 ui.spinner();
88 ui.add_space(8.0);
89 ui.label(&self.loading_text);
90 });
91 }
92 StateWithData::Finished(data) => {
93 ret = Some(Self::apply_layout(self.state_layout, ui, |ui| {
94 on_ok(ui, data)
95 }));
96 }
97 StateWithData::Failed(err) => {
98 Self::apply_layout(self.state_layout, ui, |ui| {
99 ui.label(
100 egui::RichText::new("⚠ Request Failed")
101 .color(ui.visuals().error_fg_color)
102 .size(16.0)
103 .strong(),
104 );
105 ui.add_space(8.0);
106 ui.label(format!("{err:?}"));
107 ui.add_space(12.0);
108
109 if ui.button(&self.error_retry_text).clicked() {
110 should_clear = true;
111 }
112 });
113 }
114 }
115
116 if should_clear {
118 self.bind.clear(); }
120
121 ret
122 }
123
124 fn apply_layout<R>(
126 layout: StateLayout,
127 ui: &mut egui::Ui,
128 add_contents: impl FnOnce(&mut egui::Ui) -> R,
129 ) -> R {
130 match layout {
131 StateLayout::FillAndCenter => {
132 ui.centered_and_justified(|ui| ui.vertical_centered(add_contents).inner)
133 .inner
134 }
135 StateLayout::CenterHorizontal => ui.vertical_centered(add_contents).inner,
136 StateLayout::Inline => add_contents(ui),
137 }
138 }
139}