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}