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 = {
277            let text_ptr = self.scratch_txt(text);
278            let mut out = sys::ImVec2 { x: 0.0, y: 0.0 };
279            unsafe { sys::igCalcTextSize(&mut out, 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}