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    #[inline]
100    pub(crate) fn validate(self, caller: &str) {
101        let unsupported_flags = self.flags.bits() & !TabBarFlags::all().bits();
102        assert!(
103            unsupported_flags == 0,
104            "{caller} received non-independent ImGuiTabBarFlags bits: 0x{unsupported_flags:X}"
105        );
106        let bits = self.raw();
107        let fitting_policy_mask = sys::ImGuiTabBarFlags_FittingPolicyMask_ as i32;
108        let supported = TabBarFlags::all().bits() | fitting_policy_mask;
109        let unsupported = bits & !supported;
110        assert!(
111            unsupported == 0,
112            "{caller} received unsupported ImGuiTabBarFlags bits: 0x{unsupported:X}"
113        );
114        assert!(
115            (bits & fitting_policy_mask).count_ones() <= 1,
116            "{caller} accepts at most one tab-bar fitting policy"
117        );
118    }
119}
120
121impl Default for TabBarOptions {
122    fn default() -> Self {
123        Self::new()
124    }
125}
126
127impl From<TabBarFlags> for TabBarOptions {
128    fn from(flags: TabBarFlags) -> Self {
129        Self::new().flags(flags)
130    }
131}
132
133bitflags::bitflags! {
134    /// Independent flags for tab item widgets.
135    ///
136    /// Leading/trailing placement is represented by [`TabItemPlacement`].
137    #[repr(transparent)]
138    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
139    pub struct TabItemFlags: i32 {
140        /// No flags
141        const NONE = 0;
142        /// 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.
143        const UNSAVED_DOCUMENT = sys::ImGuiTabItemFlags_UnsavedDocument as i32;
144        /// Trigger flag to programmatically make the tab selected when calling BeginTabItem()
145        const SET_SELECTED = sys::ImGuiTabItemFlags_SetSelected as i32;
146        /// 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.
147        const NO_CLOSE_WITH_MIDDLE_MOUSE_BUTTON = sys::ImGuiTabItemFlags_NoCloseWithMiddleMouseButton as i32;
148        /// Don't call PushID(tab->ID)/PopID() on BeginTabItem()/EndTabItem()
149        const NO_PUSH_ID = sys::ImGuiTabItemFlags_NoPushId as i32;
150        /// Disable tooltip for the given tab
151        const NO_TOOLTIP = sys::ImGuiTabItemFlags_NoTooltip as i32;
152        /// Disable reordering this tab or having another tab cross over this tab
153        const NO_REORDER = sys::ImGuiTabItemFlags_NoReorder as i32;
154    }
155}
156
157/// Single placement option for a tab item or tab item button.
158#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
159pub enum TabItemPlacement {
160    /// Position the tab on the leading side of the tab bar.
161    Leading,
162    /// Position the tab on the trailing side of the tab bar.
163    Trailing,
164}
165
166impl TabItemPlacement {
167    #[inline]
168    const fn raw(self) -> i32 {
169        match self {
170            Self::Leading => sys::ImGuiTabItemFlags_Leading as i32,
171            Self::Trailing => sys::ImGuiTabItemFlags_Trailing as i32,
172        }
173    }
174}
175
176/// Complete tab item options assembled from independent flags and optional
177/// single placement.
178#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
179pub struct TabItemOptions {
180    pub flags: TabItemFlags,
181    pub placement: Option<TabItemPlacement>,
182}
183
184impl TabItemOptions {
185    pub const fn new() -> Self {
186        Self {
187            flags: TabItemFlags::NONE,
188            placement: None,
189        }
190    }
191
192    pub fn flags(mut self, flags: TabItemFlags) -> Self {
193        self.flags = flags;
194        self
195    }
196
197    pub fn placement(mut self, placement: TabItemPlacement) -> Self {
198        self.placement = Some(placement);
199        self
200    }
201
202    pub fn bits(self) -> i32 {
203        self.raw()
204    }
205
206    #[inline]
207    pub(crate) fn raw(self) -> i32 {
208        self.flags.bits() | self.placement.map_or(0, TabItemPlacement::raw)
209    }
210
211    #[inline]
212    pub(crate) fn validate_for_tab_item(self, caller: &str) {
213        validate_tab_item_options(caller, self, false);
214    }
215
216    #[inline]
217    pub(crate) fn validate_for_tab_button(self, caller: &str) {
218        validate_tab_item_options(caller, self, true);
219    }
220}
221
222fn validate_tab_item_options(caller: &str, options: TabItemOptions, allow_button_bits: bool) {
223    let unsupported_flags = options.flags.bits() & !TabItemFlags::all().bits();
224    assert!(
225        unsupported_flags == 0,
226        "{caller} received non-independent ImGuiTabItemFlags bits: 0x{unsupported_flags:X}"
227    );
228    let bits = options.raw();
229    let mut supported = TabItemFlags::all().bits()
230        | (sys::ImGuiTabItemFlags_Leading as i32)
231        | (sys::ImGuiTabItemFlags_Trailing as i32);
232    if allow_button_bits {
233        supported |= sys::ImGuiTabItemFlags_Button as i32;
234    }
235    let unsupported = bits & !supported;
236    assert!(
237        unsupported == 0,
238        "{caller} received unsupported ImGuiTabItemFlags bits: 0x{unsupported:X}"
239    );
240    let placement_mask = (sys::ImGuiTabItemFlags_Leading | sys::ImGuiTabItemFlags_Trailing) as i32;
241    assert!(
242        bits & placement_mask != placement_mask,
243        "{caller} cannot combine LEADING with TRAILING"
244    );
245    if !allow_button_bits {
246        assert!(
247            bits & (sys::ImGuiTabItemFlags_Button as i32) == 0,
248            "{caller} cannot use BUTTON; use TabItemButton instead"
249        );
250    }
251}
252
253impl Default for TabItemOptions {
254    fn default() -> Self {
255        Self::new()
256    }
257}
258
259impl From<TabItemFlags> for TabItemOptions {
260    fn from(flags: TabItemFlags) -> Self {
261        Self::new().flags(flags)
262    }
263}
264
265/// Builder for a tab bar
266#[derive(Debug)]
267#[must_use]
268pub struct TabBar<T> {
269    id: T,
270    options: TabBarOptions,
271}
272
273impl<T: AsRef<str>> TabBar<T> {
274    /// Creates a new tab bar builder
275    #[doc(alias = "BeginTabBar")]
276    pub fn new(id: T) -> Self {
277        Self {
278            id,
279            options: TabBarOptions::new(),
280        }
281    }
282
283    /// Enable/Disable the reorderable property
284    ///
285    /// Disabled by default
286    pub fn reorderable(mut self, value: bool) -> Self {
287        if value {
288            self.options.flags |= TabBarFlags::REORDERABLE;
289        } else {
290            self.options.flags &= !TabBarFlags::REORDERABLE;
291        }
292        self
293    }
294
295    /// Set the flags of the tab bar
296    ///
297    /// Flags are empty by default
298    pub fn flags(mut self, flags: impl Into<TabBarOptions>) -> Self {
299        self.options = flags.into();
300        self
301    }
302
303    /// Set the tab fitting policy.
304    pub fn fitting_policy(mut self, policy: TabBarFittingPolicy) -> Self {
305        self.options.fitting_policy = Some(policy);
306        self
307    }
308
309    /// Begins the tab bar and returns a token if successful
310    pub fn begin(self, ui: &Ui) -> Option<TabBarToken<'_>> {
311        ui.tab_bar_with_flags(self.id, self.options)
312    }
313
314    /// Creates a tab bar and runs a closure to construct the contents.
315    /// Returns the result of the closure, if it is called.
316    ///
317    /// Note: the closure is not called if no tabbar content is visible
318    pub fn build<R, F: FnOnce() -> R>(self, ui: &Ui, f: F) -> Option<R> {
319        self.begin(ui).map(|_tab| f())
320    }
321}
322
323/// Token representing an active tab bar
324#[derive(Debug)]
325#[must_use]
326pub struct TabBarToken<'ui> {
327    _ui: &'ui Ui,
328}
329
330impl<'ui> TabBarToken<'ui> {
331    /// Creates a new tab bar token
332    pub(crate) fn new(ui: &'ui Ui) -> Self {
333        Self { _ui: ui }
334    }
335
336    /// Ends the tab bar
337    pub fn end(self) {
338        // Token is consumed, destructor will be called
339    }
340}
341
342impl<'ui> Drop for TabBarToken<'ui> {
343    fn drop(&mut self) {
344        unsafe {
345            sys::igEndTabBar();
346        }
347    }
348}
349
350/// Builder for a tab item
351#[derive(Debug)]
352#[must_use]
353pub struct TabItem<'a, T> {
354    label: T,
355    opened: Option<&'a mut bool>,
356    options: TabItemOptions,
357}
358
359impl<'a, T: AsRef<str>> TabItem<'a, T> {
360    /// Creates a new tab item builder
361    #[doc(alias = "BeginTabItem")]
362    pub fn new(label: T) -> Self {
363        Self {
364            label,
365            opened: None,
366            options: TabItemOptions::new(),
367        }
368    }
369
370    /// Will open or close the tab.
371    ///
372    /// True to display the tab. Tab item is visible by default.
373    pub fn opened(mut self, opened: &'a mut bool) -> Self {
374        self.opened = Some(opened);
375        self
376    }
377
378    /// Set the flags of the tab item.
379    ///
380    /// Flags are empty by default
381    pub fn flags(mut self, flags: impl Into<TabItemOptions>) -> Self {
382        self.options = flags.into();
383        self
384    }
385
386    /// Set the tab placement.
387    pub fn placement(mut self, placement: TabItemPlacement) -> Self {
388        self.options.placement = Some(placement);
389        self
390    }
391
392    /// Begins the tab item and returns a token if successful
393    pub fn begin(self, ui: &Ui) -> Option<TabItemToken<'_>> {
394        ui.tab_item_with_flags(self.label, self.opened, self.options)
395    }
396
397    /// Creates a tab item and runs a closure to construct the contents.
398    /// Returns the result of the closure, if it is called.
399    ///
400    /// Note: the closure is not called if the tab item is not selected
401    pub fn build<R, F: FnOnce() -> R>(self, ui: &Ui, f: F) -> Option<R> {
402        self.begin(ui).map(|_tab| f())
403    }
404}
405
406/// Token representing an active tab item
407#[derive(Debug)]
408#[must_use]
409pub struct TabItemToken<'ui> {
410    _ui: &'ui Ui,
411}
412
413impl<'ui> TabItemToken<'ui> {
414    /// Creates a new tab item token
415    pub(crate) fn new(ui: &'ui Ui) -> Self {
416        Self { _ui: ui }
417    }
418
419    /// Ends the tab item
420    pub fn end(self) {
421        // Token is consumed, destructor will be called
422    }
423}
424
425impl<'ui> Drop for TabItemToken<'ui> {
426    fn drop(&mut self) {
427        unsafe {
428            sys::igEndTabItem();
429        }
430    }
431}
432
433/// # Tab Widgets
434impl Ui {
435    /// Creates a tab bar and returns a tab bar token, allowing you to append
436    /// Tab items afterwards. This passes no flags. To pass flags explicitly,
437    /// use [tab_bar_with_flags](Self::tab_bar_with_flags).
438    #[doc(alias = "BeginTabBar")]
439    pub fn tab_bar(&self, id: impl AsRef<str>) -> Option<TabBarToken<'_>> {
440        self.tab_bar_with_flags(id, TabBarFlags::NONE)
441    }
442
443    /// Creates a tab bar and returns a tab bar token, allowing you to append
444    /// Tab items afterwards.
445    #[doc(alias = "BeginTabBar")]
446    pub fn tab_bar_with_flags(
447        &self,
448        id: impl AsRef<str>,
449        flags: impl Into<TabBarOptions>,
450    ) -> Option<TabBarToken<'_>> {
451        let options = flags.into();
452        options.validate("Ui::tab_bar_with_flags()");
453        let id_ptr = self.scratch_txt(id);
454        let should_render = unsafe { sys::igBeginTabBar(id_ptr, options.raw()) };
455
456        if should_render {
457            Some(TabBarToken::new(self))
458        } else {
459            None
460        }
461    }
462
463    /// Creates a new tab item and returns a token if its contents are visible.
464    ///
465    /// By default, this doesn't pass an opened bool nor any flags. See [tab_item_with_opened]
466    /// and [tab_item_with_flags] for more.
467    ///
468    /// [tab_item_with_opened]: Self::tab_item_with_opened
469    /// [tab_item_with_flags]: Self::tab_item_with_flags
470    #[doc(alias = "BeginTabItem")]
471    pub fn tab_item(&self, label: impl AsRef<str>) -> Option<TabItemToken<'_>> {
472        self.tab_item_with_flags(label, None, TabItemOptions::new())
473    }
474
475    /// Creates a new tab item and returns a token if its contents are visible.
476    ///
477    /// By default, this doesn't pass any flags. See [tab_item_with_flags] for more.
478    #[doc(alias = "BeginTabItem")]
479    pub fn tab_item_with_opened(
480        &self,
481        label: impl AsRef<str>,
482        opened: &mut bool,
483    ) -> Option<TabItemToken<'_>> {
484        self.tab_item_with_flags(label, Some(opened), TabItemOptions::new())
485    }
486
487    /// Creates a new tab item and returns a token if its contents are visible.
488    #[doc(alias = "BeginTabItem")]
489    pub fn tab_item_with_flags(
490        &self,
491        label: impl AsRef<str>,
492        opened: Option<&mut bool>,
493        flags: impl Into<TabItemOptions>,
494    ) -> Option<TabItemToken<'_>> {
495        let options = flags.into();
496        options.validate_for_tab_item("Ui::tab_item_with_flags()");
497        let label_ptr = self.scratch_txt(label);
498        let opened_ptr = opened.map(|x| x as *mut bool).unwrap_or(ptr::null_mut());
499
500        let should_render = unsafe { sys::igBeginTabItem(label_ptr, opened_ptr, options.raw()) };
501
502        if should_render {
503            Some(TabItemToken::new(self))
504        } else {
505            None
506        }
507    }
508
509    /// Creates a button on the current tab bar (e.g. to append a `+` new-tab button).
510    #[doc(alias = "TabItemButton")]
511    pub fn tab_item_button(&self, label: impl AsRef<str>) -> bool {
512        self.tab_item_button_with_flags(label, TabItemOptions::new())
513    }
514
515    /// Creates a button on the current tab bar with explicit flags.
516    #[doc(alias = "TabItemButton")]
517    pub fn tab_item_button_with_flags(
518        &self,
519        label: impl AsRef<str>,
520        flags: impl Into<TabItemOptions>,
521    ) -> bool {
522        let options = flags.into();
523        options.validate_for_tab_button("Ui::tab_item_button_with_flags()");
524        unsafe { sys::igTabItemButton(self.scratch_txt(label), options.raw()) }
525    }
526
527    /// Notifies Dear ImGui that a tab (or docked window) has been closed.
528    #[doc(alias = "SetTabItemClosed")]
529    pub fn set_tab_item_closed(&self, tab_or_docked_window_label: impl AsRef<str>) {
530        unsafe { sys::igSetTabItemClosed(self.scratch_txt(tab_or_docked_window_label)) }
531    }
532}