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}