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::event::{Event, EventResult};
17use crate::geometry::{Rect, Size};
18use crate::draw_ctx::DrawCtx;
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(
42        font:   Arc<Font>,
43        source: Rc<RefCell<Option<(Vec<u8>, u32, u32)>>>,
44    ) -> Self {
45        Self {
46            bounds:   Rect::default(),
47            children: Vec::new(),
48            base:     WidgetBase::new(),
49            font,
50            source,
51            placeholder:     "No image yet.".into(),
52            min_height:      120.0,
53            outline:         true,
54            fill_background: true,
55        }
56    }
57
58    pub fn with_placeholder(mut self, text: impl Into<String>) -> Self {
59        self.placeholder = text.into(); self
60    }
61    pub fn with_min_height(mut self, h: f64) -> Self { self.min_height = h; self }
62    pub fn with_outline(mut self, on: bool) -> Self { self.outline = on; self }
63    pub fn with_fill_background(mut self, on: bool) -> Self {
64        self.fill_background = on; self
65    }
66
67    pub fn with_margin(mut self, m: Insets)    -> Self { self.base.margin   = m; self }
68    pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
69    pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
70    pub fn with_min_size(mut self, s: Size)    -> Self { self.base.min_size = s; self }
71    pub fn with_max_size(mut self, s: Size)    -> Self { self.base.max_size = s; self }
72}
73
74impl Widget for ImageView {
75    fn type_name(&self) -> &'static str { "ImageView" }
76    fn bounds(&self) -> Rect { self.bounds }
77    fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
78    fn children(&self) -> &[Box<dyn Widget>] { &self.children }
79    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
80
81    fn margin(&self)   -> Insets  { self.base.margin }
82    fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
83    fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
84    fn min_size(&self) -> Size    { self.base.min_size }
85    fn max_size(&self) -> Size    { self.base.max_size }
86
87    fn layout(&mut self, available: Size) -> Size {
88        let h = available.height.max(self.min_height);
89        self.bounds = Rect::new(0.0, 0.0, available.width, h);
90        Size::new(self.bounds.width, self.bounds.height)
91    }
92
93    fn paint(&mut self, ctx: &mut dyn DrawCtx) {
94        let v = ctx.visuals();
95        let w = self.bounds.width;
96        let h = self.bounds.height;
97
98        if self.fill_background {
99            ctx.set_fill_color(v.bg_color);
100            ctx.begin_path();
101            ctx.rounded_rect(0.0, 0.0, w, h, 4.0);
102            ctx.fill();
103        }
104
105        let src = self.source.borrow();
106        if let Some((pixels, iw, ih)) = src.as_ref() {
107            let iwf = *iw as f64;
108            let ihf = *ih as f64;
109            let scale = (w / iwf).min(h / ihf).max(0.0);
110            let dw = iwf * scale;
111            let dh = ihf * scale;
112            let dx = (w - dw) * 0.5;
113            let dy = (h - dh) * 0.5;
114            ctx.draw_image_rgba(pixels, *iw, *ih, dx, dy, dw, dh);
115
116            if self.outline {
117                ctx.set_stroke_color(v.text_color);
118                ctx.set_line_width(1.0);
119                ctx.begin_path();
120                ctx.rect(dx, dy, dw, dh);
121                ctx.stroke();
122            }
123        } else {
124            ctx.set_font(Arc::clone(&self.font));
125            ctx.set_font_size(13.0);
126            ctx.set_fill_color(v.text_dim);
127            if let Some(m) = ctx.measure_text(&self.placeholder) {
128                let tx = (w - m.width) * 0.5;
129                let ty = h * 0.5 - (m.ascent - m.descent) * 0.5;
130                ctx.fill_text(&self.placeholder, tx, ty);
131            }
132        }
133
134        // Silence unused warning on `Color` when neither branch uses it.
135        let _ = Color::white();
136    }
137
138    fn on_event(&mut self, _: &Event) -> EventResult { EventResult::Ignored }
139}