1use super::constrain_size_preserving_aspect_ratio;
2use crate::{
3 geometry::{
4 rect::RectF,
5 vector::{vec2f, Vector2F},
6 },
7 json::{json, ToJson},
8 scene, Border, Element, ImageData, SceneBuilder, SizeConstraint, View, ViewContext,
9};
10use serde::Deserialize;
11use std::{ops::Range, sync::Arc};
12
13enum ImageSource {
14 Path(&'static str),
15 Data(Arc<ImageData>),
16}
17
18pub struct Image {
19 source: ImageSource,
20 style: ImageStyle,
21}
22
23#[derive(Copy, Clone, Default, Deserialize)]
24pub struct ImageStyle {
25 #[serde(default)]
26 pub border: Border,
27 #[serde(default)]
28 pub corner_radius: f32,
29 #[serde(default)]
30 pub height: Option<f32>,
31 #[serde(default)]
32 pub width: Option<f32>,
33 #[serde(default)]
34 pub grayscale: bool,
35}
36
37impl Image {
38 pub fn new(asset_path: &'static str) -> Self {
39 Self {
40 source: ImageSource::Path(asset_path),
41 style: Default::default(),
42 }
43 }
44
45 pub fn from_data(data: Arc<ImageData>) -> Self {
46 Self {
47 source: ImageSource::Data(data),
48 style: Default::default(),
49 }
50 }
51
52 pub fn with_style(mut self, style: ImageStyle) -> Self {
53 self.style = style;
54 self
55 }
56}
57
58impl<V: View> Element<V> for Image {
59 type LayoutState = Option<Arc<ImageData>>;
60 type PaintState = ();
61
62 fn layout(
63 &mut self,
64 constraint: SizeConstraint,
65 _: &mut V,
66 cx: &mut ViewContext<V>,
67 ) -> (Vector2F, Self::LayoutState) {
68 let data = match &self.source {
69 ImageSource::Path(path) => match cx.asset_cache.png(path) {
70 Ok(data) => data,
71 Err(error) => {
72 log::error!("could not load image: {}", error);
73 return (Vector2F::zero(), None);
74 }
75 },
76 ImageSource::Data(data) => data.clone(),
77 };
78
79 let desired_size = vec2f(
80 self.style.width.unwrap_or_else(|| constraint.max.x()),
81 self.style.height.unwrap_or_else(|| constraint.max.y()),
82 );
83 let size = constrain_size_preserving_aspect_ratio(
84 constraint.constrain(desired_size),
85 data.size().to_f32(),
86 );
87
88 (size, Some(data))
89 }
90
91 fn paint(
92 &mut self,
93 scene: &mut SceneBuilder,
94 bounds: RectF,
95 _: RectF,
96 layout: &mut Self::LayoutState,
97 _: &mut V,
98 _: &mut ViewContext<V>,
99 ) -> Self::PaintState {
100 if let Some(data) = layout {
101 scene.push_image(scene::Image {
102 bounds,
103 border: self.style.border,
104 corner_radius: self.style.corner_radius,
105 grayscale: self.style.grayscale,
106 data: data.clone(),
107 });
108 }
109 }
110
111 fn rect_for_text_range(
112 &self,
113 _: Range<usize>,
114 _: RectF,
115 _: RectF,
116 _: &Self::LayoutState,
117 _: &Self::PaintState,
118 _: &V,
119 _: &ViewContext<V>,
120 ) -> Option<RectF> {
121 None
122 }
123
124 fn debug(
125 &self,
126 bounds: RectF,
127 _: &Self::LayoutState,
128 _: &Self::PaintState,
129 _: &V,
130 _: &ViewContext<V>,
131 ) -> serde_json::Value {
132 json!({
133 "type": "Image",
134 "bounds": bounds.to_json(),
135 })
136 }
137}