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
qwasmwindownonclientarea.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
8#include "qwasmdom.h"
9#include "qwasmevent.h"
11
12#include <qpa/qwindowsysteminterface.h>
13
14#include <QtCore/qassert.h>
15
17
18WebImageButton::Callbacks::Callbacks() = default;
19WebImageButton::Callbacks::Callbacks(std::function<void()> onInteraction,
20 std::function<void()> onClick)
22{
23 Q_ASSERT_X(!!m_onInteraction == !!m_onClick, Q_FUNC_INFO,
24 "Both callbacks need to be either null or non-null");
25}
27
30
32{
33 return m_onInteraction();
34}
35
37{
38 return m_onClick();
39}
40
43 dom::document().call<emscripten::val>("createElement", emscripten::val("div"))),
44 m_imgElement(dom::document().call<emscripten::val>("createElement", emscripten::val("img")))
45{
46 m_imgElement.set("draggable", false);
47
48 m_containerElement["classList"].call<void>("add", emscripten::val("image-button"));
49 m_containerElement.call<void>("appendChild", m_imgElement);
50}
51
52WebImageButton::~WebImageButton() = default;
53
55{
56 if (callbacks) {
57 if (!m_webClickEventCallback) {
58 m_webMouseDownEventCallback = std::make_unique<qstdweb::EventCallback>(
59 m_containerElement, "pointerdown", [this](emscripten::val event) {
60 event.call<void>("preventDefault");
61 event.call<void>("stopPropagation");
62 m_callbacks.onInteraction();
63 });
64 m_webClickEventCallback = std::make_unique<qstdweb::EventCallback>(
65 m_containerElement, "click", [this](emscripten::val event) {
66 m_callbacks.onClick();
67 event.call<void>("stopPropagation");
68 });
69 }
70 } else {
71 m_webMouseDownEventCallback.reset();
72 m_webClickEventCallback.reset();
73 }
74 dom::syncCSSClassWith(m_containerElement, "action-button", !!callbacks);
75 m_callbacks = std::move(callbacks);
76}
77
78void WebImageButton::setImage(std::string_view imageData, std::string_view format)
79{
80 m_imgElement.set("src",
81 "data:image/" + std::string(format) + ";base64," + std::string(imageData));
82}
83
84void WebImageButton::setVisible(bool visible)
85{
86 m_containerElement["style"].set("display", visible ? "flex" : "none");
87}
88
89Resizer::ResizerElement::ResizerElement(emscripten::val parentElement, Qt::Edges edges,
90 Resizer *resizer)
91 : m_element(dom::document().call<emscripten::val>("createElement", emscripten::val("div"))),
93 m_resizer(resizer)
94{
95 Q_ASSERT_X(m_resizer, Q_FUNC_INFO, "Resizer cannot be null");
96
97 m_element["classList"].call<void>("add", emscripten::val("resize-outline"));
98 m_element["classList"].call<void>("add", emscripten::val(cssClassNameForEdges(edges)));
99
100 parentElement.call<void>("appendChild", m_element);
101
102 m_mouseDownEvent = std::make_unique<qstdweb::EventCallback>(
103 m_element, "pointerdown", [this](emscripten::val event) {
104 if (!onPointerDown(PointerEvent(EventType::PointerDown, event)))
105 return;
106 m_resizer->onInteraction();
107 event.call<void>("preventDefault");
108 event.call<void>("stopPropagation");
109 });
110 m_mouseMoveEvent = std::make_unique<qstdweb::EventCallback>(
111 m_element, "pointermove", [this](emscripten::val event) {
112 if (onPointerMove(PointerEvent(EventType::PointerMove, event)))
113 event.call<void>("preventDefault");
114 });
115 m_mouseUpEvent = std::make_unique<qstdweb::EventCallback>(
116 m_element, "pointerup", [this](emscripten::val event) {
117 if (onPointerUp(PointerEvent(EventType::PointerUp, event))) {
118 event.call<void>("preventDefault");
119 event.call<void>("stopPropagation");
120 }
121 });
122}
123
125{
126 m_element["parentElement"].call<emscripten::val>("removeChild", m_element);
127}
128
130
132{
133 m_element.call<void>("setPointerCapture", event.pointerId);
134 m_capturedPointerId = event.pointerId;
135
136 m_resizer->startResize(m_edges, event);
137 return true;
138}
139
141{
142 if (m_capturedPointerId != event.pointerId)
143 return false;
144
145 m_resizer->continueResize(event);
146 return true;
147}
148
150{
151 if (m_capturedPointerId != event.pointerId)
152 return false;
153
154 m_resizer->finishResize();
155 m_element.call<void>("releasePointerCapture", event.pointerId);
156 m_capturedPointerId = -1;
157 return true;
158}
159
160Resizer::Resizer(QWasmWindow *window, emscripten::val parentElement)
161 : m_window(window), m_windowElement(parentElement)
162{
163 Q_ASSERT_X(m_window, Q_FUNC_INFO, "Window must not be null");
164
165 constexpr std::array<int, 8> ResizeEdges = { Qt::TopEdge | Qt::LeftEdge,
166 Qt::TopEdge,
167 Qt::TopEdge | Qt::RightEdge,
168 Qt::LeftEdge,
169 Qt::RightEdge,
170 Qt::BottomEdge | Qt::LeftEdge,
171 Qt::BottomEdge,
172 Qt::BottomEdge | Qt::RightEdge };
173 std::transform(std::begin(ResizeEdges), std::end(ResizeEdges), std::back_inserter(m_elements),
174 [parentElement, this](int edges) {
175 return std::make_unique<ResizerElement>(parentElement,
176 Qt::Edges::fromInt(edges), this);
177 });
178}
179
180Resizer::~Resizer() = default;
181
183 const auto *window = m_window->window();
184 const auto minShrink = QPoint(window->minimumWidth() - window->geometry().width(),
185 window->minimumHeight() - window->geometry().height());
186 const auto maxGrow = QPoint(window->maximumWidth() - window->geometry().width(),
187 window->maximumHeight() - window->geometry().height());
188
189 const auto frameRect =
190 QRectF::fromDOMRect(m_windowElement.call<emscripten::val>("getBoundingClientRect"));
191 auto containerGeometry =
192 QRectF::fromDOMRect(m_window->parentNode()->containerElement().call<emscripten::val>(
193 "getBoundingClientRect"));
194
195 const int maxGrowTop = frameRect.top() - containerGeometry.top();
196
197 return ResizeConstraints{minShrink, maxGrow, maxGrowTop};
198}
199
200void Resizer::onInteraction()
201{
203}
204
205void Resizer::startResize(Qt::Edges resizeEdges, const PointerEvent &event)
206{
207 Q_ASSERT_X(!m_currentResizeData, Q_FUNC_INFO, "Another resize in progress");
208
209 m_currentResizeData.reset(new ResizeData{
210 .edges = resizeEdges,
211 .originInScreenCoords = dom::mapPoint(
212 event.target(), m_window->platformScreen()->element(), event.localPoint),
213 });
214
215 const auto resizeConstraints = getResizeConstraints();
216 m_currentResizeData->minShrink = resizeConstraints.minShrink;
217
218 m_currentResizeData->maxGrow =
219 QPoint(resizeConstraints.maxGrow.x(),
220 std::min(resizeEdges & Qt::Edge::TopEdge ? resizeConstraints.maxGrowTop : INT_MAX,
221 resizeConstraints.maxGrow.y()));
222
223 m_currentResizeData->initialBounds = m_window->window()->geometry();
224}
225
226void Resizer::continueResize(const PointerEvent &event)
227{
228 const auto pointInScreen =
229 dom::mapPoint(event.target(), m_window->platformScreen()->element(), event.localPoint);
230 const auto amount = (pointInScreen - m_currentResizeData->originInScreenCoords).toPoint();
231 const QPoint cappedGrowVector(
232 std::min(m_currentResizeData->maxGrow.x(),
233 std::max(m_currentResizeData->minShrink.x(),
234 (m_currentResizeData->edges & Qt::Edge::LeftEdge) ? -amount.x()
235 : (m_currentResizeData->edges & Qt::Edge::RightEdge)
236 ? amount.x()
237 : 0)),
238 std::min(m_currentResizeData->maxGrow.y(),
239 std::max(m_currentResizeData->minShrink.y(),
240 (m_currentResizeData->edges & Qt::Edge::TopEdge) ? -amount.y()
241 : (m_currentResizeData->edges & Qt::Edge::BottomEdge)
242 ? amount.y()
243 : 0)));
244
245 auto bounds = m_currentResizeData->initialBounds.adjusted(
246 (m_currentResizeData->edges & Qt::Edge::LeftEdge) ? -cappedGrowVector.x() : 0,
247 (m_currentResizeData->edges & Qt::Edge::TopEdge) ? -cappedGrowVector.y() : 0,
248 (m_currentResizeData->edges & Qt::Edge::RightEdge) ? cappedGrowVector.x() : 0,
249 (m_currentResizeData->edges & Qt::Edge::BottomEdge) ? cappedGrowVector.y() : 0);
250
251 m_window->window()->setGeometry(bounds);
252}
253
254void Resizer::finishResize()
255{
256 Q_ASSERT_X(m_currentResizeData, Q_FUNC_INFO, "No resize in progress");
257 m_currentResizeData.reset();
258}
259
260TitleBar::TitleBar(QWasmWindow *window, emscripten::val parentElement)
261 : m_window(window),
262 m_element(dom::document().call<emscripten::val>("createElement", emscripten::val("div"))),
263 m_label(dom::document().call<emscripten::val>("createElement", emscripten::val("div")))
264{
265 m_icon = std::make_unique<WebImageButton>();
266 m_icon->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::QtLogo), "svg+xml");
267 m_element.call<void>("appendChild", m_icon->htmlElement());
268 m_element.set("className", "title-bar");
269
270 auto spacer = dom::document().call<emscripten::val>("createElement", emscripten::val("div"));
271 spacer["style"].set("width", "4px");
272 m_element.call<void>("appendChild", spacer);
273
274 m_label.set("className", "window-name");
275
276 m_element.call<void>("appendChild", m_label);
277
278 spacer = dom::document().call<emscripten::val>("createElement", emscripten::val("div"));
279 spacer.set("className", "spacer");
280 m_element.call<void>("appendChild", spacer);
281
282 m_restore = std::make_unique<WebImageButton>();
283 m_restore->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::Restore),
284 "svg+xml");
285 m_restore->setCallbacks(
286 WebImageButton::Callbacks([this]() { m_window->onNonClientAreaInteraction(); },
287 [this]() { m_window->onRestoreClicked(); }));
288
289 m_element.call<void>("appendChild", m_restore->htmlElement());
290
291 m_maximize = std::make_unique<WebImageButton>();
292 m_maximize->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::Maximize),
293 "svg+xml");
294 m_maximize->setCallbacks(
295 WebImageButton::Callbacks([this]() { m_window->onNonClientAreaInteraction(); },
296 [this]() { m_window->onMaximizeClicked(); }));
297
298 m_element.call<void>("appendChild", m_maximize->htmlElement());
299
300 m_close = std::make_unique<WebImageButton>();
301 m_close->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::X), "svg+xml");
302 m_close->setCallbacks(
303 WebImageButton::Callbacks([this]() { m_window->onNonClientAreaInteraction(); },
304 [this]() { m_window->onCloseClicked(); }));
305
306 m_element.call<void>("appendChild", m_close->htmlElement());
307
308 parentElement.call<void>("appendChild", m_element);
309
310 m_mouseDownEvent = std::make_unique<qstdweb::EventCallback>(
311 m_element, "pointerdown", [this](emscripten::val event) {
312 if (!onPointerDown(PointerEvent(EventType::PointerDown, event)))
313 return;
314 m_window->onNonClientAreaInteraction();
315 event.call<void>("preventDefault");
316 event.call<void>("stopPropagation");
317 });
318 m_mouseMoveEvent = std::make_unique<qstdweb::EventCallback>(
319 m_element, "pointermove", [this](emscripten::val event) {
320 if (onPointerMove(PointerEvent(EventType::PointerMove, event))) {
321 event.call<void>("preventDefault");
322 }
323 });
324 m_mouseUpEvent = std::make_unique<qstdweb::EventCallback>(
325 m_element, "pointerup", [this](emscripten::val event) {
326 if (onPointerUp(PointerEvent(EventType::PointerUp, event))) {
327 event.call<void>("preventDefault");
328 event.call<void>("stopPropagation");
329 }
330 });
331 m_doubleClickEvent = std::make_unique<qstdweb::EventCallback>(
332 m_element, "dblclick", [this](emscripten::val event) {
333 if (onDoubleClick()) {
334 event.call<void>("preventDefault");
335 event.call<void>("stopPropagation");
336 }
337 });
338}
339
341{
342 m_element["parentElement"].call<emscripten::val>("removeChild", m_element);
343}
344
345void TitleBar::setTitle(const QString &title)
346{
347 m_label.set("innerText", emscripten::val(title.toStdString()));
348}
349
350void TitleBar::setRestoreVisible(bool visible)
351{
352 m_restore->setVisible(visible);
353}
354
355void TitleBar::setMaximizeVisible(bool visible)
356{
357 m_maximize->setVisible(visible);
358}
359
360void TitleBar::setCloseVisible(bool visible)
361{
362 m_close->setVisible(visible);
363}
364
365void TitleBar::setIcon(std::string_view imageData, std::string_view format)
366{
367 m_icon->setImage(imageData, format);
368}
369
370void TitleBar::setWidth(int width)
371{
372 m_element["style"].set("width", std::to_string(width) + "px");
373}
374
376{
377 return QRectF::fromDOMRect(m_element.call<emscripten::val>("getBoundingClientRect"));
378}
379
380bool TitleBar::onPointerDown(const PointerEvent &event)
381{
382 m_element.call<void>("setPointerCapture", event.pointerId);
383 m_capturedPointerId = event.pointerId;
384
385 m_moveStartWindowPosition = m_window->window()->position();
386 m_moveStartPoint = clipPointWithScreen(event.localPoint);
387 m_window->onNonClientEvent(event);
388 return true;
389}
390
391bool TitleBar::onPointerMove(const PointerEvent &event)
392{
393 if (m_capturedPointerId != event.pointerId)
394 return false;
395
396 const QPoint delta = (clipPointWithScreen(event.localPoint) - m_moveStartPoint).toPoint();
397
398 m_window->window()->setPosition(m_moveStartWindowPosition + delta);
399 m_window->onNonClientEvent(event);
400 return true;
401}
402
403bool TitleBar::onPointerUp(const PointerEvent &event)
404{
405 if (m_capturedPointerId != event.pointerId)
406 return false;
407
408 m_element.call<void>("releasePointerCapture", event.pointerId);
409 m_capturedPointerId = -1;
410 m_window->onNonClientEvent(event);
411 return true;
412}
413
414bool TitleBar::onDoubleClick()
415{
416 m_window->onToggleMaximized();
417 return true;
418}
419
420QPointF TitleBar::clipPointWithScreen(const QPointF &pointInTitleBarCoords) const
421{
422 auto containerRect =
423 QRectF::fromDOMRect(m_window->parentNode()->containerElement().call<emscripten::val>(
424 "getBoundingClientRect"));
425 const auto p = dom::mapPoint(m_element, m_window->parentNode()->containerElement(),
426 pointInTitleBarCoords);
427
428 auto result = QPointF(qBound(0., qreal(p.x()), containerRect.width()),
429 qBound(0., qreal(p.y()), containerRect.height()));
430 return m_window->parent() ? result : m_window->platformScreen()->mapFromLocal(result).toPoint();
431}
432
440
441NonClientArea::~NonClientArea() = default;
442
444{
445 m_titleBar->setWidth(width);
446}
447
449{
450 updateResizability();
451}
452
453void NonClientArea::updateResizability()
454{
455 const auto resizeConstraints = m_resizer->getResizeConstraints();
456 const bool nonResizable = resizeConstraints.minShrink.isNull()
457 && resizeConstraints.maxGrow.isNull() && resizeConstraints.maxGrowTop == 0;
458 dom::syncCSSClassWith(m_qtWindowElement, "no-resize", nonResizable);
459}
460
461QT_END_NAMESPACE
void onClientAreaWidthChange(int width)
NonClientArea(QWasmWindow *window, emscripten::val containerElement)
\inmodule QtCore\reentrant
Definition qpoint.h:30
emscripten::val element() const
void onToggleMaximized()
friend class QWasmCompositor
void onNonClientAreaInteraction()
bool onNonClientEvent(const PointerEvent &event)
QWasmScreen * platformScreen() const
bool onPointerUp(const PointerEvent &event)
ResizerElement(ResizerElement &&other)
ResizerElement(emscripten::val parentElement, Qt::Edges edges, Resizer *resizer)
bool onPointerDown(const PointerEvent &event)
bool onPointerMove(const PointerEvent &event)
Resizer(QWasmWindow *window, emscripten::val parentElement)
ResizeConstraints getResizeConstraints()
void setTitle(const QString &title)
void setMaximizeVisible(bool visible)
TitleBar(QWasmWindow *window, emscripten::val parentElement)
QRectF geometry() const
void setWidth(int width)
void setIcon(std::string_view imageData, std::string_view format)
void setCloseVisible(bool visible)
void setRestoreVisible(bool visible)
Callbacks & operator=(Callbacks &&)
Callbacks(std::function< void()> onInteraction, std::function< void()> onClick)
void setImage(std::string_view imageData, std::string_view format)
void setVisible(bool visible)
void setCallbacks(Callbacks callbacks)
Combined button and popup list for selecting options.
Definition qwasmdom.h:30