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