Skip to main content

dear_imgui_rs/widget/
tab.rs

1//! Tabs
2//!
3//! Tab bars and tab items for organizing content. Builders manage begin/end
4//! lifetimes to help keep API usage balanced.
5//!
6#![allow(
7    clippy::cast_possible_truncation,
8    clippy::cast_sign_loss,
9    clippy::as_conversions
10)]
11use crate::sys;
12use crate::ui::Ui;
13use std::ptr;
14
15bitflags::bitflags! {
16    /// Independent flags for tab bar widgets.
17    ///
18    /// The fitting policy is a single-choice setting represented by
19    /// [`TabBarFittingPolicy`].
20    #[repr(transparent)]
21    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
22    pub struct TabBarFlags: i32 {
23        /// No flags
24        const NONE = 0;
25        /// Allow manually dragging tabs to re-order them + New tabs are appended at the end of list
26        const REORDERABLE = sys::ImGuiTabBarFlags_Reorderable as i32;
27        /// Automatically select new tabs when they appear
28        const AUTO_SELECT_NEW_TABS = sys::ImGuiTabBarFlags_AutoSelectNewTabs as i32;
29        /// Disable buttons to open the tab list popup
30        const TAB_LIST_POPUP_BUTTON = sys::ImGuiTabBarFlags_TabListPopupButton as i32;
31        /// Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You can still repro this behavior on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false.
32        const NO_CLOSE_WITH_MIDDLE_MOUSE_BUTTON = sys::ImGuiTabBarFlags_NoCloseWithMiddleMouseButton as i32;
33        /// Disable scrolling buttons (apply when fitting policy is ImGuiTabBarFlags_FittingPolicyScroll)
34        const NO_TAB_LIST_SCROLLING_BUTTONS = sys::ImGuiTabBarFlags_NoTabListScrollingButtons as i32;
35        /// Disable tooltips when hovering a tab
36        const NO_TOOLTIP = sys::ImGuiTabBarFlags_NoTooltip as i32;
37        /// Draw selected tab with a different color
38        const DRAW_SELECTED_OVERLINE = sys::ImGuiTabBarFlags_DrawSelectedOverline as i32;
39    }
40}
41
42/// Single fitting policy for a tab bar.
43#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
44pub enum TabBarFittingPolicy {
45    /// Use Dear ImGui's mixed default policy.
46    Mixed,
47    /// Shrink tabs when they do not fit.
48    Shrink,
49    /// Add scrolling when tabs do not fit.
50    Scroll,
51}
52
53impl TabBarFittingPolicy {
54    #[inline]
55    const fn raw(self) -> i32 {
56        match self {
57            Self::Mixed => sys::ImGuiTabBarFlags_FittingPolicyMixed as i32,
58            Self::Shrink => sys::ImGuiTabBarFlags_FittingPolicyShrink as i32,
59            Self::Scroll => sys::ImGuiTabBarFlags_FittingPolicyScroll as i32,
60        }
61    }
62}
63
64/// Complete tab bar options assembled from independent flags and optional
65/// single fitting policy.
66#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
67pub struct TabBarOptions {
68    pub flags: TabBarFlags,
69    pub fitting_policy: Option<TabBarFittingPolicy>,
70}
71
72impl TabBarOptions {
73    pub const fn new() -> Self {
74        Self {
75            flags: TabBarFlags::NONE,
76            fitting_policy: None,
77        }
78    }
79
80    pub fn flags(mut self, flags: TabBarFlags) -> Self {
81        self.flags = flags;
82        self
83    }
84
85    pub fn fitting_policy(mut self, policy: TabBarFittingPolicy) -> Self {
86        self.fitting_policy = Some(policy);
87        self
88    }
89
90    pub fn bits(self) -> i32 {
91        self.raw()
92    }
93
94    #[inline]
95    pub(crate) fn raw(self) -> i32 {
96        self.flags.bits() | self.fitting_policy.map_or(0, TabBarFittingPolicy::raw)
97    }
98}
99
100impl Default for TabBarOptions {
101    fn default() -> Self {
102        Self::new()
103    }
104}
105
106impl From<TabBarFlags> for TabBarOptions {
107    fn from(flags: TabBarFlags) -> Self {
108        Self::new().flags(flags)
109    }
110}
111
112bitflags::bitflags! {
113    /// Independent flags for tab item widgets.
114    ///
115    /// Leading/trailing placement is represented by [`TabItemPlacement`].
116    #[repr(transparent)]
117    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
118    pub struct TabItemFlags: i32 {
119        /// No flags
120        const NONE = 0;
121        /// Display a dot next to the title + tab is selected when clicking the X + closure is not assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar.
122        const UNSAVED_DOCUMENT = sys::ImGuiTabItemFlags_UnsavedDocument as i32;
123        /// Trigger flag to programmatically make the tab selected when calling BeginTabItem()
124        const SET_SELECTED = sys::ImGuiTabItemFlags_SetSelected as i32;
125        /// Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You can still repro this behavior on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false.
126        const NO_CLOSE_WITH_MIDDLE_MOUSE_BUTTON = sys::ImGuiTabItemFlags_NoCloseWithMiddleMouseButton as i32;
127        /// Don't call PushID(tab->ID)/PopID() on BeginTabItem()/EndTabItem()
128        const NO_PUSH_ID = sys::ImGuiTabItemFlags_NoPushId as i32;
129        /// Disable tooltip for the given tab
130        const NO_TOOLTIP = sys::ImGuiTabItemFlags_NoTooltip as i32;
131        /// Disable reordering this tab or having another tab cross over this tab
132        const NO_REORDER = sys::ImGuiTabItemFlags_NoReorder as i32;
133    }
134}
135
136/// Single placement option for a tab item or tab item button.
137#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
138pub enum TabItemPlacement {
139    /// Position the tab on the leading side of the tab bar.
140    Leading,
141    /// Position the tab on the trailing side of the tab bar.
142    Trailing,
143}
144
145impl TabItemPlacement {
146    #[inline]
147    const fn raw(self) -> i32 {
148        match self {
149            Self::Leading => sys::ImGuiTabItemFlags_Leading as i32,
150            Self::Trailing => sys::ImGuiTabItemFlags_Trailing as i32,
151        }
152    }
153}
154
155/// Complete tab item options assembled from independent flags and optional
156/// single placement.
157#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
158pub struct TabItemOptions {
159    pub flags: TabItemFlags,
160    pub placement: Option<TabItemPlacement>,
161}
162
163impl TabItemOptions {
164    pub const fn new() -> Self {
165        Self {
166            flags: TabItemFlags::NONE,
167            placement: None,
168        }
169    }
170
171    pub fn flags(mut self, flags: TabItemFlags) -> Self {
172        self.flags = flags;
173        self
174    }
175
176    pub fn placement(mut self, placement: TabItemPlacement) -> Self {
177        self.placement = Some(placement);
178        self
179    }
180
181    pub fn bits(self) -> i32 {
182        self.raw()
183    }
184
185    #[inline]
186    pub(crate) fn raw(self) -> i32 {
187        self.flags.bits() | self.placement.map_or(0, TabItemPlacement::raw)
188    }
189}
190
191impl Default for TabItemOptions {
192    fn default() -> Self {
193        Self::new()
194    }
195}
196
197impl From<TabItemFlags> for TabItemOptions {
198    fn from(flags: TabItemFlags) -> Self {
199        Self::new().flags(flags)
200    }
201}
202
203/// Builder for a tab bar
204#[derive(Debug)]
205#[must_use]
206pub struct TabBar<T> {
207    id: T,
208    options: TabBarOptions,
209}
210
211impl<T: AsRef<str>> TabBar<T> {
212    /// Creates a new tab bar builder
213    #[doc(alias = "BeginTabBar")]
214    pub fn new(id: T) -> Self {
215        Self {
216            id,
217            options: TabBarOptions::new(),
218        }
219    }
220
221    /// Enable/Disable the reorderable property
222    ///
223    /// Disabled by default
224    pub fn reorderable(mut self, value: bool) -> Self {
225        if value {
226            self.options.flags |= TabBarFlags::REORDERABLE;
227        } else {
228            self.options.flags &= !TabBarFlags::REORDERABLE;
229        }
230        self
231    }
232
233    /// Set the flags of the tab bar
234    ///
235    /// Flags are empty by default
236    pub fn flags(mut self, flags: impl Into<TabBarOptions>) -> Self {
237        self.options = flags.into();
238        self
239    }
240
241    /// Set the tab fitting policy.
242    pub fn fitting_policy(mut self, policy: TabBarFittingPolicy) -> Self {
243        self.options.fitting_policy = Some(policy);
244        self
245    }
246
247    /// Begins the tab bar and returns a token if successful
248    pub fn begin(self, ui: &Ui) -> Option<TabBarToken<'_>> {
249        ui.tab_bar_with_flags(self.id, self.options)
250    }
251
252    /// Creates a tab bar and runs a closure to construct the contents.
253    /// Returns the result of the closure, if it is called.
254    ///
255    /// Note: the closure is not called if no tabbar content is visible
256    pub fn build<R, F: FnOnce() -> R>(self, ui: &Ui, f: F) -> Option<R> {
257        self.begin(ui).map(|_tab| f())
258    }
259}
260
261/// Token representing an active tab bar
262#[derive(Debug)]
263#[must_use]
264pub struct TabBarToken<'ui> {
265    _ui: &'ui Ui,
266}
267
268impl<'ui> TabBarToken<'ui> {
269    /// Creates a new tab bar token
270    pub(crate) fn new(ui: &'ui Ui) -> Self {
271        Self { _ui: ui }
272    }
273
274    /// Ends the tab bar
275    pub fn end(self) {
276        // Token is consumed, destructor will be called
277    }
278}
279
280impl<'ui> Drop for TabBarToken<'ui> {
281    fn drop(&mut self) {
282        unsafe {
283            sys::igEndTabBar();
284        }
285    }
286}
287
288/// Builder for a tab item
289#[derive(Debug)]
290#[must_use]
291pub struct TabItem<'a, T> {
292    label: T,
293    opened: Option<&'a mut bool>,
294    options: TabItemOptions,
295}
296
297impl<'a, T: AsRef<str>> TabItem<'a, T> {
298    /// Creates a new tab item builder
299    #[doc(alias = "BeginTabItem")]
300    pub fn new(label: T) -> Self {
301        Self {
302            label,
303            opened: None,
304            options: TabItemOptions::new(),
305        }
306    }
307
308    /// Will open or close the tab.
309    ///
310    /// True to display the tab. Tab item is visible by default.
311    pub fn opened(mut self, opened: &'a mut bool) -> Self {
312        self.opened = Some(opened);
313        self
314    }
315
316    /// Set the flags of the tab item.
317    ///
318    /// Flags are empty by default
319    pub fn flags(mut self, flags: impl Into<TabItemOptions>) -> Self {
320        self.options = flags.into();
321        self
322    }
323
324    /// Set the tab placement.
325    pub fn placement(mut self, placement: TabItemPlacement) -> Self {
326        self.options.placement = Some(placement);
327        self
328    }
329
330    /// Begins the tab item and returns a token if successful
331    pub fn begin(self, ui: &Ui) -> Option<TabItemToken<'_>> {
332        ui.tab_item_with_flags(self.label, self.opened, self.options)
333    }
334
335    /// Creates a tab item and runs a closure to construct the contents.
336    /// Returns the result of the closure, if it is called.
337    ///
338    /// Note: the closure is not called if the tab item is not selected
339    pub fn build<R, F: FnOnce() -> R>(self, ui: &Ui, f: F) -> Option<R> {
340        self.begin(ui).map(|_tab| f())
341    }
342}
343
344/// Token representing an active tab item
345#[derive(Debug)]
346#[must_use]
347pub struct TabItemToken<'ui> {
348    _ui: &'ui Ui,
349}
350
351impl<'ui> TabItemToken<'ui> {
352    /// Creates a new tab item token
353    pub(crate) fn new(ui: &'ui Ui) -> Self {
354        Self { _ui: ui }
355    }
356
357    /// Ends the tab item
358    pub fn end(self) {
359        // Token is consumed, destructor will be called
360    }
361}
362
363impl<'ui> Drop for TabItemToken<'ui> {
364    fn drop(&mut self) {
365        unsafe {
366            sys::igEndTabItem();
367        }
368    }
369}
370
371/// # Tab Widgets
372impl Ui {
373    /// Creates a tab bar and returns a tab bar token, allowing you to append
374    /// Tab items afterwards. This passes no flags. To pass flags explicitly,
375    /// use [tab_bar_with_flags](Self::tab_bar_with_flags).
376    #[doc(alias = "BeginTabBar")]
377    pub fn tab_bar(&self, id: impl AsRef<str>) -> Option<TabBarToken<'_>> {
378        self.tab_bar_with_flags(id, TabBarFlags::NONE)
379    }
380
381    /// Creates a tab bar and returns a tab bar token, allowing you to append
382    /// Tab items afterwards.
383    #[doc(alias = "BeginTabBar")]
384    pub fn tab_bar_with_flags(
385        &self,
386        id: impl AsRef<str>,
387        flags: impl Into<TabBarOptions>,
388    ) -> Option<TabBarToken<'_>> {
389        let options = flags.into();
390        let id_ptr = self.scratch_txt(id);
391        let should_render = unsafe { sys::igBeginTabBar(id_ptr, options.raw()) };
392
393        if should_render {
394            Some(TabBarToken::new(self))
395        } else {
396            None
397        }
398    }
399
400    /// Creates a new tab item and returns a token if its contents are visible.
401    ///
402    /// By default, this doesn't pass an opened bool nor any flags. See [tab_item_with_opened]
403    /// and [tab_item_with_flags] for more.
404    ///
405    /// [tab_item_with_opened]: Self::tab_item_with_opened
406    /// [tab_item_with_flags]: Self::tab_item_with_flags
407    #[doc(alias = "BeginTabItem")]
408    pub fn tab_item(&self, label: impl AsRef<str>) -> Option<TabItemToken<'_>> {
409        self.tab_item_with_flags(label, None, TabItemOptions::new())
410    }
411
412    /// Creates a new tab item and returns a token if its contents are visible.
413    ///
414    /// By default, this doesn't pass any flags. See [tab_item_with_flags] for more.
415    #[doc(alias = "BeginTabItem")]
416    pub fn tab_item_with_opened(
417        &self,
418        label: impl AsRef<str>,
419        opened: &mut bool,
420    ) -> Option<TabItemToken<'_>> {
421        self.tab_item_with_flags(label, Some(opened), TabItemOptions::new())
422    }
423
424    /// Creates a new tab item and returns a token if its contents are visible.
425    #[doc(alias = "BeginTabItem")]
426    pub fn tab_item_with_flags(
427        &self,
428        label: impl AsRef<str>,
429        opened: Option<&mut bool>,
430        flags: impl Into<TabItemOptions>,
431    ) -> Option<TabItemToken<'_>> {
432        let options = flags.into();
433        let label_ptr = self.scratch_txt(label);
434        let opened_ptr = opened.map(|x| x as *mut bool).unwrap_or(ptr::null_mut());
435
436        let should_render = unsafe { sys::igBeginTabItem(label_ptr, opened_ptr, options.raw()) };
437
438        if should_render {
439            Some(TabItemToken::new(self))
440        } else {
441            None
442        }
443    }
444
445    /// Creates a button on the current tab bar (e.g. to append a `+` new-tab button).
446    #[doc(alias = "TabItemButton")]
447    pub fn tab_item_button(&self, label: impl AsRef<str>) -> bool {
448        self.tab_item_button_with_flags(label, TabItemOptions::new())
449    }
450
451    /// Creates a button on the current tab bar with explicit flags.
452    #[doc(alias = "TabItemButton")]
453    pub fn tab_item_button_with_flags(
454        &self,
455        label: impl AsRef<str>,
456        flags: impl Into<TabItemOptions>,
457    ) -> bool {
458        unsafe { sys::igTabItemButton(self.scratch_txt(label), flags.into().raw()) }
459    }
460
461    /// Notifies Dear ImGui that a tab (or docked window) has been closed.
462    #[doc(alias = "SetTabItemClosed")]
463    pub fn set_tab_item_closed(&self, tab_or_docked_window_label: impl AsRef<str>) {
464        unsafe { sys::igSetTabItemClosed(self.scratch_txt(tab_or_docked_window_label)) }
465    }
466}