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
12static 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
29slint::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
43static 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
79static 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
192class MyWindow : public QWindow, public slint::platform::WindowAdapter
193{
194 std::optional<slint::platform::SkiaRenderer> m_renderer;
195 bool m_visible = false;
196
197public:
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
343struct 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
386int 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

source code of slint/examples/cpp/platform_qt/main.cpp