Skip to main content

agg_gui/widgets/
image_view.rs

1//! `ImageView` — paints an `Option<(rgba8_top_down, w, h)>` as an image.
2//!
3//! Reads its pixel data from an `Rc<RefCell<Option<(Vec<u8>, u32, u32)>>>`
4//! — the same shape [`ScreenshotHandle::image`][crate::ScreenshotHandle]
5//! produces.  Fits the image into the widget's bounds preserving aspect
6//! ratio, then outlines it in the theme text colour so the image boundary
7//! is always visible against the neutral pane.
8//!
9//! Shows a themed "No image yet." placeholder while the source is `None`.
10
11use std::cell::RefCell;
12use std::rc::Rc;
13use std::sync::Arc;
14
15use crate::color::Color;
16use crate::draw_ctx::DrawCtx;
17use crate::event::{Event, EventResult};
18use crate::geometry::{Rect, Size};
19use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
20use crate::text::Font;
21use crate::widget::Widget;
22
23pub struct ImageView {
24    bounds: Rect,
25    children: Vec<Box<dyn Widget>>, // always empty
26    base: WidgetBase,
27    font: Arc<Font>,
28    source: Rc<RefCell<Option<(Vec<u8>, u32, u32)>>>,
29    /// Text shown when `source` is `None`.
30    placeholder: String,
31    /// Height floor; the widget expands to available height but never below.
32    min_height: f64,
33    /// When `true` (default), paints a 1-px outline around the image.
34    outline: bool,
35    /// When `true` (default), fills the widget's rounded rect with
36    /// `visuals().bg_color` before drawing the image.
37    fill_background: bool,
38}
39
40impl ImageView {
41    pub fn new(font: Arc<Font>, source: Rc<RefCell<Option<(Vec<u8>, u32, u32)>>>) -> Self {
42        Self {
43            bounds: Rect::default(),
44            children: Vec::new(),
45            base: WidgetBase::new(),
46            font,
47            source,
48            placeholder: "No image yet.".into(),
49            min_height: 120.0,
50            outline: true,
51            fill_background: true,
52        }
53    }
54
55    pub fn with_placeholder(mut self, text: impl Into<String>) -> Self {
56        self.placeholder = text.into();
57        self
58    }
59    pub fn with_min_height(mut self, h: f64) -> Self {
60        self.min_height = h;
61        self
62    }
63    pub fn with_outline(mut self, on: bool) -> Self {
64        self.outline = on;
65        self
66    }
67    pub fn with_fill_background(mut self, on: bool) -> Self {
68        self.fill_background = on;
69        self
70    }
71
72    pub fn with_margin(mut self, m: Insets) -> Self {
73        self.base.margin = m;
74        self
75    }
76    pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
77        self.base.h_anchor = h;
78        self
79    }
80    pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
81        self.base.v_anchor = v;
82        self
83    }
84    pub fn with_min_size(mut self, s: Size) -> Self {
85        self.base.min_size = s;
86        self
87    }
88    pub fn with_max_size(mut self, s: Size) -> Self {
89        self.base.max_size = s;
90        self
91    }
92}
93
94impl Widget for ImageView {
95    fn type_name(&self) -> &'static str {
96        "ImageView"
97    }
98    fn bounds(&self) -> Rect {
99        self.bounds
100    }
101    fn set_bounds(&mut self, b: Rect) {
102        self.bounds = b;
103    }
104    fn children(&self) -> &[Box<dyn Widget>] {
105        &self.children
106    }
107    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
108        &mut self.children
109    }
110
111    fn margin(&self) -> Insets {
112        self.base.margin
113    }
114    fn widget_base(&self) -> Option<&WidgetBase> {
115        Some(&self.base)
116    }
117    fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
118        Some(&mut self.base)
119    }
120    fn h_anchor(&self) -> HAnchor {
121        self.base.h_anchor
122    }
123    fn v_anchor(&self) -> VAnchor {
124        self.base.v_anchor
125    }
126    fn min_size(&self) -> Size {
127        self.base.min_size
128    }
129    fn max_size(&self) -> Size {
130        self.base.max_size
131    }
132
133    fn layout(&mut self, available: Size) -> Size {
134        let h = available.height.max(self.min_height);
135        self.bounds = Rect::new(0.0, 0.0, available.width, h);
136        Size::new(self.bounds.width, self.bounds.height)
137    }
138
139    fn paint(&mut self, ctx: &mut dyn DrawCtx) {
140        let v = ctx.visuals();
141        let w = self.bounds.width;
142        let h = self.bounds.height;
143
144        if self.fill_background {
145            ctx.set_fill_color(v.bg_color);
146            ctx.begin_path();
147            ctx.rounded_rect(0.0, 0.0, w, h, 4.0);
148            ctx.fill();
149        }
150
151        let src = self.source.borrow();
152        if let Some((pixels, iw, ih)) = src.as_ref() {
153            let iwf = *iw as f64;
154            let ihf = *ih as f64;
155            let scale = (w / iwf).min(h / ihf).max(0.0);
156            let dw = iwf * scale;
157            let dh = ihf * scale;
158            let dx = (w - dw) * 0.5;
159            let dy = (h - dh) * 0.5;
160            ctx.draw_image_rgba(pixels, *iw, *ih, dx, dy, dw, dh);
161
162            if self.outline {
163                ctx.set_stroke_color(v.text_color);
164                ctx.set_line_width(1.0);
165                ctx.begin_path();
166                ctx.rect(dx, dy, dw, dh);
167                ctx.stroke();
168            }
169        } else {
170            ctx.set_font(Arc::clone(&self.font));
171            ctx.set_font_size(13.0);
172            ctx.set_fill_color(v.text_dim);
173            if let Some(m) = ctx.measure_text(&self.placeholder) {
174                let tx = (w - m.width) * 0.5;
175                let ty = h * 0.5 - (m.ascent - m.descent) * 0.5;
176                ctx.fill_text(&self.placeholder, tx, ty);
177            }
178        }
179
180        // Silence unused warning on `Color` when neither branch uses it.
181        let _ = Color::white();
182    }
183
184    fn on_event(&mut self, _: &Event) -> EventResult {
185        EventResult::Ignored
186    }
187}