i_slint_backend_qt/qt_widgets/
groupbox.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
4use i_slint_core::input::FocusEventResult;
5
6use super::*;
7
8#[repr(C)]
9#[derive(FieldOffsets, Default, SlintElement)]
10#[pin]
11pub struct NativeGroupBox {
12    pub enabled: Property<bool>,
13    pub title: Property<SharedString>,
14    pub cached_rendering_data: CachedRenderingData,
15    pub native_padding_left: Property<LogicalLength>,
16    pub native_padding_right: Property<LogicalLength>,
17    pub native_padding_top: Property<LogicalLength>,
18    pub native_padding_bottom: Property<LogicalLength>,
19    widget_ptr: std::cell::Cell<SlintTypeErasedWidgetPtr>,
20    animation_tracker: Property<i32>,
21}
22
23#[repr(C)]
24#[derive(FieldOffsets, Default)]
25#[pin]
26struct GroupBoxData {
27    title: Property<SharedString>,
28    paddings: Property<qttypes::QMargins>,
29}
30
31cpp! {{
32    QStyleOptionGroupBox create_group_box_option(QString title) {
33        QStyleOptionGroupBox option;
34        option.text = title;
35        option.lineWidth = 1;
36        option.midLineWidth = 0;
37        option.subControls = QStyle::SC_GroupBoxFrame;
38        if (!title.isEmpty()) {
39            option.subControls |= QStyle::SC_GroupBoxLabel;
40        }
41        option.textColor = QColor(qApp->style()->styleHint(
42            QStyle::SH_GroupBox_TextLabelColor, &option));
43
44        return option;
45    }
46}}
47
48fn minimum_group_box_size(title: qttypes::QString) -> qttypes::QSize {
49    cpp!(unsafe [title as "QString"] -> qttypes::QSize as "QSize" {
50        ensure_initialized();
51
52        QStyleOptionGroupBox option = create_group_box_option(title);
53
54        QFontMetrics metrics = option.fontMetrics;
55        int baseWidth = metrics.horizontalAdvance(title) + metrics.horizontalAdvance(QLatin1Char(' '));
56        int baseHeight = metrics.height();
57
58        return qApp->style()->sizeFromContents(QStyle::CT_GroupBox, &option, QSize(baseWidth, baseHeight), nullptr);
59    })
60}
61
62impl Item for NativeGroupBox {
63    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {
64        let animation_tracker_property_ptr = Self::FIELD_OFFSETS.animation_tracker.apply_pin(self);
65        self.widget_ptr.set(cpp! { unsafe [animation_tracker_property_ptr as "void*"] -> SlintTypeErasedWidgetPtr as "std::unique_ptr<SlintTypeErasedWidget>"  {
66            return make_unique_animated_widget<QGroupBox>(animation_tracker_property_ptr);
67        }});
68
69        let shared_data = Rc::pin(GroupBoxData::default());
70
71        Property::link_two_way(
72            Self::FIELD_OFFSETS.title.apply_pin(self),
73            GroupBoxData::FIELD_OFFSETS.title.apply_pin(shared_data.as_ref()),
74        );
75
76        shared_data.paddings.set_binding({
77            let shared_data_weak = pin_weak::rc::PinWeak::downgrade(shared_data.clone());
78            move || {
79                let shared_data = shared_data_weak.upgrade().unwrap();
80
81                let text: qttypes::QString = GroupBoxData::FIELD_OFFSETS.title.apply_pin(shared_data.as_ref()).get().as_str().into();
82
83                cpp!(unsafe [
84                    text as "QString"
85                ] -> qttypes::QMargins as "QMargins" {
86                    ensure_initialized();
87                    QStyleOptionGroupBox option = create_group_box_option(text);
88
89                    // Just some size big enough to be sure that the frame fits in it
90                    option.rect = QRect(0, 0, 10000, 10000);
91                    QRect contentsRect = qApp->style()->subControlRect(
92                        QStyle::CC_GroupBox, &option, QStyle::SC_GroupBoxContents);
93                    //QRect elementRect = qApp->style()->subElementRect(
94                    //    QStyle::SE_GroupBoxLayoutItem, &option);
95
96                    auto hs = qApp->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing, &option);
97                    auto vs = qApp->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing, &option);
98
99                    return {
100                        (contentsRect.left() + hs),
101                        (contentsRect.top() + vs),
102                        (option.rect.right() - contentsRect.right() + hs),
103                        (option.rect.bottom() - contentsRect.bottom() + vs)
104                    };
105                })
106            }
107        });
108
109        self.native_padding_left.set_binding({
110            let shared_data = shared_data.clone();
111            move || {
112                let margins =
113                    GroupBoxData::FIELD_OFFSETS.paddings.apply_pin(shared_data.as_ref()).get();
114                LogicalLength::new(margins.left as _)
115            }
116        });
117
118        self.native_padding_right.set_binding({
119            let shared_data = shared_data.clone();
120            move || {
121                let margins =
122                    GroupBoxData::FIELD_OFFSETS.paddings.apply_pin(shared_data.as_ref()).get();
123                LogicalLength::new(margins.right as _)
124            }
125        });
126
127        self.native_padding_top.set_binding({
128            let shared_data = shared_data.clone();
129            move || {
130                let margins =
131                    GroupBoxData::FIELD_OFFSETS.paddings.apply_pin(shared_data.as_ref()).get();
132                LogicalLength::new(margins.top as _)
133            }
134        });
135
136        self.native_padding_bottom.set_binding({
137            move || {
138                let margins =
139                    GroupBoxData::FIELD_OFFSETS.paddings.apply_pin(shared_data.as_ref()).get();
140                LogicalLength::new(margins.bottom as _)
141            }
142        });
143    }
144
145    fn layout_info(
146        self: Pin<&Self>,
147        orientation: Orientation,
148        _window_adapter: &Rc<dyn WindowAdapter>,
149        _self_rc: &ItemRc,
150    ) -> LayoutInfo {
151        let text: qttypes::QString = self.title().as_str().into();
152
153        let size = minimum_group_box_size(text);
154
155        let min = match orientation {
156            Orientation::Horizontal => size.width as f32,
157            Orientation::Vertical => size.height as f32,
158        };
159        LayoutInfo { min, preferred: min, stretch: 1., ..LayoutInfo::default() }
160    }
161
162    fn input_event_filter_before_children(
163        self: Pin<&Self>,
164        _: &MouseEvent,
165        _window_adapter: &Rc<dyn WindowAdapter>,
166        _self_rc: &ItemRc,
167    ) -> InputEventFilterResult {
168        InputEventFilterResult::ForwardEvent
169    }
170
171    fn input_event(
172        self: Pin<&Self>,
173        _: &MouseEvent,
174        _window_adapter: &Rc<dyn WindowAdapter>,
175        _self_rc: &i_slint_core::items::ItemRc,
176    ) -> InputEventResult {
177        InputEventResult::EventIgnored
178    }
179
180    fn capture_key_event(
181        self: Pin<&Self>,
182        _event: &KeyEvent,
183        _window_adapter: &Rc<dyn WindowAdapter>,
184        _self_rc: &ItemRc,
185    ) -> KeyEventResult {
186        KeyEventResult::EventIgnored
187    }
188
189    fn key_event(
190        self: Pin<&Self>,
191        _: &KeyEvent,
192        _window_adapter: &Rc<dyn WindowAdapter>,
193        _self_rc: &ItemRc,
194    ) -> KeyEventResult {
195        KeyEventResult::EventIgnored
196    }
197
198    fn focus_event(
199        self: Pin<&Self>,
200        _: &FocusEvent,
201        _window_adapter: &Rc<dyn WindowAdapter>,
202        _self_rc: &ItemRc,
203    ) -> FocusEventResult {
204        FocusEventResult::FocusIgnored
205    }
206
207    fn_render! { this dpr size painter widget initial_state =>
208        let text: qttypes::QString =
209            this.title().as_str().into();
210        let enabled = this.enabled();
211
212        cpp!(unsafe [
213            painter as "QPainterPtr*",
214            widget as "QWidget*",
215            text as "QString",
216            enabled as "bool",
217            size as "QSize",
218            dpr as "float",
219            initial_state as "int"
220        ] {
221            if (auto groupbox = qobject_cast<QGroupBox *>(widget)) {
222                // If not set, the style may render incorrectly
223                // https://github.com/qt/qtbase/blob/5be45ff6a6e157d45b0010a4f09d3a11e62fddce/src/widgets/styles/qfusionstyle.cpp#L441
224                groupbox->setTitle(text);
225            }
226            QStyleOptionGroupBox option;
227            option.styleObject = widget;
228            option.state |= QStyle::State(initial_state);
229            if (enabled) {
230                option.state |= QStyle::State_Enabled;
231            } else {
232                option.palette.setCurrentColorGroup(QPalette::Disabled);
233            }
234            option.rect = QRect(QPoint(), size / dpr);
235            option.text = text;
236            option.lineWidth = 1;
237            option.midLineWidth = 0;
238            option.subControls = QStyle::SC_GroupBoxFrame;
239            if (!text.isEmpty()) {
240                option.subControls |= QStyle::SC_GroupBoxLabel;
241            }
242            option.textColor = QColor(qApp->style()->styleHint(
243                QStyle::SH_GroupBox_TextLabelColor, &option));
244            qApp->style()->drawComplexControl(QStyle::CC_GroupBox, &option, painter->get(), widget);
245        });
246    }
247
248    fn bounding_rect(
249        self: core::pin::Pin<&Self>,
250        _window_adapter: &Rc<dyn WindowAdapter>,
251        _self_rc: &ItemRc,
252        geometry: LogicalRect,
253    ) -> LogicalRect {
254        geometry
255    }
256
257    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
258        false
259    }
260}
261
262impl ItemConsts for NativeGroupBox {
263    const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
264        Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
265}
266
267declare_item_vtable! {
268fn slint_get_NativeGroupBoxVTable() -> NativeGroupBoxVTable for NativeGroupBox
269}