rosace_widgets/tree/
image.rs1use rosace_core::types::{Point, Rect, Size};
2use rosace_render::{Color, DrawCommand};
3
4use super::{Widget, LayoutCtx, PaintCtx};
5use crate::{ImageWidget as ImageWidgetImpl, ImageFit, ImageSource};
6
7pub struct Image {
12 inner: ImageWidgetImpl,
13}
14
15impl Image {
16 pub fn file(path: impl Into<std::path::PathBuf>) -> Self {
18 Self { inner: ImageWidgetImpl::new().file(path).fit(ImageFit::Contain) }
19 }
20
21 pub fn asset(name: impl rosace_core::asset::AssetRef) -> Self {
26 Self { inner: ImageWidgetImpl::new().asset(name).fit(ImageFit::Contain) }
27 }
28
29 pub fn bytes(data: Vec<u8>) -> Self {
31 Self { inner: ImageWidgetImpl::new().bytes(data).fit(ImageFit::Contain) }
32 }
33
34 pub fn placeholder(color: Color) -> Self {
36 Self { inner: ImageWidgetImpl::new().placeholder_color(color) }
37 }
38
39 pub fn fit(mut self, f: ImageFit) -> Self { self.inner = self.inner.fit(f); self }
40 pub fn width(mut self, w: f32) -> Self { self.inner = self.inner.width(w); self }
41 pub fn height(mut self, h: f32) -> Self { self.inner = self.inner.height(h); self }
42 pub fn alt(mut self, alt: impl Into<String>) -> Self { self.inner = self.inner.alt(alt); self }
44}
45
46impl Widget for Image {
47 fn layout(&self, _ctx: &LayoutCtx) -> Size {
48 Size { width: self.inner.width, height: self.inner.height }
49 }
50
51 fn paint(&self, ctx: &mut PaintCtx) {
52 if let Some(alt) = &self.inner.alt {
55 ctx.semantics(super::Semantics::new(rosace_core::Role::Image).label(alt));
56 }
57 let x = ctx.rect.origin.x;
58 let y = ctx.rect.origin.y;
59 let w = self.inner.width;
60 let h = self.inner.height;
61 let dest_rect = Rect { origin: Point { x, y }, size: Size { width: w, height: h } };
62
63 let decoded = {
69 let mut cache = crate::ImageCache::global().lock().unwrap_or_else(|e| e.into_inner());
70 match &self.inner.source {
71 ImageSource::Placeholder => None,
72 ImageSource::File(path) => cache.get_or_load(path),
73 ImageSource::Bytes(b) => cache.get_or_decode_bytes(b),
74 }
75 };
76
77 let broken = decoded.is_none() && !matches!(self.inner.source, ImageSource::Placeholder);
83
84 if let Some(img) = decoded {
85 ctx.record(DrawCommand::BlitRgba {
97 pixels: img.pixels,
98 src_width: img.width,
99 src_height: img.height,
100 dest_rect,
101 opacity: 1.0,
102 });
103 return;
104 }
105
106 if broken {
107 ctx.fill_rect(dest_rect, Color::rgb(60, 30, 30));
112 let icon_size = (w.min(h) * 0.4).clamp(16.0, 32.0);
113 let icon_rect = Rect {
114 origin: Point { x: x + (w - icon_size) / 2.0, y: y + (h - icon_size) / 2.0 },
115 size: Size { width: icon_size, height: icon_size },
116 };
117 super::Icon::new(super::IconKind::Close)
118 .size(icon_size)
119 .color(Color::rgb(200, 90, 90))
120 .paint(&mut ctx.child(icon_rect));
121 return;
122 }
123
124 ctx.fill_rect(dest_rect, self.inner.placeholder_color);
126 ctx.fill_circle(Point { x: x + w / 2.0, y: y + h / 2.0 - 15.0 }, 12.0, Color::rgb(100, 110, 140));
127 ctx.fill_rect(Rect {
128 origin: Point { x: x + w / 2.0 - 20.0, y: y + h / 2.0 + 5.0 },
129 size: Size { width: 40.0, height: 20.0 },
130 }, Color::rgb(80, 90, 120));
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use rosace_render::FontCache;
138 use rosace_theme::built_in;
139
140 fn make_ctx(_c: rosace_layout::Constraints) -> (FontCache, rosace_theme::ThemeData) {
141 let font = FontCache::system_ui()
142 .or_else(FontCache::system_mono)
143 .expect("no system font");
144 (font, built_in::dark_theme())
145 }
146
147 #[test]
148 fn image_placeholder_layout() {
149 let img = Image::placeholder(Color::rgb(128, 128, 128)).width(320.0).height(200.0);
150 let c = rosace_layout::Constraints::loose(800.0, 600.0);
151 let (font, theme) = make_ctx(c);
152 let ctx = LayoutCtx::new(c, &font, &theme);
153 let size = img.layout(&ctx);
154 assert_eq!(size.width, 320.0);
155 assert_eq!(size.height, 200.0);
156 }
157
158 #[test]
159 fn image_file_layout() {
160 let img = Image::file("assets/photo.png").width(100.0).height(80.0);
161 let c = rosace_layout::Constraints::loose(800.0, 600.0);
162 let (font, theme) = make_ctx(c);
163 let ctx = LayoutCtx::new(c, &font, &theme);
164 let size = img.layout(&ctx);
165 assert_eq!(size.width, 100.0);
166 assert_eq!(size.height, 80.0);
167 }
168}