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