1use super::constrain_size_preserving_aspect_ratio;
2use crate::{
3 geometry::{
4 rect::RectF,
5 vector::{vec2f, Vector2F},
6 },
7 json::{json, ToJson},
8 presenter::MeasurementContext,
9 scene, Border, DebugContext, Element, ImageData, LayoutContext, PaintContext, SizeConstraint,
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 Element for Image {
60 type LayoutState = Option<Arc<ImageData>>;
61 type PaintState = ();
62
63 fn layout(
64 &mut self,
65 constraint: SizeConstraint,
66 cx: &mut LayoutContext,
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 bounds: RectF,
94 _: RectF,
95 layout: &mut Self::LayoutState,
96 cx: &mut PaintContext,
97 ) -> Self::PaintState {
98 if let Some(data) = layout {
99 cx.scene.push_image(scene::Image {
100 bounds,
101 border: self.style.border,
102 corner_radius: self.style.corner_radius,
103 grayscale: self.style.grayscale,
104 data: data.clone(),
105 });
106 }
107 }
108
109 fn rect_for_text_range(
110 &self,
111 _: Range<usize>,
112 _: RectF,
113 _: RectF,
114 _: &Self::LayoutState,
115 _: &Self::PaintState,
116 _: &MeasurementContext,
117 ) -> Option<RectF> {
118 None
119 }
120
121 fn debug(
122 &self,
123 bounds: RectF,
124 _: &Self::LayoutState,
125 _: &Self::PaintState,
126 _: &DebugContext,
127 ) -> serde_json::Value {
128 json!({
129 "type": "Image",
130 "bounds": bounds.to_json(),
131 })
132 }
133}