i_slint_backend_qt/qt_widgets/
combobox.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 NativeComboBox {
12    pub enabled: Property<bool>,
13    pub has_focus: Property<bool>,
14    pub pressed: Property<bool>,
15    pub has_hover: Property<bool>,
16    pub is_open: Property<bool>,
17    pub current_value: Property<SharedString>,
18    widget_ptr: std::cell::Cell<SlintTypeErasedWidgetPtr>,
19    animation_tracker: Property<i32>,
20    pub cached_rendering_data: CachedRenderingData,
21}
22
23impl Item for NativeComboBox {
24    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {
25        let animation_tracker_property_ptr = Self::FIELD_OFFSETS.animation_tracker.apply_pin(self);
26        self.widget_ptr.set(cpp! { unsafe [animation_tracker_property_ptr as "void*"] -> SlintTypeErasedWidgetPtr as "std::unique_ptr<SlintTypeErasedWidget>"  {
27            return make_unique_animated_widget<QComboBox>(animation_tracker_property_ptr);
28        }})
29    }
30
31    fn layout_info(
32        self: Pin<&Self>,
33        orientation: Orientation,
34        _window_adapter: &Rc<dyn WindowAdapter>,
35        _self_rc: &ItemRc,
36    ) -> LayoutInfo {
37        let widget: NonNull<()> = SlintTypeErasedWidgetPtr::qwidget_ptr(&self.widget_ptr);
38        let size = cpp!(unsafe [widget as "QWidget*"] -> qttypes::QSize as "QSize" {
39            ensure_initialized();
40            QStyleOptionComboBox option;
41            // FIXME
42            option.rect = option.fontMetrics.boundingRect("******************");
43            option.subControls = QStyle::SC_All;
44            return qApp->style()->sizeFromContents(QStyle::CT_ComboBox, &option, option.rect.size(), widget);
45        });
46        let min = match orientation {
47            Orientation::Horizontal => size.width,
48            Orientation::Vertical => size.height,
49        } as f32;
50        LayoutInfo { min, preferred: min, ..LayoutInfo::default() }
51    }
52
53    fn input_event_filter_before_children(
54        self: Pin<&Self>,
55        event: &MouseEvent,
56        _window_adapter: &Rc<dyn WindowAdapter>,
57        _self_rc: &ItemRc,
58    ) -> InputEventFilterResult {
59        Self::FIELD_OFFSETS.has_hover.apply_pin(self).set(!matches!(event, MouseEvent::Exit));
60        InputEventFilterResult::ForwardAndIgnore
61    }
62
63    fn input_event(
64        self: Pin<&Self>,
65        event: &MouseEvent,
66        _window_adapter: &Rc<dyn WindowAdapter>,
67        _self_rc: &i_slint_core::items::ItemRc,
68    ) -> InputEventResult {
69        if matches!(event, MouseEvent::Exit) {
70            Self::FIELD_OFFSETS.has_hover.apply_pin(self).set(false);
71        }
72        InputEventResult::EventIgnored
73    }
74
75    fn capture_key_event(
76        self: Pin<&Self>,
77        _event: &KeyEvent,
78        _window_adapter: &Rc<dyn WindowAdapter>,
79        _self_rc: &ItemRc,
80    ) -> KeyEventResult {
81        KeyEventResult::EventIgnored
82    }
83
84    fn key_event(
85        self: Pin<&Self>,
86        _: &KeyEvent,
87        _window_adapter: &Rc<dyn WindowAdapter>,
88        _self_rc: &ItemRc,
89    ) -> KeyEventResult {
90        KeyEventResult::EventIgnored
91    }
92
93    fn focus_event(
94        self: Pin<&Self>,
95        _: &FocusEvent,
96        _window_adapter: &Rc<dyn WindowAdapter>,
97        _self_rc: &ItemRc,
98    ) -> FocusEventResult {
99        FocusEventResult::FocusIgnored
100    }
101
102    fn_render! { this dpr size painter widget initial_state =>
103        let down: bool = this.pressed();
104        let is_open: bool = this.is_open();
105        let text: qttypes::QString =
106            this.current_value().as_str().into();
107        let enabled = this.enabled();
108        let has_focus = this.has_focus();
109        let has_hover = this.has_hover();
110        cpp!(unsafe [
111            painter as "QPainterPtr*",
112            widget as "QWidget*",
113            text as "QString",
114            enabled as "bool",
115            size as "QSize",
116            down as "bool",
117            is_open as "bool",
118            has_focus as "bool",
119            has_hover as "bool",
120            dpr as "float",
121            initial_state as "int"
122        ] {
123            ensure_initialized();
124            QStyleOptionComboBox option;
125            option.styleObject = widget;
126            option.state |= QStyle::State(initial_state);
127            option.currentText = std::move(text);
128            option.rect = QRect(QPoint(), size / dpr);
129            if (down)
130                option.state |= QStyle::State_Sunken;
131            else
132                option.state |= QStyle::State_Raised;
133            if (enabled) {
134                option.state |= QStyle::State_Enabled;
135            } else {
136                option.palette.setCurrentColorGroup(QPalette::Disabled);
137            }
138            if (has_focus) {
139                option.state |= QStyle::State_HasFocus | QStyle::State_KeyboardFocusChange | QStyle::State_Item;
140            }
141            if (has_hover) {
142                option.state |= QStyle::State_MouseOver;
143            }
144            // FIXME: This is commented out to workaround #456
145            if (is_open) {
146            //    option.state |= QStyle::State_On;
147            }
148            option.subControls = QStyle::SC_All;
149            qApp->style()->drawComplexControl(QStyle::CC_ComboBox, &option, painter->get(), widget);
150            qApp->style()->drawControl(QStyle::CE_ComboBoxLabel, &option, painter->get(), widget);
151        });
152    }
153
154    fn bounding_rect(
155        self: core::pin::Pin<&Self>,
156        _window_adapter: &Rc<dyn WindowAdapter>,
157        _self_rc: &ItemRc,
158        geometry: LogicalRect,
159    ) -> LogicalRect {
160        geometry
161    }
162
163    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
164        false
165    }
166}
167
168impl ItemConsts for NativeComboBox {
169    const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
170        Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
171}
172
173declare_item_vtable! {
174fn slint_get_NativeComboBoxVTable() -> NativeComboBoxVTable for NativeComboBox
175}
176
177#[repr(C)]
178#[derive(FieldOffsets, Default, SlintElement)]
179#[pin]
180pub struct NativeComboBoxPopup {
181    widget_ptr: std::cell::Cell<SlintTypeErasedWidgetPtr>,
182    animation_tracker: Property<i32>,
183    pub cached_rendering_data: CachedRenderingData,
184}
185
186impl Item for NativeComboBoxPopup {
187    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {
188        let animation_tracker_property_ptr = Self::FIELD_OFFSETS.animation_tracker.apply_pin(self);
189        self.widget_ptr.set(cpp! { unsafe [animation_tracker_property_ptr as "void*"] -> SlintTypeErasedWidgetPtr as "std::unique_ptr<SlintTypeErasedWidget>"  {
190            return make_unique_animated_widget<QWidget>(animation_tracker_property_ptr);
191        }})
192    }
193
194    fn layout_info(
195        self: Pin<&Self>,
196        _orientation: Orientation,
197        _window_adapter: &Rc<dyn WindowAdapter>,
198        _self_rc: &ItemRc,
199    ) -> LayoutInfo {
200        Default::default()
201    }
202
203    fn input_event_filter_before_children(
204        self: Pin<&Self>,
205        _: &MouseEvent,
206        _window_adapter: &Rc<dyn WindowAdapter>,
207        _self_rc: &ItemRc,
208    ) -> InputEventFilterResult {
209        InputEventFilterResult::ForwardAndIgnore
210    }
211
212    fn capture_key_event(
213        self: Pin<&Self>,
214        _event: &KeyEvent,
215        _window_adapter: &Rc<dyn WindowAdapter>,
216        _self_rc: &ItemRc,
217    ) -> KeyEventResult {
218        KeyEventResult::EventIgnored
219    }
220
221    fn input_event(
222        self: Pin<&Self>,
223        _: &MouseEvent,
224        _window_adapter: &Rc<dyn WindowAdapter>,
225        _self_rc: &i_slint_core::items::ItemRc,
226    ) -> InputEventResult {
227        InputEventResult::EventIgnored
228    }
229
230    fn key_event(
231        self: Pin<&Self>,
232        _: &KeyEvent,
233        _window_adapter: &Rc<dyn WindowAdapter>,
234        _self_rc: &ItemRc,
235    ) -> KeyEventResult {
236        KeyEventResult::EventIgnored
237    }
238
239    fn focus_event(
240        self: Pin<&Self>,
241        _: &FocusEvent,
242        _window_adapter: &Rc<dyn WindowAdapter>,
243        _self_rc: &ItemRc,
244    ) -> FocusEventResult {
245        FocusEventResult::FocusIgnored
246    }
247
248    fn_render! { _this dpr size painter widget initial_state =>
249        cpp!(unsafe [
250            painter as "QPainterPtr*",
251            widget as "QWidget*",
252            size as "QSize",
253            dpr as "float",
254            initial_state as "int"
255        ] {
256            ensure_initialized();
257            QStyleOptionComboBox cb_option;
258            QStyleOptionFrame option;
259            option.styleObject = widget;
260            option.state |= QStyle::State(initial_state);
261            option.lineWidth = 0;
262            option.midLineWidth = 0;
263            option.rect = QRect(QPoint(), size / dpr);
264            option.state |= QStyle::State_Sunken | QStyle::State_Enabled;
265
266            auto style = qApp->style();
267            painter->get()->fillRect(option.rect, option.palette.window());
268
269            if (style->styleHint(QStyle::SH_ComboBox_Popup, &cb_option, widget)) {
270                style->drawPrimitive(QStyle::PE_PanelMenu, &option, painter->get(), widget);
271                auto vm = style->pixelMetric(QStyle::PM_MenuVMargin, &option, widget);
272                auto hm = style->pixelMetric(QStyle::PM_MenuHMargin, &option, widget);
273                painter->get()->fillRect(option.rect.adjusted(hm, vm, -hm, -vm), option.palette.window());
274            } else {
275                option.lineWidth = 1;
276            }
277            auto frameStyle = style->styleHint(QStyle::SH_ComboBox_PopupFrameStyle, &option, widget);
278            if ((frameStyle & QFrame::Shadow_Mask) == QFrame::Sunken)
279                option.state |= QStyle::State_Sunken;
280            else if ((frameStyle & QFrame::Shadow_Mask) == QFrame::Raised)
281                option.state |= QStyle::State_Raised;
282            option.frameShape = QFrame::Shape(frameStyle & QFrame::Shape_Mask);
283            style->drawControl(QStyle::CE_ShapedFrame, &option, painter->get(), widget);
284        });
285    }
286
287    fn bounding_rect(
288        self: core::pin::Pin<&Self>,
289        _window_adapter: &Rc<dyn WindowAdapter>,
290        _self_rc: &ItemRc,
291        geometry: LogicalRect,
292    ) -> LogicalRect {
293        geometry
294    }
295
296    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
297        false
298    }
299}
300
301impl ItemConsts for NativeComboBoxPopup {
302    const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
303        Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
304}
305
306declare_item_vtable! {
307fn slint_get_NativeComboBoxPopupVTable() -> NativeComboBoxPopupVTable for NativeComboBoxPopup
308}