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