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        ImageRounding(v) => unsafe {
206            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_ImageRounding as i32, v)
207        },
208        FrameBorderSize(v) => unsafe {
209            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_FrameBorderSize as i32, v)
210        },
211        ItemSpacing(v) => {
212            let p: [f32; 2] = v;
213            let vec = sys::ImVec2 { x: p[0], y: p[1] };
214            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_ItemSpacing as i32, vec) }
215        }
216        ItemInnerSpacing(v) => {
217            let p: [f32; 2] = v;
218            let vec = sys::ImVec2 { x: p[0], y: p[1] };
219            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_ItemInnerSpacing as i32, vec) }
220        }
221        IndentSpacing(v) => unsafe {
222            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_IndentSpacing as i32, v)
223        },
224        CellPadding(v) => {
225            let p: [f32; 2] = v;
226            let vec = sys::ImVec2 { x: p[0], y: p[1] };
227            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_CellPadding as i32, vec) }
228        }
229        ScrollbarSize(v) => unsafe {
230            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_ScrollbarSize as i32, v)
231        },
232        ScrollbarRounding(v) => unsafe {
233            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_ScrollbarRounding as i32, v)
234        },
235        GrabMinSize(v) => unsafe {
236            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_GrabMinSize as i32, v)
237        },
238        GrabRounding(v) => unsafe {
239            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_GrabRounding as i32, v)
240        },
241        TabRounding(v) => unsafe {
242            sys::igPushStyleVar_Float(sys::ImGuiStyleVar_TabRounding as i32, v)
243        },
244        ButtonTextAlign(v) => {
245            let p: [f32; 2] = v;
246            let vec = sys::ImVec2 { x: p[0], y: p[1] };
247            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_ButtonTextAlign as i32, vec) }
248        }
249        SelectableTextAlign(v) => {
250            let p: [f32; 2] = v;
251            let vec = sys::ImVec2 { x: p[0], y: p[1] };
252            unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_SelectableTextAlign as i32, vec) }
253        }
254    }
255}
256
257/// # Parameter stacks (current window)
258impl Ui {
259    /// Changes the item width by pushing a change to the item width stack.
260    ///
261    /// Returns an `ItemWidthStackToken`. The pushed width item is popped when either
262    /// `ItemWidthStackToken` goes out of scope, or `.end()` is called.
263    ///
264    /// - `> 0.0`: width is `item_width` pixels
265    /// - `= 0.0`: default to ~2/3 of window width
266    /// - `< 0.0`: `item_width` pixels relative to the right of window (-1.0 always aligns width to
267    ///   the right side)
268    #[doc(alias = "PushItemWidth")]
269    pub fn push_item_width(&self, item_width: f32) -> ItemWidthStackToken<'_> {
270        unsafe { sys::igPushItemWidth(item_width) };
271        ItemWidthStackToken::new(self)
272    }
273
274    /// Sets the width of the next item(s) to be the same as the width of the given text.
275    ///
276    /// Returns an `ItemWidthStackToken`. The pushed width item is popped when either
277    /// `ItemWidthStackToken` goes out of scope, or `.end()` is called.
278    #[doc(alias = "PushItemWidth")]
279    pub fn push_item_width_text(&self, text: impl AsRef<str>) -> ItemWidthStackToken<'_> {
280        let text_width = unsafe {
281            let text_ptr = self.scratch_txt(text);
282            let out = sys::igCalcTextSize(text_ptr, std::ptr::null(), false, -1.0);
283            out.x
284        };
285        self.push_item_width(text_width)
286    }
287
288    /// Sets the position where text will wrap around.
289    ///
290    /// Returns a `TextWrapPosStackToken`. The pushed wrap position is popped when either
291    /// `TextWrapPosStackToken` goes out of scope, or `.end()` is called.
292    ///
293    /// - `wrap_pos_x < 0.0`: no wrapping
294    /// - `wrap_pos_x = 0.0`: wrap to end of window (or column)
295    /// - `wrap_pos_x > 0.0`: wrap at `wrap_pos_x` position in window local space
296    #[doc(alias = "PushTextWrapPos")]
297    pub fn push_text_wrap_pos(&self, wrap_pos_x: f32) -> TextWrapPosStackToken<'_> {
298        unsafe { sys::igPushTextWrapPos(wrap_pos_x) };
299        TextWrapPosStackToken::new(self)
300    }
301}
302
303create_token!(
304    /// Tracks a change made with [`Ui::push_item_width`] that can be popped
305    /// by calling [`ItemWidthStackToken::end`] or dropping.
306    pub struct ItemWidthStackToken<'ui>;
307
308    /// Pops an item width change made with [`Ui::push_item_width`].
309    #[doc(alias = "PopItemWidth")]
310    drop { unsafe { sys::igPopItemWidth() } }
311);
312
313create_token!(
314    /// Tracks a change made with [`Ui::push_text_wrap_pos`] that can be popped
315    /// by calling [`TextWrapPosStackToken::end`] or dropping.
316    pub struct TextWrapPosStackToken<'ui>;
317
318    /// Pops a text wrap position change made with [`Ui::push_text_wrap_pos`].
319    #[doc(alias = "PopTextWrapPos")]
320    drop { unsafe { sys::igPopTextWrapPos() } }
321);
322
323/// # ID stack
324impl Ui {
325    /// Pushes an identifier to the ID stack.
326    ///
327    /// Returns an `IdStackToken` that can be popped by calling `.end()`
328    /// or by dropping manually.
329    ///
330    /// # Examples
331    /// Dear ImGui uses labels to uniquely identify widgets. For a good explanation, see this part of the [Dear ImGui FAQ][faq]
332    ///
333    /// [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
334    ///
335    /// In `dear-imgui-rs` the same applies, we can manually specify labels with the `##` syntax:
336    ///
337    /// ```no_run
338    /// # let mut imgui = dear_imgui_rs::Context::create();
339    /// # let ui = imgui.frame();
340    ///
341    /// ui.button("Click##button1");
342    /// ui.button("Click##button2");
343    /// ```
344    ///
345    /// But sometimes we want to create widgets in a loop, or we want to avoid
346    /// having to manually give each widget a unique label. In these cases, we can
347    /// push an ID to the ID stack:
348    ///
349    /// ```no_run
350    /// # let mut imgui = dear_imgui_rs::Context::create();
351    /// # let ui = imgui.frame();
352    ///
353    /// for i in 0..10 {
354    ///     let _id = ui.push_id(i);
355    ///     ui.button("Click");
356    /// }
357    /// ```
358    #[doc(alias = "PushID")]
359    pub fn push_id<'a, T: Into<Id<'a>>>(&self, id: T) -> IdStackToken<'_> {
360        let id = id.into();
361        unsafe {
362            match id {
363                Id::Int(i) => sys::igPushID_Int(i),
364                Id::Str(s) => sys::igPushID_Str(self.scratch_txt(s)),
365                Id::Ptr(p) => sys::igPushID_Ptr(p),
366            }
367        }
368        IdStackToken::new(self)
369    }
370}
371
372create_token!(
373    /// Tracks an ID pushed to the ID stack that can be popped by calling `.pop()`
374    /// or by dropping. See [`crate::Ui::push_id`] for more details.
375    pub struct IdStackToken<'ui>;
376
377    /// Pops a change from the ID stack
378    drop { unsafe { sys::igPopID() } }
379);
380
381impl IdStackToken<'_> {
382    /// Pops a change from the ID stack.
383    pub fn pop(self) {
384        self.end()
385    }
386}
387
388// ============================================================================
389// Focus scope stack
390// ============================================================================
391
392create_token!(
393    /// Tracks a pushed focus scope, popped on drop.
394    pub struct FocusScopeToken<'ui>;
395
396    /// Pops a focus scope.
397    #[doc(alias = "PopFocusScope")]
398    drop { unsafe { sys::igPopFocusScope() } }
399);
400
401impl Ui {
402    /// Push a focus scope (affects e.g. navigation focus allocation).
403    ///
404    /// Returns a `FocusScopeToken` which will pop the focus scope when dropped.
405    #[doc(alias = "PushFocusScope")]
406    pub fn push_focus_scope(&self, id: sys::ImGuiID) -> FocusScopeToken<'_> {
407        unsafe { sys::igPushFocusScope(id) };
408        FocusScopeToken::new(self)
409    }
410}
411
412/// Represents an identifier that can be pushed to the ID stack
413#[derive(Copy, Clone, Debug)]
414pub enum Id<'a> {
415    /// Integer identifier
416    Int(i32),
417    /// String identifier
418    Str(&'a str),
419    /// Pointer identifier
420    Ptr(*const std::ffi::c_void),
421}
422
423impl From<i32> for Id<'_> {
424    fn from(i: i32) -> Self {
425        Id::Int(i)
426    }
427}
428
429impl From<usize> for Id<'_> {
430    fn from(i: usize) -> Self {
431        Id::Int(i as i32)
432    }
433}
434
435impl<'a> From<&'a str> for Id<'a> {
436    fn from(s: &'a str) -> Self {
437        Id::Str(s)
438    }
439}
440
441impl<'a> From<&'a String> for Id<'a> {
442    fn from(s: &'a String) -> Self {
443        Id::Str(s.as_str())
444    }
445}
446
447impl<T> From<*const T> for Id<'_> {
448    fn from(p: *const T) -> Self {
449        Id::Ptr(p as *const std::ffi::c_void)
450    }
451}
452
453impl<T> From<*mut T> for Id<'_> {
454    fn from(p: *mut T) -> Self {
455        Id::Ptr(p as *const std::ffi::c_void)
456    }
457}