Skip to main content

dear_imgui_rs/widget/
image.rs

1//! Image widgets
2//!
3//! Draw images from a legacy `TextureId` or from modern `TextureData` handled
4//! via `DrawData::textures_mut()` by renderer backends. See crate-level docs for texture
5//! management.
6//!
7//! Quick example (image button):
8//! ```no_run
9//! # use dear_imgui_rs::*;
10//! # let mut ctx = Context::create();
11//! # let ui = ctx.frame();
12//! let tex_id = texture::TextureId::new(42);
13//! if ui.image_button("btn", tex_id, [32.0, 32.0]) {
14//!     // clicked
15//! }
16//! ```
17//!
18use crate::sys;
19use crate::texture::TextureRef;
20use crate::ui::Ui;
21use crate::{StyleColor, StyleVar};
22use std::borrow::Cow;
23
24fn assert_non_negative_finite_vec2(caller: &str, name: &str, value: [f32; 2]) {
25    assert!(
26        value[0].is_finite() && value[1].is_finite(),
27        "{caller} {name} must contain finite values"
28    );
29    assert!(
30        value[0] >= 0.0 && value[1] >= 0.0,
31        "{caller} {name} must contain non-negative values"
32    );
33}
34
35fn assert_finite_vec2(caller: &str, name: &str, value: [f32; 2]) {
36    assert!(
37        value[0].is_finite() && value[1].is_finite(),
38        "{caller} {name} must contain finite values"
39    );
40}
41
42fn assert_finite_vec4(caller: &str, name: &str, value: [f32; 4]) {
43    assert!(
44        value.iter().all(|component| component.is_finite()),
45        "{caller} {name} must contain finite values"
46    );
47}
48
49fn is_default_tint_color(color: [f32; 4]) -> bool {
50    color == [1.0, 1.0, 1.0, 1.0]
51}
52
53fn is_transparent_color(color: [f32; 4]) -> bool {
54    color == [0.0, 0.0, 0.0, 0.0]
55}
56
57fn im_vec4(value: [f32; 4]) -> sys::ImVec4 {
58    sys::ImVec4 {
59        x: value[0],
60        y: value[1],
61        z: value[2],
62        w: value[3],
63    }
64}
65
66/// # Image Widgets
67///
68/// Examples
69/// - Using a plain texture id:
70/// ```no_run
71/// # use dear_imgui_rs::*;
72/// # fn demo(ui: &Ui) {
73/// let tex_id = texture::TextureId::new(0xDEAD_BEEF);
74/// ui.image(tex_id, [128.0, 128.0]);
75/// # }
76/// ```
77/// - Using an ImGui-managed texture:
78/// ```no_run
79/// # use dear_imgui_rs::*;
80/// # fn demo(ui: &Ui) {
81/// let mut tex = texture::TextureData::new();
82/// tex.create(texture::TextureFormat::RGBA32, 64, 64);
83/// ui.image(&mut *tex, [64.0, 64.0]);
84/// # }
85/// ```
86impl Ui {
87    /// Creates an image widget
88    #[doc(alias = "Image")]
89    pub fn image<'tex>(&self, texture: impl Into<TextureRef<'tex>>, size: [f32; 2]) {
90        self.image_config(texture, size).build()
91    }
92
93    /// Creates an image button widget
94    #[doc(alias = "ImageButton")]
95    pub fn image_button<'tex>(
96        &self,
97        str_id: impl AsRef<str>,
98        texture: impl Into<TextureRef<'tex>>,
99        size: [f32; 2],
100    ) -> bool {
101        self.image_button_config(str_id.as_ref(), texture, size)
102            .build()
103    }
104
105    /// Creates an image builder
106    pub fn image_config<'ui, 'tex>(
107        &'ui self,
108        texture: impl Into<TextureRef<'tex>>,
109        size: [f32; 2],
110    ) -> Image<'ui, 'tex> {
111        Image::new(self, texture, size)
112    }
113
114    /// Creates an image button builder
115    pub fn image_button_config<'ui, 'tex>(
116        &'ui self,
117        str_id: impl Into<Cow<'ui, str>>,
118        texture: impl Into<TextureRef<'tex>>,
119        size: [f32; 2],
120    ) -> ImageButton<'ui, 'tex> {
121        ImageButton::new(self, str_id, texture, size)
122    }
123}
124
125/// Builder for an image widget
126#[derive(Debug)]
127#[must_use]
128pub struct Image<'ui, 'tex> {
129    _ui: &'ui Ui,
130    texture: TextureRef<'tex>,
131    size: [f32; 2],
132    uv0: [f32; 2],
133    uv1: [f32; 2],
134    tint_color: [f32; 4],
135    border_color: [f32; 4],
136}
137
138impl<'ui, 'tex> Image<'ui, 'tex> {
139    /// Creates a new image builder
140    pub fn new(ui: &'ui Ui, texture: impl Into<TextureRef<'tex>>, size: [f32; 2]) -> Self {
141        Self {
142            _ui: ui,
143            texture: texture.into(),
144            size,
145            uv0: [0.0, 0.0],
146            uv1: [1.0, 1.0],
147            tint_color: [1.0, 1.0, 1.0, 1.0],
148            border_color: [0.0, 0.0, 0.0, 0.0],
149        }
150    }
151
152    /// Sets the UV coordinates for the top-left corner (default: [0.0, 0.0])
153    pub fn uv0(mut self, uv0: [f32; 2]) -> Self {
154        self.uv0 = uv0;
155        self
156    }
157
158    /// Sets the UV coordinates for the bottom-right corner (default: [1.0, 1.0])
159    pub fn uv1(mut self, uv1: [f32; 2]) -> Self {
160        self.uv1 = uv1;
161        self
162    }
163
164    /// Sets the tint color (default: white, no tint)
165    ///
166    /// Dear ImGui 1.91.9 moved image tinting from `Image()` to `ImageWithBg()`.
167    /// If this is set, [`build`](Self::build) will call the tinted path while
168    /// keeping a transparent background.
169    pub fn tint_color(mut self, tint_color: [f32; 4]) -> Self {
170        self.tint_color = tint_color;
171        self
172    }
173
174    /// Sets the border color (default: transparent, no border)
175    ///
176    /// Dear ImGui 1.91.9 moved image border thickness to `Style::ImageBorderSize`
177    /// and border color to `StyleColor::Border`; this builder applies matching
178    /// temporary style overrides around [`build`](Self::build).
179    pub fn border_color(mut self, border_color: [f32; 4]) -> Self {
180        self.border_color = border_color;
181        self
182    }
183
184    /// Builds the image widget
185    pub fn build(self) {
186        assert_non_negative_finite_vec2("Image::build()", "size", self.size);
187        assert_finite_vec2("Image::build()", "uv0", self.uv0);
188        assert_finite_vec2("Image::build()", "uv1", self.uv1);
189        assert_finite_vec4("Image::build()", "tint_color", self.tint_color);
190        assert_finite_vec4("Image::build()", "border_color", self.border_color);
191
192        let size_vec: sys::ImVec2 = self.size.into();
193        let uv0_vec: sys::ImVec2 = self.uv0.into();
194        let uv1_vec: sys::ImVec2 = self.uv1.into();
195
196        let _border_size_token = (self.border_color[3] > 0.0).then(|| {
197            let current_size = self
198                ._ui
199                .run_with_bound_context(|| unsafe { self._ui.style().image_border_size() });
200            self._ui
201                .push_style_var(StyleVar::ImageBorderSize(current_size.max(1.0)))
202        });
203        let _border_color_token = (self.border_color[3] > 0.0).then(|| {
204            self._ui
205                .push_style_color(StyleColor::Border, self.border_color)
206        });
207
208        if is_default_tint_color(self.tint_color) && is_transparent_color(self.border_color) {
209            self._ui.run_with_bound_context(|| unsafe {
210                sys::igImage(self.texture.raw(), size_vec, uv0_vec, uv1_vec)
211            })
212        } else {
213            self._ui.run_with_bound_context(|| unsafe {
214                sys::igImageWithBg(
215                    self.texture.raw(),
216                    size_vec,
217                    uv0_vec,
218                    uv1_vec,
219                    im_vec4([0.0, 0.0, 0.0, 0.0]),
220                    im_vec4(self.tint_color),
221                )
222            })
223        }
224    }
225
226    /// Builds the image widget with background color and tint (v1.92+)
227    pub fn build_with_bg(self, bg_color: [f32; 4], tint_color: [f32; 4]) {
228        assert_non_negative_finite_vec2("Image::build_with_bg()", "size", self.size);
229        assert_finite_vec2("Image::build_with_bg()", "uv0", self.uv0);
230        assert_finite_vec2("Image::build_with_bg()", "uv1", self.uv1);
231        assert_finite_vec4("Image::build_with_bg()", "bg_color", bg_color);
232        assert_finite_vec4("Image::build_with_bg()", "tint_color", tint_color);
233        assert_finite_vec4("Image::build_with_bg()", "border_color", self.border_color);
234
235        let size_vec: sys::ImVec2 = self.size.into();
236        let uv0_vec: sys::ImVec2 = self.uv0.into();
237        let uv1_vec: sys::ImVec2 = self.uv1.into();
238
239        let _border_size_token = (self.border_color[3] > 0.0).then(|| {
240            let current_size = self
241                ._ui
242                .run_with_bound_context(|| unsafe { self._ui.style().image_border_size() });
243            self._ui
244                .push_style_var(StyleVar::ImageBorderSize(current_size.max(1.0)))
245        });
246        let _border_color_token = (self.border_color[3] > 0.0).then(|| {
247            self._ui
248                .push_style_color(StyleColor::Border, self.border_color)
249        });
250
251        self._ui.run_with_bound_context(|| unsafe {
252            sys::igImageWithBg(
253                self.texture.raw(),
254                size_vec,
255                uv0_vec,
256                uv1_vec,
257                im_vec4(bg_color),
258                im_vec4(tint_color),
259            )
260        });
261    }
262}
263
264/// Builder for an image button widget
265#[derive(Debug)]
266#[must_use]
267pub struct ImageButton<'ui, 'tex> {
268    ui: &'ui Ui,
269    str_id: Cow<'ui, str>,
270    texture: TextureRef<'tex>,
271    size: [f32; 2],
272    uv0: [f32; 2],
273    uv1: [f32; 2],
274    bg_color: [f32; 4],
275    tint_color: [f32; 4],
276}
277
278impl<'ui, 'tex> ImageButton<'ui, 'tex> {
279    /// Creates a new image button builder
280    pub fn new(
281        ui: &'ui Ui,
282        str_id: impl Into<Cow<'ui, str>>,
283        texture: impl Into<TextureRef<'tex>>,
284        size: [f32; 2],
285    ) -> Self {
286        Self {
287            ui,
288            str_id: str_id.into(),
289            texture: texture.into(),
290            size,
291            uv0: [0.0, 0.0],
292            uv1: [1.0, 1.0],
293            bg_color: [0.0, 0.0, 0.0, 0.0],
294            tint_color: [1.0, 1.0, 1.0, 1.0],
295        }
296    }
297
298    /// Sets the UV coordinates for the top-left corner (default: [0.0, 0.0])
299    pub fn uv0(mut self, uv0: [f32; 2]) -> Self {
300        self.uv0 = uv0;
301        self
302    }
303
304    /// Sets the UV coordinates for the bottom-right corner (default: [1.0, 1.0])
305    pub fn uv1(mut self, uv1: [f32; 2]) -> Self {
306        self.uv1 = uv1;
307        self
308    }
309
310    /// Sets the background color (default: transparent)
311    pub fn bg_color(mut self, bg_color: [f32; 4]) -> Self {
312        self.bg_color = bg_color;
313        self
314    }
315
316    /// Sets the tint color (default: white, no tint)
317    pub fn tint_color(mut self, tint_color: [f32; 4]) -> Self {
318        self.tint_color = tint_color;
319        self
320    }
321
322    /// Builds the image button widget
323    pub fn build(self) -> bool {
324        assert_non_negative_finite_vec2("ImageButton::build()", "size", self.size);
325        assert_finite_vec2("ImageButton::build()", "uv0", self.uv0);
326        assert_finite_vec2("ImageButton::build()", "uv1", self.uv1);
327        assert_finite_vec4("ImageButton::build()", "bg_color", self.bg_color);
328        assert_finite_vec4("ImageButton::build()", "tint_color", self.tint_color);
329
330        let str_id_ptr = self.ui.scratch_txt(self.str_id.as_ref());
331        let size_vec: sys::ImVec2 = self.size.into();
332        let uv0_vec: sys::ImVec2 = self.uv0.into();
333        let uv1_vec: sys::ImVec2 = self.uv1.into();
334
335        self.ui.run_with_bound_context(|| unsafe {
336            sys::igImageButton(
337                str_id_ptr,
338                self.texture.raw(),
339                size_vec,
340                uv0_vec,
341                uv1_vec,
342                im_vec4(self.bg_color),
343                im_vec4(self.tint_color),
344            )
345        })
346    }
347}