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