Skip to main content

dear_imgui_rs/
stacks.rs

1//! Push/pop stacks (fonts, style, etc.)
2//!
3//! RAII-style wrappers for ImGui parameter stacks: fonts, style colors/vars and
4//! more. Tokens returned by `Ui::push_*` pop automatically when dropped.
5//!
6#![allow(
7    clippy::cast_possible_truncation,
8    clippy::cast_sign_loss,
9    clippy::as_conversions,
10    clippy::unnecessary_cast
11)]
12use crate::Ui;
13use crate::fonts::FontId;
14use crate::style::{StyleColor, StyleVar, validate_style_color, validate_style_var};
15use crate::sys;
16
17/// # Parameter stacks (shared)
18impl Ui {
19    /// Switches to the given font by pushing it to the font stack.
20    ///
21    /// Returns a `FontStackToken` that must be popped by calling `.pop()`
22    ///
23    /// # Panics
24    ///
25    /// Panics if the font atlas does not contain the given font
26    ///
27    /// # Examples
28    ///
29    /// ```no_run
30    /// # use dear_imgui_rs::*;
31    /// # let mut ctx = Context::create();
32    /// # let font_data_sources = [];
33    /// // At initialization time
34    /// let my_custom_font = ctx.fonts().add_font(&font_data_sources);
35    /// # let ui = ctx.frame();
36    /// // During UI construction
37    /// let font = ui.push_font(my_custom_font);
38    /// ui.text("I use the custom font!");
39    /// font.pop();
40    /// ```
41    #[doc(alias = "PushFont")]
42    pub fn push_font(&self, id: FontId) -> FontStackToken<'_> {
43        // For now, we'll use a simplified approach without full validation
44        // TODO: Add proper FontAtlas integration for validation
45        let font_ptr = id.0 as *mut sys::ImFont;
46        unsafe { sys::igPushFont(font_ptr, 0.0) };
47        FontStackToken::new(self)
48    }
49
50    /// Changes a style color by pushing a change to the color stack.
51    ///
52    /// Returns a `ColorStackToken` that must be popped by calling `.pop()`
53    ///
54    /// # Examples
55    ///
56    /// ```no_run
57    /// # use dear_imgui_rs::*;
58    /// # let mut ctx = Context::create();
59    /// # let ui = ctx.frame();
60    /// const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0];
61    /// let color = ui.push_style_color(StyleColor::Text, RED);
62    /// ui.text("I'm red!");
63    /// color.pop();
64    /// ```
65    #[doc(alias = "PushStyleColor")]
66    pub fn push_style_color(
67        &self,
68        style_color: StyleColor,
69        color: impl Into<[f32; 4]>,
70    ) -> ColorStackToken<'_> {
71        let color_array = color.into();
72        validate_style_color("Ui::push_style_color()", "color", color_array);
73        unsafe {
74            sys::igPushStyleColor_Vec4(
75                style_color as i32,
76                sys::ImVec4 {
77                    x: color_array[0],
78                    y: color_array[1],
79                    z: color_array[2],
80                    w: color_array[3],
81                },
82            )
83        };
84        ColorStackToken::new(self)
85    }
86
87    /// Changes a style variable by pushing a change to the style stack.
88    ///
89    /// Returns a `StyleStackToken` that can be popped by calling `.end()`
90    /// or by allowing to drop.
91    ///
92    /// # Examples
93    ///
94    /// ```no_run
95    /// # use dear_imgui_rs::*;
96    /// # let mut ctx = Context::create();
97    /// # let ui = ctx.frame();
98    /// let style = ui.push_style_var(StyleVar::Alpha(0.2));
99    /// ui.text("I'm transparent!");
100    /// style.pop();
101    /// ```
102    #[doc(alias = "PushStyleVar")]
103    pub fn push_style_var(&self, style_var: StyleVar) -> StyleStackToken<'_> {
104        validate_style_var("Ui::push_style_var()", style_var);
105        unsafe { push_style_var(style_var) };
106        StyleStackToken::new(self)
107    }
108}
109
110create_token!(
111    /// Tracks a font pushed to the font stack that can be popped by calling `.end()`
112    /// or by dropping.
113    pub struct FontStackToken<'ui>;
114
115    /// Pops a change from the font stack.
116    drop { unsafe { sys::igPopFont() } }
117);
118
119impl FontStackToken<'_> {
120    /// Pops a change from the font stack.
121    pub fn pop(self) {
122        self.end()
123    }
124}
125
126create_token!(
127    /// Tracks a color pushed to the color stack that can be popped by calling `.end()`
128    /// or by dropping.
129    pub struct ColorStackToken<'ui>;
130
131    /// Pops a change from the color stack.
132    drop { unsafe { sys::igPopStyleColor(1) } }
133);
134
135impl ColorStackToken<'_> {
136    /// Pops a change from the color stack.
137    pub fn pop(self) {
138        self.end()
139    }
140}
141
142create_token!(
143    /// Tracks a style pushed to the style stack that can be popped by calling `.end()`
144    /// or by dropping.
145    pub struct StyleStackToken<'ui>;
146
147    /// Pops a change from the style stack.
148    drop { unsafe { sys::igPopStyleVar(1) } }
149);
150
151impl StyleStackToken<'_> {
152    /// Pops a change from the style stack.
153    pub fn pop(self) {
154        self.end()
155    }
156}
157
158/// Helper function to push style variables
159unsafe fn push_style_var(style_var: StyleVar) {
160    use StyleVar::*;
161    match style_var {
162        Alpha(v) => unsafe { sys::igPushStyleVar_Float(sys::ImGuiStyleVar_Alpha as i32, v) },
163        DisabledAlpha(v) => unsafe {
164            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_DisabledAlpha as i32, v)
165        },
166        WindowPadding(v) => {
167            let p: [f32; 2] = v;
168            let vec = sys::ImVec2 { x: p[0], y: p[1] };
169            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_WindowPadding as i32, vec) }
170        }
171        WindowRounding(v) => unsafe {
172            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_WindowRounding as i32, v)
173        },
174        WindowBorderSize(v) => unsafe {
175            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_WindowBorderSize as i32, v)
176        },
177        WindowMinSize(v) => {
178            let p: [f32; 2] = v;
179            let vec = sys::ImVec2 { x: p[0], y: p[1] };
180            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_WindowMinSize as i32, vec) }
181        }
182        WindowTitleAlign(v) => {
183            let p: [f32; 2] = v;
184            let vec = sys::ImVec2 { x: p[0], y: p[1] };
185            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_WindowTitleAlign as i32, vec) }
186        }
187        ChildRounding(v) => unsafe {
188            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_ChildRounding as i32, v)
189        },
190        ChildBorderSize(v) => unsafe {
191            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_ChildBorderSize as i32, v)
192        },
193        PopupRounding(v) => unsafe {
194            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_PopupRounding as i32, v)
195        },
196        PopupBorderSize(v) => unsafe {
197            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_PopupBorderSize as i32, v)
198        },
199        FramePadding(v) => {
200            let p: [f32; 2] = v;
201            let vec = sys::ImVec2 { x: p[0], y: p[1] };
202            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_FramePadding as i32, vec) }
203        }
204        FrameRounding(v) => unsafe {
205            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_FrameRounding as i32, v)
206        },
207        FrameBorderSize(v) => unsafe {
208            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_FrameBorderSize as i32, v)
209        },
210        ItemSpacing(v) => {
211            let p: [f32; 2] = v;
212            let vec = sys::ImVec2 { x: p[0], y: p[1] };
213            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_ItemSpacing as i32, vec) }
214        }
215        ItemInnerSpacing(v) => {
216            let p: [f32; 2] = v;
217            let vec = sys::ImVec2 { x: p[0], y: p[1] };
218            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_ItemInnerSpacing as i32, vec) }
219        }
220        IndentSpacing(v) => unsafe {
221            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_IndentSpacing as i32, v)
222        },
223        CellPadding(v) => {
224            let p: [f32; 2] = v;
225            let vec = sys::ImVec2 { x: p[0], y: p[1] };
226            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_CellPadding as i32, vec) }
227        }
228        ScrollbarSize(v) => unsafe {
229            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_ScrollbarSize as i32, v)
230        },
231        ScrollbarRounding(v) => unsafe {
232            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_ScrollbarRounding as i32, v)
233        },
234        ScrollbarPadding(v) => unsafe {
235            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_ScrollbarPadding as i32, v)
236        },
237        GrabMinSize(v) => unsafe {
238            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_GrabMinSize as i32, v)
239        },
240        GrabRounding(v) => unsafe {
241            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_GrabRounding as i32, v)
242        },
243        ImageRounding(v) => unsafe {
244            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_ImageRounding as i32, v)
245        },
246        ImageBorderSize(v) => unsafe {
247            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_ImageBorderSize as i32, v)
248        },
249        TabRounding(v) => unsafe {
250            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_TabRounding as i32, v)
251        },
252        TabBorderSize(v) => unsafe {
253            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_TabBorderSize as i32, v)
254        },
255        TabMinWidthBase(v) => unsafe {
256            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_TabMinWidthBase as i32, v)
257        },
258        TabMinWidthShrink(v) => unsafe {
259            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_TabMinWidthShrink as i32, v)
260        },
261        TabBarBorderSize(v) => unsafe {
262            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_TabBarBorderSize as i32, v)
263        },
264        TabBarOverlineSize(v) => unsafe {
265            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_TabBarOverlineSize as i32, v)
266        },
267        TableAngledHeadersAngle(v) => unsafe {
268            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_TableAngledHeadersAngle as i32, v)
269        },
270        TableAngledHeadersTextAlign(v) => {
271            let p: [f32; 2] = v;
272            let vec = sys::ImVec2 { x: p[0], y: p[1] };
273            unsafe {
274                sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_TableAngledHeadersTextAlign as i32, vec)
275            }
276        }
277        TreeLinesSize(v) => unsafe {
278            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_TreeLinesSize as i32, v)
279        },
280        TreeLinesRounding(v) => unsafe {
281            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_TreeLinesRounding as i32, v)
282        },
283        DragDropTargetRounding(v) => unsafe {
284            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_DragDropTargetRounding as i32, v)
285        },
286        ButtonTextAlign(v) => {
287            let p: [f32; 2] = v;
288            let vec = sys::ImVec2 { x: p[0], y: p[1] };
289            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_ButtonTextAlign as i32, vec) }
290        }
291        SelectableTextAlign(v) => {
292            let p: [f32; 2] = v;
293            let vec = sys::ImVec2 { x: p[0], y: p[1] };
294            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_SelectableTextAlign as i32, vec) }
295        }
296        SeparatorSize(v) => unsafe {
297            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_SeparatorSize as i32, v)
298        },
299        SeparatorTextBorderSize(v) => unsafe {
300            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_SeparatorTextBorderSize as i32, v)
301        },
302        SeparatorTextAlign(v) => {
303            let p: [f32; 2] = v;
304            let vec = sys::ImVec2 { x: p[0], y: p[1] };
305            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_SeparatorTextAlign as i32, vec) }
306        }
307        SeparatorTextPadding(v) => {
308            let p: [f32; 2] = v;
309            let vec = sys::ImVec2 { x: p[0], y: p[1] };
310            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_SeparatorTextPadding as i32, vec) }
311        }
312        DockingSeparatorSize(v) => unsafe {
313            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_DockingSeparatorSize as i32, v)
314        },
315    }
316}
317
318/// # Parameter stacks (current window)
319impl Ui {
320    /// Changes the item width by pushing a change to the item width stack.
321    ///
322    /// Returns an `ItemWidthStackToken`. The pushed width item is popped when either
323    /// `ItemWidthStackToken` goes out of scope, or `.end()` is called.
324    ///
325    /// - `> 0.0`: width is `item_width` pixels
326    /// - `= 0.0`: default to ~2/3 of window width
327    /// - `< 0.0`: `item_width` pixels relative to the right of window (-1.0 always aligns width to
328    ///   the right side)
329    #[doc(alias = "PushItemWidth")]
330    pub fn push_item_width(&self, item_width: f32) -> ItemWidthStackToken<'_> {
331        unsafe { sys::igPushItemWidth(item_width) };
332        ItemWidthStackToken::new(self)
333    }
334
335    /// Sets the width of the next item(s) to be the same as the width of the given text.
336    ///
337    /// Returns an `ItemWidthStackToken`. The pushed width item is popped when either
338    /// `ItemWidthStackToken` goes out of scope, or `.end()` is called.
339    #[doc(alias = "PushItemWidth")]
340    pub fn push_item_width_text(&self, text: impl AsRef<str>) -> ItemWidthStackToken<'_> {
341        let text_width = unsafe {
342            let text_ptr = self.scratch_txt(text);
343            let out = sys::igCalcTextSize(text_ptr, std::ptr::null(), false, -1.0);
344            out.x
345        };
346        self.push_item_width(text_width)
347    }
348
349    /// Sets the position where text will wrap around.
350    ///
351    /// Returns a `TextWrapPosStackToken`. The pushed wrap position is popped when either
352    /// `TextWrapPosStackToken` goes out of scope, or `.end()` is called.
353    ///
354    /// - `wrap_pos_x < 0.0`: no wrapping
355    /// - `wrap_pos_x = 0.0`: wrap to end of window (or column)
356    /// - `wrap_pos_x > 0.0`: wrap at `wrap_pos_x` position in window local space
357    #[doc(alias = "PushTextWrapPos")]
358    pub fn push_text_wrap_pos(&self, wrap_pos_x: f32) -> TextWrapPosStackToken<'_> {
359        unsafe { sys::igPushTextWrapPos(wrap_pos_x) };
360        TextWrapPosStackToken::new(self)
361    }
362}
363
364create_token!(
365    /// Tracks a change made with [`Ui::push_item_width`] that can be popped
366    /// by calling [`ItemWidthStackToken::end`] or dropping.
367    pub struct ItemWidthStackToken<'ui>;
368
369    /// Pops an item width change made with [`Ui::push_item_width`].
370    #[doc(alias = "PopItemWidth")]
371    drop { unsafe { sys::igPopItemWidth() } }
372);
373
374create_token!(
375    /// Tracks a change made with [`Ui::push_text_wrap_pos`] that can be popped
376    /// by calling [`TextWrapPosStackToken::end`] or dropping.
377    pub struct TextWrapPosStackToken<'ui>;
378
379    /// Pops a text wrap position change made with [`Ui::push_text_wrap_pos`].
380    #[doc(alias = "PopTextWrapPos")]
381    drop { unsafe { sys::igPopTextWrapPos() } }
382);
383
384/// # ID stack
385impl Ui {
386    /// Pushes an identifier to the ID stack.
387    ///
388    /// Returns an `IdStackToken` that can be popped by calling `.end()`
389    /// or by dropping manually.
390    ///
391    /// # Examples
392    /// Dear ImGui uses labels to uniquely identify widgets. For a good explanation, see this part of the [Dear ImGui FAQ][faq]
393    ///
394    /// [faq]: https://github.com/ocornut/imgui/blob/v1.84.2/docs/FAQ.md#q-why-is-my-widget-not-reacting-when-i-click-on-it
395    ///
396    /// In `dear-imgui-rs` the same applies, we can manually specify labels with the `##` syntax:
397    ///
398    /// ```no_run
399    /// # let mut imgui = dear_imgui_rs::Context::create();
400    /// # let ui = imgui.frame();
401    ///
402    /// ui.button("Click##button1");
403    /// ui.button("Click##button2");
404    /// ```
405    ///
406    /// But sometimes we want to create widgets in a loop, or we want to avoid
407    /// having to manually give each widget a unique label. In these cases, we can
408    /// push an ID to the ID stack:
409    ///
410    /// ```no_run
411    /// # let mut imgui = dear_imgui_rs::Context::create();
412    /// # let ui = imgui.frame();
413    ///
414    /// for i in 0..10 {
415    ///     let _id = ui.push_id(i);
416    ///     ui.button("Click");
417    /// }
418    /// ```
419    #[doc(alias = "PushID")]
420    pub fn push_id<'a, T: Into<Id<'a>>>(&self, id: T) -> IdStackToken<'_> {
421        let id = id.into();
422        unsafe {
423            match id {
424                Id::Int(i) => sys::igPushID_Int(i),
425                Id::Str(s) => sys::igPushID_Str(self.scratch_txt(s)),
426                Id::Ptr(p) => sys::igPushID_Ptr(p),
427            }
428        }
429        IdStackToken::new(self)
430    }
431}
432
433create_token!(
434    /// Tracks an ID pushed to the ID stack that can be popped by calling `.pop()`
435    /// or by dropping. See [`crate::Ui::push_id`] for more details.
436    pub struct IdStackToken<'ui>;
437
438    /// Pops a change from the ID stack
439    drop { unsafe { sys::igPopID() } }
440);
441
442impl IdStackToken<'_> {
443    /// Pops a change from the ID stack.
444    pub fn pop(self) {
445        self.end()
446    }
447}
448
449// ============================================================================
450// Focus scope stack
451// ============================================================================
452
453create_token!(
454    /// Tracks a pushed focus scope, popped on drop.
455    pub struct FocusScopeToken<'ui>;
456
457    /// Pops a focus scope.
458    #[doc(alias = "PopFocusScope")]
459    drop { unsafe { sys::igPopFocusScope() } }
460);
461
462impl Ui {
463    /// Push a focus scope (affects e.g. navigation focus allocation).
464    ///
465    /// Returns a `FocusScopeToken` which will pop the focus scope when dropped.
466    #[doc(alias = "PushFocusScope")]
467    pub fn push_focus_scope(&self, id: sys::ImGuiID) -> FocusScopeToken<'_> {
468        unsafe { sys::igPushFocusScope(id) };
469        FocusScopeToken::new(self)
470    }
471}
472
473/// Represents an identifier that can be pushed to the ID stack
474#[derive(Copy, Clone, Debug)]
475pub enum Id<'a> {
476    /// Integer identifier
477    Int(i32),
478    /// String identifier
479    Str(&'a str),
480    /// Pointer identifier
481    Ptr(*const std::ffi::c_void),
482}
483
484impl From<i32> for Id<'_> {
485    fn from(i: i32) -> Self {
486        Id::Int(i)
487    }
488}
489
490impl From<usize> for Id<'_> {
491    fn from(i: usize) -> Self {
492        Id::Int(i as i32)
493    }
494}
495
496impl<'a> From<&'a str> for Id<'a> {
497    fn from(s: &'a str) -> Self {
498        Id::Str(s)
499    }
500}
501
502impl<'a> From<&'a String> for Id<'a> {
503    fn from(s: &'a String) -> Self {
504        Id::Str(s.as_str())
505    }
506}
507
508impl<T> From<*const T> for Id<'_> {
509    fn from(p: *const T) -> Self {
510        Id::Ptr(p as *const std::ffi::c_void)
511    }
512}
513
514impl<T> From<*mut T> for Id<'_> {
515    fn from(p: *mut T) -> Self {
516        Id::Ptr(p as *const std::ffi::c_void)
517    }
518}