i_slint_backend_qt/qt_widgets/
checkbox.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::{
5    input::{FocusEventResult, KeyEventType},
6    platform::PointerEventButton,
7};
8
9use super::*;
10
11#[repr(C)]
12#[derive(FieldOffsets, Default, SlintElement)]
13#[pin]
14pub struct NativeCheckBox {
15    pub enabled: Property<bool>,
16    pub has_focus: Property<bool>,
17    pub toggled: Callback<VoidArg>,
18    pub text: Property<SharedString>,
19    pub has_hover: Property<bool>,
20    pub checked: Property<bool>,
21    widget_ptr: std::cell::Cell<SlintTypeErasedWidgetPtr>,
22    animation_tracker: Property<i32>,
23    pub cached_rendering_data: CachedRenderingData,
24}
25
26impl Item for NativeCheckBox {
27    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {
28        let animation_tracker_property_ptr = Self::FIELD_OFFSETS.animation_tracker.apply_pin(self);
29        self.widget_ptr.set(cpp! { unsafe [animation_tracker_property_ptr as "void*"] -> SlintTypeErasedWidgetPtr as "std::unique_ptr<SlintTypeErasedWidget>"  {
30            return make_unique_animated_widget<QCheckBox>(animation_tracker_property_ptr);
31        }})
32    }
33
34    fn layout_info(
35        self: Pin<&Self>,
36        orientation: Orientation,
37        _window_adapter: &Rc<dyn WindowAdapter>,
38        _self_rc: &ItemRc,
39    ) -> LayoutInfo {
40        let text: qttypes::QString = self.text().as_str().into();
41        let widget: NonNull<()> = SlintTypeErasedWidgetPtr::qwidget_ptr(&self.widget_ptr);
42        let size = cpp!(unsafe [
43            text as "QString",
44            widget as "QWidget*"
45        ] -> qttypes::QSize as "QSize" {
46            ensure_initialized();
47            QStyleOptionButton option;
48            option.rect = option.fontMetrics.boundingRect(text);
49            option.text = std::move(text);
50            return qApp->style()->sizeFromContents(QStyle::CT_CheckBox, &option, option.rect.size(), widget);
51        });
52        match orientation {
53            Orientation::Horizontal => LayoutInfo {
54                min: size.width as f32,
55                preferred: size.width as f32,
56                stretch: 1.,
57                ..LayoutInfo::default()
58            },
59            Orientation::Vertical => LayoutInfo {
60                min: size.height as f32,
61                preferred: size.height as f32,
62                max: size.height as f32,
63                ..LayoutInfo::default()
64            },
65        }
66    }
67
68    fn input_event_filter_before_children(
69        self: Pin<&Self>,
70        event: &MouseEvent,
71        _window_adapter: &Rc<dyn WindowAdapter>,
72        _self_rc: &ItemRc,
73    ) -> InputEventFilterResult {
74        Self::FIELD_OFFSETS.has_hover.apply_pin(self).set(!matches!(event, MouseEvent::Exit));
75        InputEventFilterResult::ForwardEvent
76    }
77
78    fn input_event(
79        self: Pin<&Self>,
80        event: &MouseEvent,
81        _window_adapter: &Rc<dyn WindowAdapter>,
82        self_rc: &i_slint_core::items::ItemRc,
83    ) -> InputEventResult {
84        if matches!(event, MouseEvent::Exit) {
85            Self::FIELD_OFFSETS.has_hover.apply_pin(self).set(false);
86        }
87        if !self.enabled() {
88            return InputEventResult::EventIgnored;
89        }
90        if let MouseEvent::Released { position, button, .. } = event {
91            let geo = self_rc.geometry();
92            if *button == PointerEventButton::Left
93                && LogicalRect::new(LogicalPoint::default(), geo.size).contains(*position)
94            {
95                Self::FIELD_OFFSETS.checked.apply_pin(self).set(!self.checked());
96                Self::FIELD_OFFSETS.toggled.apply_pin(self).call(&())
97            }
98        }
99        InputEventResult::EventAccepted
100    }
101
102    fn capture_key_event(
103        self: Pin<&Self>,
104        _event: &KeyEvent,
105        _window_adapter: &Rc<dyn WindowAdapter>,
106        _self_rc: &ItemRc,
107    ) -> KeyEventResult {
108        KeyEventResult::EventIgnored
109    }
110
111    fn key_event(
112        self: Pin<&Self>,
113        event: &KeyEvent,
114        _window_adapter: &Rc<dyn WindowAdapter>,
115        _self_rc: &ItemRc,
116    ) -> KeyEventResult {
117        match event.event_type {
118            KeyEventType::KeyPressed if event.text == " " || event.text == "\n" => {
119                Self::FIELD_OFFSETS.checked.apply_pin(self).set(!self.checked());
120                Self::FIELD_OFFSETS.toggled.apply_pin(self).call(&());
121                KeyEventResult::EventAccepted
122            }
123            KeyEventType::KeyPressed => KeyEventResult::EventIgnored,
124            KeyEventType::KeyReleased => KeyEventResult::EventIgnored,
125            KeyEventType::UpdateComposition | KeyEventType::CommitComposition => {
126                KeyEventResult::EventIgnored
127            }
128        }
129    }
130
131    fn focus_event(
132        self: Pin<&Self>,
133        event: &FocusEvent,
134        _window_adapter: &Rc<dyn WindowAdapter>,
135        _self_rc: &ItemRc,
136    ) -> FocusEventResult {
137        if self.enabled() {
138            Self::FIELD_OFFSETS
139                .has_focus
140                .apply_pin(self)
141                .set(matches!(event, FocusEvent::FocusIn(_)));
142            FocusEventResult::FocusAccepted
143        } else {
144            FocusEventResult::FocusIgnored
145        }
146    }
147
148    fn_render! { this dpr size painter widget initial_state =>
149        let checked: bool = this.checked();
150        let enabled = this.enabled();
151        let has_focus = this.has_focus();
152        let has_hover = this.has_hover();
153        let text: qttypes::QString = this.text().as_str().into();
154
155        cpp!(unsafe [
156            painter as "QPainterPtr*",
157            widget as "QWidget*",
158            enabled as "bool",
159            text as "QString",
160            size as "QSize",
161            checked as "bool",
162            has_focus as "bool",
163            has_hover as "bool",
164            dpr as "float",
165            initial_state as "int"
166        ] {
167            QStyleOptionButton option;
168            option.styleObject = widget;
169            option.state |= QStyle::State(initial_state);
170            option.text = std::move(text);
171            option.rect = QRect(QPoint(), size / dpr);
172            option.state |= checked ? QStyle::State_On : QStyle::State_Off;
173            if (enabled) {
174                option.state |= QStyle::State_Enabled;
175            } else {
176                option.palette.setCurrentColorGroup(QPalette::Disabled);
177            }
178            if (has_focus) {
179                option.state |= QStyle::State_HasFocus | QStyle::State_KeyboardFocusChange | QStyle::State_Item;
180            }
181            if (has_hover) {
182                option.state |= QStyle::State_MouseOver;
183            }
184            qApp->style()->drawControl(QStyle::CE_CheckBox, &option, painter->get(), widget);
185        });
186    }
187
188    fn bounding_rect(
189        self: core::pin::Pin<&Self>,
190        _window_adapter: &Rc<dyn WindowAdapter>,
191        _self_rc: &ItemRc,
192        geometry: LogicalRect,
193    ) -> LogicalRect {
194        geometry
195    }
196
197    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
198        false
199    }
200}
201
202impl ItemConsts for NativeCheckBox {
203    const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
204        Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
205}
206
207declare_item_vtable! {
208    fn slint_get_NativeCheckBoxVTable() -> NativeCheckBoxVTable for NativeCheckBox
209}