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, SceneBuilder, SizeConstraint, View,
9 ViewContext,
10};
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)]
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: View> 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 LayoutContext<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 scene: &mut SceneBuilder,
95 bounds: RectF,
96 _: RectF,
97 layout: &mut Self::LayoutState,
98 _: &mut V,
99 _: &mut ViewContext<V>,
100 ) -> Self::PaintState {
101 if let Some(data) = layout {
102 scene.push_image(scene::Image {
103 bounds,
104 border: self.style.border,
105 corner_radius: self.style.corner_radius,
106 grayscale: self.style.grayscale,
107 data: data.clone(),
108 });
109 }
110 }
111
112 fn rect_for_text_range(
113 &self,
114 _: Range<usize>,
115 _: RectF,
116 _: RectF,
117 _: &Self::LayoutState,
118 _: &Self::PaintState,
119 _: &V,
120 _: &ViewContext<V>,
121 ) -> Option<RectF> {
122 None
123 }
124
125 fn debug(
126 &self,
127 bounds: RectF,
128 _: &Self::LayoutState,
129 _: &Self::PaintState,
130 _: &V,
131 _: &ViewContext<V>,
132 ) -> serde_json::Value {
133 json!({
134 "type": "Image",
135 "bounds": bounds.to_json(),
136 })
137 }
138}