1use std::sync::Arc;
2
3use crate::{
4 Bounds, Element, ImageData, InteractiveElement, InteractiveElementState, Interactivity,
5 IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
6};
7use futures::FutureExt;
8use util::ResultExt;
9
10#[derive(Clone, Debug)]
11pub enum ImageSource {
12 /// Image content will be loaded from provided URI at render time.
13 Uri(SharedString),
14 Data(Arc<ImageData>),
15}
16
17impl From<SharedString> for ImageSource {
18 fn from(value: SharedString) -> Self {
19 Self::Uri(value)
20 }
21}
22
23impl From<Arc<ImageData>> for ImageSource {
24 fn from(value: Arc<ImageData>) -> Self {
25 Self::Data(value)
26 }
27}
28
29pub struct Img {
30 interactivity: Interactivity,
31 source: Option<ImageSource>,
32 grayscale: bool,
33}
34
35pub fn img() -> Img {
36 Img {
37 interactivity: Interactivity::default(),
38 source: None,
39 grayscale: false,
40 }
41}
42
43impl Img {
44 pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
45 self.source = Some(ImageSource::from(uri.into()));
46 self
47 }
48 pub fn data(mut self, data: Arc<ImageData>) -> Self {
49 self.source = Some(ImageSource::from(data));
50 self
51 }
52
53 pub fn source(mut self, source: impl Into<ImageSource>) -> Self {
54 self.source = Some(source.into());
55 self
56 }
57 pub fn grayscale(mut self, grayscale: bool) -> Self {
58 self.grayscale = grayscale;
59 self
60 }
61}
62
63impl Element for Img {
64 type State = InteractiveElementState;
65
66 fn layout(
67 &mut self,
68 element_state: Option<Self::State>,
69 cx: &mut WindowContext,
70 ) -> (LayoutId, Self::State) {
71 self.interactivity.layout(element_state, cx, |style, cx| {
72 cx.request_layout(&style, None)
73 })
74 }
75
76 fn paint(
77 self,
78 bounds: Bounds<Pixels>,
79 element_state: &mut Self::State,
80 cx: &mut WindowContext,
81 ) {
82 self.interactivity.paint(
83 bounds,
84 bounds.size,
85 element_state,
86 cx,
87 |style, _scroll_offset, cx| {
88 let corner_radii = style.corner_radii;
89
90 if let Some(source) = self.source {
91 let image = match source {
92 ImageSource::Uri(uri) => {
93 let image_future = cx.image_cache.get(uri.clone());
94 if let Some(data) = image_future
95 .clone()
96 .now_or_never()
97 .and_then(|result| result.ok())
98 {
99 data
100 } else {
101 cx.spawn(|mut cx| async move {
102 if image_future.await.ok().is_some() {
103 cx.on_next_frame(|cx| cx.notify());
104 }
105 })
106 .detach();
107 return;
108 }
109 }
110 ImageSource::Data(image) => image,
111 };
112 let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
113 cx.with_z_index(1, |cx| {
114 cx.paint_image(bounds, corner_radii, image, self.grayscale)
115 .log_err()
116 });
117 }
118 },
119 )
120 }
121}
122
123impl IntoElement for Img {
124 type Element = Self;
125
126 fn element_id(&self) -> Option<crate::ElementId> {
127 self.interactivity.element_id.clone()
128 }
129
130 fn into_element(self) -> Self::Element {
131 self
132 }
133}
134
135impl Styled for Img {
136 fn style(&mut self) -> &mut StyleRefinement {
137 &mut self.interactivity.base_style
138 }
139}
140
141impl InteractiveElement for Img {
142 fn interactivity(&mut self) -> &mut Interactivity {
143 &mut self.interactivity
144 }
145}