Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwasmscreen.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qwasmscreen.h"
5
10#include "qwasmwindow.h"
11
12#include <emscripten/bind.h>
13#include <emscripten/val.h>
14
15#include <qpa/qwindowsysteminterface.h>
16#include <QtCore/qcoreapplication.h>
17#include <QtGui/qguiapplication.h>
18#include <private/qhighdpiscaling_p.h>
19
20#include <tuple>
21
23
24using namespace emscripten;
25
26const char *QWasmScreen::m_canvasResizeObserverCallbackContextPropertyName =
27 "data-qtCanvasResizeObserverCallbackContext";
28
29QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
30 : m_container(containerOrCanvas),
31 m_intermediateContainer(emscripten::val::undefined()),
32 m_shadowContainer(emscripten::val::undefined()),
33 m_compositor(new QWasmCompositor(this)),
34 m_deadKeySupport(std::make_unique<QWasmDeadKeySupport>())
35{
36 auto document = m_container["ownerDocument"];
37
38 // Create an intermediate container which we can remove during cleanup in ~QWasmScreen().
39 // This is required due to the attachShadow() call below; there is no corresponding
40 // "detachShadow()" API to return the container to its previous state.
41 m_intermediateContainer = document.call<emscripten::val>("createElement", emscripten::val("div"));
42 m_intermediateContainer.set("id", std::string("qt-shadow-container"));
43 emscripten::val intermediateContainerStyle = m_intermediateContainer["style"];
44 intermediateContainerStyle.set("width", std::string("100%"));
45 intermediateContainerStyle.set("height", std::string("100%"));
46 m_container.call<void>("appendChild", m_intermediateContainer);
47 // Each screen is represented by a div container. All of the windows exist therein as
48 // its children. Qt versions < 6.5 used to represent screens as canvas elements; this
49 // is no longer supported.
50 if (m_container["tagName"].call<std::string>("toLowerCase") == "canvas")
51 qFatal() << "Qt does not support using a canvas element as the container element. Use a div element instead";
52
53 auto shadowOptions = emscripten::val::object();
54 shadowOptions.set("mode", "open");
55 auto shadow = m_intermediateContainer.call<emscripten::val>("attachShadow", shadowOptions);
56
57 m_shadowContainer = document.call<emscripten::val>("createElement", emscripten::val("div"));
58
59 shadow.call<void>("appendChild", QWasmCSSStyle::createStyleElement(m_shadowContainer));
60
61 shadow.call<void>("appendChild", m_shadowContainer);
62
63 m_shadowContainer.set("id", std::string("qt-screen-") + std::to_string(uintptr_t(this)));
64
65 m_shadowContainer["classList"].call<void>("add", std::string("qt-screen"));
66
67 // Disable the default context menu; Qt applications typically
68 // provide custom right-click behavior.
69 m_onContextMenu = std::make_unique<qstdweb::EventCallback>(
70 m_shadowContainer, "contextmenu",
71 [](emscripten::val event) { event.call<void>("preventDefault"); });
72 // Create "specialHTMLTargets" mapping for the canvas - the element might be unreachable based
73 // on its id only under some conditions, like the target being embedded in a shadow DOM or a
74 // subframe.
75 emscripten::val::module_property("specialHTMLTargets")
76 .set(outerScreenId().toStdString(), m_container);
77
79 m_shadowContainer.call<void>("focus");
80
81 m_touchDevice = std::make_unique<QPointingDevice>(
82 "touchscreen", 1, QInputDevice::DeviceType::TouchScreen,
83 QPointingDevice::PointerType::Finger,
84 QPointingDevice::Capability::Position | QPointingDevice::Capability::Area
85 | QPointingDevice::Capability::NormalizedPosition,
86 10, 0);
87 m_tabletDevice = std::make_unique<QPointingDevice>(
88 "stylus", 2, QInputDevice::DeviceType::Stylus,
89 QPointingDevice::PointerType::Pen,
90 QPointingDevice::Capability::Position | QPointingDevice::Capability::Pressure
91 | QPointingDevice::Capability::NormalizedPosition
92 | QInputDevice::Capability::MouseEmulation
93 | QInputDevice::Capability::Hover | QInputDevice::Capability::Rotation
94 | QInputDevice::Capability::XTilt | QInputDevice::Capability::YTilt
95 | QInputDevice::Capability::TangentialPressure,
96 0, 0);
97
98 QWindowSystemInterface::registerInputDevice(m_touchDevice.get());
99}
100
102{
103 m_intermediateContainer.call<void>("remove");
104
105 m_shadowContainer.set(m_canvasResizeObserverCallbackContextPropertyName,
106 emscripten::val(intptr_t(0)));
107}
108
110{
111 // Deletes |this|!
112 QWindowSystemInterface::handleScreenRemoved(this);
113}
114
115QWasmScreen *QWasmScreen::get(QPlatformScreen *screen)
116{
117 return static_cast<QWasmScreen *>(screen);
118}
119
120QWasmScreen *QWasmScreen::get(QScreen *screen)
121{
122 if (!screen)
123 return nullptr;
124 return get(screen->handle());
125}
126
128{
129 return m_compositor.get();
130}
131
133{
134 return m_shadowContainer;
135}
136
137
139{
140 return QString("!outerscreen_%1").arg(uintptr_t(this));
141}
142
144{
145 return m_geometry;
146}
147
148int QWasmScreen::depth() const
149{
150 return m_depth;
151}
152
154{
155 return m_format;
156}
157
159{
160 emscripten::val dpi = emscripten::val::module_property("qtFontDpi");
161 if (!dpi.isUndefined()) {
162 qreal dpiValue = dpi.as<qreal>();
163 return QDpi(dpiValue, dpiValue);
164 }
165 const qreal defaultDpi = 96;
166 return QDpi(defaultDpi, defaultDpi);
167}
168
170{
171 // window.devicePixelRatio gives us the scale factor between CSS and device pixels.
172 // This property reflects hardware configuration, and also browser zoom on desktop.
173 //
174 // window.visualViewport.scale gives us the zoom factor on mobile. If the html page is
175 // configured with "<meta name="viewport" content="width=device-width">" then this scale
176 // factor will be 1. Omitting the viewport configuration typically results on a zoomed-out
177 // viewport, with a scale factor <1. User pinch-zoom will change the scale factor; an event
178 // handler is installed in the QWasmIntegration constructor. Changing zoom level on desktop
179 // does not appear to change visualViewport.scale.
180 //
181 // The effective devicePixelRatio is the product of these two scale factors, upper-bounded
182 // by window.devicePixelRatio in order to avoid e.g. allocating a 10x widget backing store.
183 double dpr = emscripten::val::global("window")["devicePixelRatio"].as<double>();
184 emscripten::val visualViewport = emscripten::val::global("window")["visualViewport"];
185 double scale = visualViewport.isUndefined() ? 1.0 : visualViewport["scale"].as<double>();
186 double effectiveDevicePixelRatio = std::min(dpr * scale, dpr);
187 return qreal(effectiveDevicePixelRatio);
188}
189
191{
192 return QString::fromEcmaString(m_shadowContainer["id"]);
193}
194
196{
197 return const_cast<QWasmCursor *>(&m_cursor);
198}
199
201{
202 if (!screen())
203 return;
204 QPlatformScreen::resizeMaximizedWindows();
205}
206
207QWindow *QWasmScreen::topWindow() const
208{
209 return activeChild() ? activeChild()->window() : nullptr;
210}
211
212QWindow *QWasmScreen::topLevelAt(const QPoint &p) const
213{
214 const auto found =
215 std::find_if(childStack().begin(), childStack().end(), [&p](const QWasmWindow *window) {
216 const QRect geometry = window->windowFrameGeometry();
217
218 return window->isVisible() && geometry.contains(p);
219 });
220 return found != childStack().end() ? (*found)->window() : nullptr;
221}
222
223QPointF QWasmScreen::mapFromLocal(const QPointF &p) const
224{
225 return geometry().topLeft() + p;
226}
227
228QPointF QWasmScreen::clipPoint(const QPointF &p) const
229{
230 const auto geometryF = screen()->geometry().toRectF();
231 return QPointF(qBound(geometryF.left(), p.x(), geometryF.right()),
232 qBound(geometryF.top(), p.y(), geometryF.bottom()));
233}
234
236{
237 m_geometry = QRect();
238}
239
240void QWasmScreen::setGeometry(const QRect &rect)
241{
242 m_geometry = rect;
243 QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(),
244 availableGeometry());
246}
247
249 QWasmWindowTreeNode *parent, QWasmWindow *child)
250{
251 Q_UNUSED(parent);
252
253 QWindow *window = child->window();
254 const bool isMaxFull = (window->windowState() & Qt::WindowMaximized) ||
255 (window->windowState() & Qt::WindowFullScreen);
256 if (changeType == QWasmWindowTreeNodeChangeType::NodeInsertion && parent == this
257 && childStack().size() == 1 && isMaxFull) {
258 window->setFlag(Qt::WindowStaysOnBottomHint);
259 }
260 QWasmWindowTreeNode::onSubtreeChanged(changeType, parent, child);
261 m_compositor->onWindowTreeChanged(changeType, child);
262}
263
265{
266 double css_width;
267 double css_height;
268 emscripten_get_element_css_size(outerScreenId().toUtf8().constData(), &css_width, &css_height);
269 QSizeF cssSize(css_width, css_height);
270
271 // Returns the html elements document/body position
272 auto getElementBodyPosition = [](const emscripten::val &element) -> QPoint {
273 emscripten::val bodyRect =
274 element["ownerDocument"]["body"].call<emscripten::val>("getBoundingClientRect");
275 emscripten::val canvasRect = element.call<emscripten::val>("getBoundingClientRect");
276 return QPoint(canvasRect["left"].as<int>() - bodyRect["left"].as<int>(),
277 canvasRect["top"].as<int>() - bodyRect["top"].as<int>());
278 };
279
280 setGeometry(QRect(getElementBodyPosition(m_shadowContainer), cssSize.toSize()));
281}
282
284{
285 int count = entries["length"].as<int>();
286 if (count == 0)
287 return;
288 emscripten::val entry = entries[0];
289 QWasmScreen *screen = reinterpret_cast<QWasmScreen *>(
290 entry["target"][m_canvasResizeObserverCallbackContextPropertyName].as<intptr_t>());
291 if (!screen) {
292 qWarning() << "QWasmScreen::canvasResizeObserverCallback: missing screen pointer";
293 return;
294 }
295
297}
298
300{
301 emscripten::function("qtCanvasResizeObserverCallback",
302 &QWasmScreen::canvasResizeObserverCallback);
303}
304
306{
307 emscripten::val ResizeObserver = emscripten::val::global("ResizeObserver");
308 if (ResizeObserver == emscripten::val::undefined())
309 return; // ResizeObserver API is not available
310 emscripten::val resizeObserver =
311 ResizeObserver.new_(emscripten::val::module_property("qtCanvasResizeObserverCallback"));
312 if (resizeObserver == emscripten::val::undefined())
313 return; // Something went horribly wrong
314
315 // We need to get back to this instance from the (static) resize callback;
316 // set a "data-" property on the canvas element.
317 m_shadowContainer.set(m_canvasResizeObserverCallbackContextPropertyName,
318 emscripten::val(intptr_t(this)));
319
320 resizeObserver.call<void>("observe", m_shadowContainer);
321}
322
324{
325 return m_shadowContainer;
326}
327
329{
330 return nullptr;
331}
332
334{
335 QList<QWasmWindow *> windows;
336 for (auto *child : childStack()) {
337 const QWindowList list = child->window()->findChildren<QWindow *>(Qt::FindChildrenRecursively);
338 for (auto child : list) {
339 auto handle = child->handle();
340 if (handle) {
341 auto wnd = static_cast<QWasmWindow *>(handle);
342 windows.push_back(wnd);
343 }
344 }
345 windows.push_back(child);
346 }
347 return windows;
348}
349
350QT_END_NAMESPACE
QWasmCompositor(QWasmScreen *screen)
QString name() const override
void installCanvasResizeObserver()
emscripten::val element() const
QPointF mapFromLocal(const QPointF &p) const
void deleteScreen()
QWindow * topLevelAt(const QPoint &p) const override
Return the given top level window for a given position.
QPointF clipPoint(const QPointF &p) const
QWasmCompositor * compositor()
QPlatformCursor * cursor() const override
Reimplement this function in subclass to return the cursor of the screen.
QImage::Format format() const override
Reimplement in subclass to return the image format which corresponds to the screen format.
QWasmWindowTreeNode * parentNode() final
qreal devicePixelRatio() const override
Reimplement this function in subclass to return the device pixel ratio for the screen.
void resizeMaximizedWindows()
void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent, QWasmWindow *child) final
void invalidateSize()
QString outerScreenId() const
int depth() const override
Reimplement in subclass to return current depth of the screen.
QWindow * topWindow() const
QDpi logicalDpi() const override
Reimplement this function in subclass to return the logical horizontal and vertical dots per inch met...
void updateQScreenSize()
static void canvasResizeObserverCallback(emscripten::val entries, emscripten::val)
emscripten::val containerElement() final
QList< QWasmWindow * > allWindows()
QRect geometry() const override
Reimplement in subclass to return the pixel geometry of the screen.
QWasmWindow * activeChild() const
virtual void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent, QWasmWindow *child)
friend class QWasmCompositor
Combined button and popup list for selecting options.
EMSCRIPTEN_BINDINGS(qtCanvasResizeObserverCallback)
QWasmWindowTreeNodeChangeType