Skip to main content

dear_imgui_rs/
layout.rs

1//! Layout and cursor helpers
2//!
3//! Spacing, separators, horizontal layout (`same_line`), grouping, cursor
4//! positioning and clipping helpers. These functions help arrange widgets and
5//! content within windows.
6//!
7//! Example:
8//! ```no_run
9//! # use dear_imgui_rs::*;
10//! # let mut ctx = Context::create();
11//! # let ui = ctx.frame();
12//! ui.text("Left");
13//! ui.same_line();
14//! ui.text("Right");
15//! ```
16//!
17#![allow(
18    clippy::cast_possible_truncation,
19    clippy::cast_sign_loss,
20    clippy::as_conversions,
21    clippy::unnecessary_cast
22)]
23use crate::Id;
24use crate::Ui;
25use crate::sys;
26use std::ffi::c_void;
27
28fn assert_finite_f32(caller: &str, name: &str, value: f32) {
29    assert!(value.is_finite(), "{caller} {name} must be finite");
30}
31
32fn assert_finite_vec2(caller: &str, name: &str, value: [f32; 2]) {
33    assert!(
34        value[0].is_finite() && value[1].is_finite(),
35        "{caller} {name} must contain finite values"
36    );
37}
38
39create_token!(
40    /// Tracks a layout group that can be ended with `end` or by dropping.
41    pub struct GroupToken<'ui>;
42
43    /// Drops the layout group manually. You can also just allow this token
44    /// to drop on its own.
45    drop { unsafe { sys::igEndGroup() } }
46);
47
48create_token!(
49    /// Tracks a pushed clip rect that will be popped on drop.
50    pub struct ClipRectToken<'ui>;
51
52    /// Pops a clip rect pushed with [`Ui::push_clip_rect`].
53    drop { unsafe { sys::igPopClipRect() } }
54);
55
56create_token!(
57    /// Tracks a stack-layout horizontal group.
58    ///
59    /// This wraps the repository-owned stack layout compatibility shim used by
60    /// the upstream imgui-node-editor blueprints example. It is not part of the
61    /// official Dear ImGui API.
62    pub struct HorizontalStackLayoutToken<'ui>;
63
64    /// Ends the stack-layout horizontal group.
65    drop { unsafe { sys::ImGuiStack_EndHorizontal() } }
66);
67
68create_token!(
69    /// Tracks a stack-layout vertical group.
70    ///
71    /// This wraps the repository-owned stack layout compatibility shim used by
72    /// the upstream imgui-node-editor blueprints example. It is not part of the
73    /// official Dear ImGui API.
74    pub struct VerticalStackLayoutToken<'ui>;
75
76    /// Ends the stack-layout vertical group.
77    drop { unsafe { sys::ImGuiStack_EndVertical() } }
78);
79
80create_token!(
81    /// Tracks a suspended stack layout and resumes it on drop.
82    pub struct StackLayoutSuspensionToken<'ui>;
83
84    /// Resumes a suspended stack layout.
85    drop { unsafe { sys::ImGuiStack_ResumeLayout() } }
86);
87
88/// Identifier accepted by the stack layout compatibility helpers.
89///
90/// The pointer form mirrors the upstream `BeginHorizontal(id.AsPointer())`
91/// usage from `imgui-node-editor`; the pointer is used only as an ID and is not
92/// dereferenced.
93#[derive(Clone, Copy, Debug)]
94pub enum StackLayoutId<'a> {
95    Str(&'a str),
96    Ptr(*const c_void),
97    Int(i32),
98    Raw(Id),
99}
100
101impl<'a> StackLayoutId<'a> {
102    /// Construct an ID from a pointer value.
103    #[inline]
104    pub const fn ptr(ptr: *const c_void) -> Self {
105        Self::Ptr(ptr)
106    }
107
108    /// Construct an ID from a pointer-sized integer, matching upstream
109    /// `NodeId::AsPointer()` / `PinId::AsPointer()` usage.
110    #[inline]
111    pub const fn pointer_value(value: usize) -> Self {
112        Self::Ptr(value as *const c_void)
113    }
114}
115
116impl<'a> From<&'a str> for StackLayoutId<'a> {
117    #[inline]
118    fn from(value: &'a str) -> Self {
119        Self::Str(value)
120    }
121}
122
123impl From<i32> for StackLayoutId<'_> {
124    #[inline]
125    fn from(value: i32) -> Self {
126        Self::Int(value)
127    }
128}
129
130impl From<Id> for StackLayoutId<'_> {
131    #[inline]
132    fn from(value: Id) -> Self {
133        Self::Raw(value)
134    }
135}
136
137/// # Cursor / Layout
138impl Ui {
139    /// Renders a separator (generally horizontal).
140    ///
141    /// This becomes a vertical separator inside a menu bar or in horizontal layout mode.
142    #[doc(alias = "Separator")]
143    pub fn separator(&self) {
144        unsafe { sys::igSeparator() }
145    }
146
147    /// Renders a separator with text.
148    #[doc(alias = "SeparatorText")]
149    pub fn separator_with_text(&self, text: impl AsRef<str>) {
150        unsafe { sys::igSeparatorText(self.scratch_txt(text)) }
151    }
152
153    /// Creates a vertical separator
154    #[doc(alias = "SeparatorEx")]
155    pub fn separator_vertical(&self) {
156        unsafe { sys::igSeparatorEx(sys::ImGuiSeparatorFlags_Vertical as i32, 1.0) }
157    }
158
159    /// Creates a horizontal separator
160    #[doc(alias = "SeparatorEx")]
161    pub fn separator_horizontal(&self) {
162        unsafe { sys::igSeparatorEx(sys::ImGuiSeparatorFlags_Horizontal as i32, 1.0) }
163    }
164
165    /// Call between widgets or groups to layout them horizontally.
166    ///
167    /// X position is given in window coordinates.
168    ///
169    /// This is equivalent to calling [same_line_with_pos](Self::same_line_with_pos)
170    /// with the `pos` set to 0.0, which uses `Style::item_spacing`.
171    #[doc(alias = "SameLine")]
172    pub fn same_line(&self) {
173        self.same_line_with_pos(0.0);
174    }
175
176    /// Call between widgets or groups to layout them horizontally.
177    ///
178    /// X position is given in window coordinates.
179    ///
180    /// This is equivalent to calling [same_line_with_spacing](Self::same_line_with_spacing)
181    /// with the `spacing` set to -1.0, which means no extra spacing.
182    #[doc(alias = "SameLine")]
183    pub fn same_line_with_pos(&self, pos_x: f32) {
184        self.same_line_with_spacing(pos_x, -1.0)
185    }
186
187    /// Call between widgets or groups to layout them horizontally.
188    ///
189    /// X position is given in window coordinates.
190    #[doc(alias = "SameLine")]
191    pub fn same_line_with_spacing(&self, pos_x: f32, spacing_w: f32) {
192        assert_finite_f32("Ui::same_line_with_spacing()", "pos_x", pos_x);
193        assert_finite_f32("Ui::same_line_with_spacing()", "spacing_w", spacing_w);
194        unsafe { sys::igSameLine(pos_x, spacing_w) }
195    }
196
197    /// Undo a `same_line` call or force a new line when in horizontal layout mode
198    #[doc(alias = "NewLine")]
199    pub fn new_line(&self) {
200        unsafe { sys::igNewLine() }
201    }
202
203    /// Adds vertical spacing
204    #[doc(alias = "Spacing")]
205    pub fn spacing(&self) {
206        unsafe { sys::igSpacing() }
207    }
208
209    /// Fills a space of `size` in pixels with nothing on the current window.
210    ///
211    /// Can be used to move the cursor on the window.
212    #[doc(alias = "Dummy")]
213    pub fn dummy(&self, size: impl Into<[f32; 2]>) {
214        let size = size.into();
215        assert_finite_vec2("Ui::dummy()", "size", size);
216        let size_vec: sys::ImVec2 = size.into();
217        unsafe { sys::igDummy(size_vec) }
218    }
219
220    /// Moves content position to the right by `Style::indent_spacing`
221    ///
222    /// This is equivalent to [indent_by](Self::indent_by) with `width` set to
223    /// `Style::indent_spacing`.
224    #[doc(alias = "Indent")]
225    pub fn indent(&self) {
226        self.indent_by(0.0)
227    }
228
229    /// Moves content position to the right by `width`
230    #[doc(alias = "Indent")]
231    pub fn indent_by(&self, width: f32) {
232        assert_finite_f32("Ui::indent_by()", "width", width);
233        unsafe { sys::igIndent(width) };
234    }
235
236    /// Moves content position to the left by `Style::indent_spacing`
237    ///
238    /// This is equivalent to [unindent_by](Self::unindent_by) with `width` set to
239    /// `Style::indent_spacing`.
240    #[doc(alias = "Unindent")]
241    pub fn unindent(&self) {
242        self.unindent_by(0.0)
243    }
244
245    /// Moves content position to the left by `width`
246    #[doc(alias = "Unindent")]
247    pub fn unindent_by(&self, width: f32) {
248        assert_finite_f32("Ui::unindent_by()", "width", width);
249        unsafe { sys::igUnindent(width) };
250    }
251
252    /// Creates a layout group and starts appending to it.
253    ///
254    /// Returns a `GroupToken` that must be ended by calling `.end()`.
255    #[doc(alias = "BeginGroup")]
256    pub fn begin_group(&self) -> GroupToken<'_> {
257        unsafe { sys::igBeginGroup() };
258        GroupToken::new(self)
259    }
260
261    /// Creates a layout group and runs a closure to construct the contents.
262    ///
263    /// May be useful to handle the same mouse event on a group of items, for example.
264    #[doc(alias = "BeginGroup")]
265    pub fn group<R, F: FnOnce() -> R>(&self, f: F) -> R {
266        let group = self.begin_group();
267        let result = f();
268        group.end();
269        result
270    }
271
272    /// Starts a stack-layout horizontal group.
273    ///
274    /// This is a compatibility helper for examples and utilities that follow
275    /// `imgui-node-editor`'s blueprint builder. It is backed by a local shim,
276    /// because Dear ImGui itself does not ship `BeginHorizontal`.
277    #[doc(alias = "BeginHorizontal")]
278    pub fn begin_horizontal_stack_layout<'ui, 'id>(
279        &'ui self,
280        id: impl Into<StackLayoutId<'id>>,
281        size: impl Into<[f32; 2]>,
282        align: f32,
283    ) -> HorizontalStackLayoutToken<'ui> {
284        let size = size.into();
285        assert_finite_vec2("Ui::begin_horizontal_stack_layout()", "size", size);
286        assert!(
287            align.is_finite(),
288            "Ui::begin_horizontal_stack_layout() align must be finite"
289        );
290        let size = sys::ImVec2::from(size);
291        unsafe {
292            match id.into() {
293                StackLayoutId::Str(value) => {
294                    sys::ImGuiStack_BeginHorizontal_Str(self.scratch_txt(value), size, align);
295                }
296                StackLayoutId::Ptr(value) => {
297                    sys::ImGuiStack_BeginHorizontal_Ptr(value, size, align);
298                }
299                StackLayoutId::Int(value) => {
300                    sys::ImGuiStack_BeginHorizontal_Int(value, size, align);
301                }
302                StackLayoutId::Raw(value) => {
303                    sys::ImGuiStack_BeginHorizontal_Id(value.raw(), size, align);
304                }
305            }
306        }
307        HorizontalStackLayoutToken::new(self)
308    }
309
310    /// Starts a stack-layout horizontal group.
311    ///
312    /// Alias for [`Self::begin_horizontal_stack_layout`] with the upstream
313    /// naming used by imgui-node-editor examples.
314    #[doc(alias = "BeginHorizontal")]
315    pub fn begin_horizontal<'ui, 'id>(
316        &'ui self,
317        id: impl Into<StackLayoutId<'id>>,
318        size: impl Into<[f32; 2]>,
319        align: f32,
320    ) -> HorizontalStackLayoutToken<'ui> {
321        self.begin_horizontal_stack_layout(id, size, align)
322    }
323
324    /// Runs a closure inside a stack-layout horizontal group.
325    #[doc(alias = "BeginHorizontal", alias = "EndHorizontal")]
326    pub fn horizontal_stack_layout<'id, R>(
327        &self,
328        id: impl Into<StackLayoutId<'id>>,
329        size: impl Into<[f32; 2]>,
330        align: f32,
331        f: impl FnOnce() -> R,
332    ) -> R {
333        let token = self.begin_horizontal_stack_layout(id, size, align);
334        let result = f();
335        token.end();
336        result
337    }
338
339    /// Runs a closure inside a stack-layout horizontal group.
340    ///
341    /// Alias for [`Self::horizontal_stack_layout`].
342    #[doc(alias = "BeginHorizontal", alias = "EndHorizontal")]
343    pub fn horizontal<'id, R>(
344        &self,
345        id: impl Into<StackLayoutId<'id>>,
346        size: impl Into<[f32; 2]>,
347        align: f32,
348        f: impl FnOnce() -> R,
349    ) -> R {
350        self.horizontal_stack_layout(id, size, align, f)
351    }
352
353    /// Starts a stack-layout vertical group.
354    ///
355    /// This is a compatibility helper for examples and utilities that follow
356    /// `imgui-node-editor`'s blueprint builder. It is backed by a local shim,
357    /// because Dear ImGui itself does not ship `BeginVertical`.
358    #[doc(alias = "BeginVertical")]
359    pub fn begin_vertical_stack_layout<'ui, 'id>(
360        &'ui self,
361        id: impl Into<StackLayoutId<'id>>,
362        size: impl Into<[f32; 2]>,
363        align: f32,
364    ) -> VerticalStackLayoutToken<'ui> {
365        let size = size.into();
366        assert_finite_vec2("Ui::begin_vertical_stack_layout()", "size", size);
367        assert!(
368            align.is_finite(),
369            "Ui::begin_vertical_stack_layout() align must be finite"
370        );
371        let size = sys::ImVec2::from(size);
372        unsafe {
373            match id.into() {
374                StackLayoutId::Str(value) => {
375                    sys::ImGuiStack_BeginVertical_Str(self.scratch_txt(value), size, align);
376                }
377                StackLayoutId::Ptr(value) => {
378                    sys::ImGuiStack_BeginVertical_Ptr(value, size, align);
379                }
380                StackLayoutId::Int(value) => {
381                    sys::ImGuiStack_BeginVertical_Int(value, size, align);
382                }
383                StackLayoutId::Raw(value) => {
384                    sys::ImGuiStack_BeginVertical_Id(value.raw(), size, align);
385                }
386            }
387        }
388        VerticalStackLayoutToken::new(self)
389    }
390
391    /// Starts a stack-layout vertical group.
392    ///
393    /// Alias for [`Self::begin_vertical_stack_layout`] with the upstream naming
394    /// used by imgui-node-editor examples.
395    #[doc(alias = "BeginVertical")]
396    pub fn begin_vertical<'ui, 'id>(
397        &'ui self,
398        id: impl Into<StackLayoutId<'id>>,
399        size: impl Into<[f32; 2]>,
400        align: f32,
401    ) -> VerticalStackLayoutToken<'ui> {
402        self.begin_vertical_stack_layout(id, size, align)
403    }
404
405    /// Runs a closure inside a stack-layout vertical group.
406    #[doc(alias = "BeginVertical", alias = "EndVertical")]
407    pub fn vertical_stack_layout<'id, R>(
408        &self,
409        id: impl Into<StackLayoutId<'id>>,
410        size: impl Into<[f32; 2]>,
411        align: f32,
412        f: impl FnOnce() -> R,
413    ) -> R {
414        let token = self.begin_vertical_stack_layout(id, size, align);
415        let result = f();
416        token.end();
417        result
418    }
419
420    /// Runs a closure inside a stack-layout vertical group.
421    ///
422    /// Alias for [`Self::vertical_stack_layout`].
423    #[doc(alias = "BeginVertical", alias = "EndVertical")]
424    pub fn vertical<'id, R>(
425        &self,
426        id: impl Into<StackLayoutId<'id>>,
427        size: impl Into<[f32; 2]>,
428        align: f32,
429        f: impl FnOnce() -> R,
430    ) -> R {
431        self.vertical_stack_layout(id, size, align, f)
432    }
433
434    /// Inserts a spring into the current stack layout.
435    ///
436    /// `weight <= 0.0` reserves only spacing. `spacing < 0.0` uses the current
437    /// style item spacing along the layout axis, matching the upstream stack
438    /// layout extension semantics.
439    #[doc(alias = "Spring")]
440    pub fn stack_layout_spring(&self, weight: f32, spacing: f32) {
441        assert!(
442            weight.is_finite(),
443            "Ui::stack_layout_spring() weight must be finite"
444        );
445        assert!(
446            spacing.is_finite(),
447            "Ui::stack_layout_spring() spacing must be finite"
448        );
449        unsafe { sys::ImGuiStack_Spring(weight, spacing) }
450    }
451
452    /// Inserts a spring into the current stack layout.
453    ///
454    /// Alias for [`Self::stack_layout_spring`] with the upstream naming used by
455    /// imgui-node-editor examples.
456    #[doc(alias = "Spring")]
457    pub fn spring(&self, weight: f32, spacing: f32) {
458        self.stack_layout_spring(weight, spacing)
459    }
460
461    /// Suspends the current stack layout until the returned token is dropped.
462    #[doc(alias = "SuspendLayout")]
463    pub fn suspend_stack_layout(&self) -> StackLayoutSuspensionToken<'_> {
464        unsafe { sys::ImGuiStack_SuspendLayout() };
465        StackLayoutSuspensionToken::new(self)
466    }
467
468    /// Returns the cursor position (in window coordinates)
469    #[doc(alias = "GetCursorPos")]
470    pub fn cursor_pos(&self) -> [f32; 2] {
471        let pos = unsafe { sys::igGetCursorPos() };
472        [pos.x, pos.y]
473    }
474
475    /// Returns the cursor position (in absolute screen coordinates)
476    #[doc(alias = "GetCursorScreenPos")]
477    pub fn cursor_screen_pos(&self) -> [f32; 2] {
478        let pos = unsafe { sys::igGetCursorScreenPos() };
479        [pos.x, pos.y]
480    }
481
482    /// Sets the cursor position (in window coordinates)
483    #[doc(alias = "SetCursorPos")]
484    pub fn set_cursor_pos(&self, pos: impl Into<[f32; 2]>) {
485        let pos_array = pos.into();
486        assert_finite_vec2("Ui::set_cursor_pos()", "position", pos_array);
487        let pos_vec = sys::ImVec2 {
488            x: pos_array[0],
489            y: pos_array[1],
490        };
491        unsafe { sys::igSetCursorPos(pos_vec) };
492    }
493
494    /// Sets the cursor position (in absolute screen coordinates)
495    #[doc(alias = "SetCursorScreenPos")]
496    pub fn set_cursor_screen_pos(&self, pos: impl Into<[f32; 2]>) {
497        let pos_array = pos.into();
498        assert_finite_vec2("Ui::set_cursor_screen_pos()", "position", pos_array);
499        let pos_vec = sys::ImVec2 {
500            x: pos_array[0],
501            y: pos_array[1],
502        };
503        unsafe { sys::igSetCursorScreenPos(pos_vec) };
504    }
505
506    /// Returns the X cursor position (in window coordinates)
507    #[doc(alias = "GetCursorPosX")]
508    pub fn cursor_pos_x(&self) -> f32 {
509        unsafe { sys::igGetCursorPosX() }
510    }
511
512    /// Returns the Y cursor position (in window coordinates)
513    #[doc(alias = "GetCursorPosY")]
514    pub fn cursor_pos_y(&self) -> f32 {
515        unsafe { sys::igGetCursorPosY() }
516    }
517
518    /// Sets the X cursor position (in window coordinates)
519    #[doc(alias = "SetCursorPosX")]
520    pub fn set_cursor_pos_x(&self, x: f32) {
521        assert_finite_f32("Ui::set_cursor_pos_x()", "x", x);
522        unsafe { sys::igSetCursorPosX(x) };
523    }
524
525    /// Sets the Y cursor position (in window coordinates)
526    #[doc(alias = "SetCursorPosY")]
527    pub fn set_cursor_pos_y(&self, y: f32) {
528        assert_finite_f32("Ui::set_cursor_pos_y()", "y", y);
529        unsafe { sys::igSetCursorPosY(y) };
530    }
531
532    /// Returns the initial cursor position (in window coordinates)
533    #[doc(alias = "GetCursorStartPos")]
534    pub fn cursor_start_pos(&self) -> [f32; 2] {
535        let pos = unsafe { sys::igGetCursorStartPos() };
536        [pos.x, pos.y]
537    }
538}
539
540// ============================================================================
541// Metrics helpers & clip rect stack
542// ============================================================================
543
544impl Ui {
545    /// Return ~ FontSize.
546    #[doc(alias = "GetTextLineHeight")]
547    pub fn text_line_height(&self) -> f32 {
548        unsafe { sys::igGetTextLineHeight() }
549    }
550
551    /// Return ~ FontSize + style.ItemSpacing.y.
552    #[doc(alias = "GetTextLineHeightWithSpacing")]
553    pub fn text_line_height_with_spacing(&self) -> f32 {
554        unsafe { sys::igGetTextLineHeightWithSpacing() }
555    }
556
557    /// Return ~ FontSize + style.FramePadding.y * 2.
558    #[doc(alias = "GetFrameHeight")]
559    pub fn frame_height(&self) -> f32 {
560        unsafe { sys::igGetFrameHeight() }
561    }
562
563    /// Return ~ FontSize + style.FramePadding.y * 2 + style.ItemSpacing.y.
564    #[doc(alias = "GetFrameHeightWithSpacing")]
565    pub fn frame_height_with_spacing(&self) -> f32 {
566        unsafe { sys::igGetFrameHeightWithSpacing() }
567    }
568
569    /// Push a clipping rectangle in screen space.
570    #[doc(alias = "PushClipRect")]
571    pub fn push_clip_rect(
572        &self,
573        min: impl Into<[f32; 2]>,
574        max: impl Into<[f32; 2]>,
575        intersect_with_current: bool,
576    ) {
577        let min = min.into();
578        let max = max.into();
579        assert_finite_vec2("Ui::push_clip_rect()", "min", min);
580        assert_finite_vec2("Ui::push_clip_rect()", "max", max);
581        let min_v = sys::ImVec2 {
582            x: min[0],
583            y: min[1],
584        };
585        let max_v = sys::ImVec2 {
586            x: max[0],
587            y: max[1],
588        };
589        unsafe { sys::igPushClipRect(min_v, max_v, intersect_with_current) }
590    }
591
592    /// Pop a clipping rectangle from the stack.
593    #[doc(alias = "PopClipRect")]
594    pub fn pop_clip_rect(&self) {
595        unsafe { sys::igPopClipRect() }
596    }
597
598    /// Run a closure with a clip rect pushed and automatically popped.
599    pub fn with_clip_rect<R>(
600        &self,
601        min: impl Into<[f32; 2]>,
602        max: impl Into<[f32; 2]>,
603        intersect_with_current: bool,
604        f: impl FnOnce() -> R,
605    ) -> R {
606        self.push_clip_rect(min, max, intersect_with_current);
607        let _t = ClipRectToken::new(self);
608        f()
609    }
610
611    /// Returns true if the specified rectangle (min,max) is visible (not clipped).
612    #[doc(alias = "IsRectVisible")]
613    pub fn is_rect_visible_min_max(
614        &self,
615        rect_min: impl Into<[f32; 2]>,
616        rect_max: impl Into<[f32; 2]>,
617    ) -> bool {
618        let mn = rect_min.into();
619        let mx = rect_max.into();
620        assert_finite_vec2("Ui::is_rect_visible_min_max()", "rect_min", mn);
621        assert_finite_vec2("Ui::is_rect_visible_min_max()", "rect_max", mx);
622        let mn_v = sys::ImVec2 { x: mn[0], y: mn[1] };
623        let mx_v = sys::ImVec2 { x: mx[0], y: mx[1] };
624        unsafe { sys::igIsRectVisible_Vec2(mn_v, mx_v) }
625    }
626
627    /// Returns true if a rectangle of given size at the current cursor pos is visible.
628    #[doc(alias = "IsRectVisible")]
629    pub fn is_rect_visible_with_size(&self, size: impl Into<[f32; 2]>) -> bool {
630        let s = size.into();
631        assert_finite_vec2("Ui::is_rect_visible_with_size()", "size", s);
632        let v = sys::ImVec2 { x: s[0], y: s[1] };
633        unsafe { sys::igIsRectVisible_Nil(v) }
634    }
635
636    /// Vertically align upcoming text baseline to FramePadding.y (align text to framed items).
637    #[doc(alias = "AlignTextToFramePadding")]
638    pub fn align_text_to_frame_padding(&self) {
639        unsafe { sys::igAlignTextToFramePadding() }
640    }
641}