jag_ui/elements/
image_element.rs1use jag_draw::{Brush, ColorLinPremul, Rect};
4use jag_surface::{Canvas, ImageFitMode};
5
6use crate::event::{
7 EventHandler, EventResult, KeyboardEvent, MouseClickEvent, MouseMoveEvent, ScrollEvent,
8};
9use crate::focus::FocusId;
10
11use super::Element;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum ImageFit {
16 Fill,
18 Contain,
20 Cover,
22}
23
24impl Default for ImageFit {
25 fn default() -> Self {
26 Self::Contain
27 }
28}
29
30impl ImageFit {
31 fn to_fit_mode(self) -> ImageFitMode {
33 match self {
34 Self::Fill => ImageFitMode::Fill,
35 Self::Contain => ImageFitMode::Contain,
36 Self::Cover => ImageFitMode::Cover,
37 }
38 }
39}
40
41pub struct ImageElement {
44 pub rect: Rect,
45 pub source: Option<String>,
47 pub fallback_color: ColorLinPremul,
49 pub fit: ImageFit,
51}
52
53impl ImageElement {
54 pub fn new(source: impl Into<String>) -> Self {
56 Self {
57 rect: Rect {
58 x: 0.0,
59 y: 0.0,
60 w: 200.0,
61 h: 150.0,
62 },
63 source: Some(source.into()),
64 fallback_color: ColorLinPremul::from_srgba_u8([200, 200, 200, 255]),
65 fit: ImageFit::default(),
66 }
67 }
68
69 pub fn placeholder(color: ColorLinPremul) -> Self {
71 Self {
72 rect: Rect {
73 x: 0.0,
74 y: 0.0,
75 w: 200.0,
76 h: 150.0,
77 },
78 source: None,
79 fallback_color: color,
80 fit: ImageFit::default(),
81 }
82 }
83
84 pub fn with_fit(mut self, fit: ImageFit) -> Self {
86 self.fit = fit;
87 self
88 }
89
90 pub fn hit_test(&self, x: f32, y: f32) -> bool {
92 x >= self.rect.x
93 && x <= self.rect.x + self.rect.w
94 && y >= self.rect.y
95 && y <= self.rect.y + self.rect.h
96 }
97}
98
99impl Element for ImageElement {
104 fn rect(&self) -> Rect {
105 self.rect
106 }
107
108 fn set_rect(&mut self, rect: Rect) {
109 self.rect = rect;
110 }
111
112 fn render(&self, canvas: &mut Canvas, z: i32) {
113 if let Some(ref path) = self.source {
114 canvas.draw_image(
115 path.clone(),
116 [self.rect.x, self.rect.y],
117 [self.rect.w, self.rect.h],
118 self.fit.to_fit_mode(),
119 z,
120 );
121 } else {
122 canvas.fill_rect(
123 self.rect.x,
124 self.rect.y,
125 self.rect.w,
126 self.rect.h,
127 Brush::Solid(self.fallback_color),
128 z,
129 );
130 }
131 }
132
133 fn focus_id(&self) -> Option<FocusId> {
134 None
135 }
136}
137
138impl EventHandler for ImageElement {
143 fn handle_mouse_click(&mut self, _event: &MouseClickEvent) -> EventResult {
144 EventResult::Ignored
145 }
146
147 fn handle_keyboard(&mut self, _event: &KeyboardEvent) -> EventResult {
148 EventResult::Ignored
149 }
150
151 fn handle_mouse_move(&mut self, _event: &MouseMoveEvent) -> EventResult {
152 EventResult::Ignored
153 }
154
155 fn handle_scroll(&mut self, _event: &ScrollEvent) -> EventResult {
156 EventResult::Ignored
157 }
158
159 fn is_focused(&self) -> bool {
160 false
161 }
162
163 fn set_focused(&mut self, _focused: bool) {}
164
165 fn contains_point(&self, x: f32, y: f32) -> bool {
166 self.hit_test(x, y)
167 }
168}
169
170#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn image_element_new() {
180 let img = ImageElement::new("/path/to/image.png");
181 assert_eq!(img.source.as_deref(), Some("/path/to/image.png"));
182 assert_eq!(img.fit, ImageFit::Contain);
183 }
184
185 #[test]
186 fn image_element_placeholder() {
187 let color = ColorLinPremul::from_srgba_u8([128, 128, 128, 255]);
188 let img = ImageElement::placeholder(color);
189 assert!(img.source.is_none());
190 assert_eq!(img.fallback_color, color);
191 }
192
193 #[test]
194 fn image_element_with_fit() {
195 let img = ImageElement::new("test.jpg").with_fit(ImageFit::Cover);
196 assert_eq!(img.fit, ImageFit::Cover);
197 }
198
199 #[test]
200 fn image_element_hit_test() {
201 let mut img = ImageElement::new("test.png");
202 img.rect = Rect {
203 x: 10.0,
204 y: 10.0,
205 w: 100.0,
206 h: 80.0,
207 };
208 assert!(img.hit_test(50.0, 50.0));
209 assert!(!img.hit_test(0.0, 0.0));
210 }
211
212 #[test]
213 fn image_element_not_focusable() {
214 let img = ImageElement::new("test.png");
215 assert!(img.focus_id().is_none());
216 }
217}