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 FrameBorderSize(v) => unsafe {
206 sys::igPushStyleVar_Float(sys::ImGuiStyleVar_FrameBorderSize as i32, v)
207 },
208 ItemSpacing(v) => {
209 let p: [f32; 2] = v;
210 let vec = sys::ImVec2 { x: p[0], y: p[1] };
211 unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_ItemSpacing as i32, vec) }
212 }
213 ItemInnerSpacing(v) => {
214 let p: [f32; 2] = v;
215 let vec = sys::ImVec2 { x: p[0], y: p[1] };
216 unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_ItemInnerSpacing as i32, vec) }
217 }
218 IndentSpacing(v) => unsafe {
219 sys::igPushStyleVar_Float(sys::ImGuiStyleVar_IndentSpacing as i32, v)
220 },
221 CellPadding(v) => {
222 let p: [f32; 2] = v;
223 let vec = sys::ImVec2 { x: p[0], y: p[1] };
224 unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_CellPadding as i32, vec) }
225 }
226 ScrollbarSize(v) => unsafe {
227 sys::igPushStyleVar_Float(sys::ImGuiStyleVar_ScrollbarSize as i32, v)
228 },
229 ScrollbarRounding(v) => unsafe {
230 sys::igPushStyleVar_Float(sys::ImGuiStyleVar_ScrollbarRounding as i32, v)
231 },
232 GrabMinSize(v) => unsafe {
233 sys::igPushStyleVar_Float(sys::ImGuiStyleVar_GrabMinSize as i32, v)
234 },
235 GrabRounding(v) => unsafe {
236 sys::igPushStyleVar_Float(sys::ImGuiStyleVar_GrabRounding as i32, v)
237 },
238 TabRounding(v) => unsafe {
239 sys::igPushStyleVar_Float(sys::ImGuiStyleVar_TabRounding as i32, v)
240 },
241 ButtonTextAlign(v) => {
242 let p: [f32; 2] = v;
243 let vec = sys::ImVec2 { x: p[0], y: p[1] };
244 unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_ButtonTextAlign as i32, vec) }
245 }
246 SelectableTextAlign(v) => {
247 let p: [f32; 2] = v;
248 let vec = sys::ImVec2 { x: p[0], y: p[1] };
249 unsafe { sys::igPushStyleVar_Vec2(sys::ImGuiStyleVar_SelectableTextAlign as i32, vec) }
250 }
251 }
252}
253
254/// # Parameter stacks (current window)
255impl Ui {
256 /// Changes the item width by pushing a change to the item width stack.
257 ///
258 /// Returns an `ItemWidthStackToken`. The pushed width item is popped when either
259 /// `ItemWidthStackToken` goes out of scope, or `.end()` is called.
260 ///
261 /// - `> 0.0`: width is `item_width` pixels
262 /// - `= 0.0`: default to ~2/3 of window width
263 /// - `< 0.0`: `item_width` pixels relative to the right of window (-1.0 always aligns width to
264 /// the right side)
265 #[doc(alias = "PushItemWidth")]
266 pub fn push_item_width(&self, item_width: f32) -> ItemWidthStackToken<'_> {
267 unsafe { sys::igPushItemWidth(item_width) };
268 ItemWidthStackToken::new(self)
269 }
270
271 /// Sets the width of the next item(s) to be the same as the width of the given text.
272 ///
273 /// Returns an `ItemWidthStackToken`. The pushed width item is popped when either
274 /// `ItemWidthStackToken` goes out of scope, or `.end()` is called.
275 #[doc(alias = "PushItemWidth")]
276 pub fn push_item_width_text(&self, text: impl AsRef<str>) -> ItemWidthStackToken<'_> {
277 let text_width = unsafe {
278 let text_ptr = self.scratch_txt(text);
279 let out = sys::igCalcTextSize(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}