Skip to main content

dear_imgui_rs/widget/
image.rs

1//! Image widgets
2//!
3//! Draw images from a legacy `TextureId` or a Context-owned `ManagedTextureId`.
4//! Managed pixels reach renderer backends as owned `TextureRequest` values on a
5//! `RenderedFrame` or `FrameSnapshot`; safe renderers never borrow `TextureData`.
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(context: &mut Context) {
81/// let mut tex = texture::OwnedTextureData::new();
82/// tex.create(texture::TextureFormat::RGBA32, 64, 64);
83/// let tex = context.register_texture(tex);
84/// let ui = context.frame();
85/// ui.image(tex, [64.0, 64.0]);
86/// # }
87/// ```
88impl Ui {
89    /// Creates an image widget
90    #[doc(alias = "Image")]
91    pub fn image<'tex>(&self, texture: impl Into<TextureRef<'tex>>, size: [f32; 2]) {
92        self.image_config(texture, size).build()
93    }
94
95    /// Creates an image button widget
96    #[doc(alias = "ImageButton")]
97    pub fn image_button<'tex>(
98        &self,
99        str_id: impl AsRef<str>,
100        texture: impl Into<TextureRef<'tex>>,
101        size: [f32; 2],
102    ) -> bool {
103        self.image_button_config(str_id.as_ref(), texture, size)
104            .build()
105    }
106
107    /// Creates an image builder
108    pub fn image_config<'ui, 'tex>(
109        &'ui self,
110        texture: impl Into<TextureRef<'tex>>,
111        size: [f32; 2],
112    ) -> Image<'ui, 'tex> {
113        Image::new(self, texture, size)
114    }
115
116    /// Creates an image button builder
117    pub fn image_button_config<'ui, 'tex>(
118        &'ui self,
119        str_id: impl Into<Cow<'ui, str>>,
120        texture: impl Into<TextureRef<'tex>>,
121        size: [f32; 2],
122    ) -> ImageButton<'ui, 'tex> {
123        ImageButton::new(self, str_id, texture, size)
124    }
125}
126
127/// Builder for an image widget
128#[derive(Debug)]
129#[must_use]
130pub struct Image<'ui, 'tex> {
131    _ui: &'ui Ui,
132    texture: TextureRef<'tex>,
133    size: [f32; 2],
134    uv0: [f32; 2],
135    uv1: [f32; 2],
136    tint_color: [f32; 4],
137    border_color: [f32; 4],
138}
139
140impl<'ui, 'tex> Image<'ui, 'tex> {
141    /// Creates a new image builder
142    pub fn new(ui: &'ui Ui, texture: impl Into<TextureRef<'tex>>, size: [f32; 2]) -> Self {
143        Self {
144            _ui: ui,
145            texture: texture.into(),
146            size,
147            uv0: [0.0, 0.0],
148            uv1: [1.0, 1.0],
149            tint_color: [1.0, 1.0, 1.0, 1.0],
150            border_color: [0.0, 0.0, 0.0, 0.0],
151        }
152    }
153
154    /// Sets the UV coordinates for the top-left corner (default: [0.0, 0.0])
155    pub fn uv0(mut self, uv0: [f32; 2]) -> Self {
156        self.uv0 = uv0;
157        self
158    }
159
160    /// Sets the UV coordinates for the bottom-right corner (default: [1.0, 1.0])
161    pub fn uv1(mut self, uv1: [f32; 2]) -> Self {
162        self.uv1 = uv1;
163        self
164    }
165
166    /// Sets the tint color (default: white, no tint)
167    ///
168    /// Dear ImGui 1.91.9 moved image tinting from `Image()` to `ImageWithBg()`.
169    /// If this is set, [`build`](Self::build) will call the tinted path while
170    /// keeping a transparent background.
171    pub fn tint_color(mut self, tint_color: [f32; 4]) -> Self {
172        self.tint_color = tint_color;
173        self
174    }
175
176    /// Sets the border color (default: transparent, no border)
177    ///
178    /// Dear ImGui 1.91.9 moved image border thickness to `Style::ImageBorderSize`
179    /// and border color to `StyleColor::Border`; this builder applies matching
180    /// temporary style overrides around [`build`](Self::build).
181    pub fn border_color(mut self, border_color: [f32; 4]) -> Self {
182        self.border_color = border_color;
183        self
184    }
185
186    /// Builds the image widget
187    pub fn build(self) {
188        assert_non_negative_finite_vec2("Image::build()", "size", self.size);
189        assert_finite_vec2("Image::build()", "uv0", self.uv0);
190        assert_finite_vec2("Image::build()", "uv1", self.uv1);
191        assert_finite_vec4("Image::build()", "tint_color", self.tint_color);
192        assert_finite_vec4("Image::build()", "border_color", self.border_color);
193
194        let size_vec: sys::ImVec2 = self.size.into();
195        let uv0_vec: sys::ImVec2 = self.uv0.into();
196        let uv1_vec: sys::ImVec2 = self.uv1.into();
197
198        let _border_size_token = (self.border_color[3] > 0.0).then(|| {
199            let current_size = self
200                ._ui
201                .run_with_bound_context(|| unsafe { self._ui.style().image_border_size() });
202            self._ui
203                .push_style_var(StyleVar::ImageBorderSize(current_size.max(1.0)))
204        });
205        let _border_color_token = (self.border_color[3] > 0.0).then(|| {
206            self._ui
207                .push_style_color(StyleColor::Border, self.border_color)
208        });
209        let texture = self._ui.run_with_bound_context(|| {
210            self._ui
211                .resolve_texture_ref(self.texture)
212                .unwrap_or_else(|error| panic!("Image::build() rejected texture: {error}"))
213        });
214
215        if is_default_tint_color(self.tint_color) && is_transparent_color(self.border_color) {
216            self._ui.run_with_bound_context(|| unsafe {
217                sys::igImage(texture, size_vec, uv0_vec, uv1_vec)
218            })
219        } else {
220            self._ui.run_with_bound_context(|| unsafe {
221                sys::igImageWithBg(
222                    texture,
223                    size_vec,
224                    uv0_vec,
225                    uv1_vec,
226                    im_vec4([0.0, 0.0, 0.0, 0.0]),
227                    im_vec4(self.tint_color),
228                )
229            })
230        }
231    }
232
233    /// Builds the image widget with background color and tint (v1.92+)
234    pub fn build_with_bg(self, bg_color: [f32; 4], tint_color: [f32; 4]) {
235        assert_non_negative_finite_vec2("Image::build_with_bg()", "size", self.size);
236        assert_finite_vec2("Image::build_with_bg()", "uv0", self.uv0);
237        assert_finite_vec2("Image::build_with_bg()", "uv1", self.uv1);
238        assert_finite_vec4("Image::build_with_bg()", "bg_color", bg_color);
239        assert_finite_vec4("Image::build_with_bg()", "tint_color", tint_color);
240        assert_finite_vec4("Image::build_with_bg()", "border_color", self.border_color);
241
242        let size_vec: sys::ImVec2 = self.size.into();
243        let uv0_vec: sys::ImVec2 = self.uv0.into();
244        let uv1_vec: sys::ImVec2 = self.uv1.into();
245
246        let _border_size_token = (self.border_color[3] > 0.0).then(|| {
247            let current_size = self
248                ._ui
249                .run_with_bound_context(|| unsafe { self._ui.style().image_border_size() });
250            self._ui
251                .push_style_var(StyleVar::ImageBorderSize(current_size.max(1.0)))
252        });
253        let _border_color_token = (self.border_color[3] > 0.0).then(|| {
254            self._ui
255                .push_style_color(StyleColor::Border, self.border_color)
256        });
257        let texture = self._ui.run_with_bound_context(|| {
258            self._ui
259                .resolve_texture_ref(self.texture)
260                .unwrap_or_else(|error| panic!("Image::build_with_bg() rejected texture: {error}"))
261        });
262
263        self._ui.run_with_bound_context(|| unsafe {
264            sys::igImageWithBg(
265                texture,
266                size_vec,
267                uv0_vec,
268                uv1_vec,
269                im_vec4(bg_color),
270                im_vec4(tint_color),
271            )
272        });
273    }
274}
275
276/// Builder for an image button widget
277#[derive(Debug)]
278#[must_use]
279pub struct ImageButton<'ui, 'tex> {
280    ui: &'ui Ui,
281    str_id: Cow<'ui, str>,
282    texture: TextureRef<'tex>,
283    size: [f32; 2],
284    uv0: [f32; 2],
285    uv1: [f32; 2],
286    bg_color: [f32; 4],
287    tint_color: [f32; 4],
288}
289
290impl<'ui, 'tex> ImageButton<'ui, 'tex> {
291    /// Creates a new image button builder
292    pub fn new(
293        ui: &'ui Ui,
294        str_id: impl Into<Cow<'ui, str>>,
295        texture: impl Into<TextureRef<'tex>>,
296        size: [f32; 2],
297    ) -> Self {
298        Self {
299            ui,
300            str_id: str_id.into(),
301            texture: texture.into(),
302            size,
303            uv0: [0.0, 0.0],
304            uv1: [1.0, 1.0],
305            bg_color: [0.0, 0.0, 0.0, 0.0],
306            tint_color: [1.0, 1.0, 1.0, 1.0],
307        }
308    }
309
310    /// Sets the UV coordinates for the top-left corner (default: [0.0, 0.0])
311    pub fn uv0(mut self, uv0: [f32; 2]) -> Self {
312        self.uv0 = uv0;
313        self
314    }
315
316    /// Sets the UV coordinates for the bottom-right corner (default: [1.0, 1.0])
317    pub fn uv1(mut self, uv1: [f32; 2]) -> Self {
318        self.uv1 = uv1;
319        self
320    }
321
322    /// Sets the background color (default: transparent)
323    pub fn bg_color(mut self, bg_color: [f32; 4]) -> Self {
324        self.bg_color = bg_color;
325        self
326    }
327
328    /// Sets the tint color (default: white, no tint)
329    pub fn tint_color(mut self, tint_color: [f32; 4]) -> Self {
330        self.tint_color = tint_color;
331        self
332    }
333
334    /// Builds the image button widget
335    pub fn build(self) -> bool {
336        assert_non_negative_finite_vec2("ImageButton::build()", "size", self.size);
337        assert_finite_vec2("ImageButton::build()", "uv0", self.uv0);
338        assert_finite_vec2("ImageButton::build()", "uv1", self.uv1);
339        assert_finite_vec4("ImageButton::build()", "bg_color", self.bg_color);
340        assert_finite_vec4("ImageButton::build()", "tint_color", self.tint_color);
341
342        let str_id_ptr = self.ui.scratch_txt(self.str_id.as_ref());
343        let size_vec: sys::ImVec2 = self.size.into();
344        let uv0_vec: sys::ImVec2 = self.uv0.into();
345        let uv1_vec: sys::ImVec2 = self.uv1.into();
346
347        self.ui.run_with_bound_context(|| {
348            let texture = self
349                .ui
350                .resolve_texture_ref(self.texture)
351                .unwrap_or_else(|error| panic!("ImageButton::build() rejected texture: {error}"));
352            unsafe {
353                sys::igImageButton(
354                    str_id_ptr,
355                    texture,
356                    size_vec,
357                    uv0_vec,
358                    uv1_vec,
359                    im_vec4(self.bg_color),
360                    im_vec4(self.tint_color),
361                )
362            }
363        })
364    }
365}