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    /// Flags for tab bar widgets
17    #[repr(transparent)]
18    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19    pub struct TabBarFlags: i32 {
20        /// No flags
21        const NONE = 0;
22        /// Allow manually dragging tabs to re-order them + New tabs are appended at the end of list
23        const REORDERABLE = sys::ImGuiTabBarFlags_Reorderable as i32;
24        /// Automatically select new tabs when they appear
25        const AUTO_SELECT_NEW_TABS = sys::ImGuiTabBarFlags_AutoSelectNewTabs as i32;
26        /// Disable buttons to open the tab list popup
27        const TAB_LIST_POPUP_BUTTON = sys::ImGuiTabBarFlags_TabListPopupButton as i32;
28        /// 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.
29        const NO_CLOSE_WITH_MIDDLE_MOUSE_BUTTON = sys::ImGuiTabBarFlags_NoCloseWithMiddleMouseButton as i32;
30        /// Disable scrolling buttons (apply when fitting policy is ImGuiTabBarFlags_FittingPolicyScroll)
31        const NO_TAB_LIST_SCROLLING_BUTTONS = sys::ImGuiTabBarFlags_NoTabListScrollingButtons as i32;
32        /// Disable tooltips when hovering a tab
33        const NO_TOOLTIP = sys::ImGuiTabBarFlags_NoTooltip as i32;
34        /// Draw selected tab with a different color
35        const DRAW_SELECTED_OVERLINE = sys::ImGuiTabBarFlags_DrawSelectedOverline as i32;
36        /// Mixed fitting policy
37        const FITTING_POLICY_MIXED = sys::ImGuiTabBarFlags_FittingPolicyMixed as i32;
38        /// Shrink tabs when they don't fit
39        const FITTING_POLICY_SHRINK = sys::ImGuiTabBarFlags_FittingPolicyShrink as i32;
40        /// Add scroll buttons when tabs don't fit
41        const FITTING_POLICY_SCROLL = sys::ImGuiTabBarFlags_FittingPolicyScroll as i32;
42        /// Mask for fitting policy flags
43        const FITTING_POLICY_MASK = sys::ImGuiTabBarFlags_FittingPolicyMask_ as i32;
44        /// Default fitting policy
45        const FITTING_POLICY_DEFAULT = sys::ImGuiTabBarFlags_FittingPolicyDefault_ as i32;
46    }
47}
48
49bitflags::bitflags! {
50    /// Flags for tab item widgets
51    #[repr(transparent)]
52    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
53    pub struct TabItemFlags: i32 {
54        /// No flags
55        const NONE = 0;
56        /// 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.
57        const UNSAVED_DOCUMENT = sys::ImGuiTabItemFlags_UnsavedDocument as i32;
58        /// Trigger flag to programmatically make the tab selected when calling BeginTabItem()
59        const SET_SELECTED = sys::ImGuiTabItemFlags_SetSelected as i32;
60        /// 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.
61        const NO_CLOSE_WITH_MIDDLE_MOUSE_BUTTON = sys::ImGuiTabItemFlags_NoCloseWithMiddleMouseButton as i32;
62        /// Don't call PushID(tab->ID)/PopID() on BeginTabItem()/EndTabItem()
63        const NO_PUSH_ID = sys::ImGuiTabItemFlags_NoPushId as i32;
64        /// Disable tooltip for the given tab
65        const NO_TOOLTIP = sys::ImGuiTabItemFlags_NoTooltip as i32;
66        /// Disable reordering this tab or having another tab cross over this tab
67        const NO_REORDER = sys::ImGuiTabItemFlags_NoReorder as i32;
68        /// Enforce the tab position to the left of the tab bar (after the tab list popup button)
69        const LEADING = sys::ImGuiTabItemFlags_Leading as i32;
70        /// Enforce the tab position to the right of the tab bar (before the scrolling buttons)
71        const TRAILING = sys::ImGuiTabItemFlags_Trailing as i32;
72    }
73}
74
75/// Builder for a tab bar
76#[derive(Debug)]
77#[must_use]
78pub struct TabBar<T> {
79    id: T,
80    flags: TabBarFlags,
81}
82
83impl<T: AsRef<str>> TabBar<T> {
84    /// Creates a new tab bar builder
85    #[doc(alias = "BeginTabBar")]
86    pub fn new(id: T) -> Self {
87        Self {
88            id,
89            flags: TabBarFlags::NONE,
90        }
91    }
92
93    /// Enable/Disable the reorderable property
94    ///
95    /// Disabled by default
96    pub fn reorderable(mut self, value: bool) -> Self {
97        if value {
98            self.flags |= TabBarFlags::REORDERABLE;
99        } else {
100            self.flags &= !TabBarFlags::REORDERABLE;
101        }
102        self
103    }
104
105    /// Set the flags of the tab bar
106    ///
107    /// Flags are empty by default
108    pub fn flags(mut self, flags: TabBarFlags) -> Self {
109        self.flags = flags;
110        self
111    }
112
113    /// Begins the tab bar and returns a token if successful
114    pub fn begin(self, ui: &Ui) -> Option<TabBarToken<'_>> {
115        ui.tab_bar_with_flags(self.id, self.flags)
116    }
117
118    /// Creates a tab bar and runs a closure to construct the contents.
119    /// Returns the result of the closure, if it is called.
120    ///
121    /// Note: the closure is not called if no tabbar content is visible
122    pub fn build<R, F: FnOnce() -> R>(self, ui: &Ui, f: F) -> Option<R> {
123        self.begin(ui).map(|_tab| f())
124    }
125}
126
127/// Token representing an active tab bar
128#[derive(Debug)]
129#[must_use]
130pub struct TabBarToken<'ui> {
131    ui: &'ui Ui,
132}
133
134impl<'ui> TabBarToken<'ui> {
135    /// Creates a new tab bar token
136    pub(crate) fn new(ui: &'ui Ui) -> Self {
137        Self { ui }
138    }
139
140    /// Ends the tab bar
141    pub fn end(self) {
142        // Token is consumed, destructor will be called
143    }
144}
145
146impl<'ui> Drop for TabBarToken<'ui> {
147    fn drop(&mut self) {
148        unsafe {
149            sys::igEndTabBar();
150        }
151    }
152}
153
154/// Builder for a tab item
155#[derive(Debug)]
156#[must_use]
157pub struct TabItem<'a, T> {
158    label: T,
159    opened: Option<&'a mut bool>,
160    flags: TabItemFlags,
161}
162
163impl<'a, T: AsRef<str>> TabItem<'a, T> {
164    /// Creates a new tab item builder
165    #[doc(alias = "BeginTabItem")]
166    pub fn new(label: T) -> Self {
167        Self {
168            label,
169            opened: None,
170            flags: TabItemFlags::NONE,
171        }
172    }
173
174    /// Will open or close the tab.
175    ///
176    /// True to display the tab. Tab item is visible by default.
177    pub fn opened(mut self, opened: &'a mut bool) -> Self {
178        self.opened = Some(opened);
179        self
180    }
181
182    /// Set the flags of the tab item.
183    ///
184    /// Flags are empty by default
185    pub fn flags(mut self, flags: TabItemFlags) -> Self {
186        self.flags = flags;
187        self
188    }
189
190    /// Begins the tab item and returns a token if successful
191    pub fn begin(self, ui: &Ui) -> Option<TabItemToken<'_>> {
192        ui.tab_item_with_flags(self.label, self.opened, self.flags)
193    }
194
195    /// Creates a tab item and runs a closure to construct the contents.
196    /// Returns the result of the closure, if it is called.
197    ///
198    /// Note: the closure is not called if the tab item is not selected
199    pub fn build<R, F: FnOnce() -> R>(self, ui: &Ui, f: F) -> Option<R> {
200        self.begin(ui).map(|_tab| f())
201    }
202}
203
204/// Token representing an active tab item
205#[derive(Debug)]
206#[must_use]
207pub struct TabItemToken<'ui> {
208    ui: &'ui Ui,
209}
210
211impl<'ui> TabItemToken<'ui> {
212    /// Creates a new tab item token
213    pub(crate) fn new(ui: &'ui Ui) -> Self {
214        Self { ui }
215    }
216
217    /// Ends the tab item
218    pub fn end(self) {
219        // Token is consumed, destructor will be called
220    }
221}
222
223impl<'ui> Drop for TabItemToken<'ui> {
224    fn drop(&mut self) {
225        unsafe {
226            sys::igEndTabItem();
227        }
228    }
229}
230
231/// # Tab Widgets
232impl Ui {
233    /// Creates a tab bar and returns a tab bar token, allowing you to append
234    /// Tab items afterwards. This passes no flags. To pass flags explicitly,
235    /// use [tab_bar_with_flags](Self::tab_bar_with_flags).
236    #[doc(alias = "BeginTabBar")]
237    pub fn tab_bar(&self, id: impl AsRef<str>) -> Option<TabBarToken<'_>> {
238        self.tab_bar_with_flags(id, TabBarFlags::NONE)
239    }
240
241    /// Creates a tab bar and returns a tab bar token, allowing you to append
242    /// Tab items afterwards.
243    #[doc(alias = "BeginTabBar")]
244    pub fn tab_bar_with_flags(
245        &self,
246        id: impl AsRef<str>,
247        flags: TabBarFlags,
248    ) -> Option<TabBarToken<'_>> {
249        let id_ptr = self.scratch_txt(id);
250        let should_render = unsafe { sys::igBeginTabBar(id_ptr, flags.bits()) };
251
252        if should_render {
253            Some(TabBarToken::new(self))
254        } else {
255            None
256        }
257    }
258
259    /// Creates a new tab item and returns a token if its contents are visible.
260    ///
261    /// By default, this doesn't pass an opened bool nor any flags. See [tab_item_with_opened]
262    /// and [tab_item_with_flags] for more.
263    ///
264    /// [tab_item_with_opened]: Self::tab_item_with_opened
265    /// [tab_item_with_flags]: Self::tab_item_with_flags
266    #[doc(alias = "BeginTabItem")]
267    pub fn tab_item(&self, label: impl AsRef<str>) -> Option<TabItemToken<'_>> {
268        self.tab_item_with_flags(label, None, TabItemFlags::NONE)
269    }
270
271    /// Creates a new tab item and returns a token if its contents are visible.
272    ///
273    /// By default, this doesn't pass any flags. See [tab_item_with_flags] for more.
274    #[doc(alias = "BeginTabItem")]
275    pub fn tab_item_with_opened(
276        &self,
277        label: impl AsRef<str>,
278        opened: &mut bool,
279    ) -> Option<TabItemToken<'_>> {
280        self.tab_item_with_flags(label, Some(opened), TabItemFlags::NONE)
281    }
282
283    /// Creates a new tab item and returns a token if its contents are visible.
284    #[doc(alias = "BeginTabItem")]
285    pub fn tab_item_with_flags(
286        &self,
287        label: impl AsRef<str>,
288        opened: Option<&mut bool>,
289        flags: TabItemFlags,
290    ) -> Option<TabItemToken<'_>> {
291        let label_ptr = self.scratch_txt(label);
292        let opened_ptr = opened.map(|x| x as *mut bool).unwrap_or(ptr::null_mut());
293
294        let should_render = unsafe { sys::igBeginTabItem(label_ptr, opened_ptr, flags.bits()) };
295
296        if should_render {
297            Some(TabItemToken::new(self))
298        } else {
299            None
300        }
301    }
302}