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