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<Arc<ImageData>> for ImageSource {
27 fn from(value: Arc<ImageData>) -> Self {
28 Self::Data(value)
29 }
30}
31
32impl From<CVImageBuffer> for ImageSource {
33 fn from(value: CVImageBuffer) -> Self {
34 Self::Surface(value)
35 }
36}
37
38pub struct Img {
39 interactivity: Interactivity,
40 source: ImageSource,
41 grayscale: bool,
42}
43
44pub fn img(source: impl Into<ImageSource>) -> Img {
45 Img {
46 interactivity: Interactivity::default(),
47 source: source.into(),
48 grayscale: false,
49 }
50}
51
52impl Img {
53 pub fn grayscale(mut self, grayscale: bool) -> Self {
54 self.grayscale = grayscale;
55 self
56 }
57}
58
59impl Element for Img {
60 type State = InteractiveElementState;
61
62 fn layout(
63 &mut self,
64 element_state: Option<Self::State>,
65 cx: &mut WindowContext,
66 ) -> (LayoutId, Self::State) {
67 self.interactivity
68 .layout(element_state, cx, |style, cx| cx.request_layout(&style, []))
69 }
70
71 fn paint(
72 self,
73 bounds: Bounds<Pixels>,
74 element_state: &mut Self::State,
75 cx: &mut WindowContext,
76 ) {
77 self.interactivity.paint(
78 bounds,
79 bounds.size,
80 element_state,
81 cx,
82 |style, _scroll_offset, cx| {
83 let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
84 cx.with_z_index(1, |cx| {
85 match self.source {
86 ImageSource::Uri(uri) => {
87 let image_future = cx.image_cache.get(uri.clone());
88 if let Some(data) = image_future
89 .clone()
90 .now_or_never()
91 .and_then(|result| result.ok())
92 {
93 let new_bounds = preserve_aspect_ratio(bounds, data.size());
94 cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
95 .log_err();
96 } else {
97 cx.spawn(|mut cx| async move {
98 if image_future.await.ok().is_some() {
99 cx.on_next_frame(|cx| cx.notify());
100 }
101 })
102 .detach();
103 }
104 }
105
106 ImageSource::Data(data) => {
107 let new_bounds = preserve_aspect_ratio(bounds, data.size());
108 cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
109 .log_err();
110 }
111
112 ImageSource::Surface(surface) => {
113 let size = size(surface.width().into(), surface.height().into());
114 let new_bounds = preserve_aspect_ratio(bounds, size);
115 // TODO: Add support for corner_radii and grayscale.
116 cx.paint_surface(new_bounds, surface);
117 }
118 };
119 });
120 },
121 )
122 }
123}
124
125impl IntoElement for Img {
126 type Element = Self;
127
128 fn element_id(&self) -> Option<crate::ElementId> {
129 self.interactivity.element_id.clone()
130 }
131
132 fn into_element(self) -> Self::Element {
133 self
134 }
135}
136
137impl Styled for Img {
138 fn style(&mut self) -> &mut StyleRefinement {
139 &mut self.interactivity.base_style
140 }
141}
142
143impl InteractiveElement for Img {
144 fn interactivity(&mut self) -> &mut Interactivity {
145 &mut self.interactivity
146 }
147}
148
149fn preserve_aspect_ratio(bounds: Bounds<Pixels>, image_size: Size<DevicePixels>) -> Bounds<Pixels> {
150 let new_size = if bounds.size.width > bounds.size.height {
151 let ratio = u32::from(image_size.height) as f32 / u32::from(image_size.width) as f32;
152 size(bounds.size.width, bounds.size.width * ratio)
153 } else {
154 let ratio = u32::from(image_size.width) as f32 / u32::from(image_size.height) as f32;
155 size(bounds.size.width * ratio, bounds.size.height)
156 };
157
158 Bounds {
159 origin: point(
160 bounds.origin.x + (bounds.size.width - new_size.width) / 2.0,
161 bounds.origin.y + (bounds.size.height - new_size.height) / 2.0,
162 ),
163 size: new_size,
164 }
165}