relm4_components/
web_image.rs1use std::collections::VecDeque;
4use std::fmt::Debug;
5
6use relm4::gtk::prelude::{BoxExt, Cast, WidgetExt};
7use relm4::{Component, ComponentParts, ComponentSender, gtk};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct WebImage {
12 current_id: usize,
13 current_widget: gtk::Widget,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum WebImageMsg {
19 LoadImage(String),
21 Unload,
23}
24
25impl Component for WebImage {
26 type CommandOutput = Option<(usize, VecDeque<u8>)>;
27 type Input = WebImageMsg;
28 type Output = ();
29 type Init = String;
30 type Root = gtk::Box;
31 type Widgets = ();
32
33 fn init_root() -> Self::Root {
34 gtk::Box::default()
35 }
36
37 fn init(
38 url: Self::Init,
39 root: Self::Root,
40 sender: ComponentSender<Self>,
41 ) -> ComponentParts<Self> {
42 let widget = gtk::Box::default();
43 root.append(&widget);
44 let current_widget = Self::set_spinner(&root, widget.upcast_ref());
45
46 let model = Self {
47 current_id: 0,
48 current_widget,
49 };
50
51 model.load_image(&sender, url);
52
53 ComponentParts { model, widgets: () }
54 }
55
56 fn update(&mut self, input: Self::Input, sender: ComponentSender<Self>, root: &Self::Root) {
57 self.current_widget = Self::set_spinner(root, &self.current_widget);
58 self.current_id = self.current_id.wrapping_add(1);
59
60 match input {
61 WebImageMsg::LoadImage(url) => {
62 self.load_image(&sender, url);
63 }
64 WebImageMsg::Unload => (),
65 }
66 }
67
68 fn update_cmd(
69 &mut self,
70 message: Self::CommandOutput,
71 sender: ComponentSender<Self>,
72 root: &Self::Root,
73 ) {
74 if let Some((id, data)) = message
75 && id == self.current_id
76 && let Some(img) = Self::generate_image(data)
77 {
78 self.current_widget = Self::set_image(root, &self.current_widget, &img);
79 sender.output(()).ok();
80 }
81 }
82}
83
84impl WebImage {
85 #[must_use]
86 fn set_spinner(root: &<Self as Component>::Root, widget: >k::Widget) -> gtk::Widget {
87 root.remove(widget);
88 relm4::view! {
89 #[local_ref]
90 root -> gtk::Box {
91 set_halign: gtk::Align::Center,
92 set_valign: gtk::Align::Center,
93
94 #[name(spinner)]
95 gtk::Spinner {
96 start: (),
97 set_hexpand: true,
98 set_vexpand: true,
99 }
100 }
101 }
102 spinner.upcast()
103 }
104
105 #[must_use]
106 fn set_image(
107 root: &<Self as Component>::Root,
108 widget: >k::Widget,
109 img: >k::Image,
110 ) -> gtk::Widget {
111 root.remove(widget);
112 relm4::view! {
113 #[local_ref]
114 root -> gtk::Box {
115 set_halign: gtk::Align::Fill,
116 set_valign: gtk::Align::Fill,
117
118 #[local_ref]
119 img -> gtk::Image {
120 set_hexpand: true,
121 set_vexpand: true,
122 }
123 }
124 }
125 img.clone().upcast()
126 }
127
128 fn load_image(&self, sender: &ComponentSender<Self>, url: String) {
129 sender.oneshot_command(Self::get_img_data(self.current_id, url));
130 }
131
132 fn generate_image(data: VecDeque<u8>) -> Option<gtk::Image> {
133 let pixbuf = gtk::gdk_pixbuf::Pixbuf::from_read(data).ok()?;
134 let texture = gtk::gdk::Texture::for_pixbuf(&pixbuf);
135 Some(gtk::Image::from_paintable(Some(&texture)))
136 }
137
138 async fn get_img_data(id: usize, url: String) -> Option<(usize, VecDeque<u8>)> {
139 let response = reqwest::get(url).await.ok()?;
140 let bytes = response.bytes().await.ok()?;
141 Some((id, bytes.into_iter().collect()))
142 }
143}