egui/widgets/
image_button.rs

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