1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: MIT |
3 | |
4 | #include "appwindow.h" |
5 | |
6 | #include <slint-platform.h> |
7 | |
8 | #include <QtGui/QtGui> |
9 | #include <QtGui/qpa/qplatformnativeinterface.h> |
10 | #include <QtWidgets/QApplication> |
11 | |
12 | static void update_timer() |
13 | { |
14 | static QTimer timer; |
15 | static auto init = [] { |
16 | timer.callOnTimeout([] { |
17 | slint::platform::update_timers_and_animations(); |
18 | update_timer(); |
19 | }); |
20 | return true; |
21 | }(); |
22 | if (auto timeout = slint::platform::duration_until_next_timer_update()) { |
23 | timer.start(*timeout); |
24 | } else { |
25 | timer.stop(); |
26 | } |
27 | } |
28 | |
29 | slint::PointerEventButton convert_button(Qt::MouseButtons b) |
30 | { |
31 | switch (b) { |
32 | case Qt::LeftButton: |
33 | return slint::PointerEventButton::Left; |
34 | case Qt::RightButton: |
35 | return slint::PointerEventButton::Right; |
36 | case Qt::MiddleButton: |
37 | return slint::PointerEventButton::Middle; |
38 | default: |
39 | return slint::PointerEventButton::Other; |
40 | } |
41 | } |
42 | |
43 | static slint::platform::NativeWindowHandle window_handle_for_qt_window(QWindow *window) |
44 | { |
45 | // Ensure that the native window surface exists |
46 | window->create(); |
47 | #ifdef __APPLE__ |
48 | QPlatformNativeInterface *native = qApp->platformNativeInterface(); |
49 | NSView *nsview = reinterpret_cast<NSView *>( |
50 | native->nativeResourceForWindow(QByteArray("nsview" ), window)); |
51 | NSWindow *nswindow = reinterpret_cast<NSWindow *>( |
52 | native->nativeResourceForWindow(QByteArray("nswindow" ), window)); |
53 | return slint::platform::NativeWindowHandle::from_appkit(nsview, nswindow); |
54 | #elif defined Q_OS_WIN |
55 | auto wid = Qt::HANDLE(window->winId()); |
56 | return slint::platform::NativeWindowHandle::from_win32(wid, GetModuleHandle(nullptr)); |
57 | #else |
58 | // Try Wayland first, then XLib, then Xcb |
59 | auto wid = window->winId(); |
60 | auto visual_id = 0; // FIXME |
61 | QPlatformNativeInterface *native = qApp->platformNativeInterface(); |
62 | auto screen = quintptr(native->nativeResourceForWindow(QByteArray("x11screen" ), window)); |
63 | if (auto *wayland_display = reinterpret_cast<wl_display *>( |
64 | native->nativeResourceForIntegration(QByteArray("wl_display" )))) { |
65 | auto *wayland_surface = reinterpret_cast<wl_surface *>( |
66 | native->nativeResourceForWindow(QByteArray("surface" ), window)); |
67 | return slint::platform::NativeWindowHandle::from_wayland(surface: wayland_surface, display: wayland_display); |
68 | } else if (auto *x11_display = native->nativeResourceForWindow(QByteArray("display" ), window)) { |
69 | return slint::platform::NativeWindowHandle::from_x11_xlib(window: wid, visual_id: wid, display: x11_display, screen: screen); |
70 | } else if (auto *xcb_connection = reinterpret_cast<xcb_connection_t *>( |
71 | native->nativeResourceForWindow(QByteArray("connection" ), window))) { |
72 | return slint::platform::NativeWindowHandle::from_x11_xcb(window: wid, visual_id: wid, connection: xcb_connection, screen: screen); |
73 | } else { |
74 | throw "Unsupported windowing system (tried wayland, xlib, and xcb)" ; |
75 | } |
76 | #endif |
77 | } |
78 | |
79 | static slint::SharedString key_event_text(QKeyEvent *e) |
80 | { |
81 | switch (e->key()) { |
82 | case Qt::Key::Key_Backspace: |
83 | return slint::platform::key_codes::Backspace; |
84 | case Qt::Key::Key_Tab: |
85 | return slint::platform::key_codes::Tab; |
86 | case Qt::Key::Key_Enter: |
87 | case Qt::Key::Key_Return: |
88 | return slint::platform::key_codes::Return; |
89 | case Qt::Key::Key_Escape: |
90 | return slint::platform::key_codes::Escape; |
91 | case Qt::Key::Key_Backtab: |
92 | return slint::platform::key_codes::Backtab; |
93 | case Qt::Key::Key_Delete: |
94 | return slint::platform::key_codes::Delete; |
95 | case Qt::Key::Key_Shift: |
96 | return slint::platform::key_codes::Shift; |
97 | case Qt::Key::Key_Control: |
98 | return slint::platform::key_codes::Control; |
99 | case Qt::Key::Key_Alt: |
100 | return slint::platform::key_codes::Alt; |
101 | case Qt::Key::Key_AltGr: |
102 | return slint::platform::key_codes::AltGr; |
103 | case Qt::Key::Key_CapsLock: |
104 | return slint::platform::key_codes::CapsLock; |
105 | case Qt::Key::Key_Meta: |
106 | return slint::platform::key_codes::Meta; |
107 | case Qt::Key::Key_Up: |
108 | return slint::platform::key_codes::UpArrow; |
109 | case Qt::Key::Key_Down: |
110 | return slint::platform::key_codes::DownArrow; |
111 | case Qt::Key::Key_Left: |
112 | return slint::platform::key_codes::LeftArrow; |
113 | case Qt::Key::Key_Right: |
114 | return slint::platform::key_codes::RightArrow; |
115 | case Qt::Key::Key_F1: |
116 | return slint::platform::key_codes::F1; |
117 | case Qt::Key::Key_F2: |
118 | return slint::platform::key_codes::F2; |
119 | case Qt::Key::Key_F3: |
120 | return slint::platform::key_codes::F3; |
121 | case Qt::Key::Key_F4: |
122 | return slint::platform::key_codes::F4; |
123 | case Qt::Key::Key_F5: |
124 | return slint::platform::key_codes::F5; |
125 | case Qt::Key::Key_F6: |
126 | return slint::platform::key_codes::F6; |
127 | case Qt::Key::Key_F7: |
128 | return slint::platform::key_codes::F7; |
129 | case Qt::Key::Key_F8: |
130 | return slint::platform::key_codes::F8; |
131 | case Qt::Key::Key_F9: |
132 | return slint::platform::key_codes::F9; |
133 | case Qt::Key::Key_F10: |
134 | return slint::platform::key_codes::F10; |
135 | case Qt::Key::Key_F11: |
136 | return slint::platform::key_codes::F11; |
137 | case Qt::Key::Key_F12: |
138 | return slint::platform::key_codes::F12; |
139 | case Qt::Key::Key_F13: |
140 | return slint::platform::key_codes::F13; |
141 | case Qt::Key::Key_F14: |
142 | return slint::platform::key_codes::F14; |
143 | case Qt::Key::Key_F15: |
144 | return slint::platform::key_codes::F15; |
145 | case Qt::Key::Key_F16: |
146 | return slint::platform::key_codes::F16; |
147 | case Qt::Key::Key_F17: |
148 | return slint::platform::key_codes::F17; |
149 | case Qt::Key::Key_F18: |
150 | return slint::platform::key_codes::F18; |
151 | case Qt::Key::Key_F19: |
152 | return slint::platform::key_codes::F19; |
153 | case Qt::Key::Key_F20: |
154 | return slint::platform::key_codes::F20; |
155 | case Qt::Key::Key_F21: |
156 | return slint::platform::key_codes::F21; |
157 | case Qt::Key::Key_F22: |
158 | return slint::platform::key_codes::F22; |
159 | case Qt::Key::Key_F23: |
160 | return slint::platform::key_codes::F23; |
161 | case Qt::Key::Key_F24: |
162 | return slint::platform::key_codes::F24; |
163 | case Qt::Key::Key_Insert: |
164 | return slint::platform::key_codes::Insert; |
165 | case Qt::Key::Key_Home: |
166 | return slint::platform::key_codes::Home; |
167 | case Qt::Key::Key_End: |
168 | return slint::platform::key_codes::End; |
169 | case Qt::Key::Key_PageUp: |
170 | return slint::platform::key_codes::PageUp; |
171 | case Qt::Key::Key_PageDown: |
172 | return slint::platform::key_codes::PageDown; |
173 | case Qt::Key::Key_ScrollLock: |
174 | return slint::platform::key_codes::ScrollLock; |
175 | case Qt::Key::Key_Pause: |
176 | return slint::platform::key_codes::Pause; |
177 | case Qt::Key::Key_SysReq: |
178 | return slint::platform::key_codes::SysReq; |
179 | case Qt::Key::Key_Stop: |
180 | return slint::platform::key_codes::Stop; |
181 | case Qt::Key::Key_Menu: |
182 | return slint::platform::key_codes::Menu; |
183 | default: |
184 | if (e->modifiers() & Qt::ControlModifier) { |
185 | // e->text() is not the key when Ctrl is pressed |
186 | return QKeySequence(e->key()).toString().toLower().toUtf8().data(); |
187 | } |
188 | return e->text().toUtf8().data(); |
189 | } |
190 | } |
191 | |
192 | class MyWindow : public QWindow, public slint::platform::WindowAdapter |
193 | { |
194 | std::optional<slint::platform::SkiaRenderer> m_renderer; |
195 | bool m_visible = false; |
196 | |
197 | public: |
198 | MyWindow(QWindow *parentWindow = nullptr) : QWindow(parentWindow) |
199 | { |
200 | resize(640, 480); |
201 | m_renderer.emplace(window_handle_for_qt_window(this), size()); |
202 | } |
203 | |
204 | slint::platform::AbstractRenderer &renderer() override { return m_renderer.value(); } |
205 | |
206 | void paintEvent(QPaintEvent *ev) override |
207 | { |
208 | slint::platform::update_timers_and_animations(); |
209 | |
210 | m_renderer->render(); |
211 | |
212 | if (window().has_active_animations()) { |
213 | requestUpdate(); |
214 | } |
215 | update_timer(); |
216 | } |
217 | |
218 | void closeEvent(QCloseEvent *event) override |
219 | { |
220 | if (m_visible) { |
221 | event->ignore(); |
222 | window().dispatch_close_requested_event(); |
223 | } |
224 | } |
225 | |
226 | bool event(QEvent *e) override |
227 | { |
228 | if (e->type() == QEvent::UpdateRequest) { |
229 | paintEvent(static_cast<QPaintEvent *>(e)); |
230 | return true; |
231 | } else if (e->type() == QEvent::KeyPress) { |
232 | auto ke = static_cast<QKeyEvent *>(e); |
233 | if (ke->isAutoRepeat()) |
234 | window().dispatch_key_press_repeat_event(key_event_text(ke)); |
235 | else |
236 | window().dispatch_key_press_event(key_event_text(ke)); |
237 | return true; |
238 | } else if (e->type() == QEvent::KeyRelease) { |
239 | window().dispatch_key_release_event(key_event_text(static_cast<QKeyEvent *>(e))); |
240 | return true; |
241 | } else if (e->type() == QEvent::WindowActivate) { |
242 | window().dispatch_window_active_changed_event(active: true); |
243 | return true; |
244 | } else if (e->type() == QEvent::WindowDeactivate) { |
245 | window().dispatch_window_active_changed_event(active: false); |
246 | return true; |
247 | } else { |
248 | return QWindow::event(e); |
249 | } |
250 | } |
251 | |
252 | void set_visible(bool visible) override |
253 | { |
254 | m_visible = visible; |
255 | if (visible) { |
256 | window().dispatch_scale_factor_change_event(factor: devicePixelRatio()); |
257 | QWindow::show(); |
258 | } else { |
259 | QWindow::close(); |
260 | } |
261 | } |
262 | |
263 | void set_size(slint::PhysicalSize size) override |
264 | { |
265 | float scale_factor = devicePixelRatio(); |
266 | resize(size.width / scale_factor, size.height / scale_factor); |
267 | } |
268 | |
269 | slint::PhysicalSize size() override |
270 | { |
271 | auto windowSize = slint::LogicalSize({ .width: float(width()), .height: float(height()) }); |
272 | float scale_factor = devicePixelRatio(); |
273 | return slint::PhysicalSize({ .width: uint32_t(windowSize.width * scale_factor), |
274 | .height: uint32_t(windowSize.height * scale_factor) }); |
275 | } |
276 | |
277 | void set_position(slint::PhysicalPosition position) override |
278 | { |
279 | float scale_factor = devicePixelRatio(); |
280 | setFramePosition(QPointF(position.x / scale_factor, position.y / scale_factor).toPoint()); |
281 | } |
282 | |
283 | std::optional<slint::PhysicalPosition> position() override |
284 | { |
285 | auto pos = framePosition(); |
286 | float scale_factor = devicePixelRatio(); |
287 | return { slint::PhysicalPosition( |
288 | { .x: int32_t(pos.x() * scale_factor), .y: int32_t(pos.y() * scale_factor) }) }; |
289 | } |
290 | |
291 | void request_redraw() override { requestUpdate(); } |
292 | |
293 | void update_window_properties(const WindowProperties &props) override |
294 | { |
295 | QWindow::setTitle(QString::fromUtf8(props.title().data())); |
296 | auto c = props.layout_constraints(); |
297 | QWindow::setMaximumSize(c.max ? QSize(c.max->width, c.max->height) |
298 | : QSize(1 << 15, 1 << 15)); |
299 | QWindow::setMinimumSize(c.min ? QSize(c.min->width, c.min->height) : QSize()); |
300 | |
301 | Qt::WindowStates states = windowState() & Qt::WindowActive; |
302 | if (props.is_fullscreen()) |
303 | states |= Qt::WindowFullScreen; |
304 | if (props.is_minimized()) |
305 | states |= Qt::WindowMinimized; |
306 | if (props.is_maximized()) |
307 | states |= Qt::WindowMaximized; |
308 | setWindowStates(states); |
309 | } |
310 | |
311 | void resizeEvent(QResizeEvent *ev) override |
312 | { |
313 | auto logicalSize = ev->size(); |
314 | window().dispatch_resize_event( |
315 | s: slint::LogicalSize({ .width: float(logicalSize.width()), .height: float(logicalSize.height()) })); |
316 | } |
317 | |
318 | void mousePressEvent(QMouseEvent *event) override |
319 | { |
320 | slint::platform::update_timers_and_animations(); |
321 | window().dispatch_pointer_press_event( |
322 | slint::LogicalPosition({ float(event->pos().x()), float(event->pos().y()) }), |
323 | convert_button(event->button())); |
324 | update_timer(); |
325 | } |
326 | void mouseReleaseEvent(QMouseEvent *event) override |
327 | { |
328 | slint::platform::update_timers_and_animations(); |
329 | window().dispatch_pointer_release_event( |
330 | slint::LogicalPosition({ float(event->pos().x()), float(event->pos().y()) }), |
331 | convert_button(event->button())); |
332 | update_timer(); |
333 | } |
334 | void mouseMoveEvent(QMouseEvent *event) override |
335 | { |
336 | slint::platform::update_timers_and_animations(); |
337 | window().dispatch_pointer_move_event( |
338 | pos: slint::LogicalPosition({ .x: float(event->pos().x()), .y: float(event->pos().y()) })); |
339 | update_timer(); |
340 | } |
341 | }; |
342 | |
343 | struct MyPlatform : public slint::platform::Platform |
344 | { |
345 | std::unique_ptr<QWindow> parentWindow; |
346 | |
347 | std::unique_ptr<slint::platform::WindowAdapter> create_window_adapter() override |
348 | { |
349 | return std::make_unique<MyWindow>(parentWindow.get()); |
350 | } |
351 | |
352 | void set_clipboard_text(const slint::SharedString &str, |
353 | slint::platform::Platform::Clipboard clipboard) override |
354 | { |
355 | switch (clipboard) { |
356 | case slint::platform::Platform::Clipboard::DefaultClipboard: |
357 | qApp->clipboard()->setText(QString::fromUtf8(str.data()), QClipboard::Clipboard); |
358 | break; |
359 | case slint::platform::Platform::Clipboard::SelectionClipboard: |
360 | qApp->clipboard()->setText(QString::fromUtf8(str.data()), QClipboard::Selection); |
361 | break; |
362 | } |
363 | } |
364 | |
365 | std::optional<slint::SharedString> clipboard_text(Clipboard clipboard) override |
366 | { |
367 | QString text; |
368 | switch (clipboard) { |
369 | case slint::platform::Platform::Clipboard::DefaultClipboard: |
370 | text = qApp->clipboard()->text(QClipboard::Clipboard); |
371 | break; |
372 | case slint::platform::Platform::Clipboard::SelectionClipboard: |
373 | text = qApp->clipboard()->text(QClipboard::Selection); |
374 | break; |
375 | default: |
376 | return {}; |
377 | } |
378 | if (text.isNull()) { |
379 | return {}; |
380 | } else { |
381 | return slint::SharedString(text.toUtf8().data()); |
382 | } |
383 | } |
384 | }; |
385 | |
386 | int main(int argc, char **argv) |
387 | { |
388 | QApplication app(argc, argv); |
389 | |
390 | static MyPlatform *plarform = [] { |
391 | auto platform = std::make_unique<MyPlatform>(); |
392 | auto p2 = platform.get(); |
393 | slint::platform::set_platform(std::move(platform)); |
394 | return p2; |
395 | }(); |
396 | |
397 | slint::platform::update_timers_and_animations(); |
398 | |
399 | auto my_ui = App::create(); |
400 | // mu_ui->set_property(....); |
401 | my_ui->show(); |
402 | |
403 | return app.exec(); |
404 | } |
405 | |