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