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}