i_slint_backend_qt/qt_widgets/
tabwidget.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cSpell: ignore hframe qreal tabbar vframe
5
6use i_slint_core::{
7    input::{FocusEventResult, FocusReason},
8    platform::PointerEventButton,
9};
10
11use super::*;
12
13#[repr(C)]
14#[derive(FieldOffsets, Default, SlintElement)]
15#[pin]
16pub struct NativeTabWidget {
17    pub width: Property<LogicalLength>,
18    pub height: Property<LogicalLength>,
19    pub cached_rendering_data: CachedRenderingData,
20    pub content_min_height: Property<LogicalLength>,
21    pub content_min_width: Property<LogicalLength>,
22    pub tabbar_preferred_height: Property<LogicalLength>,
23    pub tabbar_preferred_width: Property<LogicalLength>,
24    pub current_index: Property<i32>,
25    pub current_focused: Property<i32>,
26
27    // outputs
28    pub content_x: Property<LogicalLength>,
29    pub content_y: Property<LogicalLength>,
30    pub content_height: Property<LogicalLength>,
31    pub content_width: Property<LogicalLength>,
32    pub tabbar_x: Property<LogicalLength>,
33    pub tabbar_y: Property<LogicalLength>,
34    pub tabbar_height: Property<LogicalLength>,
35    pub tabbar_width: Property<LogicalLength>,
36
37    widget_ptr: std::cell::Cell<SlintTypeErasedWidgetPtr>,
38    animation_tracker: Property<i32>,
39}
40
41impl Item for NativeTabWidget {
42    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {
43        let animation_tracker_property_ptr = Self::FIELD_OFFSETS.animation_tracker.apply_pin(self);
44        self.widget_ptr.set(cpp! { unsafe [animation_tracker_property_ptr as "void*"] -> SlintTypeErasedWidgetPtr as "std::unique_ptr<SlintTypeErasedWidget>" {
45            return make_unique_animated_widget<QTabWidget>(animation_tracker_property_ptr);
46        }});
47
48        #[derive(Default, Clone)]
49        #[repr(C)]
50        struct TabWidgetMetrics {
51            content_start: qttypes::qreal,
52            content_size: qttypes::qreal,
53            tabbar_start: qttypes::qreal,
54            tabbar_size: qttypes::qreal,
55        }
56        cpp! {{ struct TabWidgetMetrics { qreal content_start, content_size, tabbar_start, tabbar_size; }; }}
57
58        #[repr(C)]
59        #[derive(FieldOffsets, Default)]
60        #[pin]
61        struct TabBarSharedData {
62            width: Property<LogicalLength>,
63            height: Property<LogicalLength>,
64            tabbar_preferred_height: Property<LogicalLength>,
65            tabbar_preferred_width: Property<LogicalLength>,
66            horizontal_metrics: Property<TabWidgetMetrics>,
67            vertical_metrics: Property<TabWidgetMetrics>,
68        }
69        let shared_data = Rc::pin(TabBarSharedData::default());
70        macro_rules! link {
71            ($prop:ident) => {
72                Property::link_two_way(
73                    Self::FIELD_OFFSETS.$prop.apply_pin(self),
74                    TabBarSharedData::FIELD_OFFSETS.$prop.apply_pin(shared_data.as_ref()),
75                );
76            };
77        }
78        link!(width);
79        link!(height);
80        link!(tabbar_preferred_width);
81        link!(tabbar_preferred_height);
82
83        let shared_data_weak = pin_weak::rc::PinWeak::downgrade(shared_data.clone());
84
85        let query_tabbar_metrics = move |orientation: Orientation| {
86            let shared_data = shared_data_weak.upgrade().unwrap();
87
88            let (size, tabbar_size) = match orientation {
89                Orientation::Horizontal => (
90                    qttypes::QSizeF {
91                        width: TabBarSharedData::FIELD_OFFSETS
92                            .width
93                            .apply_pin(shared_data.as_ref())
94                            .get()
95                            .get() as _,
96                        height: (std::i32::MAX / 2) as _,
97                    },
98                    qttypes::QSizeF {
99                        width: TabBarSharedData::FIELD_OFFSETS
100                            .tabbar_preferred_width
101                            .apply_pin(shared_data.as_ref())
102                            .get()
103                            .get() as _,
104                        height: (std::i32::MAX / 2) as _,
105                    },
106                ),
107                Orientation::Vertical => (
108                    qttypes::QSizeF {
109                        width: (std::i32::MAX / 2) as _,
110                        height: TabBarSharedData::FIELD_OFFSETS
111                            .height
112                            .apply_pin(shared_data.as_ref())
113                            .get()
114                            .get() as _,
115                    },
116                    qttypes::QSizeF {
117                        width: (std::i32::MAX / 2) as _,
118                        height: TabBarSharedData::FIELD_OFFSETS
119                            .tabbar_preferred_height
120                            .apply_pin(shared_data.as_ref())
121                            .get()
122                            .get() as _,
123                    },
124                ),
125            };
126
127            let horizontal: bool = matches!(orientation, Orientation::Horizontal);
128
129            cpp!(unsafe [horizontal as "bool", size as "QSizeF", tabbar_size as "QSizeF"] -> TabWidgetMetrics as "TabWidgetMetrics" {
130                ensure_initialized();
131                QStyleOptionTabWidgetFrame option;
132                auto style = qApp->style();
133                option.lineWidth = style->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, nullptr);
134                option.shape = QTabBar::RoundedNorth;
135                option.rect = QRect(QPoint(), size.toSize());
136                option.tabBarSize = tabbar_size.toSize();
137                option.tabBarRect = QRect(QPoint(), option.tabBarSize);
138                option.rightCornerWidgetSize = QSize(0, 0);
139                option.leftCornerWidgetSize = QSize(0, 0);
140                QRectF contentsRect = style->subElementRect(QStyle::SE_TabWidgetTabContents, &option, nullptr);
141                QRectF tabbarRect = style->subElementRect(QStyle::SE_TabWidgetTabBar, &option, nullptr);
142                if (horizontal) {
143                    return {contentsRect.x(), contentsRect.width(), tabbarRect.x(), tabbarRect.width()};
144                } else {
145                    return {contentsRect.y(), contentsRect.height(), tabbarRect.y(), tabbarRect.height()};
146                }
147            })
148        };
149
150        shared_data.horizontal_metrics.set_binding({
151            let query_tabbar_metrics = query_tabbar_metrics.clone();
152            move || query_tabbar_metrics(Orientation::Horizontal)
153        });
154        shared_data
155            .vertical_metrics
156            .set_binding(move || query_tabbar_metrics(Orientation::Vertical));
157
158        macro_rules! bind {
159            ($prop:ident = $field1:ident.$field2:ident) => {
160                let shared_data = shared_data.clone();
161                self.$prop.set_binding(move || {
162                    let metrics = TabBarSharedData::FIELD_OFFSETS
163                        .$field1
164                        .apply_pin(shared_data.as_ref())
165                        .get();
166                    LogicalLength::new(metrics.$field2 as f32)
167                });
168            };
169        }
170        bind!(content_x = horizontal_metrics.content_start);
171        bind!(content_y = vertical_metrics.content_start);
172        bind!(content_width = horizontal_metrics.content_size);
173        bind!(content_height = vertical_metrics.content_size);
174        bind!(tabbar_x = horizontal_metrics.tabbar_start);
175        bind!(tabbar_y = vertical_metrics.tabbar_start);
176        bind!(tabbar_width = horizontal_metrics.tabbar_size);
177        bind!(tabbar_height = vertical_metrics.tabbar_size);
178    }
179
180    fn layout_info(
181        self: Pin<&Self>,
182        orientation: Orientation,
183        _window_adapter: &Rc<dyn WindowAdapter>,
184        _self_rc: &ItemRc,
185    ) -> LayoutInfo {
186        let (content_size, tabbar_size) = match orientation {
187            Orientation::Horizontal => (
188                qttypes::QSizeF {
189                    width: self.content_min_width().get() as _,
190                    height: (std::i32::MAX / 2) as _,
191                },
192                qttypes::QSizeF {
193                    width: self.tabbar_preferred_width().get() as _,
194                    height: (std::i32::MAX / 2) as _,
195                },
196            ),
197            Orientation::Vertical => (
198                qttypes::QSizeF {
199                    width: (std::i32::MAX / 2) as _,
200                    height: self.content_min_height().get() as _,
201                },
202                qttypes::QSizeF {
203                    width: (std::i32::MAX / 2) as _,
204                    height: self.tabbar_preferred_height().get() as _,
205                },
206            ),
207        };
208        let widget: NonNull<()> = SlintTypeErasedWidgetPtr::qwidget_ptr(&self.widget_ptr);
209
210        let size = cpp!(unsafe [content_size as "QSizeF", tabbar_size as "QSizeF", widget as "QWidget*"] -> qttypes::QSize as "QSize" {
211            ensure_initialized();
212
213            QStyleOptionTabWidgetFrame option;
214            auto style = qApp->style();
215            option.lineWidth = style->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, widget);
216            option.shape = QTabBar::RoundedNorth;
217            option.tabBarSize = tabbar_size.toSize();
218            option.rightCornerWidgetSize = QSize(0, 0);
219            option.leftCornerWidgetSize = QSize(0, 0);
220            auto sz = QSize(qMax(content_size.width(), tabbar_size.width()),
221                content_size.height() + tabbar_size.height());
222            return style->sizeFromContents(QStyle::CT_TabWidget, &option, sz, widget);
223        });
224        LayoutInfo {
225            min: match orientation {
226                Orientation::Horizontal => size.width as f32,
227                Orientation::Vertical => size.height as f32,
228            },
229            preferred: match orientation {
230                Orientation::Horizontal => size.width as f32,
231                Orientation::Vertical => size.height as f32,
232            },
233            stretch: 1.,
234            ..LayoutInfo::default()
235        }
236    }
237
238    fn input_event_filter_before_children(
239        self: Pin<&Self>,
240        _: &MouseEvent,
241        _window_adapter: &Rc<dyn WindowAdapter>,
242        _self_rc: &ItemRc,
243    ) -> InputEventFilterResult {
244        InputEventFilterResult::ForwardEvent
245    }
246
247    fn input_event(
248        self: Pin<&Self>,
249        _: &MouseEvent,
250        _window_adapter: &Rc<dyn WindowAdapter>,
251        _self_rc: &i_slint_core::items::ItemRc,
252    ) -> InputEventResult {
253        InputEventResult::EventIgnored
254    }
255
256    fn capture_key_event(
257        self: Pin<&Self>,
258        _event: &KeyEvent,
259        _window_adapter: &Rc<dyn WindowAdapter>,
260        _self_rc: &ItemRc,
261    ) -> KeyEventResult {
262        KeyEventResult::EventIgnored
263    }
264
265    fn key_event(
266        self: Pin<&Self>,
267        _: &KeyEvent,
268        _window_adapter: &Rc<dyn WindowAdapter>,
269        _self_rc: &ItemRc,
270    ) -> KeyEventResult {
271        KeyEventResult::EventIgnored
272    }
273
274    fn focus_event(
275        self: Pin<&Self>,
276        _: &FocusEvent,
277        _window_adapter: &Rc<dyn WindowAdapter>,
278        _self_rc: &ItemRc,
279    ) -> FocusEventResult {
280        FocusEventResult::FocusIgnored
281    }
282
283    fn_render! { this dpr size painter widget initial_state =>
284        let tabbar_size = qttypes::QSizeF {
285            width: this.tabbar_preferred_width().get() as _,
286            height: this.tabbar_preferred_height().get() as _,
287        };
288        cpp!(unsafe [
289            painter as "QPainterPtr*",
290            widget as "QWidget*",
291            size as "QSize",
292            dpr as "float",
293            tabbar_size as "QSizeF",
294            initial_state as "int"
295        ] {
296            QStyleOptionTabWidgetFrame option;
297            option.styleObject = widget;
298            option.state |= QStyle::State(initial_state);
299            auto style = qApp->style();
300            option.lineWidth = style->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, widget);
301            option.shape = QTabBar::RoundedNorth;
302            if (true /*enabled*/) {
303                option.state |= QStyle::State_Enabled;
304            } else {
305                option.palette.setCurrentColorGroup(QPalette::Disabled);
306            }
307            option.rect = QRect(QPoint(), size / dpr);
308            option.tabBarSize = tabbar_size.toSize();
309            option.rightCornerWidgetSize = QSize(0, 0);
310            option.leftCornerWidgetSize = QSize(0, 0);
311            option.tabBarRect = style->subElementRect(QStyle::SE_TabWidgetTabBar, &option, widget);
312            option.rect = style->subElementRect(QStyle::SE_TabWidgetTabPane, &option, widget);
313            style->drawPrimitive(QStyle::PE_FrameTabWidget, &option, painter->get(), widget);
314
315            /* -- we don't need to draw the base since we already draw the frame
316                QStyleOptionTab tabOverlap;
317                tabOverlap.shape = option.shape;
318                int overlap = style->pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap, widget);
319                QStyleOptionTabBarBase optTabBase;
320                static_cast<QStyleOption&>(optTabBase) = (option);
321                optTabBase.shape = option.shape;
322                optTabBase.rect = option.tabBarRect;
323                if (overlap > 0) {
324                    optTabBase.rect.setHeight(optTabBase.rect.height() - overlap);
325                }
326                optTabBase.tabBarRect = option.tabBarRect;
327                optTabBase.selectedTabRect = option.selectedTabRect;
328                style->drawPrimitive(QStyle::PE_FrameTabBarBase, &optTabBase, painter->get(), widget);*/
329        });
330    }
331
332    fn bounding_rect(
333        self: core::pin::Pin<&Self>,
334        _window_adapter: &Rc<dyn WindowAdapter>,
335        _self_rc: &ItemRc,
336        geometry: LogicalRect,
337    ) -> LogicalRect {
338        geometry
339    }
340
341    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
342        false
343    }
344}
345
346impl ItemConsts for NativeTabWidget {
347    const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
348        Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
349}
350
351declare_item_vtable! {
352fn slint_get_NativeTabWidgetVTable() -> NativeTabWidgetVTable for NativeTabWidget
353}
354
355#[repr(C)]
356#[derive(FieldOffsets, Default, SlintElement)]
357#[pin]
358pub struct NativeTab {
359    pub title: Property<SharedString>,
360    pub icon: Property<i_slint_core::graphics::Image>,
361    pub enabled: Property<bool>,
362    pub pressed: Property<bool>,
363    pub current: Property<i32>,
364    pub current_focused: Property<i32>,
365    pub tab_index: Property<i32>,
366    pub num_tabs: Property<i32>,
367    widget_ptr: std::cell::Cell<SlintTypeErasedWidgetPtr>,
368    animation_tracker: Property<i32>,
369    pub cached_rendering_data: CachedRenderingData,
370}
371
372impl Item for NativeTab {
373    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {
374        let animation_tracker_property_ptr = Self::FIELD_OFFSETS.animation_tracker.apply_pin(self);
375        self.widget_ptr.set(cpp! { unsafe [animation_tracker_property_ptr as "void*"] -> SlintTypeErasedWidgetPtr as "std::unique_ptr<SlintTypeErasedWidget>" {
376            return make_unique_animated_widget<QWidget>(animation_tracker_property_ptr);
377        }});
378    }
379
380    fn layout_info(
381        self: Pin<&Self>,
382        orientation: Orientation,
383        _window_adapter: &Rc<dyn WindowAdapter>,
384        _self_rc: &ItemRc,
385    ) -> LayoutInfo {
386        let text: qttypes::QString = self.title().as_str().into();
387        let icon: qttypes::QPixmap =
388            crate::qt_window::image_to_pixmap((&self.icon()).into(), None).unwrap_or_default();
389        let tab_index: i32 = self.tab_index();
390        let num_tabs: i32 = self.num_tabs();
391        let widget: NonNull<()> = SlintTypeErasedWidgetPtr::qwidget_ptr(&self.widget_ptr);
392        let size = cpp!(unsafe [
393            text as "QString",
394            icon as "QPixmap",
395            tab_index as "int",
396            num_tabs as "int",
397            widget as "QWidget*"
398        ] -> qttypes::QSize as "QSize" {
399            ensure_initialized();
400            QStyleOptionTab option;
401            option.rect = option.fontMetrics.boundingRect(text);
402            option.text = text;
403            option.icon = icon;
404            option.shape = QTabBar::RoundedNorth;
405            option.position = num_tabs == 1 ? QStyleOptionTab::OnlyOneTab
406                : tab_index == 0 ? QStyleOptionTab::Beginning
407                : tab_index == num_tabs - 1 ? QStyleOptionTab::End
408                : QStyleOptionTab::Middle;
409            auto style = qApp->style();
410            int hframe = style->pixelMetric(QStyle::PM_TabBarTabHSpace, &option, widget);
411            int vframe = style->pixelMetric(QStyle::PM_TabBarTabVSpace, &option, widget);
412            int padding = icon.isNull() ? 0 : 4;
413            int textWidth = option.fontMetrics.size(Qt::TextShowMnemonic, text).width();
414            auto iconSize = icon.isNull() ? 0 : style->pixelMetric(QStyle::PM_TabBarIconSize, nullptr, widget);
415            QSize csz = QSize(textWidth + iconSize + hframe + padding, qMax(option.fontMetrics.height(), iconSize) + vframe);
416            return style->sizeFromContents(QStyle::CT_TabBarTab, &option, csz, nullptr);
417        });
418        LayoutInfo {
419            min: match orientation {
420                // FIXME: the minimum width is arbitrary, Qt uses the size of two letters + ellipses
421                Orientation::Horizontal => size.width.min(size.height * 2) as f32,
422                Orientation::Vertical => size.height as f32,
423            },
424            preferred: match orientation {
425                Orientation::Horizontal => size.width as f32,
426                Orientation::Vertical => size.height as f32,
427            },
428            ..LayoutInfo::default()
429        }
430    }
431
432    fn input_event_filter_before_children(
433        self: Pin<&Self>,
434        _: &MouseEvent,
435        _window_adapter: &Rc<dyn WindowAdapter>,
436        _self_rc: &ItemRc,
437    ) -> InputEventFilterResult {
438        InputEventFilterResult::ForwardEvent
439    }
440
441    fn input_event(
442        self: Pin<&Self>,
443        event: &MouseEvent,
444        window_adapter: &Rc<dyn WindowAdapter>,
445        self_rc: &i_slint_core::items::ItemRc,
446    ) -> InputEventResult {
447        let enabled = self.enabled();
448        if !enabled {
449            return InputEventResult::EventIgnored;
450        }
451
452        Self::FIELD_OFFSETS.pressed.apply_pin(self).set(match event {
453            MouseEvent::Pressed { button, .. } => *button == PointerEventButton::Left,
454            MouseEvent::Exit | MouseEvent::Released { .. } => false,
455            MouseEvent::Moved { .. } => {
456                return if self.pressed() {
457                    InputEventResult::GrabMouse
458                } else {
459                    InputEventResult::EventIgnored
460                }
461            }
462            MouseEvent::Wheel { .. } => return InputEventResult::EventIgnored,
463            MouseEvent::DragMove(..) | MouseEvent::Drop(..) => {
464                return InputEventResult::EventIgnored
465            }
466        });
467        let click_on_press = cpp!(unsafe [] -> bool as "bool" {
468            return qApp->style()->styleHint(QStyle::SH_TabBar_SelectMouseType, nullptr, nullptr) == QEvent::MouseButtonPress;
469        });
470        if matches!(event, MouseEvent::Released { button: PointerEventButton::Left, .. } if !click_on_press)
471            || matches!(event, MouseEvent::Pressed { button: PointerEventButton::Left, .. } if click_on_press)
472        {
473            WindowInner::from_pub(window_adapter.window()).set_focus_item(
474                self_rc,
475                true,
476                FocusReason::PointerClick,
477            );
478            self.current.set(self.tab_index());
479            InputEventResult::EventAccepted
480        } else {
481            InputEventResult::GrabMouse
482        }
483    }
484
485    fn capture_key_event(
486        self: Pin<&Self>,
487        _event: &KeyEvent,
488        _window_adapter: &Rc<dyn WindowAdapter>,
489        _self_rc: &ItemRc,
490    ) -> KeyEventResult {
491        KeyEventResult::EventIgnored
492    }
493
494    fn key_event(
495        self: Pin<&Self>,
496        _: &KeyEvent,
497        _window_adapter: &Rc<dyn WindowAdapter>,
498        _self_rc: &ItemRc,
499    ) -> KeyEventResult {
500        KeyEventResult::EventIgnored
501    }
502
503    fn focus_event(
504        self: Pin<&Self>,
505        _: &FocusEvent,
506        _window_adapter: &Rc<dyn WindowAdapter>,
507        _self_rc: &ItemRc,
508    ) -> FocusEventResult {
509        FocusEventResult::FocusIgnored
510    }
511
512    fn_render! { this dpr size painter widget initial_state =>
513        let down: bool = this.pressed();
514        let text: qttypes::QString = this.title().as_str().into();
515        let icon: qttypes::QPixmap = crate::qt_window::image_to_pixmap(
516            (&this.icon()).into(),
517            None,
518        )
519        .unwrap_or_default();
520        let enabled: bool = this.enabled();
521        let current: i32 = this.current();
522        let current_focused: i32 = this.current_focused();
523        let tab_index: i32 = this.tab_index();
524        let num_tabs: i32 = this.num_tabs();
525
526        cpp!(unsafe [
527            painter as "QPainterPtr*",
528            widget as "QWidget*",
529            text as "QString",
530            icon as "QPixmap",
531            enabled as "bool",
532            size as "QSize",
533            down as "bool",
534            dpr as "float",
535            tab_index as "int",
536            current as "int",
537            current_focused as "int",
538            num_tabs as "int",
539            initial_state as "int"
540        ] {
541            ensure_initialized();
542            QStyleOptionTab option;
543            option.styleObject = widget;
544            option.state |= QStyle::State(initial_state);
545            option.rect = QRect(QPoint(), size / dpr);;
546            option.text = text;
547            option.icon = icon;
548            option.shape = QTabBar::RoundedNorth;
549            option.position = num_tabs == 1 ? QStyleOptionTab::OnlyOneTab
550                : tab_index == 0 ? QStyleOptionTab::Beginning
551                : tab_index == num_tabs - 1 ? QStyleOptionTab::End
552                : QStyleOptionTab::Middle;
553            /* -- does not render correctly with the fusion style because we don't draw the selected on top
554                option.selectedPosition = current == tab_index - 1 ? QStyleOptionTab::NextIsSelected
555                    : current == tab_index + 1 ? QStyleOptionTab::PreviousIsSelected : QStyleOptionTab::NotAdjacent;*/
556            if (down)
557                option.state |= QStyle::State_Sunken;
558            else
559                option.state |= QStyle::State_Raised;
560            if (enabled) {
561                option.state |= QStyle::State_Enabled;
562            } else {
563                option.palette.setCurrentColorGroup(QPalette::Disabled);
564            }
565            if (current == tab_index)
566                option.state |= QStyle::State_Selected;
567            if (current_focused == tab_index) {
568                option.state |= QStyle::State_HasFocus | QStyle::State_KeyboardFocusChange | QStyle::State_Item;
569            }
570            option.features |= QStyleOptionTab::HasFrame;
571            qApp->style()->drawControl(QStyle::CE_TabBarTab, &option, painter->get(), widget);
572        });
573    }
574
575    fn bounding_rect(
576        self: core::pin::Pin<&Self>,
577        _window_adapter: &Rc<dyn WindowAdapter>,
578        _self_rc: &ItemRc,
579        geometry: LogicalRect,
580    ) -> LogicalRect {
581        geometry
582    }
583
584    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
585        false
586    }
587}
588
589impl ItemConsts for NativeTab {
590    const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
591        Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
592}
593
594declare_item_vtable! {
595fn slint_get_NativeTabVTable() -> NativeTabVTable for NativeTab
596}