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