i_slint_backend_qt/qt_widgets/
tabwidget.rs1use 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 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 ) {
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 });
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 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 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}