1pub mod viewer;
20use iced_runtime::core::widget::Id;
21pub use viewer::Viewer;
22
23use crate::core::image;
24use crate::core::layout;
25use crate::core::mouse;
26use crate::core::renderer;
27use crate::core::widget::Tree;
28use crate::core::{
29 ContentFit, Element, Layout, Length, Point, Rectangle, Rotation, Size,
30 Vector, Widget,
31};
32
33pub use image::{FilterMethod, Handle};
34
35#[cfg(feature = "a11y")]
36use std::borrow::Cow;
37
38pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> {
40 Viewer::new(handle)
41}
42
43#[derive(Debug)]
62pub struct Image<'a, Handle = image::Handle> {
63 id: Id,
64 #[cfg(feature = "a11y")]
65 name: Option<Cow<'a, str>>,
66 #[cfg(feature = "a11y")]
67 description: Option<iced_accessibility::Description<'a>>,
68 #[cfg(feature = "a11y")]
69 label: Option<Vec<iced_accessibility::accesskit::NodeId>>,
70 handle: Handle,
71 width: Length,
72 height: Length,
73 content_fit: ContentFit,
74 filter_method: FilterMethod,
75 rotation: Rotation,
76 opacity: f32,
77 border_radius: [f32; 4],
78 phantom_data: std::marker::PhantomData<&'a ()>,
79}
80
81impl<'a, Handle> Image<'a, Handle> {
82 pub fn new(handle: impl Into<Handle>) -> Self {
84 Image {
85 id: Id::unique(),
86 #[cfg(feature = "a11y")]
87 name: None,
88 #[cfg(feature = "a11y")]
89 description: None,
90 #[cfg(feature = "a11y")]
91 label: None,
92 handle: handle.into(),
93 width: Length::Shrink,
94 height: Length::Shrink,
95 content_fit: ContentFit::default(),
96 filter_method: FilterMethod::default(),
97 rotation: Rotation::default(),
98 opacity: 1.0,
99 border_radius: [0.0; 4],
100 phantom_data: std::marker::PhantomData,
101 }
102 }
103
104 pub fn border_radius(mut self, border_radius: [f32; 4]) -> Self {
106 self.border_radius = border_radius;
107 self
108 }
109
110 pub fn width(mut self, width: impl Into<Length>) -> Self {
112 self.width = width.into();
113 self
114 }
115
116 pub fn height(mut self, height: impl Into<Length>) -> Self {
118 self.height = height.into();
119 self
120 }
121
122 pub fn content_fit(mut self, content_fit: ContentFit) -> Self {
126 self.content_fit = content_fit;
127 self
128 }
129
130 pub fn filter_method(mut self, filter_method: FilterMethod) -> Self {
132 self.filter_method = filter_method;
133 self
134 }
135
136 pub fn rotation(mut self, rotation: impl Into<Rotation>) -> Self {
138 self.rotation = rotation.into();
139 self
140 }
141
142 pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
147 self.opacity = opacity.into();
148 self
149 }
150
151 #[cfg(feature = "a11y")]
152 pub fn name(mut self, name: impl Into<Cow<'a, str>>) -> Self {
154 self.name = Some(name.into());
155 self
156 }
157
158 #[cfg(feature = "a11y")]
159 pub fn description_widget<T: iced_accessibility::Describes>(
161 mut self,
162 description: &T,
163 ) -> Self {
164 self.description = Some(iced_accessibility::Description::Id(
165 description.description(),
166 ));
167 self
168 }
169
170 #[cfg(feature = "a11y")]
171 pub fn description(mut self, description: impl Into<Cow<'a, str>>) -> Self {
173 self.description =
174 Some(iced_accessibility::Description::Text(description.into()));
175 self
176 }
177
178 #[cfg(feature = "a11y")]
179 pub fn label(mut self, label: &dyn iced_accessibility::Labels) -> Self {
181 self.label =
182 Some(label.label().into_iter().map(|l| l.into()).collect());
183 self
184 }
185}
186
187pub fn layout<Renderer, Handle>(
189 renderer: &Renderer,
190 limits: &layout::Limits,
191 handle: &Handle,
192 width: Length,
193 height: Length,
194 content_fit: ContentFit,
195 rotation: Rotation,
196 _border_radius: [f32; 4],
197) -> layout::Node
198where
199 Renderer: image::Renderer<Handle = Handle>,
200{
201 let image_size = renderer.measure_image(handle);
203 let image_size =
204 Size::new(image_size.width as f32, image_size.height as f32);
205
206 let rotated_size = rotation.apply(image_size);
208
209 let raw_size = limits.resolve(width, height, rotated_size);
211
212 let full_size = content_fit.fit(rotated_size, raw_size);
214
215 let final_size = Size {
217 width: match width {
218 Length::Shrink => f32::min(raw_size.width, full_size.width),
219 _ => raw_size.width,
220 },
221 height: match height {
222 Length::Shrink => f32::min(raw_size.height, full_size.height),
223 _ => raw_size.height,
224 },
225 };
226
227 layout::Node::new(final_size)
228}
229
230pub fn draw<Renderer, Handle>(
232 renderer: &mut Renderer,
233 layout: Layout<'_>,
234 handle: &Handle,
235 content_fit: ContentFit,
236 filter_method: FilterMethod,
237 rotation: Rotation,
238 opacity: f32,
239 border_radius: [f32; 4],
240) where
241 Renderer: image::Renderer<Handle = Handle>,
242 Handle: Clone,
243{
244 let Size { width, height } = renderer.measure_image(handle);
245 let image_size = Size::new(width as f32, height as f32);
246 let rotated_size = rotation.apply(image_size);
247
248 let bounds = layout.bounds();
249 let adjusted_fit = content_fit.fit(rotated_size, bounds.size());
250
251 let scale = Vector::new(
252 adjusted_fit.width / rotated_size.width,
253 adjusted_fit.height / rotated_size.height,
254 );
255
256 let final_size = image_size * scale;
257
258 let position = match content_fit {
259 ContentFit::None => Point::new(
260 bounds.x + (rotated_size.width - adjusted_fit.width) / 2.0,
261 bounds.y + (rotated_size.height - adjusted_fit.height) / 2.0,
262 ),
263 _ => Point::new(
264 bounds.center_x() - final_size.width / 2.0,
265 bounds.center_y() - final_size.height / 2.0,
266 ),
267 };
268
269 let drawing_bounds = Rectangle::new(position, final_size);
270
271 let offset = Vector::new(
272 (bounds.width - adjusted_fit.width).max(0.0) / 2.0,
273 (bounds.height - adjusted_fit.height).max(0.0) / 2.0,
274 );
275
276 let render = |renderer: &mut Renderer| {
277 renderer.draw_image(
278 handle.clone(),
279 filter_method,
280 drawing_bounds + offset,
281 rotation.radians(),
282 opacity,
283 border_radius,
284 );
285 };
286
287 if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height
288 {
289 renderer.with_layer(bounds, render);
290 } else {
291 render(renderer);
292 }
293}
294
295impl<'a, Message, Theme, Renderer, Handle> Widget<Message, Theme, Renderer>
296 for Image<'a, Handle>
297where
298 Renderer: image::Renderer<Handle = Handle>,
299 Handle: Clone,
300{
301 fn size(&self) -> Size<Length> {
302 Size {
303 width: self.width,
304 height: self.height,
305 }
306 }
307
308 fn layout(
309 &self,
310 _tree: &mut Tree,
311 renderer: &Renderer,
312 limits: &layout::Limits,
313 ) -> layout::Node {
314 layout(
315 renderer,
316 limits,
317 &self.handle,
318 self.width,
319 self.height,
320 self.content_fit,
321 self.rotation,
322 self.border_radius,
323 )
324 }
325
326 fn draw(
327 &self,
328 _state: &Tree,
329 renderer: &mut Renderer,
330 _theme: &Theme,
331 _style: &renderer::Style,
332 layout: Layout<'_>,
333 _cursor: mouse::Cursor,
334 _viewport: &Rectangle,
335 ) {
336 draw(
337 renderer,
338 layout,
339 &self.handle,
340 self.content_fit,
341 self.filter_method,
342 self.rotation,
343 self.opacity,
344 self.border_radius,
345 );
346 }
347
348 #[cfg(feature = "a11y")]
349 fn a11y_nodes(
350 &self,
351 layout: Layout<'_>,
352 _state: &Tree,
353 _cursor: mouse::Cursor,
354 ) -> iced_accessibility::A11yTree {
355 use iced_accessibility::{
356 accesskit::{NodeBuilder, NodeId, Rect, Role},
357 A11yTree,
358 };
359
360 let bounds = layout.bounds();
361 let Rectangle {
362 x,
363 y,
364 width,
365 height,
366 } = bounds;
367 let bounds = Rect::new(
368 x as f64,
369 y as f64,
370 (x + width) as f64,
371 (y + height) as f64,
372 );
373 let mut node = NodeBuilder::new(Role::Image);
374 node.set_bounds(bounds);
375 if let Some(name) = self.name.as_ref() {
376 node.set_name(name.clone());
377 }
378 match self.description.as_ref() {
379 Some(iced_accessibility::Description::Id(id)) => {
380 node.set_described_by(
381 id.iter()
382 .cloned()
383 .map(|id| NodeId::from(id))
384 .collect::<Vec<_>>(),
385 );
386 }
387 Some(iced_accessibility::Description::Text(text)) => {
388 node.set_description(text.clone());
389 }
390 None => {}
391 }
392
393 if let Some(label) = self.label.as_ref() {
394 node.set_labelled_by(label.clone());
395 }
396
397 A11yTree::leaf(node, self.id.clone())
398 }
399
400 fn id(&self) -> Option<Id> {
401 Some(self.id.clone())
402 }
403
404 fn set_id(&mut self, id: Id) {
405 self.id = id;
406 }
407}
408
409impl<'a, Message, Theme, Renderer, Handle> From<Image<'a, Handle>>
410 for Element<'a, Message, Theme, Renderer>
411where
412 Renderer: image::Renderer<Handle = Handle>,
413 Handle: Clone + 'a,
414{
415 fn from(image: Image<'a, Handle>) -> Element<'a, Message, Theme, Renderer> {
416 Element::new(image)
417 }
418}