1use std::sync::Arc;
2
3use crate::{
4 point, size, Bounds, DevicePixels, Element, ImageData, InteractiveElement,
5 InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedString, 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(SharedString),
16 Data(Arc<ImageData>),
17 Surface(CVImageBuffer),
18}
19
20impl From<SharedString> for ImageSource {
21 fn from(value: SharedString) -> 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 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 self,
85 bounds: Bounds<Pixels>,
86 element_state: &mut Self::State,
87 cx: &mut WindowContext,
88 ) {
89 self.interactivity.paint(
90 bounds,
91 bounds.size,
92 element_state,
93 cx,
94 |style, _scroll_offset, cx| {
95 let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
96 cx.with_z_index(1, |cx| {
97 match self.source {
98 ImageSource::Uri(uri) => {
99 let image_future = cx.image_cache.get(uri.clone());
100 if let Some(data) = image_future
101 .clone()
102 .now_or_never()
103 .and_then(|result| result.ok())
104 {
105 let new_bounds = preserve_aspect_ratio(bounds, data.size());
106 cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
107 .log_err();
108 } else {
109 cx.spawn(|mut cx| async move {
110 if image_future.await.ok().is_some() {
111 cx.on_next_frame(|cx| cx.notify());
112 }
113 })
114 .detach();
115 }
116 }
117
118 ImageSource::Data(data) => {
119 let new_bounds = preserve_aspect_ratio(bounds, data.size());
120 cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
121 .log_err();
122 }
123
124 ImageSource::Surface(surface) => {
125 let size = size(surface.width().into(), surface.height().into());
126 let new_bounds = preserve_aspect_ratio(bounds, size);
127 // TODO: Add support for corner_radii and grayscale.
128 cx.paint_surface(new_bounds, surface);
129 }
130 };
131 });
132 },
133 )
134 }
135}
136
137impl IntoElement for Img {
138 type Element = Self;
139
140 fn element_id(&self) -> Option<crate::ElementId> {
141 self.interactivity.element_id.clone()
142 }
143
144 fn into_element(self) -> Self::Element {
145 self
146 }
147}
148
149impl Styled for Img {
150 fn style(&mut self) -> &mut StyleRefinement {
151 &mut self.interactivity.base_style
152 }
153}
154
155impl InteractiveElement for Img {
156 fn interactivity(&mut self) -> &mut Interactivity {
157 &mut self.interactivity
158 }
159}
160
161fn preserve_aspect_ratio(bounds: Bounds<Pixels>, image_size: Size<DevicePixels>) -> Bounds<Pixels> {
162 let image_size = image_size.map(|dimension| Pixels::from(u32::from(dimension)));
163 let image_ratio = image_size.width / image_size.height;
164 let bounds_ratio = bounds.size.width / bounds.size.height;
165
166 let new_size = if bounds_ratio > image_ratio {
167 size(
168 image_size.width * (bounds.size.height / image_size.height),
169 bounds.size.height,
170 )
171 } else {
172 size(
173 bounds.size.width,
174 image_size.height * (bounds.size.width / image_size.width),
175 )
176 };
177
178 Bounds {
179 origin: point(
180 bounds.origin.x + (bounds.size.width - new_size.width) / 2.0,
181 bounds.origin.y + (bounds.size.height - new_size.height) / 2.0,
182 ),
183 size: new_size,
184 }
185}