egui/widgets/
image_button.rs

1use crate::{
2    widgets, Color32, CornerRadius, Image, Rect, Response, Sense, Ui, Vec2, Widget, WidgetInfo,
3    WidgetType,
4};
5
6/// A clickable image within a frame.
7#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
8#[derive(Clone, Debug)]
9pub struct ImageButton<'a> {
10    pub(crate) image: Image<'a>,
11    sense: Sense,
12    frame: bool,
13    selected: bool,
14    alt_text: Option<String>,
15}
16
17impl<'a> ImageButton<'a> {
18    pub fn new(image: impl Into<Image<'a>>) -> Self {
19        Self {
20            image: image.into(),
21            sense: Sense::click(),
22            frame: true,
23            selected: false,
24            alt_text: None,
25        }
26    }
27
28    /// Select UV range. Default is (0,0) in top-left, (1,1) bottom right.
29    #[inline]
30    pub fn uv(mut self, uv: impl Into<Rect>) -> Self {
31        self.image = self.image.uv(uv);
32        self
33    }
34
35    /// Multiply image color with this. Default is WHITE (no tint).
36    #[inline]
37    pub fn tint(mut self, tint: impl Into<Color32>) -> Self {
38        self.image = self.image.tint(tint);
39        self
40    }
41
42    /// If `true`, mark this button as "selected".
43    #[inline]
44    pub fn selected(mut self, selected: bool) -> Self {
45        self.selected = selected;
46        self
47    }
48
49    /// Turn off the frame
50    #[inline]
51    pub fn frame(mut self, frame: bool) -> Self {
52        self.frame = frame;
53        self
54    }
55
56    /// By default, buttons senses clicks.
57    /// Change this to a drag-button with `Sense::drag()`.
58    #[inline]
59    pub fn sense(mut self, sense: Sense) -> Self {
60        self.sense = sense;
61        self
62    }
63
64    /// Set rounding for the `ImageButton`.
65    ///
66    /// If the underlying image already has rounding, this
67    /// will override that value.
68    #[inline]
69    pub fn corner_radius(mut self, corner_radius: impl Into<CornerRadius>) -> Self {
70        self.image = self.image.corner_radius(corner_radius.into());
71        self
72    }
73
74    /// Set rounding for the `ImageButton`.
75    ///
76    /// If the underlying image already has rounding, this
77    /// will override that value.
78    #[inline]
79    #[deprecated = "Renamed to `corner_radius`"]
80    pub fn rounding(self, corner_radius: impl Into<CornerRadius>) -> Self {
81        self.corner_radius(corner_radius)
82    }
83}
84
85impl Widget for ImageButton<'_> {
86    fn ui(self, ui: &mut Ui) -> Response {
87        let padding = if self.frame {
88            // so we can see that it is a button:
89            Vec2::splat(ui.spacing().button_padding.x)
90        } else {
91            Vec2::ZERO
92        };
93
94        let available_size_for_image = ui.available_size() - 2.0 * padding;
95        let tlr = self.image.load_for_size(ui.ctx(), available_size_for_image);
96        let original_image_size = tlr.as_ref().ok().and_then(|t| t.size());
97        let image_size = self
98            .image
99            .calc_size(available_size_for_image, original_image_size);
100
101        let padded_size = image_size + 2.0 * padding;
102        let (rect, response) = ui.allocate_exact_size(padded_size, self.sense);
103        response.widget_info(|| {
104            let mut info = WidgetInfo::new(WidgetType::ImageButton);
105            info.label = self.alt_text.clone();
106            info
107        });
108
109        if ui.is_rect_visible(rect) {
110            let (expansion, rounding, fill, stroke) = if self.selected {
111                let selection = ui.visuals().selection;
112                (
113                    Vec2::ZERO,
114                    self.image.image_options().corner_radius,
115                    selection.bg_fill,
116                    selection.stroke,
117                )
118            } else if self.frame {
119                let visuals = ui.style().interact(&response);
120                let expansion = Vec2::splat(visuals.expansion);
121                (
122                    expansion,
123                    self.image.image_options().corner_radius,
124                    visuals.weak_bg_fill,
125                    visuals.bg_stroke,
126                )
127            } else {
128                Default::default()
129            };
130
131            // Draw frame background (for transparent images):
132            ui.painter()
133                .rect_filled(rect.expand2(expansion), rounding, fill);
134
135            let image_rect = ui
136                .layout()
137                .align_size_within_rect(image_size, rect.shrink2(padding));
138            // let image_rect = image_rect.expand2(expansion); // can make it blurry, so let's not
139            let image_options = self.image.image_options().clone();
140
141            widgets::image::paint_texture_load_result(
142                ui,
143                &tlr,
144                image_rect,
145                None,
146                &image_options,
147                self.alt_text.as_deref(),
148            );
149
150            // Draw frame outline:
151            ui.painter().rect_stroke(
152                rect.expand2(expansion),
153                rounding,
154                stroke,
155                epaint::StrokeKind::Inside,
156            );
157        }
158
159        widgets::image::texture_load_result_response(&self.image.source(ui.ctx()), &tlr, response)
160    }
161}