1#![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 #[repr(transparent)]
21 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
22 pub struct TabBarFlags: i32 {
23 const NONE = 0;
25 const REORDERABLE = sys::ImGuiTabBarFlags_Reorderable as i32;
27 const AUTO_SELECT_NEW_TABS = sys::ImGuiTabBarFlags_AutoSelectNewTabs as i32;
29 const TAB_LIST_POPUP_BUTTON = sys::ImGuiTabBarFlags_TabListPopupButton as i32;
31 const NO_CLOSE_WITH_MIDDLE_MOUSE_BUTTON = sys::ImGuiTabBarFlags_NoCloseWithMiddleMouseButton as i32;
33 const NO_TAB_LIST_SCROLLING_BUTTONS = sys::ImGuiTabBarFlags_NoTabListScrollingButtons as i32;
35 const NO_TOOLTIP = sys::ImGuiTabBarFlags_NoTooltip as i32;
37 const DRAW_SELECTED_OVERLINE = sys::ImGuiTabBarFlags_DrawSelectedOverline as i32;
39 }
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
44pub enum TabBarFittingPolicy {
45 Mixed,
47 Shrink,
49 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#[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 #[repr(transparent)]
138 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
139 pub struct TabItemFlags: i32 {
140 const NONE = 0;
142 const UNSAVED_DOCUMENT = sys::ImGuiTabItemFlags_UnsavedDocument as i32;
144 const SET_SELECTED = sys::ImGuiTabItemFlags_SetSelected as i32;
146 const NO_CLOSE_WITH_MIDDLE_MOUSE_BUTTON = sys::ImGuiTabItemFlags_NoCloseWithMiddleMouseButton as i32;
148 const NO_PUSH_ID = sys::ImGuiTabItemFlags_NoPushId as i32;
150 const NO_TOOLTIP = sys::ImGuiTabItemFlags_NoTooltip as i32;
152 const NO_REORDER = sys::ImGuiTabItemFlags_NoReorder as i32;
154 }
155}
156
157#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
159pub enum TabItemPlacement {
160 Leading,
162 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#[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#[derive(Debug)]
267#[must_use]
268pub struct TabBar<T> {
269 id: T,
270 options: TabBarOptions,
271}
272
273impl<T: AsRef<str>> TabBar<T> {
274 #[doc(alias = "BeginTabBar")]
276 pub fn new(id: T) -> Self {
277 Self {
278 id,
279 options: TabBarOptions::new(),
280 }
281 }
282
283 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 pub fn flags(mut self, flags: impl Into<TabBarOptions>) -> Self {
299 self.options = flags.into();
300 self
301 }
302
303 pub fn fitting_policy(mut self, policy: TabBarFittingPolicy) -> Self {
305 self.options.fitting_policy = Some(policy);
306 self
307 }
308
309 pub fn begin(self, ui: &Ui) -> Option<TabBarToken<'_>> {
311 ui.tab_bar_with_flags(self.id, self.options)
312 }
313
314 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#[derive(Debug)]
325#[must_use]
326pub struct TabBarToken<'ui> {
327 _ui: &'ui Ui,
328}
329
330impl<'ui> TabBarToken<'ui> {
331 pub(crate) fn new(ui: &'ui Ui) -> Self {
333 Self { _ui: ui }
334 }
335
336 pub fn end(self) {
338 }
340}
341
342impl<'ui> Drop for TabBarToken<'ui> {
343 fn drop(&mut self) {
344 unsafe {
345 sys::igEndTabBar();
346 }
347 }
348}
349
350#[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 #[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 pub fn opened(mut self, opened: &'a mut bool) -> Self {
374 self.opened = Some(opened);
375 self
376 }
377
378 pub fn flags(mut self, flags: impl Into<TabItemOptions>) -> Self {
382 self.options = flags.into();
383 self
384 }
385
386 pub fn placement(mut self, placement: TabItemPlacement) -> Self {
388 self.options.placement = Some(placement);
389 self
390 }
391
392 pub fn begin(self, ui: &Ui) -> Option<TabItemToken<'_>> {
394 ui.tab_item_with_flags(self.label, self.opened, self.options)
395 }
396
397 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#[derive(Debug)]
408#[must_use]
409pub struct TabItemToken<'ui> {
410 _ui: &'ui Ui,
411}
412
413impl<'ui> TabItemToken<'ui> {
414 pub(crate) fn new(ui: &'ui Ui) -> Self {
416 Self { _ui: ui }
417 }
418
419 pub fn end(self) {
421 }
423}
424
425impl<'ui> Drop for TabItemToken<'ui> {
426 fn drop(&mut self) {
427 unsafe {
428 sys::igEndTabItem();
429 }
430 }
431}
432
433impl Ui {
435 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}