1use std::sync::Arc;
2
3use crate::{
4 point, size, BorrowWindow, Bounds, DevicePixels, Element, ImageData, InteractiveElement,
5 InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUrl, Size,
6 StyleRefinement, Styled, WindowContext,
7};
8use futures::FutureExt;
9use media::core_video::CVImageBuffer;
10use util::ResultExt;
11
12#[derive(Clone, Debug)]
13pub enum ImageSource {
14 /// Image content will be loaded from provided URI at render time.
15 Uri(SharedUrl),
16 Data(Arc<ImageData>),
17 Surface(CVImageBuffer),
18}
19
20impl From<SharedUrl> for ImageSource {
21 fn from(value: SharedUrl) -> Self {
22 Self::Uri(value)
23 }
24}
25
26impl From<&'static str> for ImageSource {
27 fn from(uri: &'static str) -> Self {
28 Self::Uri(uri.into())
29 }
30}
31
32impl From<String> for ImageSource {
33 fn from(uri: String) -> Self {
34 Self::Uri(uri.into())
35 }
36}
37
38impl From<Arc<ImageData>> for ImageSource {
39 fn from(value: Arc<ImageData>) -> Self {
40 Self::Data(value)
41 }
42}
43
44impl From<CVImageBuffer> for ImageSource {
45 fn from(value: CVImageBuffer) -> Self {
46 Self::Surface(value)
47 }
48}
49
50pub struct Img {
51 interactivity: Interactivity,
52 source: ImageSource,
53 grayscale: bool,
54}
55
56pub fn img(source: impl Into<ImageSource>) -> Img {
57 Img {
58 interactivity: Interactivity::default(),
59 source: source.into(),
60 grayscale: false,
61 }
62}
63
64impl Img {
65 pub fn grayscale(mut self, grayscale: bool) -> Self {
66 self.grayscale = grayscale;
67 self
68 }
69}
70
71impl Element for Img {
72 type State = InteractiveElementState;
73
74 fn request_layout(
75 &mut self,
76 element_state: Option<Self::State>,
77 cx: &mut WindowContext,
78 ) -> (LayoutId, Self::State) {
79 self.interactivity
80 .layout(element_state, cx, |style, cx| cx.request_layout(&style, []))
81 }
82
83 fn paint(
84 &mut self,
85 bounds: Bounds<Pixels>,
86 element_state: &mut Self::State,
87 cx: &mut WindowContext,
88 ) {
89 let source = self.source.clone();
90 self.interactivity.paint(
91 bounds,
92 bounds.size,
93 element_state,
94 cx,
95 |style, _scroll_offset, cx| {
96 let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
97 cx.with_z_index(1, |cx| {
98 match source {
99 ImageSource::Uri(uri) => {
100 let image_future = cx.image_cache.get(uri.clone());
101 if let Some(data) = image_future
102 .clone()
103 .now_or_never()
104 .and_then(|result| result.ok())
105 {
106 let new_bounds = preserve_aspect_ratio(bounds, data.size());
107 cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
108 .log_err();
109 } else {
110 cx.spawn(|mut cx| async move {
111 if image_future.await.ok().is_some() {
112 cx.on_next_frame(|cx| cx.notify());
113 }
114 })
115 .detach();
116 }
117 }
118
119 ImageSource::Data(data) => {
120 let new_bounds = preserve_aspect_ratio(bounds, data.size());
121 cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
122 .log_err();
123 }
124
125 ImageSource::Surface(surface) => {
126 let size = size(surface.width().into(), surface.height().into());
127 let new_bounds = preserve_aspect_ratio(bounds, size);
128 // TODO: Add support for corner_radii and grayscale.
129 cx.paint_surface(new_bounds, surface);
130 }
131 };
132 });
133 },
134 )
135 }
136}
137
138impl IntoElement for Img {
139 type Element = Self;
140
141 fn element_id(&self) -> Option<crate::ElementId> {
142 self.interactivity.element_id.clone()
143 }
144
145 fn into_element(self) -> Self::Element {
146 self
147 }
148}
149
150impl Styled for Img {
151 fn style(&mut self) -> &mut StyleRefinement {
152 &mut self.interactivity.base_style
153 }
154}
155
156impl InteractiveElement for Img {
157 fn interactivity(&mut self) -> &mut Interactivity {
158 &mut self.interactivity
159 }
160}
161
162fn preserve_aspect_ratio(bounds: Bounds<Pixels>, image_size: Size<DevicePixels>) -> Bounds<Pixels> {
163 let image_size = image_size.map(|dimension| Pixels::from(u32::from(dimension)));
164 let image_ratio = image_size.width / image_size.height;
165 let bounds_ratio = bounds.size.width / bounds.size.height;
166
167 let new_size = if bounds_ratio > image_ratio {
168 size(
169 image_size.width * (bounds.size.height / image_size.height),
170 bounds.size.height,
171 )
172 } else {
173 size(
174 bounds.size.width,
175 image_size.height * (bounds.size.width / image_size.width),
176 )
177 };
178
179 Bounds {
180 origin: point(
181 bounds.origin.x + (bounds.size.width - new_size.width) / 2.0,
182 bounds.origin.y + (bounds.size.height - new_size.height) / 2.0,
183 ),
184 size: new_size,
185 }
186}